Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Making it possible to add custom options #41

Open
wants to merge 57 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
4f06367
Adding the possiblity for custom options
suhrawardi Apr 15, 2020
bf0dd1b
Hacking in an OData scope
suhrawardi Jun 24, 2020
ac4092d
Made it work for me
suhrawardi Jun 30, 2020
a61e543
Fixing the patch url
suhrawardi Jun 30, 2020
56d0ca8
Added a GET
suhrawardi Jul 1, 2020
cd2217d
returning the shit
suhrawardi Jul 1, 2020
70b99c2
Removed clutter
suhrawardi Jul 1, 2020
f527309
Fixing a case
suhrawardi Jul 2, 2020
ac10d5c
Make it persisted if it has an id already
suhrawardi Jul 14, 2020
870cdfd
Proper persisting
suhrawardi Jul 14, 2020
29caf3e
Caching metadata
suhrawardi Jul 20, 2020
787cd87
s/info/debug
suhrawardi Aug 24, 2020
df46d98
Not None check
suhrawardi Aug 24, 2020
e775774
Flush metadata cache
suhrawardi Nov 23, 2020
ff71e10
Flush is a class method
suhrawardi Nov 23, 2020
5aff686
Added persisted_id
suhrawardi Jan 11, 2021
88dc3de
Also resetting persisted_id
suhrawardi Jan 11, 2021
26daf04
Fixing a memory leak
suhrawardi Feb 16, 2021
ad92cbd
Closing response on failure
suhrawardi Feb 22, 2021
306a1c0
even better response closing
suhrawardi Feb 22, 2021
c8f308b
Even more response closing forced
suhrawardi Feb 22, 2021
3a4553b
Made it an OrderedDict
suhrawardi Apr 7, 2021
38ee446
Don't pop the FK values
suhrawardi Apr 7, 2021
112ce7f
Don't need to log this
suhrawardi Apr 7, 2021
00aedce
Bugfix
suhrawardi May 3, 2021
4018df0
State improved
suhrawardi May 3, 2021
aa0a00c
Added .github unittest action
suhrawardi May 5, 2021
8765f48
Improved the State
suhrawardi May 5, 2021
c14b5f5
Fix an OData state thingie
suhrawardi May 5, 2021
f7b1949
Also log response for debug level
suhrawardi Oct 11, 2021
109e902
With full metadata
suhrawardi Oct 11, 2021
f4ba4c8
metadata values
suhrawardi Oct 12, 2021
74b1520
Graceful
suhrawardi Oct 14, 2021
839dc1e
gracefull
suhrawardi Oct 14, 2021
cd199b1
urling
suhrawardi Oct 25, 2021
49a1990
url
suhrawardi Oct 25, 2021
293bfb2
url
suhrawardi Oct 25, 2021
b5a366b
navigation
suhrawardi Oct 25, 2021
bc6fdc1
urling
suhrawardi Oct 26, 2021
d2986a5
urling
suhrawardi Oct 26, 2021
2c77e36
urling
suhrawardi Oct 26, 2021
620c93d
urling
suhrawardi Oct 26, 2021
8cb30c2
urling
suhrawardi Oct 26, 2021
3c5796d
Bugfix
suhrawardi Oct 28, 2021
b45f0f1
Bugfix
suhrawardi Oct 28, 2021
aa30eaf
Bugfix
suhrawardi Oct 28, 2021
f47ce29
bugfix
suhrawardi Oct 28, 2021
b84e5d6
bugfix
suhrawardi Oct 28, 2021
58da7bd
Scoped inserts
suhrawardi Oct 28, 2021
4ed11f2
Fix
suhrawardi Oct 28, 2021
f6f1e74
Bugfix
suhrawardi Nov 8, 2021
b9d52ac
fix
suhrawardi Nov 8, 2021
80696eb
fix
suhrawardi Nov 8, 2021
8a8f883
Fixing relations
suhrawardi Dec 15, 2021
e5689e4
This might break things?
suhrawardi Jan 12, 2022
6bbb8bc
Bugfix
suhrawardi May 18, 2022
b79a43e
Bugfix
suhrawardi May 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/workflows/unittest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Python Unit Test

on: [push]

jobs:
build:
name: GitHub Action for Pytest

runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8]

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi

- name: Test with unittest
run: |
pytest ./odata/tests
142 changes: 94 additions & 48 deletions odata/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ def inner(*args, **kwargs):
class ODataConnection(object):

base_headers = {
'Accept': 'application/json',
'OData-Version': '4.0',
'User-Agent': 'python-odata {0}'.format(version),
}
Expand All @@ -38,6 +37,9 @@ def __init__(self, session=None, auth=None):
self.auth = auth
self.log = logging.getLogger('odata.connection')

def __del__(self):
self.session.close()

def _apply_options(self, kwargs):
kwargs['timeout'] = self.timeout

Expand Down Expand Up @@ -75,6 +77,7 @@ def _handle_odata_error(self, response):
response_ct = response.headers.get('content-type', '')

if 'application/json' in response_ct:
self.log.debug(u'JSON: {0}'.format(response.json()))
errordata = response.json()

if 'error' in errordata:
Expand All @@ -88,6 +91,9 @@ def _handle_odata_error(self, response):
ie = odata_error['innererror']
detailed_message = ie.get('message') or detailed_message

response.close()
self.log.info(u'Closed response on failure with HTTP status {0}'.format(code))

msg = ' | '.join([status_code, code, message, detailed_message])
err = ODataError(msg)
err.status_code = status_code
Expand All @@ -97,64 +103,104 @@ def _handle_odata_error(self, response):
raise err

def execute_get(self, url, params=None):
headers = {}
headers.update(self.base_headers)

self.log.info(u'GET {0}'.format(url))
if params:
self.log.info(u'Query: {0}'.format(params))

response = self._do_get(url, params=params, headers=headers)
self._handle_odata_error(response)
response_ct = response.headers.get('content-type', '')
if response.status_code == requests.codes.no_content:
return
if 'application/json' in response_ct:
data = response.json()
return data
else:
msg = u'Unsupported response Content-Type: {0}'.format(response_ct)
raise ODataError(msg)
try:
response = None
headers = {}
headers.update(self.base_headers)

self.log.info(u'GET {0}'.format(url))
if params:
self.log.info(u'Query: {0}'.format(params))

response = self._do_get(url, params=params, headers=headers)
self._handle_odata_error(response)
response_ct = response.headers.get('content-type', '')
if response.status_code == requests.codes.no_content:
return
if 'application/json' in response_ct:
self.log.debug(u'JSON: {0}'.format(response.json()))
return response.json()
else:
msg = u'Unsupported response Content-Type: {0}'.format(response_ct)
raise ODataError(msg)
except:
raise
finally:
if response:
response.close()
self.log.info(u'Closed GET response for {0}'.format(url))

def execute_post(self, url, data, params=None):
headers = {
'Content-Type': 'application/json',
}
headers.update(self.base_headers)
try:
response = None
headers = {
'Content-Type': 'application/json',
}
headers.update(self.base_headers)

data = json.dumps(data)
data = json.dumps(data)

self.log.info(u'POST {0}'.format(url))
self.log.info(u'Payload: {0}'.format(data))
self.log.info(u'POST {0}'.format(url))
self.log.info(u'Payload: {0}'.format(data))

response = self._do_post(url, data=data, headers=headers, params=params)
self._handle_odata_error(response)
response_ct = response.headers.get('content-type', '')
if response.status_code == requests.codes.no_content:
return
if 'application/json' in response_ct:
return response.json()
# no exceptions here, POSTing to Actions may not return data
response = self._do_post(url, data=data, headers=headers, params=params)
self._handle_odata_error(response)
response_ct = response.headers.get('content-type', '')
if response.status_code == requests.codes.no_content:
return
if 'application/json' in response_ct:
self.log.debug(u'JSON: {0}'.format(response.json()))
return response.json()
# no exceptions here, POSTing to Actions may not return data
except:
raise
finally:
if response:
response.close()
self.log.info(u'Closed POST response for {0}'.format(url))

def execute_patch(self, url, data):
headers = {
'Content-Type': 'application/json',
}
headers.update(self.base_headers)
try:
response = None
headers = {
'Content-Type': 'application/json',
}
headers.update(self.base_headers)

data = json.dumps(data)
data = json.dumps(data)

self.log.info(u'PATCH {0}'.format(url))
self.log.info(u'Payload: {0}'.format(data))
self.log.info(u'PATCH {0}'.format(url))
self.log.info(u'Payload: {0}'.format(data))

response = self._do_patch(url, data=data, headers=headers)
self._handle_odata_error(response)
response = self._do_patch(url, data=data, headers=headers)
self._handle_odata_error(response)
response_ct = response.headers.get('content-type', '')
if 'application/json' in response_ct:
self.log.debug(u'JSON: {0}'.format(response.json()))
return response.json()
except:
raise
finally:
if response:
response.close()
self.log.info(u'Closed PATCH response for {0}'.format(url))

def execute_delete(self, url):
headers = {}
headers.update(self.base_headers)
try:
response = None
headers = {}
headers.update(self.base_headers)

self.log.info(u'DELETE {0}'.format(url))
self.log.info(u'DELETE {0}'.format(url))

response = self._do_delete(url, headers=headers)
self._handle_odata_error(response)
response = self._do_delete(url, headers=headers)
self._handle_odata_error(response)
response_ct = response.headers.get('content-type', '')
if 'application/json' in response_ct:
self.log.debug(u'JSON: {0}'.format(response.json()))
except:
raise
finally:
if response:
response.close()
self.log.info(u'Closed DELETE response for {0}'.format(url))
43 changes: 33 additions & 10 deletions odata/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ def __init__(self, session=None, auth=None):
self.log = logging.getLogger('odata.context')
self.connection = ODataConnection(session=session, auth=auth)

def query(self, entitycls):
q = Query(entitycls, connection=self.connection)
def query(self, entitycls, options=None):
q = Query(entitycls, connection=self.connection, options=options)
return q

def call(self, action_or_function, **parameters):
Expand Down Expand Up @@ -44,11 +44,29 @@ def delete(self, entity):
:type entity: EntityBase
:raises ODataConnectionError: Delete not allowed or a serverside error. Server returned an HTTP error code
"""
self.log.info(u'Deleting entity: {0}'.format(entity))
self.log.debug(u'Deleting entity: {0}'.format(entity))
url = entity.__odata__.instance_url
self.connection.execute_delete(url)
entity.__odata__.persisted = False
self.log.info(u'Success')
entity.__odata__.persisted_id = None
self.log.debug(u'Success')

def get(self, entity):
"""
Creates a GET call to the service, fetching the entity

:type entity: EntityBase
"""
self.log.debug(u'Fetching entity: {0}'.format(entity))
url = entity.__odata__.instance_url
data = self.connection.execute_get(url)
entity.__odata__.reset()
if data is not None:
entity.__odata__.update(data)
entity.__odata__.persisted = True
entity.__odata__.persisted_id = entity.__odata__.id
self.log.debug(u'Success')
return entity

def save(self, entity, force_refresh=True):
"""
Expand Down Expand Up @@ -76,12 +94,16 @@ def _insert_new(self, entity):

:type entity: EntityBase
"""
url = entity.__odata_url__()

if entity.__odata__.odata_scope:
url = entity.__odata__.odata_scope
else:
url = entity.__odata_url__()
if url is None:
msg = 'Cannot insert Entity that does not belong to EntitySet: {0}'.format(entity)
raise ODataError(msg)

self.log.info(u'Saving new entity')
self.log.debug(u'Saving new entity')

es = entity.__odata__
insert_data = es.data_for_insert()
Expand All @@ -92,8 +114,9 @@ def _insert_new(self, entity):

if saved_data is not None:
es.update(saved_data)
es.persisted_id = es.id

self.log.info(u'Success')
self.log.debug(u'Success')

def _update_existing(self, entity, force_refresh=True):
"""
Expand All @@ -112,18 +135,18 @@ def _update_existing(self, entity, force_refresh=True):
self.log.debug(u'Nothing to update: {0}'.format(entity))
return

self.log.info(u'Updating existing entity: {0}'.format(entity))
self.log.debug(u'Updating existing entity: {0}'.format(entity))

url = es.instance_url

saved_data = self.connection.execute_patch(url, patch_data)
es.reset()

if saved_data is None and force_refresh:
self.log.info(u'Reloading entity from service')
self.log.debug(u'Reloading entity from service')
saved_data = self.connection.execute_get(url)

if saved_data is not None:
entity.__odata__.update(saved_data)

self.log.info(u'Success')
self.log.debug(u'Success')
Loading