SAP / python-pyodata

Enterprise-ready Python OData client
Apache License 2.0
221 stars 93 forks source link

Malformed value for primitive Edm.DateTimeOffset type #231

Closed sti0 closed 2 years ago

sti0 commented 2 years ago

We are using pyodata v1.7.1 the whole time and I'm tried to update to the latest v1.10.0 version. But now I get this error when querying data from the odata service:

Traceback (most recent call last):
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/model.py", line 534, in from_json
    milliseconds_since_epoch = matches.group('milliseconds_since_epoch')
AttributeError: 'NoneType' object has no attribute 'group'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/prefect/engine.py", line 1192, in orchestrate_task_run
    result = await run_sync(task.fn, *args, **kwargs)
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/prefect/utilities/asyncutils.py", line 57, in run_sync_in_worker_thread
    return await anyio.to_thread.run_sync(call, cancellable=True)
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/anyio/to_thread.py", line 31, in run_sync
    return await get_asynclib().run_sync_in_worker_thread(
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 937, in run_sync_in_worker_thread
    return await future
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 867, in run
    result = context.run(func, *args)
  File "/workspace/odatavenv/flow_utilities/prefect/tasks/odata.py", line 60, in fetch_odata
    for cnt, batch in enumerate(batches, start=1):
  File "/workspace/odatavenv/flow_utilities/api/odata.py", line 114, in query_all
    yield list(self.query(skip, top))
  File "/workspace/odatavenv/flow_utilities/api/odata.py", line 98, in query
    response = self._entity_service().skip(skip).top(top).execute()
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/service.py", line 349, in execute
    return self._call_handler(response)
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/service.py", line 362, in _call_handler
    return self._handler(response)
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/service.py", line 1509, in get_entities_handler
    entity = EntityProxy(self._service, self._entity_set, self._entity_set.entity_type, props)
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/service.py", line 814, in __init__
    self._cache[type_proprty.name] = type_proprty.from_json(proprties[type_proprty.name])
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/model.py", line 872, in from_json
    return self.typ.traits.from_json(value)
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/pyodata/v2/model.py", line 537, in from_json
    raise PyODataModelError(
pyodata.exceptions.PyODataModelError: Malformed value /Date(1599659339088)/ for primitive Edm.DateTimeOffset type. Expected format is /Date(<ticks>±<offset>)/

Maybe this is related to our instance because they are uncompatible dates (see https://github.com/SAP/python-pyodata/issues/143).

phanak-sap commented 2 years ago

Hi sti0, you created a PR in this library, so you know this is not quite an actionable issue just from information provided so far :)

  1. check the changelog, but IMHO you are somehow affected be the changes introduced in #230 and #184
  2. are you using the FIX_SCREWED_UP_MINIMAL_DATETIME_VALUE / FIX_SCREWED_UP_MAXIMUM_DATETIME_VALUE that is related to the #143 you mentioned? This may be omitted in the unit tests for the DateTimeOffset fixes, need to check this.
  3. check the tests added for these PRs. Maybe you will be able to find a failing test input just in the parametrized test sections, that would be ideal (post a PR with test update that will be failing but will reproduce the bug). It will be essentially a first commit in a PR with fix for your case.
  4. second option for how you could provide a sample failing test with your service metadata and mocked response (use the tests from above PRs as guidelines). Ideally push it as PR to this repo - https://github.com/phanak-sap/pyodata-issue-files. If the tests pass on 1.7.1 and fail on 1.10.0 that is the ideal state of bug report, something which can be debugged. But from info so far, I IMHO cannot write such test myself, simply because I don't have the metadata and the actual response, which is failing on themodel.py", line 534 with 'NoneType' object has no attribute 'group'

bonus: I am quite frankly confused by these lines in the stack trace:

  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 937, in run_sync_in_worker_thread
    return await future
  File "/workspace/tmp/venv/odatavenv/lib/python3.10/site-packages/anyio/_backends/_asyncio.py", line 867, in run
    result = context.run(func, *args)

you were running async on 1.7.1 version of pyodata? or your code is mostly async, but pyodata was used in sync call, with requests library? Not that this should affect the parsing of DateTimeOffset, just curious.

Also, you may check the new async service initialization feature in 1.10.0 - or was it the true intent behind the version upgrade :) ? It is not documented yet, but from the tests it should be enough of a starting point. You should be able to move to "all http networking is async" in your codebase. Check https://github.com/SAP/python-pyodata/commit/fd28f5f22063f5cb9f8687985dc82c4f55fb173b

Since I am frankly starting with the whole python async thing, feel free to add any "code review" notes if this feature should be implemented in any better way, perhaps in a new issue. It is so fresh that even backward incompatible changes would be IMHO OK for 1.11.0

phanak-sap commented 2 years ago

Hi sti0, I have tried to reproduce the bug with the info you provided (metadata would be helpful to be sure) and it seems OK to me.

The errorpyodata.exceptions.PyODataModelError: Malformed value /Date(1599659339088)/ for primitive Edm.DateTimeOffset type. is valid for provided input and Edm.DateTimeOffset type.

The correct type should be Edm.DateTime for provided input, then is parsed from json to datetime.datetime(2020, 9, 9, 13, 48, 59, 88000, tzinfo=datetime.timezone.utc). Or the service should return the data according the declared type, Edm.DateTimeOffset, e.g. /Date(1599652139000+0060)/.

Unless you provide more info or ideally script to reproduce the bug, I am seeing it as Not a bug / Works as designed.

sti0 commented 2 years ago

Hi @phanak-sap ,

sorry for the late response. The async calls coming out of our orchestration flows. They have nothing todo with this problem I could reproduce it with a simple script.

First of all here the metadata for one endpoint:

<EntityType Name="CorporateAccountAddress" c4c:parent-entity-type="CorporateAccount">
    <Key>
        <PropertyRef Name="ObjectID"/>
    </Key>
    <Property Name="ObjectID" Type="Edm.String" Nullable="false" MaxLength="70" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="ParentObjectID" Type="Edm.String" Nullable="true" MaxLength="70" FixedLength="true" sap:creatable="true" sap:updatable="false" sap:filterable="true"/>
    <Property Name="AccountID" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="false" sap:filterable="true" c4c:is-parent-internal-key="true"/>
    <Property Name="UUID" Type="Edm.Guid" Nullable="true" sap:creatable="true" sap:updatable="false" sap:filterable="true"/>
    <Property Name="MainIndicator" Type="Edm.Boolean" Nullable="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="ShipTo" Type="Edm.Boolean" Nullable="true" sap:creatable="true" sap:updatable="true" sap:filterable="false"/>
    <Property Name="DefaultShipTo" Type="Edm.Boolean" Nullable="true" sap:creatable="true" sap:updatable="true" sap:filterable="false"/>
    <Property Name="BillTo" Type="Edm.Boolean" Nullable="true" sap:creatable="true" sap:updatable="true" sap:filterable="false"/>
    <Property Name="DefaultBillTo" Type="Edm.Boolean" Nullable="true" sap:creatable="true" sap:updatable="true" sap:filterable="false"/>
    <Property Name="FormattedPostalAddressDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="FormattedAddressFirstLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="FormattedAddressSecondLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="FormattedAddressThirdLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="FormattedAddressFourthLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="FormattedPostalAddressFirstLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="FormattedPostalAddressSecondLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="FormattedPostalAddressThirdLineDescription" Type="Edm.String" Nullable="true" MaxLength="480" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <Property Name="CountryCode" Type="Edm.String" Nullable="true" MaxLength="3" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="CountryCodeText" c4c:value-help="CorporateAccountAddressCountryCodeCollection"/>
    <Property Name="CountryCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="StateCode" Type="Edm.String" Nullable="true" MaxLength="6" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="StateCodeText" c4c:context-property="CountryCode" c4c:value-help="CorporateAccountAddressStateCodeCollection"/>
    <Property Name="StateCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="CareOfName" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="AddressLine1" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="AddressLine2" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="HouseNumber" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="AdditionalHouseNumber" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Street" Type="Edm.String" Nullable="true" MaxLength="60" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="AddressLine4" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="AddressLine5" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="District" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="City" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="DifferentCity" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="StreetPostalCode" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="County" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="CompanyPostalCode" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="POBoxIndicator" Type="Edm.Boolean" Nullable="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="POBox" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="POBoxPostalCode" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="POBoxDeviatingCountryCode" Type="Edm.String" Nullable="true" MaxLength="3" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="POBoxDeviatingCountryCodeText" c4c:value-help="CorporateAccountAddressPOBoxDeviatingCountryCodeCollection"/>
    <Property Name="POBoxDeviatingCountryCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="POBoxDeviatingStateCode" Type="Edm.String" Nullable="true" MaxLength="6" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="POBoxDeviatingStateCodeText" c4c:context-property="POBoxDeviatingCountryCode" c4c:value-help="CorporateAccountAddressPOBoxDeviatingStateCodeCollection"/>
    <Property Name="POBoxDeviatingStateCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="POBoxDeviatingCity" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="TimeZoneCode" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="TimeZoneCodeText" c4c:value-help="CorporateAccountAddressTimeZoneCodeCollection"/>
    <Property Name="TimeZoneCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="Latitude" Type="Edm.Decimal" Nullable="true" Precision="31" Scale="14" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Longitude" Type="Edm.Decimal" Nullable="true" Precision="31" Scale="14" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Building" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Floor" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Room" Type="Edm.String" Nullable="true" MaxLength="10" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Phone" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="NormalisedPhone" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="Mobile" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="NormalisedMobile" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="Fax" Type="Edm.String" Nullable="true" MaxLength="40" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="Email" Type="Edm.String" Nullable="true" MaxLength="255" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="WebSite" Type="Edm.String" Nullable="true" MaxLength="1280" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true"/>
    <Property Name="LanguageCode" Type="Edm.String" Nullable="true" MaxLength="2" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="LanguageCodeText" c4c:value-help="CorporateAccountAddressLanguageCodeCollection"/>
    <Property Name="LanguageCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="BestReachedByCode" Type="Edm.String" Nullable="true" MaxLength="3" FixedLength="true" sap:creatable="true" sap:updatable="true" sap:filterable="true" sap:text="BestReachedByCodeText" c4c:value-help="CorporateAccountAddressBestReachedByCodeCollection"/>
    <Property Name="BestReachedByCodeText" Type="Edm.String" Nullable="true" sap:creatable="false" sap:updatable="false" sap:filterable="true"/>
    <Property Name="ETag" Type="Edm.DateTimeOffset" Nullable="true" Precision="7" ConcurrencyMode="Fixed" sap:creatable="false" sap:updatable="false" sap:filterable="false"/>
    <NavigationProperty Name="CorporateAccount" Relationship="c4codata.CorporateAccount_CorporateAccountAddress" FromRole="CorporateAccountAddress" ToRole="CorporateAccount"/>
</EntityType>

As you can see the ETag attribute is defined as Edm.DateTimeOffset. This breaks with pyodata 1.10.0.

Minimal script:

import requests
import pyodata
import logging
from getpass import getpass
logging.basicConfig()
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)

user = "<<user>>"
pw = getpass()

SERVICE_URL = "<<service url>>"

session = requests.Session()
session.auth = (user, pw)
theservice = pyodata.Client(SERVICE_URL, session)

data = theservice.entity_sets.CorporateAccountAddressCollection.get_entities()

for d in data.execute():
    print(d.ETag)

Error:

DEBUG:pyodata.service:New entity proxy instance of type CorporateAccountAddress from properties: {'__metadata': {'uri': "https://<<service url>>/odata/v1/c4codataapi/CorporateAccountAddressCollection('???')", 'type': 'c4codata.CorporateAccountAddress', 'etag': 'W/"datetimeoffset\'2022-08-02T08%3A55%3A17.4611980Z\'"'}, 'ObjectID': '???', 'ParentObjectID': '???', 'AccountID': '???', 'UUID': '???', 'MainIndicator': True, 'ShipTo': True, 'DefaultShipTo': True, 'BillTo': True, 'DefaultBillTo': True, 'FormattedPostalAddressDescription': '???', 'FormattedAddressFirstLineDescription': '???', 'FormattedAddressSecondLineDescription': '???', 'FormattedAddressThirdLineDescription': '???', 'FormattedAddressFourthLineDescription': '???', 'FormattedPostalAddressFirstLineDescription': '???', 'FormattedPostalAddressSecondLineDescription': '???', 'FormattedPostalAddressThirdLineDescription': '???', 'CountryCode': '??', 'CountryCodeText': '???', 'StateCode': '??', 'StateCodeText': '???', 'CareOfName': '???', 'AddressLine1': '', 'AddressLine2': '', 'HouseNumber': '??', 'AdditionalHouseNumber': '', 'Street': '???', 'AddressLine4': '', 'AddressLine5': '', 'District': '', 'City': '???', 'DifferentCity': '', 'StreetPostalCode': '???', 'County': '', 'CompanyPostalCode': '', 'POBoxIndicator': False, 'POBox': '', 'POBoxPostalCode': '', 'POBoxDeviatingCountryCode': '', 'POBoxDeviatingCountryCodeText': '', 'POBoxDeviatingStateCode': '', 'POBoxDeviatingStateCodeText': '', 'POBoxDeviatingCity': '', 'TimeZoneCode': 'CET', 'TimeZoneCodeText': '(UTC+01:00) Central European Time', 'Latitude': '0.00000000000000', 'Longitude': '0.00000000000000', 'Building': '', 'Floor': '', 'Room': '', 'Phone': '???', 'NormalisedPhone': '???', 'Mobile': '', 'NormalisedMobile': '', 'Fax': '', 'Email': '', 'WebSite': '', 'LanguageCode': 'DE', 'LanguageCodeText': 'German', 'BestReachedByCode': '', 'BestReachedByCodeText': '', 'ETag': '/Date(1659430517461)/', 'CorporateAccount': {'__deferred': {'uri': "https://<<service url>>/odata/v1/c4codataapi/CorporateAccountAddressCollection('???')/CorporateAccount"}}}
Traceback (most recent call last):
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/model.py", line 534, in from_json
    milliseconds_since_epoch = matches.group('milliseconds_since_epoch')
AttributeError: 'NoneType' object has no attribute 'group'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/workspace/tmp/c4c_test.py", line 29, in <module>
    for d in data.execute():
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/service.py", line 349, in execute
    return self._call_handler(response)
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/service.py", line 362, in _call_handler
    return self._handler(response)
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/service.py", line 1509, in get_entities_handler
    entity = EntityProxy(self._service, self._entity_set, self._entity_set.entity_type, props)
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/service.py", line 814, in __init__
    self._cache[type_proprty.name] = type_proprty.from_json(proprties[type_proprty.name])
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/model.py", line 872, in from_json
    return self.typ.traits.from_json(value)
  File "/home/vscode/.local/lib/python3.10/site-packages/pyodata/v2/model.py", line 537, in from_json
    raise PyODataModelError(
pyodata.exceptions.PyODataModelError: Malformed value /Date(1659430517461)/ for primitive Edm.DateTimeOffset type. Expected format is /Date(<ticks>±<offset>)/

In this case I think the Odata Service provides the data in a wrong format (like this one https://github.com/SAP/python-pyodata/issues/143#issuecomment-775188255). Maybe there is any chance to do something like the SCREWED_UP-parameters?

What do you think about?

phanak-sap commented 2 years ago

OK, it seems like something to change for Edm.DateTimeOffset parsing of JSON values.

We are currently expecting that the offset part is mandatory and that your service should return /Date(1659430517461+0000)/ for offset set to UTC. I am not able to find in the odata.org specification exactly if the offset part is mandatory, nor in the W3C XML schema. But it seems that at least SAP thinks this should be correct - "As of Release Q1 2019, DateTimeOffset values without timezone information are also accepted in query URLs and edit request payloads. The value will be processed by the server as UTC time."

So the fix should be to apply this logic as well - if the offset part is missing, work as if +0000 was provided. But IMHO this should be just for the from_json logic, the to_jsonshould always contain offset information.

Relevant code: https://github.com/SAP/python-pyodata/blob/5d1490871f4b824a82dd6eaa148444a99ea4f47d/pyodata/v2/model.py#L531 https://github.com/SAP/python-pyodata/blob/5d1490871f4b824a82dd6eaa148444a99ea4f47d/tests/test_model_v2.py#L749

Possible quick/ugly workaround:

Seems to me that the literal parsing is correct. Could you try with the call with $value for literal form of the specific entity? IMHO this modification for your script should work (if not, maybe secondary bug :) ):

for d in data.execute().get_value():

Longer backgroung explanation:

IMHO it is working on old version, well, just because in 1.7.1 the Edm.DateTimeOffset was effectively "not implemented", with no Traits and in both literal and json format it was just passing the value up trough base class...

links to 1.7.1 tag:

https://github.com/SAP/python-pyodata/blob/5731a4cac63fd2fdceb28f061a71fa078a7f454b/pyodata/v2/model.py#L213

https://github.com/SAP/python-pyodata/blob/5731a4cac63fd2fdceb28f061a71fa078a7f454b/pyodata/v2/model.py#L343

Now, it is worth going trough the commit ec56995551779cb9 that implements the Edm.DateTimeOffset as starting point.

https://blogs.sap.com/2017/01/05/date-and-time-in-sap-gateway-foundation/

phanak-sap commented 2 years ago

Hi @sti0, check the PR #232 - should this work for you? Also, were you able to use the workaround with the get_value()?