Skip to content

Commit

Permalink
WIP - fixes in docs, failing 8/442
Browse files Browse the repository at this point in the history
  • Loading branch information
collerek committed Dec 17, 2023
1 parent 69660f7 commit b91bd49
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 19 deletions.
1 change: 0 additions & 1 deletion ormar/fields/many_to_many.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
overload,
)

from pydantic.v1.typing import evaluate_forwardref

import ormar # noqa: I100
from ormar import ModelDefinitionError
Expand Down
24 changes: 15 additions & 9 deletions ormar/models/helpers/relations.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import inspect
from typing import List, TYPE_CHECKING, Type, Union, cast
from typing import List, TYPE_CHECKING, Optional, Type, Union, cast

from pydantic import BaseModel, create_model
from pydantic.fields import FieldInfo
Expand Down Expand Up @@ -138,7 +138,7 @@ def register_reverse_model_fields(model_field: "ForeignKeyField") -> None:
)
if not model_field.skip_reverse:
field_type = model_field.to.ormar_config.model_fields[related_name].__type__
field_type = replace_models_with_copy(annotation=field_type)
field_type = replace_models_with_copy(annotation=field_type, source_model_field=model_field.name)
if not model_field.is_multi:
field_type = Union[field_type, List[field_type], None]
model_field.to.model_fields[related_name] = FieldInfo.from_annotated_attribute(
Expand All @@ -148,7 +148,7 @@ def register_reverse_model_fields(model_field: "ForeignKeyField") -> None:
setattr(model_field.to, related_name, RelationDescriptor(name=related_name))


def replace_models_with_copy(annotation: Type) -> Type:
def replace_models_with_copy(annotation: Type, source_model_field: Optional[str] = None) -> Type:
"""
Replaces all models in annotation with their copies to avoid circular references.
Expand All @@ -157,25 +157,31 @@ def replace_models_with_copy(annotation: Type) -> Type:
:return: annotation with replaced models
:rtype: Type
"""
if inspect.isclass(annotation) and issubclass(annotation, BaseModel):
return create_copy_to_avoid_circular_references(model=annotation)
if inspect.isclass(annotation) and issubclass(annotation, ormar.Model):
return create_copy_to_avoid_circular_references(model=annotation, source_model_field=source_model_field)
elif hasattr(annotation, "__origin__"):
if annotation.__origin__ == list:
return List[
replace_models_with_copy(annotation=annotation.__args__[0])
replace_models_with_copy(annotation=annotation.__args__[0], source_model_field=source_model_field)
] # type: ignore
elif annotation.__origin__ == Union:
args = annotation.__args__
new_args = [replace_models_with_copy(annotation=arg) for arg in args]
new_args = [replace_models_with_copy(annotation=arg, source_model_field=source_model_field) for arg in args]
return Union[tuple(new_args)]
else:
return annotation
else:
return annotation


def create_copy_to_avoid_circular_references(model: Type["Model"]) -> Type["BaseModel"]:
return cast(Type[BaseModel], type(model.__name__, (model,), {}))
def create_copy_to_avoid_circular_references(model: Type["Model"], source_model_field: Optional[str] = None) -> Type["BaseModel"]:
new_model = create_model(
model.__name__,
__base__=model,
**{k: (v.annotation, v.default) for k, v in model.model_fields.items() if k != source_model_field},
)
new_model.model_fields.pop(source_model_field, None)
return new_model


def register_through_shortcut_fields(model_field: "ManyToManyField") -> None:
Expand Down
20 changes: 18 additions & 2 deletions ormar/models/metaclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import databases
import pydantic
import sqlalchemy
from pydantic._internal._generics import PydanticGenericMetadata
from pydantic._internal._model_construction import complete_model_class
from pydantic.fields import ComputedFieldInfo, FieldInfo
from sqlalchemy.sql.schema import ColumnCollectionConstraint
Expand Down Expand Up @@ -569,7 +570,14 @@ def add_field_descriptor(

class ModelMetaclass(pydantic._internal._model_construction.ModelMetaclass):
def __new__( # type: ignore # noqa: CCR001
mcs: "ModelMetaclass", name: str, bases: Any, attrs: dict
mcs: "ModelMetaclass",
name: str,
bases: Any,
attrs: dict,
__pydantic_generic_metadata__: PydanticGenericMetadata | None = None,
__pydantic_reset_parent_namespace__: bool = True,
_create_model_module: str | None = None,
**kwargs
) -> "ModelMetaclass":
"""
Metaclass used by ormar Models that performs configuration
Expand Down Expand Up @@ -614,7 +622,15 @@ def __new__( # type: ignore # noqa: CCR001
if "ormar_config" in attrs:
attrs["model_config"]["ignored_types"] = (OrmarConfig,)
attrs["model_config"]["from_attributes"] = True
new_model = super().__new__(mcs, name, bases, attrs) # type: ignore
new_model = super().__new__(
mcs, # type: ignore
name,
bases,
attrs,
__pydantic_generic_metadata__=__pydantic_generic_metadata__,
__pydantic_reset_parent_namespace__=__pydantic_reset_parent_namespace__,
_create_model_module=_create_model_module,
**kwargs)

add_cached_properties(new_model)

Expand Down
2 changes: 0 additions & 2 deletions ormar/models/newbasemodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,6 @@ def __getattr__(self, item: str) -> Any:
# TODO: Check __pydantic_extra__
if item == "__pydantic_extra__":
return None
if item =="__pydantic_serializer__":
breakpoint()
return super().__getattr__(item)

def __getstate__(self) -> Dict[Any, Any]:
Expand Down
14 changes: 10 additions & 4 deletions tests/test_fastapi/test_binary_fields.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import base64
import json
import uuid
from enum import Enum
from typing import List

import databases
Expand Down Expand Up @@ -47,15 +48,20 @@ async def shutdown() -> None:
database=database,
)


class BinaryEnum(Enum):
blob3 = blob3
blob4 = blob4
blob5 = blob5
blob6 = blob6


class BinaryThing(ormar.Model):
ormar_config = base_ormar_config.copy(tablename = "things")

id: uuid.UUID = ormar.UUID(primary_key=True, default=uuid.uuid4)
name: str = ormar.Text(default="")
bt: str = ormar.LargeBinary(
max_length=1000,
choices=[blob3, blob4, blob5, blob6],
represent_as_base64_str=True,
bt: str = ormar.Enum(enum_class=BinaryEnum, represent_as_base64_str=True,
)


Expand Down
5 changes: 4 additions & 1 deletion tests/test_fastapi/test_fastapi_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pydantic
import pytest
import sqlalchemy
import uvicorn
from asgi_lifespan import LifespanManager
from fastapi import FastAPI
from httpx import AsyncClient
Expand Down Expand Up @@ -160,4 +161,6 @@ def test_schema_modification():
def test_schema_gen():
schema = app.openapi()
assert "Category" in schema["components"]["schemas"]
assert "Item" in schema["components"]["schemas"]
subschemas = [x.split("__")[-1] for x in schema["components"]["schemas"]]
assert "Item-Input" in subschemas
assert "Item-Output" in subschemas

0 comments on commit b91bd49

Please sign in to comment.