odmantic icon indicating copy to clipboard operation
odmantic copied to clipboard

Use of Optional Fields in ODMantic Models can change global type annotations handling

Open QSHolzner opened this issue 3 years ago • 2 comments

Bug

If i use the type Optional[datetime.datetime] in a class which extends odmantic.model the type is mapped to typing.Optional[odmantic.bson._datetime]. This is intended. But after the fist us of this type, the type mapping is performed for all following Optional[datetime.datetime] types used in different classes. Even if the class is a "plain" class, which does not extend odmantic.Model.

Current Behavior

Test Script:

import datetime
from typing import Optional, get_type_hints
from odmantic import Model

class Model1:
    a: datetime.datetime
    b: Optional[datetime.datetime]

class MyDBModel(Model):
    c: datetime.datetime
    d: Optional[datetime.datetime]

class Model2:
    e: datetime.datetime
    f: Optional[datetime.datetime]


print("Model1", get_type_hints(Model1))
print("MyDBModel", get_type_hints(MyDBModel))
print("Model2", get_type_hints(Model2))

Output:

Model1 {'a': <class 'datetime.datetime'>, 'b': typing.Optional[datetime.datetime]}

MyDBModel {'c': <class 'odmantic.bson._datetime'>, 'd': typing.Optional[odmantic.bson._datetime], 'id': <class 'odmantic.bson.ObjectId'>}

Model2 {'e': <class 'datetime.datetime'>, 'f': typing.Optional[odmantic.bson._datetime]}

Expected behavior

The type of Field f in Model2 should be typing.Optional[datetime.datetime] like in Model1 and not typing.Optional[odmantic.bson._datetime].

Environment

  • ODMantic version: 0.3.5
  • Pydantic infos (output of python -c "import pydantic.utils; print(pydantic.utils.version_info())): pydantic version: 1.8.2 pydantic compiled: True install path: /home/vscode/.local/lib/python3.9/site-packages/pydantic python version: 3.9.7 (default, Oct 13 2021, 09:00:49) [GCC 10.2.1 20210110] platform: Linux-5.4.72-microsoft-standard-WSL2-x86_64-with-glibc2.31 optional deps. installed: ['dotenv', 'typing-extensions']

QSHolzner avatar Nov 17 '21 10:11 QSHolzner

I have this exact error too, also with odmantic 0.3.5 in the following enviroment

Current behaviour

>>> import typing
>>> import datetime
>>> import odmantic
>>> class A:
...     a: typing.Optional[datetime.datetime]
...
>>> A.__annotations__
{'a': typing.Optional[datetime.datetime]}
>>> class B(odmantic.Model):
...     b: typing.Optional[datetime.datetime]
...
>>> A.__annotations__
{'a': typing.Optional[odmantic.bson._datetime]}

I use annotations to generate function signatures for FastAPI.

Expected behaviour

A.__annotations__ should remain the same.

Environment:

  • OS MacOS: Darwin MacBookErny 19.6.0 Darwin Kernel Version 19.6.0: Tue Jun 22 19:49:55 PDT 2021; root:xnu-6153.141.35~1/RELEASE_X86_64 x86_64
  • OS Linux: Linux 5.4.0-107-generic #121~18.04.1-Ubuntu SMP Thu Mar 24 17:21:33 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
  • Python 3.10.4
  • pydantic: 1.8.2
  • odmantic 0.3.5 / 0.4.0

erny avatar Apr 28 '22 07:04 erny

I debugged this, and it happens exactly here: https://github.com/art049/odmantic/blob/e58b987c09898744a3bca66d389f284ab1d28e87/odmantic/model.py#L180

Instead of creating a new type, the args for composed types are change inline.

For python >= 3.9 I found that replacing the lines:

from types import FunctionType

with

from types import FunctionType, GenericAlias

and

        setattr(type_, "__args__", new_arg_types)

with

        type_ = GenericAlias(type_origin, new_arg_types)

would resolve this specific problem, But it seems not to be compatible with Python 3.8 and below.

erny avatar Apr 28 '22 10:04 erny