Skip to content

Commit

Permalink
Add support for Computed properties
Browse files Browse the repository at this point in the history
  • Loading branch information
Tuomas Mursu committed May 10, 2017
1 parent abb85de commit bf3351a
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 1 deletion.
13 changes: 13 additions & 0 deletions odata/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ class MetaData(object):
'Edm.Guid': UUIDProperty,
}

_annotation_term_computed = 'Org.OData.Core.V1.Computed'

def __init__(self, service):
self.url = service.url + '$metadata/'
self.connection = service.default_context.connection
Expand Down Expand Up @@ -135,11 +137,13 @@ def _create_entities(self, all_types, entities, entity_sets, entity_base_class,

if property_type and issubclass(property_type, EnumType):
property_instance = EnumTypeProperty(prop_name, enum_class=property_type)
property_instance.is_computed_value = prop['is_computed_value']
else:
type_ = self.property_type_to_python(prop['type'])
type_options = {
'primary_key': prop['is_primary_key'],
'is_collection': prop['is_collection'],
'is_computed_value': prop['is_computed_value'],
}
property_instance = type_(prop_name, **type_options)
setattr(entity_class, prop_name, property_instance)
Expand Down Expand Up @@ -356,11 +360,20 @@ def _parse_entity(self, xmlq, entity_element, schema_name, schema_alias):
p_type = entity_property.attrib['Type']

is_collection, p_type = self._type_is_collection(p_type)
is_computed_value = False

for annotation in xmlq(entity_property, 'edm:Annotation'):
annotation_term = annotation.attrib.get('Term', '')
annotation_bool = annotation.attrib.get('Bool') == 'true'
if annotation_term == self._annotation_term_computed:
is_computed_value = annotation_bool

entity['properties'].append({
'name': p_name,
'type': p_type,
'is_primary_key': p_name in entity_pks,
'is_collection': is_collection,
'is_computed_value': is_computed_value,
})

for nav_property in xmlq(entity_element, 'edm:NavigationProperty'):
Expand Down
3 changes: 2 additions & 1 deletion odata/property.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,15 @@ class PropertyBase(object):
:param primary_key: This property is a primary key
:param is_collection: This property contains multiple values
"""
def __init__(self, name, primary_key=False, is_collection=False):
def __init__(self, name, primary_key=False, is_collection=False, is_computed_value=False):
"""
:type name: str
:type primary_key: bool
"""
self.name = name
self.primary_key = primary_key
self.is_collection = is_collection
self.is_computed_value = is_computed_value

def __repr__(self):
return '<Property({0})>'.format(self.name)
Expand Down
6 changes: 6 additions & 0 deletions odata/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ def data_for_update(self):
update_data['@odata.type'] = self.entity.__odata_type__

for _, prop in self.dirty_properties:
if prop.is_computed_value:
continue

update_data[prop.name] = self.data[prop.name]

for prop_name, prop in self.navigation_properties:
Expand All @@ -162,6 +165,9 @@ def _clean_new_entity(self, entity):

es = entity.__odata__
for _, prop in es.properties:
if prop.is_computed_value:
continue

insert_data[prop.name] = es[prop.name]

# Allow pk properties only if they have values
Expand Down
3 changes: 3 additions & 0 deletions odata/tests/demo_metadata.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<Property Name="Category" Type="Edm.String"/>
<Property Name="Price" Type="Edm.Decimal"/>
<Property Name="ColorSelection" Type="d.ColorSelection"/>
<Property Name="ExampleComputed" Type="Edm.Int32">
<Annotation Term="Org.OData.Core.V1.Computed" Bool="true" />
</Property>
</EntityType>
<EntityType Name="Manufacturer">
<Key>
Expand Down
25 changes: 25 additions & 0 deletions odata/tests/test_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import os
from unittest import TestCase
import json

import requests
import responses

from odata import ODataService
Expand Down Expand Up @@ -42,3 +44,26 @@ def test_read(self):
self.assertIn('Manufacturer', Service.entities)

self.assertIn('DemoUnboundAction', Service.actions)

def test_computed_value_in_insert(self):
with responses.RequestsMock() as rsps:
rsps.add(rsps.GET, 'http://demo.local/odata/$metadata/',
body=metadata_xml, content_type='text/xml')
Service = ODataService('http://demo.local/odata/', reflect_entities=True)

Product = Service.entities['Product']
test_product = Product()

def request_callback_part(request):
payload = json.loads(request.body)
self.assertNotIn('ExampleComputed', payload)
headers = {}
return requests.codes.created, headers, json.dumps(payload)

with responses.RequestsMock() as rsps:
rsps.add_callback(
rsps.POST, Product.__odata_url__(),
callback=request_callback_part,
content_type='application/json',
)
Service.save(test_product)

0 comments on commit bf3351a

Please sign in to comment.