Summary
When filtering on an enum-typed property (Entity.SomeEnumProp == SomeEnum.Value), the generated OData filter literal is hardcoded to the Microsoft.Dynamics.DataEntities namespace regardless of the actual namespace declared in the service's $metadata. This breaks filtering on enum properties against any OData v4 service that isn't Microsoft Dynamics 365 (e.g. SAP, IFS Cloud, and presumably most other real-world OData v4 services).
Steps to reproduce
from odata import ODataService
Service = ODataService(service_url, session=session, reflect_entities=True)
SomeEntity = Service.entities["SomeEntitySet"]
# SomeEnumProp is correctly reflected as an enum property from $metadata
prop = SomeEntity.SomeEnumProp
enum_cls = prop.enum_class
print(list(enum_cls)) # correctly shows enum members from the real service
# But the escaped filter value is wrong:
print(prop.escape_value(list(enum_cls)[0]))
# -> "Microsoft.Dynamics.DataEntities.SomeEnumProp'FirstValue'"
Tested live against a real IFS Cloud OData v4 service, whose actual EDM namespace for this type is IfsApp.RequestHandling.SrvRequestState (not Microsoft.Dynamics.DataEntities). Sending the generated filter causes the service to reject the request (type not defined in that namespace).
Root cause
In odata/enumtype.py:
def escape_value(self, value):
if self.enum_class.__module__:
return f"Microsoft.Dynamics.DataEntities.{self.enum_class.__name__}'{value.name}'"
return f"{self.enum_class.__name__}'{value.name}'"
self.enum_class.__module__ is essentially always truthy (a Python module name), so the Microsoft Dynamics branch is effectively hit unconditionally — the fallback branch is unreachable in practice, and neither branch is correct for a generic service.
The interesting part: the correct fully-qualified namespace is available during metadata parsing but gets discarded before reaching this code. In odata/metadata.py:
for enum_type in schema['enum_types']:
...
created_enum = EnumType(enum_type['name'], names=names)
all_types[enum_type['fully_qualified_name']] = created_enum
enum_type['fully_qualified_name'] (e.g. IfsApp.RequestHandling.SrvRequestState) is used only as a dict key to register the type — it's never stored on the EnumType/EnumTypeProperty object itself, so escape_value() has no way to reconstruct the real namespace later and falls back to the hardcoded string.
Suggested fix
Store the fully-qualified namespace on the EnumType (or on EnumTypeProperty) when it's constructed in metadata.py, and have escape_value() use that instead of the hardcoded Microsoft.Dynamics.DataEntities string, e.g.:
def escape_value(self, value):
return f"{self.enum_class.namespace}.{self.enum_class.__name__}'{value.name}'"
Environment
python-odata 0.8.1
- Python 3.14
- Tested against IFS Cloud (OData v4), reflected via
reflect_entities=True
Summary
When filtering on an enum-typed property (
Entity.SomeEnumProp == SomeEnum.Value), the generated OData filter literal is hardcoded to theMicrosoft.Dynamics.DataEntitiesnamespace regardless of the actual namespace declared in the service's$metadata. This breaks filtering on enum properties against any OData v4 service that isn't Microsoft Dynamics 365 (e.g. SAP, IFS Cloud, and presumably most other real-world OData v4 services).Steps to reproduce
Tested live against a real IFS Cloud OData v4 service, whose actual EDM namespace for this type is
IfsApp.RequestHandling.SrvRequestState(notMicrosoft.Dynamics.DataEntities). Sending the generated filter causes the service to reject the request (type not defined in that namespace).Root cause
In
odata/enumtype.py:self.enum_class.__module__is essentially always truthy (a Python module name), so the Microsoft Dynamics branch is effectively hit unconditionally — the fallback branch is unreachable in practice, and neither branch is correct for a generic service.The interesting part: the correct fully-qualified namespace is available during metadata parsing but gets discarded before reaching this code. In
odata/metadata.py:enum_type['fully_qualified_name'](e.g.IfsApp.RequestHandling.SrvRequestState) is used only as a dict key to register the type — it's never stored on theEnumType/EnumTypePropertyobject itself, soescape_value()has no way to reconstruct the real namespace later and falls back to the hardcoded string.Suggested fix
Store the fully-qualified namespace on the
EnumType(or onEnumTypeProperty) when it's constructed inmetadata.py, and haveescape_value()use that instead of the hardcodedMicrosoft.Dynamics.DataEntitiesstring, e.g.:Environment
python-odata0.8.1reflect_entities=True