Skip to content

Commit 2b0f622

Browse files
committed
Add support for OData V4 primitive types
https://www.odata.org/documentation/
1 parent c93cccd commit 2b0f622

File tree

10 files changed

+549
-85
lines changed

10 files changed

+549
-85
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ install:
1111
- pip install .
1212
- pip install -r dev-requirements.txt
1313
- pip install -r requirements.txt
14+
- pip install -r optional-requirements.txt
1415
- pip install bandit
1516

1617
# command to run tests

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
1111
- support all standard EDM schema versions - Jakub Filak
1212
- Splits python representation of metadata and metadata parsing - Martin Miksik
1313
- Separate type repositories for individual versions of OData - Martin Miksik
14+
- Support for OData V4 primitive types - Martin Miksik
1415

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

optional-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
geojson

pyodata/model/type_traits.py

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# pylint: disable=missing-docstring
22

3-
import datetime
43
import re
54

65
from pyodata.exceptions import PyODataException, PyODataModelError
@@ -99,87 +98,6 @@ def from_literal(self, value):
9998
return matches.group(1)
10099

101100

102-
class EdmDateTimeTypTraits(EdmPrefixedTypTraits):
103-
"""Emd.DateTime traits
104-
105-
Represents date and time with values ranging from 12:00:00 midnight,
106-
January 1, 1753 A.D. through 11:59:59 P.M, December 9999 A.D.
107-
108-
Literal form:
109-
datetime'yyyy-mm-ddThh:mm[:ss[.fffffff]]'
110-
NOTE: Spaces are not allowed between datetime and quoted portion.
111-
datetime is case-insensitive
112-
113-
Example 1: datetime'2000-12-12T12:00'
114-
JSON has following format: /Date(1516614510000)/
115-
https://blogs.sap.com/2017/01/05/date-and-time-in-sap-gateway-foundation/
116-
"""
117-
118-
def __init__(self):
119-
super(EdmDateTimeTypTraits, self).__init__('datetime')
120-
121-
def to_literal(self, value):
122-
"""Convert python datetime representation to literal format
123-
124-
None: this could be done also via formatting string:
125-
value.strftime('%Y-%m-%dT%H:%M:%S.%f')
126-
"""
127-
128-
if not isinstance(value, datetime.datetime):
129-
raise PyODataModelError(
130-
'Cannot convert value of type {} to literal. Datetime format is required.'.format(type(value)))
131-
132-
return super(EdmDateTimeTypTraits, self).to_literal(value.replace(tzinfo=None).isoformat())
133-
134-
def to_json(self, value):
135-
if isinstance(value, str):
136-
return value
137-
138-
# Converts datetime into timestamp in milliseconds in UTC timezone as defined in ODATA specification
139-
# https://www.odata.org/documentation/odata-version-2-0/json-format/
140-
return f'/Date({int(value.replace(tzinfo=datetime.timezone.utc).timestamp()) * 1000})/'
141-
142-
def from_json(self, value):
143-
144-
if value is None:
145-
return None
146-
147-
matches = re.match(r"^/Date\((.*)\)/$", value)
148-
if not matches:
149-
raise PyODataModelError(
150-
"Malformed value {0} for primitive Edm type. Expected format is /Date(value)/".format(value))
151-
value = matches.group(1)
152-
153-
try:
154-
# https://stackoverflow.com/questions/36179914/timestamp-out-of-range-for-platform-localtime-gmtime-function
155-
value = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(
156-
milliseconds=int(value))
157-
except ValueError:
158-
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
159-
160-
return value
161-
162-
def from_literal(self, value):
163-
164-
if value is None:
165-
return None
166-
167-
value = super(EdmDateTimeTypTraits, self).from_literal(value)
168-
169-
try:
170-
value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
171-
except ValueError:
172-
try:
173-
value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
174-
except ValueError:
175-
try:
176-
value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M')
177-
except ValueError:
178-
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
179-
180-
return value
181-
182-
183101
class EdmStringTypTraits(TypTraits):
184102
"""Edm.String traits"""
185103

pyodata/v2/__init__.py

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

7-
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmDateTimeTypTraits, EdmPrefixedTypTraits, \
8-
EdmIntTypTraits, EdmLongIntTypTraits, EdmStringTypTraits
7+
from pyodata.v2.type_traits import EdmDateTimeTypTraits
8+
9+
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmPrefixedTypTraits, EdmIntTypTraits, \
10+
EdmLongIntTypTraits, EdmStringTypTraits
911
from pyodata.policies import ParserError
1012
from pyodata.config import ODATAVersion, Config
1113
from pyodata.exceptions import PyODataParserError, PyODataModelError

pyodata/v2/type_traits.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
""" Type traits for types specific to the ODATA V4"""
2+
3+
import datetime
4+
import re
5+
6+
from pyodata.exceptions import PyODataModelError
7+
from pyodata.model.type_traits import EdmPrefixedTypTraits
8+
9+
10+
class EdmDateTimeTypTraits(EdmPrefixedTypTraits):
11+
"""Emd.DateTime traits
12+
13+
Represents date and time with values ranging from 12:00:00 midnight,
14+
January 1, 1753 A.D. through 11:59:59 P.M, December 9999 A.D.
15+
16+
Literal form:
17+
datetime'yyyy-mm-ddThh:mm[:ss[.fffffff]]'
18+
NOTE: Spaces are not allowed between datetime and quoted portion.
19+
datetime is case-insensitive
20+
21+
Example 1: datetime'2000-12-12T12:00'
22+
JSON has following format: /Date(1516614510000)/
23+
https://blogs.sap.com/2017/01/05/date-and-time-in-sap-gateway-foundation/
24+
"""
25+
26+
def __init__(self):
27+
super(EdmDateTimeTypTraits, self).__init__('datetime')
28+
29+
def to_literal(self, value):
30+
"""Convert python datetime representation to literal format
31+
32+
None: this could be done also via formatting string:
33+
value.strftime('%Y-%m-%dT%H:%M:%S.%f')
34+
"""
35+
36+
if not isinstance(value, datetime.datetime):
37+
raise PyODataModelError(
38+
'Cannot convert value of type {} to literal. Datetime format is required.'.format(type(value)))
39+
40+
return super(EdmDateTimeTypTraits, self).to_literal(value.replace(tzinfo=None).isoformat())
41+
42+
def to_json(self, value):
43+
if isinstance(value, str):
44+
return value
45+
46+
# Converts datetime into timestamp in milliseconds in UTC timezone as defined in ODATA specification
47+
# https://www.odata.org/documentation/odata-version-2-0/json-format/
48+
return f'/Date({int(value.replace(tzinfo=datetime.timezone.utc).timestamp()) * 1000})/'
49+
50+
def from_json(self, value):
51+
52+
if value is None:
53+
return None
54+
55+
matches = re.match(r"^/Date\((.*)\)/$", value)
56+
if not matches:
57+
raise PyODataModelError(
58+
"Malformed value {0} for primitive Edm type. Expected format is /Date(value)/".format(value))
59+
value = matches.group(1)
60+
61+
try:
62+
# https://stackoverflow.com/questions/36179914/timestamp-out-of-range-for-platform-localtime-gmtime-function
63+
value = datetime.datetime(1970, 1, 1, tzinfo=datetime.timezone.utc) + datetime.timedelta(
64+
milliseconds=int(value))
65+
except ValueError:
66+
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
67+
68+
return value
69+
70+
def from_literal(self, value):
71+
72+
if value is None:
73+
return None
74+
75+
value = super(EdmDateTimeTypTraits, self).from_literal(value)
76+
77+
try:
78+
value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S.%f')
79+
except ValueError:
80+
try:
81+
value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S')
82+
except ValueError:
83+
try:
84+
value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M')
85+
except ValueError:
86+
raise PyODataModelError('Cannot decode datetime from value {}.'.format(value))
87+
88+
return value

pyodata/v4/__init__.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
""" This module represents implementation of ODATA V4 """
2+
3+
from typing import List
4+
5+
from pyodata.config import ODATAVersion
6+
from pyodata.model.type_traits import EdmBooleanTypTraits, EdmIntTypTraits
7+
from pyodata.model.elements import Typ
8+
from pyodata.v4.type_traits import EdmDateTypTraits, GeoTypeTraits, EdmDoubleQuotesEncapsulatedTypTraits, \
9+
EdmTimeOfDay, EdmDateTimeOffsetTypTraits, EdmDuration
10+
11+
12+
class ODataV4(ODATAVersion):
13+
""" Definition of OData V4 """
14+
15+
@staticmethod
16+
def from_etree_callbacks():
17+
return {
18+
}
19+
20+
@staticmethod
21+
def primitive_types() -> List[Typ]:
22+
# TODO: We currently lack support for:
23+
# 'Edm.Geometry',
24+
# 'Edm.GeometryPoint',
25+
# 'Edm.GeometryLineString',
26+
# 'Edm.GeometryPolygon',
27+
# 'Edm.GeometryMultiPoint',
28+
# 'Edm.GeometryMultiLineString',
29+
# 'Edm.GeometryMultiPolygon',
30+
# 'Edm.GeometryCollection',
31+
32+
return [
33+
Typ('Null', 'null'),
34+
Typ('Edm.Binary', '', EdmDoubleQuotesEncapsulatedTypTraits()),
35+
Typ('Edm.Boolean', 'false', EdmBooleanTypTraits()),
36+
Typ('Edm.Byte', '0'),
37+
Typ('Edm.Date', '0000-00-00', EdmDateTypTraits()),
38+
Typ('Edm.Decimal', '0.0'),
39+
Typ('Edm.Double', '0.0'),
40+
Typ('Edm.Duration', 'P', EdmDuration()),
41+
Typ('Edm.Stream', 'null', EdmDoubleQuotesEncapsulatedTypTraits()),
42+
Typ('Edm.Single', '0.0', EdmDoubleQuotesEncapsulatedTypTraits()),
43+
Typ('Edm.Guid', '\"00000000-0000-0000-0000-000000000000\"', EdmDoubleQuotesEncapsulatedTypTraits()),
44+
Typ('Edm.Int16', '0', EdmIntTypTraits()),
45+
Typ('Edm.Int32', '0', EdmIntTypTraits()),
46+
Typ('Edm.Int64', '0', EdmIntTypTraits()),
47+
Typ('Edm.SByte', '0'),
48+
Typ('Edm.String', '\"\"', EdmDoubleQuotesEncapsulatedTypTraits()),
49+
Typ('Edm.TimeOfDay', '00:00:00', EdmTimeOfDay()),
50+
Typ('Edm.DateTimeOffset', '0000-00-00T00:00:00', EdmDateTimeOffsetTypTraits()),
51+
Typ('Edm.Geography', '', GeoTypeTraits()),
52+
Typ('Edm.GeographyPoint', '', GeoTypeTraits()),
53+
Typ('Edm.GeographyLineString', '', GeoTypeTraits()),
54+
Typ('Edm.GeographyPolygon', '', GeoTypeTraits()),
55+
Typ('Edm.GeographyMultiPoint', '', GeoTypeTraits()),
56+
Typ('Edm.GeographyMultiLineString', '', GeoTypeTraits()),
57+
Typ('Edm.GeographyMultiPolygon', '', GeoTypeTraits()),
58+
]

0 commit comments

Comments
 (0)