art049 / odmantic

Sync and Async ODM (Object Document Mapper) for MongoDB based on python type hints
http://art049.github.io/odmantic
ISC License
1.06k stars 92 forks source link

Support for Optional types as X | None #399

Open santigandolfo opened 9 months ago

santigandolfo commented 9 months ago

I'm trying to update my code to Odmantic 1.0.0, Pydantic 2.5.2 and also start using Python 3.11, but when I define one of my classes like this:

from typing import Optional
from odmantic import Model
from pydantic import EmailStr
class User(Model):
    firebase_uid: str
    permissions: list[str] = []
    name: str | None = None
    email: str | None= None

I'm getting this error:

webhook-1  |   File "/app/app/domain/entities.py", line 8, in <module>
webhook-1  |     class User(Model):
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/odmantic/model.py", line 488, in __new__
webhook-1  |     return super().__new__(mcs, name, bases, namespace, **kwargs)
webhook-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/odmantic/model.py", line 406, in __new__
webhook-1  |     cls = super().__new__(mcs, name, bases, namespace, **kwargs)
webhook-1  |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 181, in __new__
webhook-1  |     set_model_fields(cls, bases, config_wrapper, types_namespace)
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_model_construction.py", line 426, in set_model_fields
webhook-1  |     fields, class_vars = collect_model_fields(cls, bases, config_wrapper, types_namespace, typevars_map=typevars_map)
webhook-1  |                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_fields.py", line 120, in collect_model_fields
webhook-1  |     type_hints = get_cls_type_hints_lenient(cls, types_namespace)
webhook-1  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_typing_extra.py", line 212, in get_cls_type_hints_lenient
webhook-1  |     hints[name] = eval_type_lenient(value, globalns, localns)
webhook-1  |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_typing_extra.py", line 224, in eval_type_lenient
webhook-1  |     return typing._eval_type(value, globalns, localns)  # type: ignore
webhook-1  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/typing.py", line 392, in _eval_type
webhook-1  |     t = t.__origin__[args]
webhook-1  |         ~~~~~~~~~~~~^^^^^^
webhook-1  | TypeError: type 'types.UnionType' is not subscriptable

How can I define the entity? If I define it like this it works, but I'm using ruff and it automatically suggests the other format (and I personally prefer it):

class User(Model):
    firebase_uid: str
    permissions: list[str] = []
    name: Optional[str] = None
    email: Optional[str]= None

I'm also having problems if instead of the email being of type str I use EmailStr:

class User(Model):
    firebase_uid: str
    permissions: list[str] = []
    name: Optional[str] = None
    email: Optional[EmailStr]= None

In that case I get this error when I do users = await self.engine.find(User):

webhook-1  |   File "/app/app/domain/usecases/users_usecases.py", line 23, in list_users
webhook-1  |     users = await self.engine.find(User)
webhook-1  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/odmantic/engine.py", line 110, in __await__
webhook-1  |     instances.append(self._parse_document(raw_doc))
webhook-1  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/odmantic/engine.py", line 83, in _parse_document
webhook-1  |     instance = self._model.model_validate_doc(raw_doc)
webhook-1  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/odmantic/model.py", line 806, in model_validate_doc
webhook-1  |     raise DocumentParsingError(
webhook-1  |           ^^^^^^^^^^^^^^^^^^^^^
webhook-1  |   File "/usr/local/lib/python3.11/site-packages/odmantic/exceptions.py", line 111, in __init__
webhook-1  |     self.inner = ValidationError.from_exception_data(
webhook-1  |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
webhook-1  | TypeError: ValueError: 'error' required in context

If I define it like this there are no errors:

class User(Model):
    firebase_uid: str
    permissions: list[str] = []
    name: str
    email: EmailStr
Kludex commented 8 months ago

Yes, this would be nice.