Closed sti0 closed 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 :)
model.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
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.
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?
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_json
should 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:
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/
Hi @sti0,
check the PR #232 - should this work for you? Also, were you able to use the workaround with the get_value()?
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:
Maybe this is related to our instance because they are uncompatible dates (see https://github.com/SAP/python-pyodata/issues/143).