Skip to content
Open
17 changes: 10 additions & 7 deletions odata/context.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-

import logging
from typing import Union

from odata.query import Query
from odata.connection import ODataConnection
Expand Down Expand Up @@ -49,7 +50,7 @@ def delete(self, entity):
entity.__odata__.persisted = False
self.log.info(u'Success')

def save(self, entity, force_refresh=True, extra_headers=None):
def save(self, entity, force_refresh: bool = True, extra_headers=None, omit_null_props: Union[bool, list[str]] = False):
"""
Creates a POST or PATCH call to the service. If the entity already has
a primary key, an update is called. Otherwise the entity is inserted
Expand All @@ -58,19 +59,21 @@ def save(self, entity, force_refresh=True, extra_headers=None):
:param entity: Model instance to insert or update
:type entity: EntityBase
:param force_refresh: Read full entity data again from service after PATCH call
:param omit_null_props: True/False or a list of properties to omit.
If set to true or given a list of properties these will be ommited from the request if they're set to None/null
:param extra_headers: Add custom headers on patch, post (Example:B1S-ReplaceCollectionsOnPatch=true)
:raises ODataConnectionError: Invalid data or serverside error. Server returned an HTTP error code
"""

if self.is_entity_saved(entity):
self._update_existing(entity, force_refresh=force_refresh, extra_headers=extra_headers)
self._update_existing(entity, force_refresh=force_refresh, extra_headers=extra_headers, omit_null_props=omit_null_props)
else:
self._insert_new(entity)
self._insert_new(entity, omit_null_props=omit_null_props)

def is_entity_saved(self, entity):
return entity.__odata__.persisted

def _insert_new(self, entity):
def _insert_new(self, entity, omit_null_props: Union[bool, list[str]] = False):
"""
Creates a POST call to the service, sending the complete new entity

Expand All @@ -84,7 +87,7 @@ def _insert_new(self, entity):
self.log.info(u'Saving new entity')

es = entity.__odata__
insert_data = es.data_for_insert()
insert_data = es.data_for_insert(omit_null_props)
saved_data = self.connection.execute_post(url, insert_data)
es.reset()
es.connection = self.connection
Expand All @@ -95,7 +98,7 @@ def _insert_new(self, entity):

self.log.info(u'Success')

def _update_existing(self, entity, force_refresh=True, extra_headers=None):
def _update_existing(self, entity, force_refresh=True, extra_headers=None, omit_null_props: Union[bool, list[str]] = False):
"""
Creates a PATCH call to the service, sending only the modified values

Expand All @@ -106,7 +109,7 @@ def _update_existing(self, entity, force_refresh=True, extra_headers=None):
msg = 'Cannot update Entity that does not belong to EntitySet: {0}'.format(entity)
raise ODataError(msg)

patch_data = es.data_for_update()
patch_data = es.data_for_update(omit_null_props)

if len([i for i in patch_data if not i.startswith('@')]) == 0:
self.log.debug(u'Nothing to update: {0}'.format(entity))
Expand Down
8 changes: 5 additions & 3 deletions odata/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
import logging
import sys
import urllib.parse
from typing import Optional, TypeVar
from typing import Optional, TypeVar, Union

import rich
import rich.console
Expand Down Expand Up @@ -244,14 +244,16 @@ def delete(self, entity):
"""
return self.default_context.delete(entity)

def save(self, entity, force_refresh=True):
def save(self, entity, force_refresh=True, omit_null_props: Union[bool, list[str]] = False):
"""
Creates a POST or PATCH call to the service. If the entity already has
a primary key, an update is called. Otherwise the entity is inserted
as new. Updating an entity will only send the changed values

:param entity: Model instance to insert or update
:param omit_null_props: True/False or a list of properties to omit.
If set to true or given a list of properties these will be ommited from the request if they're set to None/null
:param force_refresh: Read full entity data again from service after PATCH call
:raises ODataConnectionError: Invalid data or serverside error. Server returned an HTTP error code
"""
return self.default_context.save(entity, force_refresh=force_refresh)
return self.default_context.save(entity, force_refresh=force_refresh, omit_null_props=omit_null_props)
36 changes: 29 additions & 7 deletions odata/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import inspect
import itertools
from collections import OrderedDict
from typing import Optional
from typing import Optional, Union

import rich
import rich.panel
Expand Down Expand Up @@ -188,10 +188,10 @@ def set_property_dirty(self, prop):
if prop.name not in self.dirty:
self.dirty.append(prop.name)

def data_for_insert(self):
return self._clean_new_entity(self.entity)
def data_for_insert(self, omit_null_props: Union[bool, list[str]] = False):
return self._clean_new_entity(self.entity, omit_null_props)

def data_for_update(self):
def data_for_update(self, omit_null_props: Union[bool, list[str]] = False):
update_data = OrderedDict()
update_data['@odata.type'] = self.entity.__odata_type__

Expand All @@ -211,9 +211,23 @@ def data_for_update(self):
update_data[key] = [i.__odata__.id for i in value]
else:
update_data[key] = value.__odata__.id
return update_data

def _clean_new_entity(self, entity):
return EntityState._filter_null_properties(update_data, omit_null_props)

@staticmethod
def _filter_null_properties(properties: dict, omit_null_props: Union[bool, list[str]]) -> dict:
if omit_null_props is True or (type(omit_null_props) == list and len(omit_null_props) > 0):
filtered_properties = {}
for prop_name, prop in properties.items():
if omit_null_props is True or prop_name in omit_null_props:
# Should omit unless not None
if prop is not None:
filtered_properties[prop_name] = prop
return filtered_properties
else:
return properties

def _clean_new_entity(self, entity, omit_null_props: Union[str, list[str]] = False):
""":type entity: odata.entity.EntityBase """
insert_data = OrderedDict()
insert_data['@odata.type'] = entity.__odata_type__
Expand Down Expand Up @@ -259,7 +273,15 @@ def _clean_new_entity(self, entity):
else:
if value.__odata__.id:
insert_data['{0}@odata.bind'.format(prop.name)] = value.__odata__.id

# Put the foreign key back into the request for compatibility with
# systems that don't handle {entity} odata.bind correctly
try:
insert_data[prop.foreign_key] = getattr(value, prop.foreign_key)
except:
pass

else:
insert_data[prop.name] = self._clean_new_entity(value)

return insert_data
return EntityState._filter_null_properties(insert_data, omit_null_props)