Skip to content

Commit 1f9cce9

Browse files
committed
Fix import error in python 3.6
In Python 3.7 importlib which resolves imports was updated. It allowed to use "import x.y as c" in __x__.py, but as we support python 3.6 as well we needed to optimize to work properly even with previous versions of importlib. Thus, the reason for this commit. All imports of files defined in the same folder as __module__.py are now in the form of "from .x import a,b,c". Also, since now all relevant classes for using pyodata e. g. elements, types are now directly imported in the appropriate module. User should always use API exposed directly from importing "pyodata.v2" or "pyodata.v4" Moreover, to remove cyclic imports: 1) Adapter function for build_entity_set(Credit to Jakub Filak) was added as well as class. 2) ODATAVersion was moved to separate file. 3) Redundant function schema_from_xml which required importing pyodata.v2 was removed. Used MetadataBuilder(xml, Config(ODataV2)) instead.
1 parent 4f2bf88 commit 1f9cce9

11 files changed

+137
-132
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2626
- make sure configured error policies are applied for Annotations referencing
2727
unknown type/member - Martin Miksik
2828
- Race condition in `test_types_repository_separation` - Martin Miksik
29+
- Import error while using python version prior to 3.7 - Martin Miksik
2930

3031
## [1.3.0]
3132

pyodata/config.py

+5-38
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,8 @@
1-
""" Contains definition of configuration class for PyOData and for ODATA versions. """
2-
3-
from abc import ABC, abstractmethod
4-
from typing import Type, List, Dict, Callable, TYPE_CHECKING
1+
""" Contains definition of configuration class for PyOData"""
52

3+
from typing import Type, Dict
64
from pyodata.policies import PolicyFatal, ParserError, ErrorPolicy
7-
8-
# pylint: disable=cyclic-import
9-
if TYPE_CHECKING:
10-
from pyodata.model.elements import Typ, Annotation # noqa
11-
12-
13-
class ODATAVersion(ABC):
14-
""" This is base class for different OData releases. In it we define what are supported types, elements and so on.
15-
Furthermore, we specify how individual elements are parsed or represented by python objects.
16-
"""
17-
18-
def __init__(self):
19-
raise RuntimeError('ODATAVersion and its children are intentionally stateless, '
20-
'therefore you can not create instance of them')
21-
22-
# Separate dictionary of all registered types (primitive, complex and collection variants) for each child
23-
Types: Dict[str, 'Typ'] = dict()
24-
25-
@staticmethod
26-
@abstractmethod
27-
def primitive_types() -> List['Typ']:
28-
""" Here we define which primitive types are supported and what is their python representation"""
29-
30-
@staticmethod
31-
@abstractmethod
32-
def build_functions() -> Dict[type, Callable]:
33-
""" Here we define which elements are supported and what is their python representation"""
34-
35-
@staticmethod
36-
@abstractmethod
37-
def annotations() -> Dict['Annotation', Callable]:
38-
""" Here we define which annotations are supported and what is their python representation"""
5+
import pyodata.version
396

407

418
class Config:
@@ -46,7 +13,7 @@ class Config:
4613
""" This is configuration class for PyOData. All session dependent settings should be stored here. """
4714

4815
def __init__(self,
49-
odata_version: Type[ODATAVersion],
16+
odata_version: Type[pyodata.version.ODATAVersion],
5017
custom_error_policies=None,
5118
default_error_policy=None,
5219
xml_namespaces=None
@@ -108,7 +75,7 @@ def namespaces(self, value: Dict[str, str]):
10875
self._namespaces = value
10976

11077
@property
111-
def odata_version(self) -> Type[ODATAVersion]:
78+
def odata_version(self) -> Type[pyodata.version.ODATAVersion]:
11279
return self._odata_version
11380

11481
@property

pyodata/model/build_functions.py

+6-14
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
Types, EnumType, EnumMember, EntitySet, ValueHelper, ValueHelperParameter, FunctionImportParameter, \
1212
FunctionImport, metadata_attribute_get, EntityType, ComplexType, build_element, NullType
1313

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
17-
1814

1915
def modlog():
2016
return logging.getLogger("callbacks")
@@ -166,14 +162,10 @@ def build_enum_type(config: Config, type_node, namespace):
166162
return NullType(type_node.get('Name'))
167163

168164

169-
def build_entity_set(config, entity_set_node):
165+
def build_entity_set(config, entity_set_node, builder=None):
170166
name = entity_set_node.get('Name')
171167
et_info = Types.parse_type_name(entity_set_node.get('EntityType'))
172168

173-
nav_prop_bins = []
174-
for nav_prop_bin in entity_set_node.xpath('edm:NavigationPropertyBinding', namespaces=config.namespaces):
175-
nav_prop_bins.append(build_element('NavigationPropertyBinding', config, node=nav_prop_bin, et_info=et_info))
176-
177169
# TODO: create a class SAP attributes
178170
addressable = sap_attribute_get_bool(entity_set_node, 'addressable', True)
179171
creatable = sap_attribute_get_bool(entity_set_node, 'creatable', True)
@@ -186,12 +178,12 @@ def build_entity_set(config, entity_set_node):
186178
req_filter = sap_attribute_get_bool(entity_set_node, 'requires-filter', False)
187179
label = sap_attribute_get_string(entity_set_node, 'label')
188180

189-
if config.odata_version == v4.ODataV4:
190-
return v4.EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable,
191-
pageable, topable, req_filter, label, nav_prop_bins)
181+
if builder:
182+
return builder(config, entity_set_node, name, et_info, addressable, creatable, updatable, deletable, searchable,
183+
countable, pageable, topable, req_filter, label)
192184

193-
return EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable, pageable,
194-
topable, req_filter, label)
185+
return EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable,
186+
pageable, topable, req_filter, label)
195187

196188

197189
def build_value_helper(config, target, annotation_node, schema):

pyodata/model/builder.py

+1-18
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from pyodata.config import Config
77
from pyodata.exceptions import PyODataParserError
88
from pyodata.model.elements import ValueHelperParameter, Schema, build_element
9-
import pyodata.v2 as v2
109

1110

1211
ANNOTATION_NAMESPACES = {
@@ -40,11 +39,8 @@ class MetadataBuilder:
4039
'http://docs.oasis-open.org/odata/ns/edm'
4140
]
4241

43-
def __init__(self, xml, config=None):
42+
def __init__(self, xml, config):
4443
self._xml = xml
45-
46-
if config is None:
47-
config = Config(v2.ODataV2)
4844
self._config = config
4945

5046
# pylint: disable=missing-docstring
@@ -132,16 +128,3 @@ def update_alias(aliases, config: Config):
132128
if alias_namespace == namespace:
133129
config._sap_value_helper_directions[alias + '.' + suffix] = \
134130
config.sap_value_helper_directions[direction_key]
135-
136-
137-
def schema_from_xml(metadata_xml, namespaces=None):
138-
"""Parses XML data and returns Schema representing OData Metadata"""
139-
140-
meta = MetadataBuilder(
141-
metadata_xml,
142-
config=Config(
143-
v2.ODataV2,
144-
xml_namespaces=namespaces,
145-
))
146-
147-
return meta.build()

pyodata/v2/__init__.py

+24-24
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,20 @@
33
import logging
44
from typing import List
55

6-
from pyodata.v2.type_traits import EdmDateTimeTypTraits
76

7+
from pyodata.version import ODATAVersion
8+
from pyodata.model.elements import StructTypeProperty, StructType, ComplexType, EntityType, EntitySet, ValueHelper, \
9+
ValueHelperParameter, FunctionImport, Typ
10+
from pyodata.model.build_functions import build_value_helper, build_entity_type, build_complex_type, \
11+
build_value_helper_parameter, build_entity_set, build_struct_type_property, build_struct_type, build_function_import
812
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmPrefixedTypTraits, EdmIntTypTraits, \
913
EdmLongIntTypTraits, EdmStringTypTraits
10-
from pyodata.config import ODATAVersion
1114

12-
from pyodata.v2.elements import NavigationTypeProperty, EndRole, Association, AssociationSetEndRole, AssociationSet, \
15+
from .elements import NavigationTypeProperty, EndRole, Association, AssociationSetEndRole, AssociationSet, \
1316
ReferentialConstraint, Schema
14-
from pyodata.model.elements import StructTypeProperty, StructType, ComplexType, EntityType, EntitySet, ValueHelper, \
15-
ValueHelperParameter, FunctionImport, Typ
16-
17-
18-
import pyodata.v2.build_functions as build_functions_v2
19-
import pyodata.model.build_functions as build_functions
17+
from .build_functions import build_association_set, build_end_role, build_association, build_schema, \
18+
build_navigation_type_property, build_referential_constraint, build_association_set_end_role
19+
from .type_traits import EdmDateTimeTypTraits
2020

2121

2222
def modlog():
@@ -30,20 +30,20 @@ class ODataV2(ODATAVersion):
3030
@staticmethod
3131
def build_functions():
3232
return {
33-
StructTypeProperty: build_functions.build_struct_type_property,
34-
StructType: build_functions.build_struct_type,
35-
NavigationTypeProperty: build_functions_v2.build_navigation_type_property,
36-
ComplexType: build_functions.build_complex_type,
37-
EntityType: build_functions.build_entity_type,
38-
EntitySet: build_functions.build_entity_set,
39-
EndRole: build_functions_v2.build_end_role,
40-
ReferentialConstraint: build_functions_v2.build_referential_constraint,
41-
Association: build_functions_v2.build_association,
42-
AssociationSetEndRole: build_functions_v2.build_association_set_end_role,
43-
AssociationSet: build_functions_v2.build_association_set,
44-
ValueHelperParameter: build_functions.build_value_helper_parameter,
45-
FunctionImport: build_functions.build_function_import,
46-
Schema: build_functions_v2.build_schema
33+
StructTypeProperty: build_struct_type_property,
34+
StructType: build_struct_type,
35+
NavigationTypeProperty: build_navigation_type_property,
36+
ComplexType: build_complex_type,
37+
EntityType: build_entity_type,
38+
EntitySet: build_entity_set,
39+
EndRole: build_end_role,
40+
ReferentialConstraint: build_referential_constraint,
41+
Association: build_association,
42+
AssociationSetEndRole: build_association_set_end_role,
43+
AssociationSet: build_association_set,
44+
ValueHelperParameter: build_value_helper_parameter,
45+
FunctionImport: build_function_import,
46+
Schema: build_schema
4747
}
4848

4949
@staticmethod
@@ -70,5 +70,5 @@ def primitive_types() -> List[Typ]:
7070
@staticmethod
7171
def annotations():
7272
return {
73-
ValueHelper: build_functions.build_value_helper
73+
ValueHelper: build_value_helper
7474
}

pyodata/v4/__init__.py

+19-18
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,35 @@
22

33
from typing import List
44

5-
from pyodata.config import ODATAVersion
6-
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmIntTypTraits
5+
from pyodata.version import ODATAVersion
76
from pyodata.model.elements import Typ, Schema, EnumType, ComplexType, StructType, StructTypeProperty, EntityType
7+
from pyodata.model.build_functions import build_entity_type, build_complex_type, build_struct_type_property, \
8+
build_enum_type, build_struct_type
9+
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmIntTypTraits
810

9-
from pyodata.v4.elements import NavigationTypeProperty, NavigationPropertyBinding, EntitySet, Unit
10-
from pyodata.v4.type_traits import EdmDateTypTraits, GeoTypeTraits, EdmDoubleQuotesEncapsulatedTypTraits, \
11+
from .elements import NavigationTypeProperty, NavigationPropertyBinding, EntitySet, Unit
12+
from .build_functions import build_unit_annotation, build_type_definition, build_schema, \
13+
build_navigation_type_property, build_navigation_property_binding, build_entity_set_with_v4_builder
14+
from .type_traits import EdmDateTypTraits, GeoTypeTraits, EdmDoubleQuotesEncapsulatedTypTraits, \
1115
EdmTimeOfDay, EdmDateTimeOffsetTypTraits, EdmDuration
1216

13-
import pyodata.v4.build_functions as build_functions_v4
14-
import pyodata.model.build_functions as build_functions
15-
1617

1718
class ODataV4(ODATAVersion):
1819
""" Definition of OData V4 """
1920

2021
@staticmethod
2122
def build_functions():
2223
return {
23-
StructTypeProperty: build_functions.build_struct_type_property,
24-
StructType: build_functions.build_struct_type,
25-
NavigationTypeProperty: build_functions_v4.build_navigation_type_property,
26-
NavigationPropertyBinding: build_functions_v4.build_navigation_property_binding,
27-
EnumType: build_functions.build_enum_type,
28-
ComplexType: build_functions.build_complex_type,
29-
EntityType: build_functions.build_entity_type,
30-
EntitySet: build_functions.build_entity_set,
31-
Typ: build_functions_v4.build_type_definition,
32-
Schema: build_functions_v4.build_schema,
24+
StructTypeProperty: build_struct_type_property,
25+
StructType: build_struct_type,
26+
NavigationTypeProperty: build_navigation_type_property,
27+
NavigationPropertyBinding: build_navigation_property_binding,
28+
EnumType: build_enum_type,
29+
ComplexType: build_complex_type,
30+
EntityType: build_entity_type,
31+
EntitySet: build_entity_set_with_v4_builder,
32+
Typ: build_type_definition,
33+
Schema: build_schema,
3334
}
3435

3536
@staticmethod
@@ -75,5 +76,5 @@ def primitive_types() -> List[Typ]:
7576
@staticmethod
7677
def annotations():
7778
return {
78-
Unit: build_functions_v4.build_unit_annotation
79+
Unit: build_unit_annotation
7980
}

pyodata/v4/build_functions.py

+18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from pyodata.config import Config
1010
from pyodata.exceptions import PyODataParserError, PyODataModelError
11+
from pyodata.model.build_functions import build_entity_set
1112
from pyodata.model.elements import ComplexType, Schema, EnumType, NullType, build_element, EntityType, Types, \
1213
StructTypeProperty, build_annotation, Typ
1314
from pyodata.policies import ParserError
@@ -157,3 +158,20 @@ def build_type_definition(config: Config, node):
157158
build_annotation(annotation_node.get('Term'), config, target=typ, annotation_node=annotation_node)
158159

159160
return typ
161+
162+
163+
# pylint: disable=too-many-arguments
164+
def build_entity_set_v4(config, entity_set_node, name, et_info, addressable, creatable, updatable, deletable,
165+
searchable, countable, pageable, topable, req_filter, label):
166+
nav_prop_bins = []
167+
for nav_prop_bin in entity_set_node.xpath('edm:NavigationPropertyBinding', namespaces=config.namespaces):
168+
nav_prop_bins.append(build_element(NavigationPropertyBinding, config, node=nav_prop_bin, et_info=et_info))
169+
170+
return EntitySet(name, et_info, addressable, creatable, updatable, deletable, searchable, countable, pageable,
171+
topable, req_filter, label, nav_prop_bins)
172+
173+
174+
def build_entity_set_with_v4_builder(config, entity_set_node):
175+
"""Adapter inserting the V4 specific builder"""
176+
177+
return build_entity_set(config, entity_set_node, builder=build_entity_set_v4)

pyodata/version.py

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
""" Base class for defining ODATA versions. """
2+
3+
from abc import ABC, abstractmethod
4+
from typing import List, Dict, Callable, TYPE_CHECKING
5+
6+
# pylint: disable=cyclic-import
7+
if TYPE_CHECKING:
8+
from pyodata.model.elements import Typ, Annotation # noqa
9+
10+
11+
class ODATAVersion(ABC):
12+
""" This is base class for different OData releases. In it we define what are supported types, elements and so on.
13+
Furthermore, we specify how individual elements are parsed or represented by python objects.
14+
"""
15+
16+
def __init__(self):
17+
raise RuntimeError('ODATAVersion and its children are intentionally stateless, '
18+
'therefore you can not create instance of them')
19+
20+
# Separate dictionary of all registered types (primitive, complex and collection variants) for each child
21+
Types: Dict[str, 'Typ'] = dict()
22+
23+
@staticmethod
24+
@abstractmethod
25+
def primitive_types() -> List['Typ']:
26+
""" Here we define which primitive types are supported and what is their python representation"""
27+
28+
@staticmethod
29+
@abstractmethod
30+
def build_functions() -> Dict[type, Callable]:
31+
""" Here we define which elements are supported and what is their python representation"""
32+
33+
@staticmethod
34+
@abstractmethod
35+
def annotations() -> Dict['Annotation', Callable]:
36+
""" Here we define which annotations are supported and what is their python representation"""

tests/conftest.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
import pytest
55

66
from pyodata.config import Config
7-
from pyodata.model.builder import schema_from_xml, MetadataBuilder
7+
from pyodata.model.builder import MetadataBuilder
8+
from pyodata.v2 import ODataV2
89
from pyodata.v4 import ODataV4
910

1011

@@ -124,7 +125,12 @@ def schema(metadata_v2):
124125

125126
# pylint: disable=redefined-outer-name
126127

127-
return schema_from_xml(metadata_v2)
128+
meta = MetadataBuilder(
129+
metadata_v2,
130+
config=Config(ODataV2)
131+
)
132+
133+
return meta.build()
128134

129135

130136
@pytest.fixture

tests/test_model.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from typing import List
22
import pytest
33

4-
from pyodata.config import Config, ODATAVersion
4+
from pyodata.config import Config
5+
from pyodata.version import ODATAVersion
56
from pyodata.exceptions import PyODataParserError
67
from pyodata.model.builder import MetadataBuilder
78
from pyodata.model.elements import Schema, Types, Typ

0 commit comments

Comments
 (0)