Skip to content

Commit 75100cb

Browse files
committed
Change parsing of path values for navigation property bindings
Target Path can be (and is) bit more complex than just namespace and target name http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html#sec_IdentifierandPathValues
1 parent 9837b3a commit 75100cb

File tree

5 files changed

+50
-38
lines changed

5 files changed

+50
-38
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2525
- Implementation and naming schema of `from_etree` - Martin Miksik
2626
- Build functions of struct types now handle invalid metadata independently. - Martin Miksik
2727
- Default value of precision if non is provided in metadata - Martin Miksik
28+
- Parsing of path values for navigation property bindings - Martin Miksik
2829

2930
### Fixed
3031
- make sure configured error policies are applied for Annotations referencing

Diff for: pyodata/model/elements.py

+13-6
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,19 @@ def name(self):
121121

122122
@staticmethod
123123
def parse(value):
124-
parts = value.split('.')
125-
126-
if len(parts) == 1:
127-
return IdentifierInfo(None, value)
128-
129-
return IdentifierInfo('.'.join(parts[:-1]), parts[-1])
124+
segments = value.split('/')
125+
path = []
126+
for segment in segments:
127+
parts = segment.split('.')
128+
129+
if len(parts) == 1:
130+
path.append(IdentifierInfo(None, parts[-1]))
131+
else:
132+
path.append(IdentifierInfo('.'.join(parts[:-1]), parts[-1]))
133+
134+
if len(path) == 1:
135+
return path[0]
136+
return path
130137

131138

132139
class Types:

Diff for: pyodata/v4/build_functions.py

+23-8
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010
from pyodata.exceptions import PyODataParserError, PyODataModelError
1111
from pyodata.model.build_functions import build_entity_set
1212
from pyodata.model.elements import ComplexType, Schema, NullType, build_element, EntityType, Types, \
13-
StructTypeProperty, build_annotation, Typ
13+
StructTypeProperty, build_annotation, Typ, Identifier
1414
from pyodata.policies import ParserError
1515
from pyodata.v4.elements import NavigationTypeProperty, NullProperty, ReferentialConstraint, \
16-
NavigationPropertyBinding, to_path_info, EntitySet, Unit, EnumMember, EnumType
16+
NavigationPropertyBinding, EntitySet, Unit, EnumMember, EnumType
1717

1818

1919
# pylint: disable=protected-access,too-many-locals,too-many-branches,too-many-statements
@@ -115,16 +115,29 @@ def build_schema(config: Config, schema_nodes):
115115
config.err_policy(ParserError.ENTITY_SET).resolve(ex)
116116

117117
# After all entity sets are parsed resolve the individual bindings among them and entity types
118+
entity_set: EntitySet
118119
for entity_set in schema.entity_sets:
120+
nav_prop_bin: NavigationPropertyBinding
119121
for nav_prop_bin in entity_set.navigation_property_bindings:
120-
path_info = nav_prop_bin.path_info
121122
try:
122-
nav_prop_bin.path = schema.entity_type(path_info.type,
123-
namespace=path_info.namespace).nav_proprty(path_info.proprty)
124-
nav_prop_bin.target = schema.entity_set(nav_prop_bin.target_info)
123+
identifiers = nav_prop_bin.path_info
124+
entity_identifier = identifiers[0] if isinstance(identifiers, list) else entity_set.entity_type_info
125+
entity = schema.entity_type(entity_identifier.name, namespace=entity_identifier.namespace)
126+
name = identifiers[-1].name if isinstance(identifiers, list) else identifiers.name
127+
nav_prop_bin.path = entity.nav_proprty(name)
128+
129+
identifiers = nav_prop_bin.target_info
130+
if isinstance(identifiers, list):
131+
name = identifiers[-1].name
132+
namespace = identifiers[-1].namespace
133+
else:
134+
name = identifiers.name
135+
namespace = identifiers.namespace
136+
137+
nav_prop_bin.target = schema.entity_set(name, namespace)
125138
except PyODataModelError as ex:
126139
config.err_policy(ParserError.NAVIGATION_PROPERTY_BIDING).resolve(ex)
127-
nav_prop_bin.path = NullType(path_info.type)
140+
nav_prop_bin.path = NullType(nav_prop_bin.path_info[-1].name)
128141
nav_prop_bin.target = NullProperty(nav_prop_bin.target_info)
129142

130143
# TODO: Finally, process Annotation nodes when all Scheme nodes are completely processed.
@@ -148,7 +161,9 @@ def build_navigation_type_property(config: Config, node):
148161

149162

150163
def build_navigation_property_binding(config: Config, node, et_info):
151-
return NavigationPropertyBinding(to_path_info(node.get('Path'), et_info), node.get('Target'))
164+
# return NavigationPropertyBinding(to_path_info(node.get('Path'), et_info), node.get('Target'))
165+
166+
return NavigationPropertyBinding(Identifier.parse(node.get('Path')), Identifier.parse(node.get('Target')))
152167

153168

154169
def build_unit_annotation(config: Config, target: Typ, annotation_node):

Diff for: pyodata/v4/elements.py

+3-17
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
""" Repository of elements specific to the ODATA V4"""
22
from typing import Optional, List
33

4-
import collections
5-
64
from pyodata.model import elements
75
from pyodata.exceptions import PyODataModelError, PyODataException
8-
from pyodata.model.elements import VariableDeclaration, StructType, TypeInfo, Annotation, Identifier
6+
from pyodata.model.elements import VariableDeclaration, StructType, Annotation, Identifier, IdentifierInfo
97
from pyodata.model.type_traits import TypTraits
108
from pyodata.v4.type_traits import EnumTypTrait
119

12-
PathInfo = collections.namedtuple('PathInfo', 'namespace type proprty')
13-
14-
15-
def to_path_info(value: str, et_info: TypeInfo):
16-
""" Helper function for parsing Path attribute on NavigationPropertyBinding property """
17-
if '/' in value:
18-
parts = value.split('.')
19-
entity_name, property_name = parts[-1].split('/')
20-
return PathInfo('.'.join(parts[:-1]), entity_name, property_name)
21-
22-
return PathInfo(et_info.namespace, et_info.name, value)
23-
2410

2511
class NullProperty:
2612
""" Defines fallback class when parser is unable to process property defined in xml """
@@ -114,7 +100,7 @@ class NavigationPropertyBinding:
114100
https://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/csprd06/odata-csdl-xml-v4.01-csprd06.html#sec_NavigationPropertyBinding
115101
"""
116102

117-
def __init__(self, path_info: PathInfo, target_info: str):
103+
def __init__(self, path_info: [IdentifierInfo], target_info: str):
118104
self._path_info = path_info
119105
self._target_info = target_info
120106
self._path: Optional[NavigationTypeProperty] = None
@@ -127,7 +113,7 @@ def __str__(self):
127113
return f"{self.__class__.__name__}({self.path}, {self.target})"
128114

129115
@property
130-
def path_info(self) -> PathInfo:
116+
def path_info(self) -> [IdentifierInfo]:
131117
return self._path_info
132118

133119
@property

Diff for: tests/v4/test_build_functions.py

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import pytest
22
from lxml import etree
33

4-
from pyodata.model.elements import build_element, TypeInfo, Typ, ComplexType, EntityType, StructTypeProperty
4+
from pyodata.model.elements import build_element, TypeInfo, Typ, ComplexType, EntityType, StructTypeProperty, \
5+
IdentifierInfo
56
from pyodata.model.type_traits import EdmIntTypTraits, EdmBooleanTypTraits
67
from pyodata.v4 import NavigationTypeProperty, NavigationPropertyBinding
7-
from pyodata.v4.elements import PathInfo, Unit, EntitySet, EnumType
8+
from pyodata.v4.elements import Unit, EntitySet, EnumType
89

910

1011
class TestSchema:
@@ -73,15 +74,17 @@ def test_build_navigation_property_binding(config):
7374
et_info = TypeInfo('SampleService', 'Person', False)
7475
node = etree.fromstring('<NavigationPropertyBinding Path="Friends" Target="People" />')
7576
navigation_property_binding = build_element(NavigationPropertyBinding, config, node=node, et_info=et_info)
76-
assert navigation_property_binding.path_info == PathInfo('SampleService', 'Person', 'Friends')
77-
assert navigation_property_binding.target_info == 'People'
77+
assert navigation_property_binding.path_info == IdentifierInfo(None, 'Friends')
78+
assert navigation_property_binding.target_info == IdentifierInfo(None, 'People')
7879

7980
node = etree.fromstring(
8081
'<NavigationPropertyBinding Path="SampleService.Flight/Airline" Target="Airlines" />'
8182
)
8283
navigation_property_binding = build_element(NavigationPropertyBinding, config, node=node, et_info=et_info)
83-
assert navigation_property_binding.path_info == PathInfo('SampleService', 'Flight', 'Airline')
84-
assert navigation_property_binding.target_info == 'Airlines'
84+
assert navigation_property_binding.path_info == [
85+
IdentifierInfo('SampleService', 'Flight'),
86+
IdentifierInfo(None, 'Airline')]
87+
assert navigation_property_binding.target_info == IdentifierInfo(None, 'Airlines')
8588

8689

8790
def test_build_unit_annotation(config):
@@ -122,7 +125,7 @@ def test_build_entity_set_with_v4_builder(config, inline_namespaces):
122125
entity_set = build_element(EntitySet, config, entity_set_node=entity_set_node)
123126
assert entity_set.name == 'People'
124127
assert entity_set.entity_type_info == TypeInfo('SampleService', 'Person', False)
125-
assert entity_set.navigation_property_bindings[0].path_info == PathInfo('SampleService', 'Person', 'Friends')
128+
assert entity_set.navigation_property_bindings[0].path_info == IdentifierInfo(None, 'Friends')
126129

127130

128131
def test_build_enum_type(config, inline_namespaces):

0 commit comments

Comments
 (0)