Skip to content

Commit aa68a10

Browse files
Martin Miksikmamiksik
authored andcommitted
Change the implementation of dynamic dispatch function from_etree
MixinFromEtree was removed in favour of the function called build_element. This is because A) Not always etree was passed, so the name was misleading. B) The purpose of splitting model and parse function was to be able to reuse certain part of code among different version of ODATA, but that was not fully possible with the previous solution as you had to import given element's class(which could be different for each version). Thus, you would have to introduce unnecessary inherency to solve this. New function build_element takes two positional arguments and rest is kwargs. First one is element's class or element's class name as a string. Element class is preferred as it makes refactoring easier and does not introduce magical strings to the code. The name string option is here to solve those cases when you cant import needed element class due to version differences. Second argument is config.
1 parent f2b854b commit aa68a10

File tree

13 files changed

+401
-379
lines changed

13 files changed

+401
-379
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1313
- Separate type repositories for individual versions of OData - Martin Miksik
1414
- Support for OData V4 primitive types - Martin Miksik
1515

16+
### Changed
17+
- Implementation and naming schema of `from_etree` - Martin Miksik
18+
1619
### Fixed
1720
- make sure configured error policies are applied for Annotations referencing
1821
unknown type/member - Martin Miksik

pyodata/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ def __init__(self):
2222
# Separate dictionary of all registered types (primitive, complex and collection variants) for each child
2323
Types: Dict[str, 'Typ'] = dict()
2424

25+
2526
@staticmethod
2627
@abstractmethod
2728
def primitive_types() -> List['Typ']:
2829
""" Here we define which primitive types are supported and what is their python representation"""
2930

3031
@staticmethod
3132
@abstractmethod
32-
def from_etree_callbacks() -> Dict[object, Callable]:
33+
def build_functions() -> Dict[type, Callable]:
3334
""" Here we define which elements are supported and what is their python representation"""
3435

3536

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
""" Reusable implementation of from_etree methods for the most of edm elements """
1+
""" Reusable implementation of build functions for the most of edm elements """
22

33
# pylint: disable=unused-argument, missing-docstring, invalid-name
44
import copy
@@ -7,17 +7,16 @@
77
from pyodata.config import Config
88
from pyodata.exceptions import PyODataParserError, PyODataModelError
99
from pyodata.model.elements import sap_attribute_get_bool, sap_attribute_get_string, StructType, StructTypeProperty, \
10-
NavigationTypeProperty, Identifier, Types, EnumType, EnumMember, EntitySet, EndRole, ReferentialConstraint, \
11-
PrincipalRole, DependentRole, Association, AssociationSetEndRole, AssociationSet, \
12-
ValueHelper, ValueHelperParameter, FunctionImportParameter, \
13-
FunctionImport, metadata_attribute_get, EntityType, ComplexType, Annotation
10+
Identifier, Types, EnumType, EnumMember, EntitySet, ReferentialConstraint, PrincipalRole, DependentRole, \
11+
ValueHelper, ValueHelperParameter, FunctionImportParameter, FunctionImport, metadata_attribute_get, EntityType, \
12+
ComplexType, Annotation, build_element, Association, EndRole, AssociationSetEndRole, AssociationSet
1413

1514

1615
def modlog():
1716
return logging.getLogger("callbacks")
1817

1918

20-
def struct_type_property_from_etree(entity_type_property_node, config: Config):
19+
def build_struct_type_property(config: Config, entity_type_property_node):
2120
return StructTypeProperty(
2221
entity_type_property_node.get('Name'),
2322
Types.parse_type_name(entity_type_property_node.get('Type')),
@@ -41,7 +40,7 @@ def struct_type_property_from_etree(entity_type_property_node, config: Config):
4140

4241

4342
# pylint: disable=protected-access
44-
def struct_type_from_etree(type_node, config: Config, typ, schema=None):
43+
def build_struct_type(config: Config, type_node, typ, schema=None):
4544
name = type_node.get('Name')
4645
base_type = type_node.get('BaseType')
4746

@@ -60,7 +59,7 @@ def struct_type_from_etree(type_node, config: Config, typ, schema=None):
6059
stype._name = name
6160

6261
for proprty in type_node.xpath('edm:Property', namespaces=config.namespaces):
63-
stp = StructTypeProperty.from_etree(proprty, config)
62+
stp = build_element(StructTypeProperty, config, entity_type_property_node=proprty)
6463

6564
if stp.name in stype._properties:
6665
raise KeyError('{0} already has property {1}'.format(stype, stp.name))
@@ -77,19 +76,19 @@ def struct_type_from_etree(type_node, config: Config, typ, schema=None):
7776
return stype
7877

7978

80-
def complex_type_from_etree(etree, config: Config, schema=None):
81-
return StructType.from_etree(etree, config, typ=ComplexType, schema=schema)
79+
def build_complex_type(config: Config, type_node, schema=None):
80+
return build_element(StructType, config, type_node=type_node, typ=ComplexType, schema=schema)
8281

8382

8483
# pylint: disable=protected-access
85-
def entity_type_from_etree(etree, config: Config, schema=None):
86-
etype = StructType.from_etree(etree, config, typ=EntityType, schema=schema)
84+
def build_entity_type(config: Config, type_node, schema=None):
85+
etype = build_element(StructType, config, type_node=type_node, typ=EntityType, schema=schema)
8786

88-
for proprty in etree.xpath('edm:Key/edm:PropertyRef', namespaces=config.namespaces):
87+
for proprty in type_node.xpath('edm:Key/edm:PropertyRef', namespaces=config.namespaces):
8988
etype._key.append(etype.proprty(proprty.get('Name')))
9089

91-
for proprty in etree.xpath('edm:NavigationProperty', namespaces=config.namespaces):
92-
navp = NavigationTypeProperty.from_etree(proprty, config)
90+
for proprty in type_node.xpath('edm:NavigationProperty', namespaces=config.namespaces):
91+
navp = build_element('NavigationTypeProperty', config, node=proprty)
9392

9493
if navp.name in etype._nav_properties:
9594
raise KeyError('{0} already has navigation property {1}'.format(etype, navp.name))
@@ -100,7 +99,7 @@ def entity_type_from_etree(etree, config: Config, schema=None):
10099

101100

102101
# pylint: disable=protected-access, too-many-locals
103-
def enum_type_from_etree(type_node, config: Config, namespace):
102+
def build_enum_type(config: Config, type_node, namespace):
104103
ename = type_node.get('Name')
105104
is_flags = type_node.get('IsFlags')
106105

@@ -149,7 +148,7 @@ def enum_type_from_etree(type_node, config: Config, namespace):
149148
return etype
150149

151150

152-
def entity_set_from_etree(entity_set_node, config):
151+
def build_entity_set(config, entity_set_node):
153152
name = entity_set_node.get('Name')
154153
et_info = Types.parse_type_name(entity_set_node.get('EntityType'))
155154

@@ -169,15 +168,15 @@ def entity_set_from_etree(entity_set_node, config):
169168
topable, req_filter, label)
170169

171170

172-
def end_role_from_etree(end_role_node, config):
171+
def build_end_role(config: Config, end_role_node):
173172
entity_type_info = Types.parse_type_name(end_role_node.get('Type'))
174173
multiplicity = end_role_node.get('Multiplicity')
175174
role = end_role_node.get('Role')
176175

177176
return EndRole(entity_type_info, multiplicity, role)
178177

179178

180-
def referential_constraint_from_etree(referential_constraint_node, config: Config):
179+
def build_referential_constraint(config: Config, referential_constraint_node):
181180
principal = referential_constraint_node.xpath('edm:Principal', namespaces=config.namespaces)
182181
if len(principal) != 1:
183182
raise RuntimeError('Referential constraint must contain exactly one principal element')
@@ -212,12 +211,12 @@ def referential_constraint_from_etree(referential_constraint_node, config: Confi
212211

213212

214213
# pylint: disable=protected-access
215-
def association_from_etree(association_node, config: Config):
214+
def build_association(config: Config, association_node):
216215
name = association_node.get('Name')
217216
association = Association(name)
218217

219218
for end in association_node.xpath('edm:End', namespaces=config.namespaces):
220-
end_role = EndRole.from_etree(end, config)
219+
end_role = build_element(EndRole, config, end_role_node=end)
221220
if end_role.entity_type_info is None:
222221
raise RuntimeError('End type is not specified in the association {}'.format(name))
223222
association._end_roles.append(end_role)
@@ -232,21 +231,21 @@ def association_from_etree(association_node, config: Config):
232231
if not refer:
233232
referential_constraint = None
234233
else:
235-
referential_constraint = ReferentialConstraint.from_etree(refer[0], config)
234+
referential_constraint = build_element(ReferentialConstraint, config, referential_constraint_node=refer[0])
236235

237236
association._referential_constraint = referential_constraint
238237

239238
return association
240239

241240

242-
def association_set_end_role_from_etree(end_node, config):
241+
def build_association_set_end_role(config: Config, end_node):
243242
role = end_node.get('Role')
244243
entity_set = end_node.get('EntitySet')
245244

246245
return AssociationSetEndRole(role, entity_set)
247246

248247

249-
def association_set_from_etree(association_set_node, config: Config):
248+
def build_association_set(config: Config, association_set_node):
250249
end_roles = []
251250
name = association_set_node.get('Name')
252251
association = Identifier.parse(association_set_node.get('Association'))
@@ -256,36 +255,36 @@ def association_set_from_etree(association_set_node, config: Config):
256255
raise PyODataModelError('Association {} cannot have more than 2 end roles'.format(name))
257256

258257
for end_role in end_roles_list:
259-
end_roles.append(AssociationSetEndRole.from_etree(end_role, config))
258+
end_roles.append(build_element(AssociationSetEndRole, config, end_node=end_role))
260259

261260
return AssociationSet(name, association.name, association.namespace, end_roles)
262261

263262

264-
def external_annotation_from_etree(annotations_node, config):
263+
def build_external_annotation(config, annotations_node):
265264
target = annotations_node.get('Target')
266265

267266
if annotations_node.get('Qualifier'):
268267
modlog().warning('Ignoring qualified Annotations of %s', target)
269268
return
270269

271270
for annotation in annotations_node.xpath('edm:Annotation', namespaces=config.annotation_namespace):
272-
annot = Annotation.from_etree(target, config, annotation_node=annotation)
271+
annot = build_element(Annotation, config, target=target, annotation_node=annotation)
273272
if annot is None:
274273
continue
275274
yield annot
276275

277276

278-
def annotation_from_etree(target, config, annotation_node):
277+
def build_annotation(config, target, annotation_node):
279278
term = annotation_node.get('Term')
280279

281280
if term in config.sap_annotation_value_list:
282-
return ValueHelper.from_etree(target, config, annotation_node=annotation_node)
281+
return build_element(ValueHelper, config, target=target, annotation_node=annotation_node)
283282

284283
modlog().warning('Unsupported Annotation( %s )', term)
285284
return None
286285

287286

288-
def value_helper_from_etree(target, config, annotation_node):
287+
def build_value_helper(config, target, annotation_node):
289288
label = None
290289
collection_path = None
291290
search_supported = False
@@ -306,14 +305,14 @@ def value_helper_from_etree(target, config, annotation_node):
306305

307306
if params_node is not None:
308307
for prm in params_node.xpath('edm:Collection/edm:Record', namespaces=config.annotation_namespace):
309-
param = ValueHelperParameter.from_etree(prm, config)
308+
param = build_element(ValueHelperParameter, config, value_help_parameter_node=prm)
310309
param.value_helper = value_helper
311310
value_helper._parameters.append(param)
312311

313312
return value_helper
314313

315314

316-
def value_helper_parameter_from_etree(value_help_parameter_node, config):
315+
def build_value_helper_parameter(config, value_help_parameter_node):
317316
typ = value_help_parameter_node.get('Type')
318317
direction = config.sap_value_helper_directions[typ]
319318
local_prop_name = None
@@ -329,7 +328,7 @@ def value_helper_parameter_from_etree(value_help_parameter_node, config):
329328

330329

331330
# pylint: disable=too-many-locals
332-
def function_import_from_etree(function_import_node, config: Config):
331+
def build_function_import(config: Config, function_import_node):
333332
name = function_import_node.get('Name')
334333
entity_set = function_import_node.get('EntitySet')
335334
http_method = metadata_attribute_get(function_import_node, 'HttpMethod')

pyodata/model/builder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from pyodata.config import Config
88
from pyodata.exceptions import PyODataParserError
9-
from pyodata.model.elements import ValueHelperParameter, Schema
9+
from pyodata.model.elements import ValueHelperParameter, Schema, build_element
1010
import pyodata.v2 as v2
1111

1212

@@ -105,7 +105,7 @@ def build(self):
105105
self.update_alias(self.get_aliases(xml, self._config), self._config)
106106

107107
edm_schemas = xml.xpath('/edmx:Edmx/edmx:DataServices/edm:Schema', namespaces=self._config.namespaces)
108-
return Schema.from_etree(edm_schemas, self._config)
108+
return build_element(Schema, self._config, schema_nodes=edm_schemas)
109109

110110
@staticmethod
111111
def get_aliases(edmx, config: Config):

0 commit comments

Comments
 (0)