Skip to content

Commit 47cc1bf

Browse files
authored
feat: make url encode default, add encoding for delete and get entity set (#243)
* add option to not encode path for get and update requests * doc: update encode path description * tests: rename path not encoded tests
1 parent 98b5c49 commit 47cc1bf

File tree

6 files changed

+268
-79
lines changed

6 files changed

+268
-79
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

77
## [Unreleased]
8+
- Service: Encode paths with entity keys with option to disable it - linda-sap
89

910
## [1.10.1]
1011

docs/usage/deleting.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,16 @@ or by passing the EntityKey object
4141
4242
key = EntityKey(service.schema.entity_type('Employee'), ID=23)
4343
request = service.entity_sets.Employees.delete_entity(key=key)
44-
request.execute()
44+
request.execute()
45+
46+
47+
Encode OData URL Path
48+
-------------------------------------------
49+
50+
By default the resource paths of requests are percent encoded. However if this is not what your API expects,
51+
you can disable the encoding with the variable encode_path by setting it to False.
52+
53+
54+
.. code-block:: python
55+
56+
request = service.entity_sets.Employees.delete_entity(key=key, encode_path=False)

docs/usage/querying.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,15 @@ these parameters by the method `custom(name: str, value: str)`.
154154
155155
employees = northwind.entity_sets.Employees.get_entities().custom('sap-client', '100').custom('$skiptoken', 'ABCD').top(10).execute()
156156
157+
Encode OData URL Path
158+
-------------------------------------------
159+
160+
By default the resource paths of requests are percent encoded. However if this is not what your API expects,
161+
you can disable the encoding with the variable encode_path by setting it to False.
162+
163+
.. code-block:: python
157164
165+
employee = northwind.entity_sets.Employees.get_entity(1, encode_path=False).execute()
158166
159167
(Experimental) Query server-side paginations using the __next field
160168
-------------------------------------------------------------------

docs/usage/updating.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,14 @@ then you can consider setting the default service's update method to *PUT*.
3434
.. code-block:: python
3535
3636
northwind.config['http']['update_method'] = 'PUT'
37+
38+
Encode OData URL Path
39+
-------------------------------------------
40+
41+
By default the resource paths of requests are percent encoded. However if this is not what your API expects,
42+
you can disable the encoding with the variable encode_path by setting it to False.
43+
44+
45+
.. code-block:: python
46+
47+
update_request = northwind.entity_sets.Customers.update_entity(CustomerID='ALFKI', encode_path=False)

pyodata/v2/service.py

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from email.parser import Parser
1414
from http.client import HTTPResponse
1515
from io import BytesIO
16-
from urllib.parse import urlencode
16+
from urllib.parse import urlencode, quote
1717

1818

1919
from pyodata.exceptions import HttpError, PyODataException, ExpressionError, ProgramError
@@ -371,14 +371,15 @@ def custom(self, name, value):
371371
class EntityGetRequest(ODataHttpRequest):
372372
"""Used for GET operations of a single entity"""
373373

374-
def __init__(self, handler, entity_key, entity_set_proxy):
374+
def __init__(self, handler, entity_key, entity_set_proxy, encode_path=True):
375375
super(EntityGetRequest, self).__init__(entity_set_proxy.service.url, entity_set_proxy.service.connection,
376376
handler)
377377
self._logger = logging.getLogger(LOGGER_NAME)
378378
self._entity_key = entity_key
379379
self._entity_set_proxy = entity_set_proxy
380380
self._select = None
381381
self._expand = None
382+
self._encode_path = encode_path
382383

383384
self._logger.debug('New instance of EntityGetRequest for last segment: %s', self._entity_set_proxy.last_segment)
384385

@@ -403,6 +404,8 @@ def expand(self, expand):
403404
return self
404405

405406
def get_path(self):
407+
if self.get_encode_path():
408+
return quote(self._entity_set_proxy.last_segment + self._entity_key.to_key_string())
406409
return self._entity_set_proxy.last_segment + self._entity_key.to_key_string()
407410

408411
def get_default_headers(self):
@@ -439,6 +442,10 @@ def stream_handler(response):
439442
connection,
440443
stream_handler)
441444

445+
def get_encode_path(self):
446+
"""Getter for encode path flag"""
447+
return self._encode_path
448+
442449

443450
class NavEntityGetRequest(EntityGetRequest):
444451
"""Used for GET operations of a single entity accessed via a Navigation property"""
@@ -545,22 +552,30 @@ def set(self, **kwargs):
545552
class EntityDeleteRequest(ODataHttpRequest):
546553
"""Used for deleting entity (DELETE operations on a single entity)"""
547554

548-
def __init__(self, url, connection, handler, entity_set, entity_key):
555+
def __init__(self, url, connection, handler, entity_set, entity_key, encode_path=True):
549556
super(EntityDeleteRequest, self).__init__(url, connection, handler)
550557
self._logger = logging.getLogger(LOGGER_NAME)
551558
self._entity_set = entity_set
552559
self._entity_key = entity_key
560+
self._encode_path = encode_path
553561

554562
self._logger.debug('New instance of EntityDeleteRequest for entity type: %s', entity_set.entity_type.name)
555563

556564
def get_path(self):
565+
if self.get_encode_path():
566+
return quote(self._entity_set.name + self._entity_key.to_key_string())
557567
return self._entity_set.name + self._entity_key.to_key_string()
558568

569+
def get_encode_path(self):
570+
"""Getter for encode path flag"""
571+
return self._encode_path
572+
559573
def get_method(self):
560574
# pylint: disable=no-self-use
561575
return 'DELETE'
562576

563577

578+
# pylint: disable=too-many-instance-attributes
564579
class EntityModifyRequest(ODataHttpRequest):
565580
"""Used for modyfing entities (UPDATE/MERGE operations on a single entity)
566581
@@ -569,12 +584,14 @@ class EntityModifyRequest(ODataHttpRequest):
569584

570585
ALLOWED_HTTP_METHODS = ['PATCH', 'PUT', 'MERGE']
571586

572-
def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH"):
587+
# pylint: disable=too-many-arguments
588+
def __init__(self, url, connection, handler, entity_set, entity_key, method="PATCH", encode_path=True):
573589
super(EntityModifyRequest, self).__init__(url, connection, handler)
574590
self._logger = logging.getLogger(LOGGER_NAME)
575591
self._entity_set = entity_set
576592
self._entity_type = entity_set.entity_type
577593
self._entity_key = entity_key
594+
self._encode_path = encode_path
578595

579596
self._method = method.upper()
580597
if self._method not in EntityModifyRequest.ALLOWED_HTTP_METHODS:
@@ -589,6 +606,8 @@ def __init__(self, url, connection, handler, entity_set, entity_key, method="PAT
589606
self._logger.debug('New instance of EntityModifyRequest for entity type: %s', self._entity_type.name)
590607

591608
def get_path(self):
609+
if self.get_encode_path():
610+
return quote(self._entity_set.name + self._entity_key.to_key_string())
592611
return self._entity_set.name + self._entity_key.to_key_string()
593612

594613
def get_method(self):
@@ -605,6 +624,10 @@ def get_body(self):
605624
def get_default_headers(self):
606625
return {'Accept': 'application/json', 'Content-Type': 'application/json'}
607626

627+
def get_encode_path(self):
628+
"""Getter for encode path flag"""
629+
return self._encode_path
630+
608631
def set(self, **kwargs):
609632
"""Set properties to be changed."""
610633

@@ -1307,10 +1330,11 @@ def __str__(self):
13071330
class GetEntitySetRequest(QueryRequest):
13081331
"""GET on EntitySet"""
13091332

1310-
def __init__(self, url, connection, handler, last_segment, entity_type):
1333+
def __init__(self, url, connection, handler, last_segment, entity_type, encode_path=True):
13111334
super(GetEntitySetRequest, self).__init__(url, connection, handler, last_segment)
13121335

13131336
self._entity_type = entity_type
1337+
self._encode_path = encode_path
13141338

13151339
def __getattr__(self, name):
13161340
proprty = self._entity_type.proprty(name)
@@ -1329,6 +1353,20 @@ def filter(self, *args, **kwargs):
13291353

13301354
return self
13311355

1356+
def get_path(self):
1357+
if self.get_encode_path():
1358+
path = quote(self._last_segment)
1359+
else:
1360+
path = self._last_segment
1361+
1362+
if self._count:
1363+
return urljoin(path, '/$count')
1364+
return path
1365+
1366+
def get_encode_path(self):
1367+
"""Getter for encode path flag"""
1368+
return self._encode_path
1369+
13321370

13331371
class ListWithTotalCount(list):
13341372
"""
@@ -1452,7 +1490,7 @@ def get_entity_handler(parent, nav_property, navigation_entity_set, response):
14521490
self,
14531491
nav_property)
14541492

1455-
def get_entity(self, key=None, **args):
1493+
def get_entity(self, key=None, encode_path=True, **args):
14561494
"""Get entity based on provided key properties"""
14571495

14581496
def get_entity_handler(response):
@@ -1474,9 +1512,9 @@ def get_entity_handler(response):
14741512

14751513
self._logger.info('Getting entity %s for key %s and args %s', self._entity_set.entity_type.name, key, args)
14761514

1477-
return EntityGetRequest(get_entity_handler, entity_key, self)
1515+
return EntityGetRequest(get_entity_handler, entity_key, self, encode_path=encode_path)
14781516

1479-
def get_entities(self):
1517+
def get_entities(self, encode_path=True):
14801518
"""Get some, potentially all entities"""
14811519

14821520
def get_entities_handler(response):
@@ -1513,7 +1551,8 @@ def get_entities_handler(response):
15131551

15141552
entity_set_name = self._alias if self._alias is not None else self._entity_set.name
15151553
return GetEntitySetRequest(self._service.url, self._service.connection, get_entities_handler,
1516-
self._parent_last_segment + entity_set_name, self._entity_set.entity_type)
1554+
self._parent_last_segment + entity_set_name, self._entity_set.entity_type,
1555+
encode_path=encode_path)
15171556

15181557
def create_entity(self, return_code=HTTP_CODE_CREATED):
15191558
"""Creates a new entity in the given entity-set."""
@@ -1533,7 +1572,7 @@ def create_entity_handler(response):
15331572
return EntityCreateRequest(self._service.url, self._service.connection, create_entity_handler, self._entity_set,
15341573
self.last_segment)
15351574

1536-
def update_entity(self, key=None, method=None, **kwargs):
1575+
def update_entity(self, key=None, method=None, encode_path=True, **kwargs):
15371576
"""Updates an existing entity in the given entity-set."""
15381577

15391578
def update_entity_handler(response):
@@ -1554,9 +1593,9 @@ def update_entity_handler(response):
15541593
method = self._service.config['http']['update_method']
15551594

15561595
return EntityModifyRequest(self._service.url, self._service.connection, update_entity_handler, self._entity_set,
1557-
entity_key, method=method)
1596+
entity_key, method=method, encode_path=encode_path)
15581597

1559-
def delete_entity(self, key: EntityKey = None, **kwargs):
1598+
def delete_entity(self, key: EntityKey = None, encode_path=True, **kwargs):
15601599
"""Delete the entity"""
15611600

15621601
def delete_entity_handler(response):
@@ -1573,7 +1612,7 @@ def delete_entity_handler(response):
15731612
entity_key = EntityKey(self._entity_set.entity_type, key, **kwargs)
15741613

15751614
return EntityDeleteRequest(self._service.url, self._service.connection, delete_entity_handler, self._entity_set,
1576-
entity_key)
1615+
entity_key, encode_path=encode_path)
15771616

15781617

15791618
# pylint: disable=too-few-public-methods

0 commit comments

Comments
 (0)