Skip to content

Commit 9c47514

Browse files
committed
Add support for TypeDefinition in OData V4
TypeDefinitions are simply aliases for primitive types. They can be annotated. Thus, reimplementation of annotation for V4 is included in this commit. Children of ODataVersion have to specify which annotations are supported and how they should be processed. Annotation are parsed using function 'build_annotation'. As annotation are always tied to specific element and there is no centralized repository of annotations this function must return void. http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part3-csdl/odata-v4.0-errata03-os-part3-csdl-complete.html#_Toc453752574
1 parent 5ab9113 commit 9c47514

13 files changed

Lines changed: 258 additions & 115 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1414
- Support for OData V4 primitive types - Martin Miksik
1515
- Support for navigation property in OData v4 - Martin Miksik
1616
- Support for EntitySet in OData v4 - Martin Miksik
17+
- Support for TypeDefinition in OData v4 - Martin Miksik
1718

1819
### Changed
1920
- Implementation and naming schema of `from_etree` - Martin Miksik

pyodata/config.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
# pylint: disable=cyclic-import
99
if TYPE_CHECKING:
10-
from pyodata.model.elements import Typ # noqa
10+
from pyodata.model.elements import Typ, Annotation # noqa
1111

1212

1313
class ODATAVersion(ABC):
@@ -32,6 +32,11 @@ def primitive_types() -> List['Typ']:
3232
def build_functions() -> Dict[type, Callable]:
3333
""" Here we define which elements are supported and what is their python representation"""
3434

35+
@staticmethod
36+
@abstractmethod
37+
def annotations() -> Dict['Annotation', Callable]:
38+
""" Here we define which annotations are supported and what is their python representation"""
39+
3540

3641
class Config:
3742
# pylint: disable=too-many-instance-attributes,missing-docstring
@@ -73,8 +78,8 @@ def __init__(self,
7378
self._odata_version = odata_version
7479

7580
self._sap_value_helper_directions = None
76-
self._sap_annotation_value_list = None
7781
self._annotation_namespaces = None
82+
self._aliases: Dict[str, str] = dict()
7883

7984
def err_policy(self, error: ParserError) -> ErrorPolicy:
8085
""" Returns error policy for given error. If custom error policy fo error is set, then returns that."""
@@ -111,8 +116,12 @@ def sap_value_helper_directions(self):
111116
return self._sap_value_helper_directions
112117

113118
@property
114-
def sap_annotation_value_list(self):
115-
return self._sap_annotation_value_list
119+
def aliases(self) -> Dict[str, str]:
120+
return self._aliases
121+
122+
@aliases.setter
123+
def aliases(self, value: Dict[str, str]):
124+
self._aliases = value
116125

117126
@property
118127
def annotation_namespace(self):

pyodata/model/build_functions.py

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
import copy
55
import logging
66

7+
from pyodata.policies import ParserError
78
from pyodata.config import Config
8-
from pyodata.exceptions import PyODataParserError
9+
from pyodata.exceptions import PyODataParserError, PyODataModelError
910
from pyodata.model.elements import sap_attribute_get_bool, sap_attribute_get_string, StructType, StructTypeProperty, \
1011
Types, EntitySet, ValueHelper, ValueHelperParameter, FunctionImportParameter, \
11-
FunctionImport, metadata_attribute_get, EntityType, ComplexType, Annotation, build_element
12+
FunctionImport, metadata_attribute_get, EntityType, ComplexType, build_element
1213

13-
from pyodata.v4 import ODataV4
14-
import pyodata.v4.elements as v4
14+
# pylint: disable=cyclic-import
15+
# When using `import xxx as yyy` it is not a problem and we need this dependency
16+
import pyodata.v4 as v4
1517

1618

1719
def modlog():
@@ -122,39 +124,15 @@ def build_entity_set(config, entity_set_node):
122124
req_filter = sap_attribute_get_bool(entity_set_node, 'requires-filter', False)
123125
label = sap_attribute_get_string(entity_set_node, 'label')
124126

125-
if config.odata_version == ODataV4:
126-
return v4.EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable, pageable,
127-
topable, req_filter, label, nav_prop_bins)
127+
if config.odata_version == v4.ODataV4:
128+
return v4.EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable,
129+
pageable, topable, req_filter, label, nav_prop_bins)
128130

129131
return EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable, pageable,
130132
topable, req_filter, label)
131133

132134

133-
def build_external_annotation(config, annotations_node):
134-
target = annotations_node.get('Target')
135-
136-
if annotations_node.get('Qualifier'):
137-
modlog().warning('Ignoring qualified Annotations of %s', target)
138-
return
139-
140-
for annotation in annotations_node.xpath('edm:Annotation', namespaces=config.annotation_namespace):
141-
annot = build_element(Annotation, config, target=target, annotation_node=annotation)
142-
if annot is None:
143-
continue
144-
yield annot
145-
146-
147-
def build_annotation(config, target, annotation_node):
148-
term = annotation_node.get('Term')
149-
150-
if term in config.sap_annotation_value_list:
151-
return build_element(ValueHelper, config, target=target, annotation_node=annotation_node)
152-
153-
modlog().warning('Unsupported Annotation( %s )', term)
154-
return None
155-
156-
157-
def build_value_helper(config, target, annotation_node):
135+
def build_value_helper(config, target, annotation_node, schema):
158136
label = None
159137
collection_path = None
160138
search_supported = False
@@ -179,7 +157,31 @@ def build_value_helper(config, target, annotation_node):
179157
param.value_helper = value_helper
180158
value_helper._parameters.append(param)
181159

182-
return value_helper
160+
try:
161+
try:
162+
value_helper.entity_set = schema.entity_set(
163+
value_helper.collection_path, namespace=value_helper.element_namespace)
164+
except KeyError:
165+
raise RuntimeError(f'Entity Set {value_helper.collection_path} '
166+
f'for {value_helper} does not exist')
167+
168+
try:
169+
vh_type = schema.typ(value_helper.proprty_entity_type_name,
170+
namespace=value_helper.element_namespace)
171+
except KeyError:
172+
raise RuntimeError(f'Target Type {value_helper.proprty_entity_type_name} '
173+
f'of {value_helper} does not exist')
174+
175+
try:
176+
target_proprty = vh_type.proprty(value_helper.proprty_name)
177+
except KeyError:
178+
raise RuntimeError(f'Target Property {value_helper.proprty_name} '
179+
f'of {vh_type} as defined in {value_helper} does not exist')
180+
181+
value_helper.proprty = target_proprty
182+
target_proprty.value_helper = value_helper
183+
except (RuntimeError, PyODataModelError) as ex:
184+
config.err_policy(ParserError.ANNOTATION).resolve(ex)
183185

184186

185187
def build_value_helper_parameter(config, value_help_parameter_node):

pyodata/model/builder.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
"""Metadata Builder Implementation"""
22

3-
import collections
43
import io
54
from lxml import etree
65

@@ -24,9 +23,6 @@
2423
}
2524

2625

27-
SAP_ANNOTATION_VALUE_LIST = ['com.sap.vocabularies.Common.v1.ValueList']
28-
29-
3026
# pylint: disable=protected-access
3127
class MetadataBuilder:
3228
"""Metadata builder"""
@@ -99,7 +95,6 @@ def build(self):
9995
self._config.namespaces = namespaces
10096

10197
self._config._sap_value_helper_directions = SAP_VALUE_HELPER_DIRECTIONS
102-
self._config._sap_annotation_value_list = SAP_ANNOTATION_VALUE_LIST
10398
self._config._annotation_namespaces = ANNOTATION_NAMESPACES
10499

105100
self.update_alias(self.get_aliases(xml, self._config), self._config)
@@ -111,31 +106,32 @@ def build(self):
111106
def get_aliases(edmx, config: Config):
112107
"""Get all aliases"""
113108

114-
aliases = collections.defaultdict(set)
109+
# aliases = collections.defaultdict(set)
110+
aliases = {}
115111
edm_root = edmx.xpath('/edmx:Edmx', namespaces=config.namespaces)
116112
if edm_root:
117113
edm_ref_includes = edm_root[0].xpath('edmx:Reference/edmx:Include', namespaces=config.annotation_namespace)
118114
for ref_incl in edm_ref_includes:
119115
namespace = ref_incl.get('Namespace')
120116
alias = ref_incl.get('Alias')
121117
if namespace is not None and alias is not None:
122-
aliases[namespace].add(alias)
118+
aliases[alias] = namespace
119+
# aliases[namespace].add(alias)
123120

124121
return aliases
125122

126123
@staticmethod
127124
def update_alias(aliases, config: Config):
128125
"""Update config with aliases"""
129-
130-
namespace, suffix = config.sap_annotation_value_list[0].rsplit('.', 1)
131-
config._sap_annotation_value_list.extend([alias + '.' + suffix for alias in aliases[namespace]])
132-
126+
config.aliases = aliases
133127
helper_direction_keys = list(config.sap_value_helper_directions.keys())
128+
134129
for direction_key in helper_direction_keys:
135130
namespace, suffix = direction_key.rsplit('.', 1)
136-
for alias in aliases[namespace]:
137-
config._sap_value_helper_directions[alias + '.' + suffix] = \
138-
config.sap_value_helper_directions[direction_key]
131+
for alias, alias_namespace in aliases.items():
132+
if alias_namespace == namespace:
133+
config._sap_value_helper_directions[alias + '.' + suffix] = \
134+
config.sap_value_helper_directions[direction_key]
139135

140136

141137
def schema_from_xml(metadata_xml, namespaces=None):

0 commit comments

Comments
 (0)