From d671ca793eca5b00fdddfe3a2fd2ab078747fc23 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Wed, 8 Mar 2023 16:24:33 +0100 Subject: [PATCH 1/8] Add option to encode path for get and update requests --- docs/usage/querying.rst | 10 +++++ docs/usage/updating.rst | 12 +++++ pyodata/v2/service.py | 35 ++++++++++----- tests/test_service_v2.py | 97 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 10 deletions(-) diff --git a/docs/usage/querying.rst b/docs/usage/querying.rst index 33c7952c..7f8d9609 100644 --- a/docs/usage/querying.rst +++ b/docs/usage/querying.rst @@ -154,7 +154,17 @@ these parameters by the method `custom(name: str, value: str)`. employees = northwind.entity_sets.Employees.get_entities().custom('sap-client', '100').custom('$skiptoken', 'ABCD').top(10).execute() +Encode OData URL Path +------------------------------------------- + +Sometimes services expect the path to be percent encoded. This can especially be important +when special variable types are key fields like Date type, where a ':' will appear in the path. +In this case you can use an optional parameter to make the request encode the path. +Per default the variable encode_path is set to False. + +.. code-block:: python + employee = northwind.entity_sets.Employees.get_entity(1, encode_path=True).execute() (Experimental) Query server-side paginations using the __next field ------------------------------------------------------------------- diff --git a/docs/usage/updating.rst b/docs/usage/updating.rst index 29829970..a7bb059f 100644 --- a/docs/usage/updating.rst +++ b/docs/usage/updating.rst @@ -34,3 +34,15 @@ then you can consider setting the default service's update method to *PUT*. .. code-block:: python northwind.config['http']['update_method'] = 'PUT' + +Encode OData URL Path +------------------------------------------- + +Sometimes services expect the path to be percent encoded. This can especially be important +when special variable types are key fields like Date type, where a ':' will appear in the path. +In this case you can use an optional parameter to make the request encode the path. +Per default the variable encode_path is set to False. + +.. code-block:: python + + update_request = northwind.entity_sets.Customers.update_entity(CustomerID='ALFKI', encode_path=True) \ No newline at end of file diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index e69ef55a..7643a2a7 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -13,7 +13,7 @@ from email.parser import Parser from http.client import HTTPResponse from io import BytesIO -from urllib.parse import urlencode +from urllib.parse import urlencode, quote from pyodata.exceptions import HttpError, PyODataException, ExpressionError, ProgramError @@ -371,7 +371,7 @@ def custom(self, name, value): class EntityGetRequest(ODataHttpRequest): """Used for GET operations of a single entity""" - def __init__(self, handler, entity_key, entity_set_proxy): + def __init__(self, handler, entity_key, entity_set_proxy, encode_path=False): super(EntityGetRequest, self).__init__(entity_set_proxy.service.url, entity_set_proxy.service.connection, handler) self._logger = logging.getLogger(LOGGER_NAME) @@ -379,6 +379,7 @@ def __init__(self, handler, entity_key, entity_set_proxy): self._entity_set_proxy = entity_set_proxy self._select = None self._expand = None + self._encode_path = encode_path self._logger.debug('New instance of EntityGetRequest for last segment: %s', self._entity_set_proxy.last_segment) @@ -403,7 +404,10 @@ def expand(self, expand): return self def get_path(self): - return self._entity_set_proxy.last_segment + self._entity_key.to_key_string() + if self.get_encode_path(): + return quote(self._entity_set_proxy.last_segment + self._entity_key.to_key_string()) + else: + return self._entity_set_proxy.last_segment + self._entity_key.to_key_string() def get_default_headers(self): return {'Accept': 'application/json'} @@ -438,7 +442,10 @@ def stream_handler(response): urljoin(self._url, self.get_path(), '/$value'), connection, stream_handler) - + + def get_encode_path(self): + """Getter for encode path flag""" + return self._encode_path class NavEntityGetRequest(EntityGetRequest): """Used for GET operations of a single entity accessed via a Navigation property""" @@ -569,12 +576,13 @@ class EntityModifyRequest(ODataHttpRequest): ALLOWED_HTTP_METHODS = ['PATCH', 'PUT', 'MERGE'] - def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH"): + def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH", encode_path=False): super(EntityModifyRequest, self).__init__(url, connection, handler) self._logger = logging.getLogger(LOGGER_NAME) self._entity_set = entity_set self._entity_type = entity_set.entity_type self._entity_key = entity_key + self._encode_path = encode_path self._method = method.upper() if self._method not in EntityModifyRequest.ALLOWED_HTTP_METHODS: @@ -589,7 +597,10 @@ def __init__(self, url, connection, handler, entity_set, entity_key, method="PAT self._logger.debug('New instance of EntityModifyRequest for entity type: %s', self._entity_type.name) def get_path(self): - return self._entity_set.name + self._entity_key.to_key_string() + if self.get_encode_path(): + return quote(self._entity_set.name + self._entity_key.to_key_string()) + else: + return self._entity_set.name + self._entity_key.to_key_string() def get_method(self): # pylint: disable=no-self-use @@ -604,6 +615,10 @@ def get_body(self): def get_default_headers(self): return {'Accept': 'application/json', 'Content-Type': 'application/json'} + + def get_encode_path(self): + """Getter for encode path flag""" + return self._encode_path def set(self, **kwargs): """Set properties to be changed.""" @@ -1452,7 +1467,7 @@ def get_entity_handler(parent, nav_property, navigation_entity_set, response): self, nav_property) - def get_entity(self, key=None, **args): + def get_entity(self, key=None, encode_path=False, **args): """Get entity based on provided key properties""" def get_entity_handler(response): @@ -1474,7 +1489,7 @@ def get_entity_handler(response): self._logger.info('Getting entity %s for key %s and args %s', self._entity_set.entity_type.name, key, args) - return EntityGetRequest(get_entity_handler, entity_key, self) + return EntityGetRequest(get_entity_handler, entity_key, self, encode_path=encode_path) def get_entities(self): """Get some, potentially all entities""" @@ -1533,7 +1548,7 @@ def create_entity_handler(response): return EntityCreateRequest(self._service.url, self._service.connection, create_entity_handler, self._entity_set, self.last_segment) - def update_entity(self, key=None, method=None, **kwargs): + def update_entity(self, key=None, method=None, encode_path=False, **kwargs): """Updates an existing entity in the given entity-set.""" def update_entity_handler(response): @@ -1554,7 +1569,7 @@ def update_entity_handler(response): method = self._service.config['http']['update_method'] return EntityModifyRequest(self._service.url, self._service.connection, update_entity_handler, self._entity_set, - entity_key, method=method) + entity_key, method=method, encode_path=encode_path) def delete_entity(self, key: EntityKey = None, **kwargs): """Delete the entity""" diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index 39769dc9..d1bc5192 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -1457,6 +1457,32 @@ def test_get_entity(service): assert emp.NameFirst == 'Rob' assert emp.NameLast == 'Ickes' +@responses.activate +def test_get_entity_with_encoded_path(service): + """Check getting entities""" + + # pylint: disable=redefined-outer-name + + responses.add( + responses.GET, + f"{service.url}/TemperatureMeasurements%28Sensor%3D%27sensor1%27%2CDate%3Ddatetime%272017-12-24T18%3A00%3A00%27%29", + json={'d': { + 'Sensor': 'sensor1', + 'Date': '/Date(1514138400000)/', + 'Value': 34.0 + }}, + status=200) + + request = service.entity_sets.TemperatureMeasurements.get_entity(Sensor='sensor1', Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc), encode_path=True) + + assert isinstance(request, pyodata.v2.service.EntityGetRequest) + assert request.get_path() == 'TemperatureMeasurements%28Sensor%3D%27sensor1%27%2CDate%3Ddatetime%272017-12-24T18%3A00%3A00%27%29' + + emp = request.execute() + assert emp.Sensor == 'sensor1' + assert emp.Date == datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc) + assert emp.Value == 34.0 + @responses.activate def test_get_entity_expanded(service): @@ -1563,6 +1589,77 @@ def test_batch_request(service): assert len(chset_response) == 1 assert chset_response[0] is None # response to update request is None +@responses.activate +def test_batch_request_with_encoded_path(service): + """Batch requests""" + + # pylint: disable=redefined-outer-name + + response_body = (b'--batch_r1\n' + b'Content-Type: application/http\n' + b'Content-Transfer-Encoding: binary\n' + b'\n' + b'HTTP/1.1 200 OK\n' + b'Content-Type: application/json\n' + b'\n' + b'{"d": {"ID": 23, "NameFirst": "Rob", "NameLast": "Ickes", "Address": { "ID": 456, "Street": "Baker Street", "City": "London"} }}' + b'\n' + b'--batch_r1\n' + b'Content-Type: multipart/mixed; boundary=changeset_1\n' + b'\n' + b'--changeset_1\n' + b'Content-Type: application/http\n' + b'Content-Transfer-Encoding: binary\n' + b'\n' + b'HTTP/1.1 204 Updated\n' + b'Content-Type: application/json\n' + b'\n' + b"{b'd': {'Sensor': 'Sensor-address', 'Date': datetime\'2017-12-24T18:00\', 'Value': '34.0d'}}" + b'\n' + b'--changeset_1--\n' + b'\n' + b'--batch_r1--') + + responses.add( + responses.POST, + f'{URL_ROOT}/$batch', + body=response_body, + content_type='multipart/mixed; boundary=batch_r1', + status=202) + + batch = service.create_batch('batch1') + + chset = service.create_changeset('chset1') + + employee_request = service.entity_sets.Employees.get_entity(23, encode_path=True) + + assert employee_request.get_path() == 'Employees%2823%29' + + temp_request = service.entity_sets.TemperatureMeasurements.update_entity( + Sensor='sensor1', + Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc), + encode_path=True).set(Value=34.0) + + batch.add_request(employee_request) + + chset.add_request(temp_request) + + assert f'{temp_request.get_method()} TemperatureMeasurements%28Sensor%3D%27sensor1%27%2CDate%3Ddatetime%272017-12-24T18%3A00%3A00%27%29' in chset.get_body() + + batch.add_request(chset) + batch.add_request(temp_request) + + response = batch.execute() + + assert len(response) == 2 + + employee_response = response[0] + assert isinstance(employee_response, pyodata.v2.service.EntityProxy) + + chset_response = response[1] + assert isinstance(chset_response, list) + assert len(chset_response) == 1 + assert chset_response[0] is None # response to update request is None @responses.activate def test_enormous_batch_request(service): From 1b05db91e18a42e6d026bdb6a32504d5ab74cc65 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Wed, 8 Mar 2023 16:55:01 +0100 Subject: [PATCH 2/8] linting --- pyodata/v2/service.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 7643a2a7..e083eb77 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -442,11 +442,12 @@ def stream_handler(response): urljoin(self._url, self.get_path(), '/$value'), connection, stream_handler) - + def get_encode_path(self): """Getter for encode path flag""" return self._encode_path + class NavEntityGetRequest(EntityGetRequest): """Used for GET operations of a single entity accessed via a Navigation property""" @@ -615,7 +616,7 @@ def get_body(self): def get_default_headers(self): return {'Accept': 'application/json', 'Content-Type': 'application/json'} - + def get_encode_path(self): """Getter for encode path flag""" return self._encode_path From dcb4ac2a12ac4a0941a8bcbcd7923a024408938d Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Mon, 13 Mar 2023 09:26:14 +0100 Subject: [PATCH 3/8] feat: make url encode default, add encoding for delete and get entity set --- pyodata/v2/service.py | 49 ++++++++---- tests/test_service_v2.py | 169 ++++++++++++++++++++++----------------- 2 files changed, 130 insertions(+), 88 deletions(-) diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index e083eb77..95d822c7 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -371,7 +371,7 @@ def custom(self, name, value): class EntityGetRequest(ODataHttpRequest): """Used for GET operations of a single entity""" - def __init__(self, handler, entity_key, entity_set_proxy, encode_path=False): + def __init__(self, handler, entity_key, entity_set_proxy, encode_path=True): super(EntityGetRequest, self).__init__(entity_set_proxy.service.url, entity_set_proxy.service.connection, handler) self._logger = logging.getLogger(LOGGER_NAME) @@ -406,8 +406,7 @@ def expand(self, expand): def get_path(self): if self.get_encode_path(): return quote(self._entity_set_proxy.last_segment + self._entity_key.to_key_string()) - else: - return self._entity_set_proxy.last_segment + self._entity_key.to_key_string() + return self._entity_set_proxy.last_segment + self._entity_key.to_key_string() def get_default_headers(self): return {'Accept': 'application/json'} @@ -553,17 +552,24 @@ def set(self, **kwargs): class EntityDeleteRequest(ODataHttpRequest): """Used for deleting entity (DELETE operations on a single entity)""" - def __init__(self, url, connection, handler, entity_set, entity_key): + def __init__(self, url, connection, handler, entity_set, entity_key, encode_path=True): super(EntityDeleteRequest, self).__init__(url, connection, handler) self._logger = logging.getLogger(LOGGER_NAME) self._entity_set = entity_set self._entity_key = entity_key + self._encode_path = encode_path self._logger.debug('New instance of EntityDeleteRequest for entity type: %s', entity_set.entity_type.name) def get_path(self): + if self.get_encode_path(): + return quote(self._entity_set.name + self._entity_key.to_key_string()) return self._entity_set.name + self._entity_key.to_key_string() + def get_encode_path(self): + """Getter for encode path flag""" + return self._encode_path + def get_method(self): # pylint: disable=no-self-use return 'DELETE' @@ -577,7 +583,7 @@ class EntityModifyRequest(ODataHttpRequest): ALLOWED_HTTP_METHODS = ['PATCH', 'PUT', 'MERGE'] - def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH", encode_path=False): + def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH", encode_path=True): super(EntityModifyRequest, self).__init__(url, connection, handler) self._logger = logging.getLogger(LOGGER_NAME) self._entity_set = entity_set @@ -600,8 +606,7 @@ def __init__(self, url, connection, handler, entity_set, entity_key, method="PAT def get_path(self): if self.get_encode_path(): return quote(self._entity_set.name + self._entity_key.to_key_string()) - else: - return self._entity_set.name + self._entity_key.to_key_string() + return self._entity_set.name + self._entity_key.to_key_string() def get_method(self): # pylint: disable=no-self-use @@ -1323,10 +1328,11 @@ def __str__(self): class GetEntitySetRequest(QueryRequest): """GET on EntitySet""" - def __init__(self, url, connection, handler, last_segment, entity_type): + def __init__(self, url, connection, handler, last_segment, entity_type, encode_path=True): super(GetEntitySetRequest, self).__init__(url, connection, handler, last_segment) self._entity_type = entity_type + self._encode_path = encode_path def __getattr__(self, name): proprty = self._entity_type.proprty(name) @@ -1345,6 +1351,20 @@ def filter(self, *args, **kwargs): return self + def get_path(self): + if self.get_encode_path(): + path = quote(self._last_segment) + else: + path = self._last_segment + + if self._count: + return urljoin(path, '/$count') + return path + + def get_encode_path(self): + """Getter for encode path flag""" + return self._encode_path + class ListWithTotalCount(list): """ @@ -1468,7 +1488,7 @@ def get_entity_handler(parent, nav_property, navigation_entity_set, response): self, nav_property) - def get_entity(self, key=None, encode_path=False, **args): + def get_entity(self, key=None, encode_path=True, **args): """Get entity based on provided key properties""" def get_entity_handler(response): @@ -1492,7 +1512,7 @@ def get_entity_handler(response): return EntityGetRequest(get_entity_handler, entity_key, self, encode_path=encode_path) - def get_entities(self): + def get_entities(self, encode_path=True): """Get some, potentially all entities""" def get_entities_handler(response): @@ -1529,7 +1549,8 @@ def get_entities_handler(response): entity_set_name = self._alias if self._alias is not None else self._entity_set.name return GetEntitySetRequest(self._service.url, self._service.connection, get_entities_handler, - self._parent_last_segment + entity_set_name, self._entity_set.entity_type) + self._parent_last_segment + entity_set_name, self._entity_set.entity_type, + encode_path=encode_path) def create_entity(self, return_code=HTTP_CODE_CREATED): """Creates a new entity in the given entity-set.""" @@ -1549,7 +1570,7 @@ def create_entity_handler(response): return EntityCreateRequest(self._service.url, self._service.connection, create_entity_handler, self._entity_set, self.last_segment) - def update_entity(self, key=None, method=None, encode_path=False, **kwargs): + def update_entity(self, key=None, method=None, encode_path=True, **kwargs): """Updates an existing entity in the given entity-set.""" def update_entity_handler(response): @@ -1572,7 +1593,7 @@ def update_entity_handler(response): return EntityModifyRequest(self._service.url, self._service.connection, update_entity_handler, self._entity_set, entity_key, method=method, encode_path=encode_path) - def delete_entity(self, key: EntityKey = None, **kwargs): + def delete_entity(self, key: EntityKey = None, encode_path=True, **kwargs): """Delete the entity""" def delete_entity_handler(response): @@ -1589,7 +1610,7 @@ def delete_entity_handler(response): entity_key = EntityKey(self._entity_set.entity_type, key, **kwargs) return EntityDeleteRequest(self._service.url, self._service.connection, delete_entity_handler, self._entity_set, - entity_key) + entity_key, encode_path=encode_path) # pylint: disable=too-few-public-methods diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index d1bc5192..5710c3e6 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -4,6 +4,7 @@ import responses import requests import pytest +from urllib.parse import quote from unittest.mock import patch import pyodata.v2.model @@ -143,9 +144,10 @@ def test_create_entity_nested(service): }}, status=201) + path = quote("Cars('Hadraplan')") responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')/IDPic/$value/", + f"{service.url}/{path}/IDPic/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEEF', status=200) @@ -209,9 +211,10 @@ def test_get_entity_property(service): # pylint: disable=redefined-outer-name + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')", + f"{service.url}/{path}", headers={ 'ETag': 'W/\"J0FtZXJpY2FuIEFpcmxpbmVzJw==\"', 'Content-type': 'application/json', @@ -229,10 +232,10 @@ def test_entity_url(service): """Test correct build of entity url""" # pylint: disable=redefined-outer-name - + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json={'d': {'Key': '12345'}}, status=200) @@ -246,10 +249,10 @@ def test_entity_entity_set_name(service): """Test correct entity set name""" # pylint: disable=redefined-outer-name - + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json={'d': {'Key': '12345'}}, status=200) @@ -263,10 +266,10 @@ def test_entity_key_simple(service): """Test simple key of entity""" # pylint: disable=redefined-outer-name - + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json={'d': {'Key': '12345'}}, status=200) @@ -281,10 +284,10 @@ def test_entity_key_complex(service): """Test complex key of entity""" # pylint: disable=redefined-outer-name - + path = quote("TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')") responses.add( responses.GET, - f"{service.url}/TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json={'d': { 'Sensor': 'sensor1', @@ -611,10 +614,10 @@ def test_update_entity(service): """Check updating of entity properties""" # pylint: disable=redefined-outer-name - + path = quote("TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')") responses.add( responses.PATCH, - f"{service.url}/TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')", + f"{service.url}/{path}", json={'d': { 'Sensor': 'Sensor-address', 'Date': "/Date(1714138400000)/", @@ -647,18 +650,30 @@ def test_update_entity(service): def test_delete_entity(service): """Check deleting of entity""" - responses.add(responses.DELETE, f"{service.url}/Employees(23)", status=204) + path = quote("Employees(23)") + responses.add(responses.DELETE, f"{service.url}/{path}", status=204) request = service.entity_sets.Employees.delete_entity(23) assert isinstance(request, pyodata.v2.service.EntityDeleteRequest) assert request.execute() is None +@responses.activate +def test_delete_entity_with_decoded_path(service): + """Check deleting of entity""" + + responses.add(responses.DELETE, f"{service.url}/Employees(23)", status=204) + request = service.entity_sets.Employees.delete_entity(23, encode_path=False) + + assert isinstance(request, pyodata.v2.service.EntityDeleteRequest) + assert request.execute() is None + @responses.activate def test_delete_entity_with_key(service): """Check deleting of entity with key""" - responses.add(responses.DELETE, f"{service.url}/Employees(ID=23)", status=204) + path = quote("Employees(ID=23)") + responses.add(responses.DELETE, f"{service.url}/{path}", status=204) key = EntityKey(service.schema.entity_type('Employee'), ID=23) request = service.entity_sets.Employees.delete_entity(key=key) @@ -670,7 +685,8 @@ def test_delete_entity_with_key(service): def test_delete_entity_http_error(service): """Check if error is raisen when deleting unknown entity""" - responses.add(responses.DELETE, f"{service.url}/Employees(ID=23)", status=404) + path = quote("Employees(ID=23)") + responses.add(responses.DELETE, f"{service.url}/{path}", status=404) key = EntityKey(service.schema.entity_type('Employee'), ID=23) request = service.entity_sets.Employees.delete_entity(key=key) @@ -695,7 +711,7 @@ def test_update_entity_with_entity_key(service): Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc)) query = service.entity_sets.TemperatureMeasurements.update_entity(key) - assert query.get_path() == "TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')" + assert query.get_path() == quote("TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')") def test_update_entity_with_put_method_specified(service): @@ -980,10 +996,10 @@ def test_navigation_multi(service): """Get entities via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)/Addresses") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses", + f"{service.url}/{path}", json={'d': { 'results': [ { @@ -1024,10 +1040,10 @@ def test_navigation(service): """Check getting entity via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)/Addresses(456)") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses(456)", + f"{service.url}/{path}", json={'d': { 'ID': 456, 'Street': 'Baker Street', @@ -1050,10 +1066,10 @@ def test_navigation_multi_on1(service): """Check getting entity via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Customers('Mammon')/ReferredBy") responses.add( responses.GET, - f"{service.url}/Customers('Mammon')/ReferredBy", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json = { 'd': { 'Name': 'John', @@ -1078,10 +1094,10 @@ def test_navigation_1on1(service): """Check getting entity via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Cars('Hadraplan')") responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')/IDPic", + f"{service.url}/{path}/IDPic", headers={'Content-type': 'application/json'}, json = { 'd': { 'CarName': 'Hadraplan', @@ -1108,10 +1124,10 @@ def test_navigation_1on1_from_entity_proxy(service): """Check getting entity via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Cars('Hadraplan')") responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json = { 'd': { 'Name': 'Hadraplan', @@ -1121,7 +1137,7 @@ def test_navigation_1on1_from_entity_proxy(service): responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')/IDPic", + f"{service.url}/{path}/IDPic", headers={'Content-type': 'application/json'}, json = { 'd': { 'CarName': 'Hadraplan', @@ -1153,10 +1169,10 @@ def test_navigation_1on1_get_value_without_proxy(service): """Check getting $value via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Cars('Hadraplan')") responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')/IDPic/$value/", + f"{service.url}/{path}/IDPic/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEAF', status=200) @@ -1175,10 +1191,10 @@ def test_navigation_when_nes_in_another_ns(service): """ # pylint: disable=redefined-outer-name - + path = quote("Customers('Mammon')/Orders") responses.add( responses.GET, - f"{service.url}/Customers('Mammon')/Orders", + f"{service.url}/{path}", json={'d': {'results' : [{ 'Number': '456', 'Owner': 'Mammon', @@ -1202,10 +1218,10 @@ def test_entity_get_value_1on1_with_proxy(service): """Check getting $value""" # pylint: disable=redefined-outer-name - + path = quote("Cars('Hadraplan')") responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')/IDPic", + f"{service.url}/{path}/IDPic", headers={'Content-type': 'application/json'}, json = { 'd': { 'CarName': 'Hadraplan', @@ -1216,7 +1232,7 @@ def test_entity_get_value_1on1_with_proxy(service): responses.add( responses.GET, - f"{service.url}/Cars('Hadraplan')/IDPic/$value/", + f"{service.url}/{path}/IDPic/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEAF', status=200) @@ -1233,10 +1249,10 @@ def test_entity_get_value_without_proxy(service): """Check getting $value without proxy""" # pylint: disable=redefined-outer-name - + path = quote("CarIDPics('Hadraplan')") responses.add( responses.GET, - f"{service.url}/CarIDPics('Hadraplan')/$value/", + f"{service.url}/{path}/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEAF', status=200) @@ -1253,10 +1269,10 @@ def test_entity_get_value_with_proxy(service): """Check getting $value with proxy""" # pylint: disable=redefined-outer-name - + path = quote("CarIDPics('Hadraplan')") responses.add( responses.GET, - f"{service.url}/CarIDPics('Hadraplan')", + f"{service.url}/{path}", headers={'Content-type': 'application/json'}, json = { 'd': { 'CarName': 'Hadraplan', @@ -1267,7 +1283,7 @@ def test_entity_get_value_with_proxy(service): responses.add( responses.GET, - f"{service.url}/CarIDPics('Hadraplan')/$value/", + f"{service.url}/{path}/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEAF', status=200) @@ -1284,10 +1300,10 @@ def test_entity_get_value_without_proxy_error(service): """Check getting $value without proxy""" # pylint: disable=redefined-outer-name - + path = quote("CarIDPics('Hadraplan')") responses.add( responses.GET, - f"{service.url}/CarIDPics('Hadraplan')/$value/", + f"{service.url}/{path}/$value/", headers={'Content-type': 'text/plain'}, body='Internal Server Error', status=500) @@ -1334,10 +1350,10 @@ def test_navigation_from_entity_multi(service): """Get entities via navigation property from entity proxy""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)") responses.add( responses.GET, - f"{service.url}/Employees(23)", + f"{service.url}/{path}", json={'d': { 'ID': 23, 'NameFirst': 'Rob', @@ -1347,7 +1363,7 @@ def test_navigation_from_entity_multi(service): responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses", + f"{service.url}/{path}/Addresses", json={'d': { 'results': [ { @@ -1394,10 +1410,10 @@ def test_navigation_from_entity(service): """Check getting entity via navigation property from entity proxy""" # pylint: disable=redefined-outer-name - + path_employes = quote("Employees(23)") responses.add( responses.GET, - f"{service.url}/Employees(23)", + f"{service.url}/{path_employes}", json={'d': { 'ID': 23, 'NameFirst': 'Rob', @@ -1405,9 +1421,10 @@ def test_navigation_from_entity(service): }}, status=200) + path_employes_adresses = quote("Employees(23)/Addresses(456)") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses(456)", + f"{service.url}/{path_employes_adresses}", json={'d': { 'ID': 456, 'Street': 'Baker Street', @@ -1437,10 +1454,10 @@ def test_get_entity(service): """Check getting entities""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)") responses.add( responses.GET, - f"{service.url}/Employees(23)", + f"{service.url}/{path}", json={'d': { 'ID': 23, 'NameFirst': 'Rob', @@ -1458,14 +1475,14 @@ def test_get_entity(service): assert emp.NameLast == 'Ickes' @responses.activate -def test_get_entity_with_encoded_path(service): +def test_get_entity_with_decoded_path(service): """Check getting entities""" # pylint: disable=redefined-outer-name responses.add( responses.GET, - f"{service.url}/TemperatureMeasurements%28Sensor%3D%27sensor1%27%2CDate%3Ddatetime%272017-12-24T18%3A00%3A00%27%29", + f"{service.url}/TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')", json={'d': { 'Sensor': 'sensor1', 'Date': '/Date(1514138400000)/', @@ -1473,10 +1490,10 @@ def test_get_entity_with_encoded_path(service): }}, status=200) - request = service.entity_sets.TemperatureMeasurements.get_entity(Sensor='sensor1', Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc), encode_path=True) + request = service.entity_sets.TemperatureMeasurements.get_entity(Sensor='sensor1', Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc), encode_path=False) assert isinstance(request, pyodata.v2.service.EntityGetRequest) - assert request.get_path() == 'TemperatureMeasurements%28Sensor%3D%27sensor1%27%2CDate%3Ddatetime%272017-12-24T18%3A00%3A00%27%29' + assert request.get_path() == "TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')" emp = request.execute() assert emp.Sensor == 'sensor1' @@ -1489,10 +1506,10 @@ def test_get_entity_expanded(service): """Check getting entities with expanded navigation properties""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)") responses.add( responses.GET, - f"{service.url}/Employees(23)", + f"{service.url}/{path}", json={'d': { 'ID': 23, 'NameFirst': 'Rob', @@ -1590,7 +1607,7 @@ def test_batch_request(service): assert chset_response[0] is None # response to update request is None @responses.activate -def test_batch_request_with_encoded_path(service): +def test_batch_request_with_decoded_path(service): """Batch requests""" # pylint: disable=redefined-outer-name @@ -1631,9 +1648,9 @@ def test_batch_request_with_encoded_path(service): chset = service.create_changeset('chset1') - employee_request = service.entity_sets.Employees.get_entity(23, encode_path=True) + employee_request = service.entity_sets.Employees.get_entity(23, encode_path=False) - assert employee_request.get_path() == 'Employees%2823%29' + assert employee_request.get_path() == 'Employees(23)' temp_request = service.entity_sets.TemperatureMeasurements.update_entity( Sensor='sensor1', @@ -1743,7 +1760,7 @@ def test_get_entity_with_entity_key(service): Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc)) query = service.entity_sets.TemperatureMeasurements.get_entity(key) - assert query.get_path() == "TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')" + assert query.get_path() == quote("TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')") def test_get_entity_with_entity_key_and_other_params(service): @@ -1757,7 +1774,7 @@ def test_get_entity_with_entity_key_and_other_params(service): Date=datetime.datetime(2017, 12, 24, 18, 0, tzinfo=datetime.timezone.utc)) query = service.entity_sets.TemperatureMeasurements.get_entity(key=key, Foo='Bar') - assert query.get_path() == "TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')" + assert query.get_path() == quote("TemperatureMeasurements(Sensor='sensor1',Date=datetime'2017-12-24T18:00:00')") def test_entity_proxy_equals(service): @@ -1973,10 +1990,10 @@ def test_navigation_inlinecount(service): """Check getting entities with $inlinecount via navigation property""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)/Addresses") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses?$inlinecount=allpages", + f"{service.url}/{path}?$inlinecount=allpages", json={'d': { '__count': 3, 'results': [ @@ -2010,10 +2027,10 @@ def test_inlinecount_with_filter(service): """Check getting entities with $inlinecount and $filter""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)/Addresses") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses?$inlinecount=allpages&%24filter=City%20eq%20%27London%27", + f"{service.url}/{path}?$inlinecount=allpages&%24filter=City%20eq%20%27London%27", json={'d': { '__count': 2, 'results': [ @@ -2121,9 +2138,10 @@ def test_navigation_count(service): # pylint: disable=redefined-outer-name + path = quote("Employees(23)/Addresses") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses/$count", + f"{service.url}/{path}/$count", json=458, status=200) @@ -2140,10 +2158,10 @@ def test_count_with_filter(service): """Check getting $count with $filter""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)/Addresses") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses/$count?%24filter=City%20eq%20%27London%27", + f"{service.url}/{path}/$count?%24filter=City%20eq%20%27London%27", json=3, status=200) @@ -2160,10 +2178,10 @@ def test_count_with_chainable_filter(service): """Check getting $count with $filter and using new filter syntax""" # pylint: disable=redefined-outer-name - + path = quote("Employees(23)/Addresses") responses.add( responses.GET, - f"{service.url}/Employees(23)/Addresses/$count?%24filter=City%20eq%20%27London%27", + f"{service.url}/{path}/$count?%24filter=City%20eq%20%27London%27", json=3, status=200) @@ -2793,9 +2811,10 @@ def test_odata_http_response(): def test_custom_with_get_entity(service): """ Test that `custom` can be called after `get_entity`""" + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')?foo=bar", + f"{service.url}/{path}?foo=bar", headers={'Content-type': 'application/json'}, json={'d': {'Key': '12345'}}, status=200) @@ -2807,16 +2826,17 @@ def test_custom_with_get_entity(service): def test_custom_with_get_entity_url_params(service): """ Test that `custom` after `get_entity` is setting up correctly URL parts """ + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')?foo=bar", + f"{service.url}/{path}?foo=bar", headers={'Content-type': 'application/json'}, json={'d': {'Key': '12345'}}, status=200) oDataHttpRequest = service.entity_sets.MasterEntities.get_entity('12345').custom("foo", "bar") assert oDataHttpRequest.get_query_params() == {'foo': 'bar'} - assert oDataHttpRequest.get_path() == "MasterEntities('12345')" + assert oDataHttpRequest.get_path() == quote("MasterEntities('12345')") entity = oDataHttpRequest.execute() assert entity.Key == '12345' @@ -2825,16 +2845,17 @@ def test_custom_with_get_entity_url_params(service): def test_multiple_custom_with_get_entity_url_params(service): """ Test that `custom` after `get_entity` called several times is setting up correctly URL parts """ + path = quote("MasterEntities('12345')") responses.add( responses.GET, - f"{service.url}/MasterEntities('12345')?foo=bar&$fizz=buzz", + f"{service.url}/{path}?foo=bar&$fizz=buzz", headers={'Content-type': 'application/json'}, json={'d': {'Key': '12345'}}, status=200) oDataHttpRequest = service.entity_sets.MasterEntities.get_entity('12345').custom("foo", "bar").custom("$fizz", "buzz") assert oDataHttpRequest.get_query_params() == {'foo': 'bar', '$fizz': 'buzz'} - assert oDataHttpRequest.get_path() == "MasterEntities('12345')" + assert oDataHttpRequest.get_path() == quote("MasterEntities('12345')") entity = oDataHttpRequest.execute() assert entity.Key == '12345' From 327016749ef69af58e716a9bdbb096822fa59245 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Tue, 14 Mar 2023 08:43:31 +0100 Subject: [PATCH 4/8] doc: update encode path description --- CHANGELOG.md | 1 + docs/usage/deleting.rst | 14 +++++++++++++- docs/usage/querying.rst | 8 +++----- docs/usage/updating.rst | 9 ++++----- pyodata/v2/service.py | 3 ++- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a902002..4afb10b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +- Service: Encode paths with entity keys with option to disable it - linda-sap ## [1.10.1] diff --git a/docs/usage/deleting.rst b/docs/usage/deleting.rst index f683fbf1..31f61260 100644 --- a/docs/usage/deleting.rst +++ b/docs/usage/deleting.rst @@ -41,4 +41,16 @@ or by passing the EntityKey object key = EntityKey(service.schema.entity_type('Employee'), ID=23) request = service.entity_sets.Employees.delete_entity(key=key) - request.execute() \ No newline at end of file + request.execute() + + +Encode OData URL Path +------------------------------------------- + +By default the paths of requests are percent encoded. However if this is not what you API expects, +you can disable the encoding with the variable encode_path by setting it to False. + + +.. code-block:: python + + request = service.entity_sets.Employees.delete_entity(key=key, encode_path=False) \ No newline at end of file diff --git a/docs/usage/querying.rst b/docs/usage/querying.rst index 7f8d9609..21e95f52 100644 --- a/docs/usage/querying.rst +++ b/docs/usage/querying.rst @@ -157,14 +157,12 @@ these parameters by the method `custom(name: str, value: str)`. Encode OData URL Path ------------------------------------------- -Sometimes services expect the path to be percent encoded. This can especially be important -when special variable types are key fields like Date type, where a ':' will appear in the path. -In this case you can use an optional parameter to make the request encode the path. -Per default the variable encode_path is set to False. +By default the paths of requests are percent encoded. However if this is not what you API expects, +you can disable the encoding with the variable encode_path by setting it to False. .. code-block:: python - employee = northwind.entity_sets.Employees.get_entity(1, encode_path=True).execute() + employee = northwind.entity_sets.Employees.get_entity(1, encode_path=False).execute() (Experimental) Query server-side paginations using the __next field ------------------------------------------------------------------- diff --git a/docs/usage/updating.rst b/docs/usage/updating.rst index a7bb059f..f8db338b 100644 --- a/docs/usage/updating.rst +++ b/docs/usage/updating.rst @@ -38,11 +38,10 @@ then you can consider setting the default service's update method to *PUT*. Encode OData URL Path ------------------------------------------- -Sometimes services expect the path to be percent encoded. This can especially be important -when special variable types are key fields like Date type, where a ':' will appear in the path. -In this case you can use an optional parameter to make the request encode the path. -Per default the variable encode_path is set to False. +By default the paths of requests are percent encoded. However if this is not what you API expects, +you can disable the encoding with the variable encode_path by setting it to False. + .. code-block:: python - update_request = northwind.entity_sets.Customers.update_entity(CustomerID='ALFKI', encode_path=True) \ No newline at end of file + update_request = northwind.entity_sets.Customers.update_entity(CustomerID='ALFKI', encode_path=False) \ No newline at end of file diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 95d822c7..207850e9 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -575,6 +575,7 @@ def get_method(self): return 'DELETE' +# pylint: disable=too-many-instance-attributes class EntityModifyRequest(ODataHttpRequest): """Used for modyfing entities (UPDATE/MERGE operations on a single entity) @@ -582,7 +583,7 @@ class EntityModifyRequest(ODataHttpRequest): and get the modified entity.""" ALLOWED_HTTP_METHODS = ['PATCH', 'PUT', 'MERGE'] - + # pylint: disable=too-many-arguments def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH", encode_path=True): super(EntityModifyRequest, self).__init__(url, connection, handler) self._logger = logging.getLogger(LOGGER_NAME) From 1e6e1a982ea6ee8b16d32058526d7fc7fd7d11c5 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Tue, 14 Mar 2023 11:08:23 +0100 Subject: [PATCH 5/8] tests: rename path not encoded tests --- docs/usage/deleting.rst | 2 +- docs/usage/querying.rst | 2 +- docs/usage/updating.rst | 2 +- tests/test_service_v2.py | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/usage/deleting.rst b/docs/usage/deleting.rst index 31f61260..9ddc5f7c 100644 --- a/docs/usage/deleting.rst +++ b/docs/usage/deleting.rst @@ -47,7 +47,7 @@ or by passing the EntityKey object Encode OData URL Path ------------------------------------------- -By default the paths of requests are percent encoded. However if this is not what you API expects, +By default the paths of requests are percent encoded. However if this is not what your API expects, you can disable the encoding with the variable encode_path by setting it to False. diff --git a/docs/usage/querying.rst b/docs/usage/querying.rst index 21e95f52..f2b0c967 100644 --- a/docs/usage/querying.rst +++ b/docs/usage/querying.rst @@ -157,7 +157,7 @@ these parameters by the method `custom(name: str, value: str)`. Encode OData URL Path ------------------------------------------- -By default the paths of requests are percent encoded. However if this is not what you API expects, +By default the paths of requests are percent encoded. However if this is not what your API expects, you can disable the encoding with the variable encode_path by setting it to False. .. code-block:: python diff --git a/docs/usage/updating.rst b/docs/usage/updating.rst index f8db338b..472b0395 100644 --- a/docs/usage/updating.rst +++ b/docs/usage/updating.rst @@ -38,7 +38,7 @@ then you can consider setting the default service's update method to *PUT*. Encode OData URL Path ------------------------------------------- -By default the paths of requests are percent encoded. However if this is not what you API expects, +By default the paths of requests are percent encoded. However if this is not what your API expects, you can disable the encoding with the variable encode_path by setting it to False. diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index 5710c3e6..422bba67 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -658,7 +658,7 @@ def test_delete_entity(service): assert request.execute() is None @responses.activate -def test_delete_entity_with_decoded_path(service): +def test_delete_entity_not_encoded_path(service): """Check deleting of entity""" responses.add(responses.DELETE, f"{service.url}/Employees(23)", status=204) @@ -1475,7 +1475,7 @@ def test_get_entity(service): assert emp.NameLast == 'Ickes' @responses.activate -def test_get_entity_with_decoded_path(service): +def test_get_entity_not_encoded_path(service): """Check getting entities""" # pylint: disable=redefined-outer-name @@ -1607,7 +1607,7 @@ def test_batch_request(service): assert chset_response[0] is None # response to update request is None @responses.activate -def test_batch_request_with_decoded_path(service): +def test_batch_request_not_encoded_path(service): """Batch requests""" # pylint: disable=redefined-outer-name From 424fba4e77f16139d42b08ee1906264d7d6c2b67 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Tue, 14 Mar 2023 11:19:51 +0100 Subject: [PATCH 6/8] doc: more precise descrption resource paths --- docs/usage/deleting.rst | 2 +- docs/usage/querying.rst | 2 +- docs/usage/updating.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage/deleting.rst b/docs/usage/deleting.rst index 9ddc5f7c..9dc2124b 100644 --- a/docs/usage/deleting.rst +++ b/docs/usage/deleting.rst @@ -47,7 +47,7 @@ or by passing the EntityKey object Encode OData URL Path ------------------------------------------- -By default the paths of requests are percent encoded. However if this is not what your API expects, +By default the resource paths of requests are percent encoded. However if this is not what your API expects, you can disable the encoding with the variable encode_path by setting it to False. diff --git a/docs/usage/querying.rst b/docs/usage/querying.rst index f2b0c967..110b3f77 100644 --- a/docs/usage/querying.rst +++ b/docs/usage/querying.rst @@ -157,7 +157,7 @@ these parameters by the method `custom(name: str, value: str)`. Encode OData URL Path ------------------------------------------- -By default the paths of requests are percent encoded. However if this is not what your API expects, +By default the resource paths of requests are percent encoded. However if this is not what your API expects, you can disable the encoding with the variable encode_path by setting it to False. .. code-block:: python diff --git a/docs/usage/updating.rst b/docs/usage/updating.rst index 472b0395..454e2c6e 100644 --- a/docs/usage/updating.rst +++ b/docs/usage/updating.rst @@ -38,7 +38,7 @@ then you can consider setting the default service's update method to *PUT*. Encode OData URL Path ------------------------------------------- -By default the paths of requests are percent encoded. However if this is not what your API expects, +By default the resource paths of requests are percent encoded. However if this is not what your API expects, you can disable the encoding with the variable encode_path by setting it to False. From 7c7d84cc1db02f8c3669d7a948ac0a8812a740c8 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Tue, 14 Mar 2023 12:30:43 +0100 Subject: [PATCH 7/8] tests: expect not encoded url for get value --- tests/test_service_v2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_service_v2.py b/tests/test_service_v2.py index 422bba67..d4bf82ca 100644 --- a/tests/test_service_v2.py +++ b/tests/test_service_v2.py @@ -1232,7 +1232,7 @@ def test_entity_get_value_1on1_with_proxy(service): responses.add( responses.GET, - f"{service.url}/{path}/IDPic/$value/", + f"{service.url}/Cars('Hadraplan')/IDPic/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEAF', status=200) @@ -1283,7 +1283,7 @@ def test_entity_get_value_with_proxy(service): responses.add( responses.GET, - f"{service.url}/{path}/$value/", + f"{service.url}/CarIDPics('Hadraplan')/$value/", headers={'Content-type': 'application/jpeg'}, body='DEADBEAF', status=200) From 0c70a6e8da51525b039ac1ca4936569b95f5ada2 Mon Sep 17 00:00:00 2001 From: Linda Jahn Date: Thu, 6 Apr 2023 15:25:19 +0200 Subject: [PATCH 8/8] chore: linting --- pyodata/v2/service.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyodata/v2/service.py b/pyodata/v2/service.py index 207850e9..0da34632 100644 --- a/pyodata/v2/service.py +++ b/pyodata/v2/service.py @@ -583,6 +583,7 @@ class EntityModifyRequest(ODataHttpRequest): and get the modified entity.""" ALLOWED_HTTP_METHODS = ['PATCH', 'PUT', 'MERGE'] + # pylint: disable=too-many-arguments def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH", encode_path=True): super(EntityModifyRequest, self).__init__(url, connection, handler)