Skip to content

Commit c93cccd

Browse files
committed
Add separate type repository for each child of ODATAVersion
In every release of OData new types are not only added and removed but also there are changes to the existing types notably in formatting. Thus, there is a need to have separate type repository for each OData version. Let's see an example of Edm.Double JSON format: OData V2: 3.141d OData V4: 3.141 https://www.odata.org/documentation/odata-version-2-0/overview/#AbstractTypeSystem http://docs.oasis-open.org/odata/odata-json-format/v4.01/odata-json-format-v4.01.html
1 parent 3515a36 commit c93cccd

File tree

5 files changed

+74
-78
lines changed

5 files changed

+74
-78
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1010
- Client can be created from local metadata - Jakub Filak
1111
- support all standard EDM schema versions - Jakub Filak
1212
- Splits python representation of metadata and metadata parsing - Martin Miksik
13+
- Separate type repositories for individual versions of OData - Martin Miksik
1314

1415
### Fixed
1516
- make sure configured error policies are applied for Annotations referencing

pyodata/config.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
""" Contains definition of configuration class for PyOData and for ODATA versions. """
22

33
from abc import ABC, abstractmethod
4-
from typing import Type, List, Dict, Callable
4+
from typing import Type, List, Dict, Callable, TYPE_CHECKING
55

66
from pyodata.policies import PolicyFatal, ParserError, ErrorPolicy
77

8+
# pylint: disable=cyclic-import
9+
if TYPE_CHECKING:
10+
from pyodata.model.elements import Typ # noqa
11+
812

913
class ODATAVersion(ABC):
1014
""" This is base class for different OData releases. In it we define what are supported types, elements and so on.
@@ -15,21 +19,19 @@ def __init__(self):
1519
raise RuntimeError('ODATAVersion and its children are intentionally stateless, '
1620
'therefore you can not create instance of them')
1721

22+
# Separate dictionary of all registered types (primitive, complex and collection variants) for each child
23+
Types: Dict[str, 'Typ'] = dict()
24+
1825
@staticmethod
1926
@abstractmethod
20-
def supported_primitive_types() -> List[str]:
27+
def primitive_types() -> List['Typ']:
2128
""" Here we define which primitive types are supported and what is their python representation"""
2229

2330
@staticmethod
2431
@abstractmethod
2532
def from_etree_callbacks() -> Dict[object, Callable]:
2633
""" Here we define which elements are supported and what is their python representation"""
2734

28-
@classmethod
29-
def is_primitive_type_supported(cls, type_name):
30-
""" Convenience method which decides whatever given type is supported."""
31-
return type_name in cls.supported_primitive_types()
32-
3335

3436
class Config:
3537
# pylint: disable=too-many-instance-attributes,missing-docstring

pyodata/model/elements.py

Lines changed: 18 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
from pyodata.config import Config
99
from pyodata.exceptions import PyODataModelError, PyODataException, PyODataParserError
1010

11-
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmDateTimeTypTraits, EdmPrefixedTypTraits, \
12-
EdmIntTypTraits, EdmLongIntTypTraits, EdmStringTypTraits, TypTraits, EdmStructTypTraits, EnumTypTrait
11+
from pyodata.model.type_traits import TypTraits, EdmStructTypTraits, EnumTypTrait
1312

1413

1514
IdentifierInfo = collections.namedtuple('IdentifierInfo', 'namespace name')
@@ -80,69 +79,39 @@ def parse(value):
8079

8180

8281
class Types:
83-
"""Repository of all available OData types
82+
""" Repository of all available OData types in given version
8483
8584
Since each type has instance of appropriate type, this
8685
repository acts as central storage for all instances. The
8786
rule is: don't create any type instances if not necessary,
8887
always reuse existing instances if possible
8988
"""
9089

91-
# dictionary of all registered types (primitive, complex and collection variants)
92-
Types = None
93-
9490
@staticmethod
95-
def _build_types():
96-
"""Create and register instances of all primitive Edm types"""
97-
98-
if Types.Types is None:
99-
Types.Types = {}
100-
101-
Types.register_type(Typ('Null', 'null'))
102-
Types.register_type(Typ('Edm.Binary', 'binary\'\''))
103-
Types.register_type(Typ('Edm.Boolean', 'false', EdmBooleanTypTraits()))
104-
Types.register_type(Typ('Edm.Byte', '0'))
105-
Types.register_type(Typ('Edm.DateTime', 'datetime\'2000-01-01T00:00\'', EdmDateTimeTypTraits()))
106-
Types.register_type(Typ('Edm.Decimal', '0.0M'))
107-
Types.register_type(Typ('Edm.Double', '0.0d'))
108-
Types.register_type(Typ('Edm.Single', '0.0f'))
109-
Types.register_type(
110-
Typ('Edm.Guid', 'guid\'00000000-0000-0000-0000-000000000000\'', EdmPrefixedTypTraits('guid')))
111-
Types.register_type(Typ('Edm.Int16', '0', EdmIntTypTraits()))
112-
Types.register_type(Typ('Edm.Int32', '0', EdmIntTypTraits()))
113-
Types.register_type(Typ('Edm.Int64', '0L', EdmLongIntTypTraits()))
114-
Types.register_type(Typ('Edm.SByte', '0'))
115-
Types.register_type(Typ('Edm.String', '\'\'', EdmStringTypTraits()))
116-
Types.register_type(Typ('Edm.Time', 'time\'PT00H00M\''))
117-
Types.register_type(Typ('Edm.DateTimeOffset', 'datetimeoffset\'0000-00-00T00:00:00\''))
91+
def register_type(typ: 'Typ', config: Config):
92+
"""Add new type to the ODATA version type repository as well as its collection variant"""
11893

119-
@staticmethod
120-
def register_type(typ):
121-
"""Add new type to the type repository as well as its collection variant"""
122-
123-
# build types hierarchy on first use (lazy creation)
124-
if Types.Types is None:
125-
Types._build_types()
94+
o_version = config.odata_version
12695

12796
# register type only if it doesn't exist
128-
# pylint: disable=unsupported-membership-test
129-
if typ.name not in Types.Types:
130-
# pylint: disable=unsupported-assignment-operation
131-
Types.Types[typ.name] = typ
97+
if typ.name not in o_version.Types:
98+
o_version.Types[typ.name] = typ
13299

133100
# automatically create and register collection variant if not exists
134101
collection_name = 'Collection({})'.format(typ.name)
135-
# pylint: disable=unsupported-membership-test
136-
if collection_name not in Types.Types:
102+
if collection_name not in o_version.Types:
137103
collection_typ = Collection(typ.name, typ)
138-
# pylint: disable=unsupported-assignment-operation
139-
Types.Types[collection_name] = collection_typ
104+
o_version.Types[collection_name] = collection_typ
140105

141106
@staticmethod
142107
def from_name(name, config: Config):
108+
o_version = config.odata_version
109+
143110
# build types hierarchy on first use (lazy creation)
144-
if Types.Types is None:
145-
Types._build_types()
111+
if not o_version.Types:
112+
o_version.Types = dict()
113+
for typ in o_version.primitive_types():
114+
Types.register_type(typ, config)
146115

147116
search_name = name
148117

@@ -152,12 +121,11 @@ def from_name(name, config: Config):
152121
name = name[11:-1] # strip collection() decorator
153122
search_name = 'Collection({})'.format(name)
154123

155-
if not config.odata_version.is_primitive_type_supported(name):
124+
try:
125+
return o_version.Types[search_name]
126+
except KeyError:
156127
raise KeyError('Requested primitive type is not supported in this version of ODATA')
157128

158-
# pylint: disable=unsubscriptable-object
159-
return Types.Types[search_name]
160-
161129
@staticmethod
162130
def parse_type_name(type_name):
163131

pyodata/v2/__init__.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import logging
55
from typing import List
66

7+
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmDateTimeTypTraits, EdmPrefixedTypTraits, \
8+
EdmIntTypTraits, EdmLongIntTypTraits, EdmStringTypTraits
79
from pyodata.policies import ParserError
810
from pyodata.config import ODATAVersion, Config
911
from pyodata.exceptions import PyODataParserError, PyODataModelError
@@ -52,24 +54,24 @@ def from_etree_callbacks():
5254
}
5355

5456
@staticmethod
55-
def supported_primitive_types() -> List[str]:
57+
def primitive_types() -> List[Typ]:
5658
return [
57-
'Null',
58-
'Edm.Binary',
59-
'Edm.Boolean',
60-
'Edm.Byte',
61-
'Edm.DateTime',
62-
'Edm.Decimal',
63-
'Edm.Double',
64-
'Edm.Single',
65-
'Edm.Guid',
66-
'Edm.Int16',
67-
'Edm.Int32',
68-
'Edm.Int64',
69-
'Edm.SByte',
70-
'Edm.String',
71-
'Edm.Time',
72-
'Edm.DateTimeOffset',
59+
Typ('Null', 'null'),
60+
Typ('Edm.Binary', 'binary\'\''),
61+
Typ('Edm.Boolean', 'false', EdmBooleanTypTraits()),
62+
Typ('Edm.Byte', '0'),
63+
Typ('Edm.DateTime', 'datetime\'2000-01-01T00:00\'', EdmDateTimeTypTraits()),
64+
Typ('Edm.Decimal', '0.0M'),
65+
Typ('Edm.Double', '0.0d'),
66+
Typ('Edm.Single', '0.0f'),
67+
Typ('Edm.Guid', 'guid\'00000000-0000-0000-0000-000000000000\'', EdmPrefixedTypTraits('guid')),
68+
Typ('Edm.Int16', '0', EdmIntTypTraits()),
69+
Typ('Edm.Int32', '0', EdmIntTypTraits()),
70+
Typ('Edm.Int64', '0L', EdmLongIntTypTraits()),
71+
Typ('Edm.SByte', '0'),
72+
Typ('Edm.String', '\'\'', EdmStringTypTraits()),
73+
Typ('Edm.Time', 'time\'PT00H00M\''),
74+
Typ('Edm.DateTimeOffset', 'datetimeoffset\'0000-00-00T00:00:00\'')
7375
]
7476

7577
# pylint: disable=too-many-locals,too-many-branches,too-many-statements, protected-access,missing-docstring

tests/test_model.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
from pyodata.config import Config, ODATAVersion
55
from pyodata.exceptions import PyODataParserError
66
from pyodata.model.builder import MetadataBuilder
7-
from pyodata.model.elements import Schema, Types
7+
from pyodata.model.elements import Schema, Types, Typ
8+
from v2 import ODataV2
89

910

1011
def test_from_etree_mixin(metadata):
@@ -29,9 +30,9 @@ def test_supported_primitive_types():
2930

3031
class EmptyODATA(ODATAVersion):
3132
@staticmethod
32-
def supported_primitive_types() -> List[str]:
33+
def primitive_types() -> List[Typ]:
3334
return [
34-
'Edm.Binary'
35+
Typ('Edm.Binary', 'binary\'\'')
3536
]
3637

3738
config = Config(EmptyODATA)
@@ -51,11 +52,33 @@ def from_etree_callbacks():
5152
return {}
5253

5354
@staticmethod
54-
def supported_primitive_types() -> List[str]:
55+
def primitive_types() -> List[Typ]:
5556
return []
5657

5758
with pytest.raises(RuntimeError) as typ_ex_info:
5859
EmptyODATA()
5960

6061
assert typ_ex_info.value.args[0] == 'ODATAVersion and its children are intentionally stateless, ' \
6162
'therefore you can not create instance of them'
63+
64+
65+
def test_types_repository_separation():
66+
67+
class TestODATA(ODATAVersion):
68+
@staticmethod
69+
def primitive_types() -> List['Typ']:
70+
return [
71+
Typ('PrimitiveType', '0')
72+
]
73+
74+
config_test = Config(TestODATA)
75+
config_v2 = Config(ODataV2)
76+
77+
assert TestODATA.Types is None
78+
assert TestODATA.Types == ODataV2.Types
79+
80+
# Build type repository by initial call
81+
Types.from_name('PrimitiveType', config_test)
82+
Types.from_name('Edm.Int16', config_v2)
83+
84+
assert TestODATA.Types != ODataV2.Types

0 commit comments

Comments
 (0)