Skip to content

Commit f2b854b

Browse files
committed
Add implementation of schema for OData V4
- Enum types is working - Complex type is working(including BaseType) - Entity type needs editing
1 parent 2b0f622 commit f2b854b

File tree

12 files changed

+475
-41
lines changed

12 files changed

+475
-41
lines changed

.pylintrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ confidence=
6565
# --enable=similarities". If you want to run only the classes checker, but have
6666
# no Warning level messages displayed, use"--disable=all --enable=classes
6767
# --disable=W"
68-
disable=locally-disabled
68+
disable=locally-disabled,duplicate-code
6969

70+
# As parts of definitions are the same even for different versions,
71+
# pylint detects them as duplicate code which it is not. Disabling pylint 'duplicate-code' inside module did not work.
7072

7173
[REPORTS]
7274

pyodata/model/elements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def from_etree(cls, etree, config: Config, **kwargs):
2929
raise PyODataParserError(f'{cls.__name__} is unsupported in {config.odata_version.__name__}')
3030

3131
if kwargs:
32-
return callback(etree, config, kwargs)
32+
return callback(etree, config, **kwargs)
3333

3434
return callback(etree, config)
3535

pyodata/model/from_etree_callbacks.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
""" Reusable implementation of from_etree methods for the most of edm elements """
22

33
# pylint: disable=unused-argument, missing-docstring, invalid-name
4+
import copy
45
import logging
56

67
from pyodata.config import Config
@@ -40,12 +41,23 @@ def struct_type_property_from_etree(entity_type_property_node, config: Config):
4041

4142

4243
# pylint: disable=protected-access
43-
def struct_type_from_etree(type_node, config: Config, kwargs):
44+
def struct_type_from_etree(type_node, config: Config, typ, schema=None):
4445
name = type_node.get('Name')
45-
label = sap_attribute_get_string(type_node, 'label')
46-
is_value_list = sap_attribute_get_bool(type_node, 'value-list', False)
46+
base_type = type_node.get('BaseType')
4747

48-
stype = kwargs['type'](name, label, is_value_list)
48+
if base_type is None:
49+
label = sap_attribute_get_string(type_node, 'label')
50+
is_value_list = sap_attribute_get_bool(type_node, 'value-list', False)
51+
stype = typ(name, label, is_value_list)
52+
else:
53+
base_type = Types.parse_type_name(base_type)
54+
55+
try:
56+
stype = copy.deepcopy(schema.get_type(base_type))
57+
except KeyError:
58+
raise PyODataParserError(f'BaseType \'{base_type.name}\' not found in schema')
59+
60+
stype._name = name
4961

5062
for proprty in type_node.xpath('edm:Property', namespaces=config.namespaces):
5163
stp = StructTypeProperty.from_etree(proprty, config)
@@ -59,23 +71,19 @@ def struct_type_from_etree(type_node, config: Config, kwargs):
5971
# all properites are loaded because
6072
# there might be links between them.
6173
for ctp in list(stype._properties.values()):
62-
ctp.struct_type = stype
74+
if ctp.struct_type is None: # TODO: Is it correct
75+
ctp.struct_type = stype
6376

6477
return stype
6578

6679

67-
def navigation_type_property_from_etree(node, config: Config):
68-
return NavigationTypeProperty(
69-
node.get('Name'), node.get('FromRole'), node.get('ToRole'), Identifier.parse(node.get('Relationship')))
70-
71-
72-
def complex_type_from_etree(etree, config: Config):
73-
return StructType.from_etree(etree, config, type=ComplexType)
80+
def complex_type_from_etree(etree, config: Config, schema=None):
81+
return StructType.from_etree(etree, config, typ=ComplexType, schema=schema)
7482

7583

7684
# pylint: disable=protected-access
77-
def entity_type_from_etree(etree, config: Config):
78-
etype = StructType.from_etree(etree, config, type=EntityType)
85+
def entity_type_from_etree(etree, config: Config, schema=None):
86+
etype = StructType.from_etree(etree, config, typ=EntityType, schema=schema)
7987

8088
for proprty in etree.xpath('edm:Key/edm:PropertyRef', namespaces=config.namespaces):
8189
etype._key.append(etype.proprty(proprty.get('Name')))
@@ -92,14 +100,18 @@ def entity_type_from_etree(etree, config: Config):
92100

93101

94102
# pylint: disable=protected-access, too-many-locals
95-
def enum_type_from_etree(type_node, config: Config, kwargs):
103+
def enum_type_from_etree(type_node, config: Config, namespace):
96104
ename = type_node.get('Name')
97105
is_flags = type_node.get('IsFlags')
98106

99-
namespace = kwargs['namespace']
107+
# namespace = kwargs['namespace']
100108

101109
underlying_type = type_node.get('UnderlyingType')
102110

111+
# https://docs.oasis-open.org/odata/odata-csdl-json/v4.01/csprd04/odata-csdl-json-v4.01-csprd04.html#sec_EnumerationType
112+
if underlying_type is None:
113+
underlying_type = 'Edm.Int32'
114+
103115
valid_types = {
104116
'Edm.Byte': [0, 2 ** 8 - 1],
105117
'Edm.Int16': [-2 ** 15, 2 ** 15 - 1],
@@ -263,8 +275,7 @@ def external_annotation_from_etree(annotations_node, config):
263275
yield annot
264276

265277

266-
def annotation_from_etree(target, config, kwargs):
267-
annotation_node = kwargs['annotation_node']
278+
def annotation_from_etree(target, config, annotation_node):
268279
term = annotation_node.get('Term')
269280

270281
if term in config.sap_annotation_value_list:
@@ -274,13 +285,12 @@ def annotation_from_etree(target, config, kwargs):
274285
return None
275286

276287

277-
def value_helper_from_etree(target, config, kwargs):
288+
def value_helper_from_etree(target, config, annotation_node):
278289
label = None
279290
collection_path = None
280291
search_supported = False
281292
params_node = None
282293

283-
annotation_node = kwargs['annotation_node']
284294
for prop_value in annotation_node.xpath('edm:Record/edm:PropertyValue', namespaces=config.annotation_namespace):
285295
rprop = prop_value.get('Property')
286296
if rprop == 'Label':

pyodata/v2/__init__.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818
NullAssociation
1919

2020
from pyodata.model.from_etree_callbacks import struct_type_property_from_etree, struct_type_from_etree, \
21-
navigation_type_property_from_etree, complex_type_from_etree, entity_type_from_etree, enum_type_from_etree, \
22-
entity_set_from_etree, end_role_from_etree, referential_constraint_from_etree, association_from_etree, \
23-
association_set_end_role_from_etree, association_set_from_etree, external_annotation_from_etree, \
24-
annotation_from_etree, value_helper_from_etree, value_helper_parameter_from_etree, function_import_from_etree
21+
entity_type_from_etree, enum_type_from_etree, entity_set_from_etree, end_role_from_etree, \
22+
referential_constraint_from_etree, association_from_etree, association_set_end_role_from_etree, \
23+
association_set_from_etree, external_annotation_from_etree, annotation_from_etree, value_helper_from_etree, \
24+
value_helper_parameter_from_etree, function_import_from_etree, complex_type_from_etree
25+
26+
27+
from pyodata.v2.from_etree_callbacks import navigation_type_property_from_etree
2528

2629

2730
def modlog():

pyodata/v2/from_etree_callbacks.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# pylint: disable=missing-docstring,invalid-name,unused-argument
2+
3+
from pyodata.model.elements import NavigationTypeProperty, Identifier
4+
from pyodata.config import Config
5+
6+
7+
def navigation_type_property_from_etree(node, config: Config):
8+
return NavigationTypeProperty(
9+
node.get('Name'), node.get('FromRole'), node.get('ToRole'), Identifier.parse(node.get('Relationship')))

pyodata/v4/__init__.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,29 @@
22

33
from typing import List
44

5+
from pyodata.model.from_etree_callbacks import enum_type_from_etree, struct_type_property_from_etree, \
6+
struct_type_from_etree, complex_type_from_etree
57
from pyodata.config import ODATAVersion
68
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmIntTypTraits
7-
from pyodata.model.elements import Typ
9+
from pyodata.model.elements import Typ, Schema, EnumType, ComplexType, StructType, StructTypeProperty
810
from pyodata.v4.type_traits import EdmDateTypTraits, GeoTypeTraits, EdmDoubleQuotesEncapsulatedTypTraits, \
911
EdmTimeOfDay, EdmDateTimeOffsetTypTraits, EdmDuration
1012

13+
from pyodata.v4.from_etree_callbacks import schema_from_etree
14+
1115

1216
class ODataV4(ODATAVersion):
1317
""" Definition of OData V4 """
1418

1519
@staticmethod
1620
def from_etree_callbacks():
1721
return {
22+
StructTypeProperty: struct_type_property_from_etree,
23+
StructType: struct_type_from_etree,
24+
# NavigationTypeProperty: navigation_type_property_from_etree,
25+
EnumType: enum_type_from_etree,
26+
ComplexType: complex_type_from_etree,
27+
Schema: schema_from_etree,
1828
}
1929

2030
@staticmethod

pyodata/v4/from_etree_callbacks.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# pylint: disable=missing-docstring,invalid-name,unused-argument,protected-access
2+
from pyodata.config import Config
3+
from pyodata.exceptions import PyODataParserError
4+
from pyodata.model.elements import ComplexType, Schema, EnumType, NullType
5+
from pyodata.policies import ParserError
6+
7+
8+
def schema_from_etree(schema_nodes, config: Config):
9+
schema = Schema(config)
10+
11+
# Parse Schema nodes by parts to get over the problem of not-yet known
12+
# entity types referenced by entity sets, function imports and
13+
# annotations.
14+
15+
# TODO: First, process EnumType, EntityType and ComplexType nodes.
16+
# They have almost no dependencies on other elements.
17+
for schema_node in schema_nodes:
18+
namespace = schema_node.get('Namespace')
19+
decl = Schema.Declaration(namespace)
20+
schema._decls[namespace] = decl
21+
22+
for enum_type in schema_node.xpath('edm:EnumType', namespaces=config.namespaces):
23+
try:
24+
etype = EnumType.from_etree(enum_type, config, namespace=namespace)
25+
except (PyODataParserError, AttributeError) as ex:
26+
config.err_policy(ParserError.ENUM_TYPE).resolve(ex)
27+
etype = NullType(enum_type.get('Name'))
28+
29+
decl.add_enum_type(etype)
30+
31+
for complex_type in schema_node.xpath('edm:ComplexType', namespaces=config.namespaces):
32+
try:
33+
ctype = ComplexType.from_etree(complex_type, config, schema=schema)
34+
except (KeyError, AttributeError) as ex:
35+
config.err_policy(ParserError.COMPLEX_TYPE).resolve(ex)
36+
ctype = NullType(complex_type.get('Name'))
37+
38+
decl.add_complex_type(ctype)
39+
40+
# TODO: resolve types of properties
41+
# TODO: Then, process Associations nodes because they refer EntityTypes and they are referenced by AssociationSets.
42+
# TODO: resolve navigation properties
43+
# TODO: Then, process EntitySet, FunctionImport and AssociationSet nodes.
44+
# TODO: Finally, process Annotation nodes when all Scheme nodes are completely processed.
45+
46+
return Schema

tests/conftest.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@
66

77

88
@pytest.fixture
9-
def metadata():
9+
def metadata_v2():
10+
return metadata('metadata.xml')
11+
12+
13+
@pytest.fixture
14+
def metadata_v4():
15+
return metadata('metadata_v4.xml')
16+
17+
18+
def metadata(file_name: str):
1019
"""Example OData metadata"""
1120
path_to_current_file = os.path.realpath(__file__)
1221
current_directory = os.path.split(path_to_current_file)[0]
13-
path_to_file = os.path.join(current_directory, "metadata.xml")
22+
path_to_file = os.path.join(current_directory, file_name)
1423

1524
return open(path_to_file, 'rb').read()
1625

@@ -107,12 +116,12 @@ def _data_services_epilogue(self):
107116

108117

109118
@pytest.fixture
110-
def schema(metadata):
119+
def schema(metadata_v2):
111120
"""Parsed metadata"""
112121

113122
# pylint: disable=redefined-outer-name
114123

115-
return schema_from_xml(metadata)
124+
return schema_from_xml(metadata_v2)
116125

117126

118127
def assert_logging_policy(mock_warning, *args):

0 commit comments

Comments
 (0)