126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
import warnings
|
|
from typing import Any, List, Optional, Union, no_type_check
|
|
|
|
from django.db.models import Model as DjangoModel
|
|
from pydantic.dataclasses import dataclass
|
|
|
|
from ninja.errors import ConfigError
|
|
from ninja.orm.factory import create_schema
|
|
from ninja.schema import ResolverMetaclass, Schema
|
|
|
|
_is_modelschema_class_defined = False
|
|
|
|
|
|
@dataclass
|
|
class MetaConf:
|
|
model: Any
|
|
fields: Optional[List[str]] = None
|
|
exclude: Union[List[str], str, None] = None
|
|
fields_optional: Union[List[str], str, None] = None
|
|
|
|
@staticmethod
|
|
def from_schema_class(name: str, namespace: dict) -> "MetaConf":
|
|
if "Meta" in namespace:
|
|
meta = namespace["Meta"]
|
|
model = meta.model
|
|
fields = getattr(meta, "fields", None)
|
|
exclude = getattr(meta, "exclude", None)
|
|
optional_fields = getattr(meta, "fields_optional", None)
|
|
|
|
elif "Config" in namespace:
|
|
config = namespace["Config"]
|
|
model = config.model
|
|
fields = getattr(config, "model_fields", None)
|
|
exclude = getattr(config, "model_exclude", None)
|
|
optional_fields = getattr(config, "model_fields_optional", None)
|
|
|
|
warnings.warn(
|
|
"The use of `Config` class is deprecated for ModelSchema, use 'Meta' instead",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
|
|
else:
|
|
raise ConfigError(
|
|
f"ModelSchema class '{name}' requires a 'Meta' (or a 'Config') subclass"
|
|
)
|
|
|
|
assert issubclass(model, DjangoModel)
|
|
|
|
if not fields and not exclude:
|
|
raise ConfigError(
|
|
"Creating a ModelSchema without either the 'fields' attribute"
|
|
" or the 'exclude' attribute is prohibited"
|
|
)
|
|
|
|
if fields == "__all__":
|
|
fields = None
|
|
# ^ when None is passed to create_schema - all fields are selected
|
|
|
|
return MetaConf(
|
|
model=model,
|
|
fields=fields,
|
|
exclude=exclude,
|
|
fields_optional=optional_fields,
|
|
)
|
|
|
|
|
|
class ModelSchemaMetaclass(ResolverMetaclass):
|
|
@no_type_check
|
|
def __new__(
|
|
mcs,
|
|
name: str,
|
|
bases: tuple,
|
|
namespace: dict,
|
|
**kwargs,
|
|
):
|
|
cls = super().__new__(
|
|
mcs,
|
|
name,
|
|
bases,
|
|
namespace,
|
|
**kwargs,
|
|
)
|
|
for base in reversed(bases):
|
|
if (
|
|
_is_modelschema_class_defined
|
|
and issubclass(base, ModelSchema)
|
|
and base == ModelSchema
|
|
):
|
|
meta_conf = MetaConf.from_schema_class(name, namespace)
|
|
|
|
custom_fields = []
|
|
annotations = namespace.get("__annotations__", {})
|
|
for attr_name, type in annotations.items():
|
|
if attr_name.startswith("_"):
|
|
continue
|
|
default = namespace.get(attr_name, ...)
|
|
custom_fields.append((attr_name, type, default))
|
|
|
|
# # cls.__doc__ = namespace.get("__doc__", config.model.__doc__)
|
|
# cls.__fields__ = {} # forcing pydantic recreate
|
|
# # assert False, "!! cls.model_fields"
|
|
|
|
# print(config.model, name, fields, exclude, "!!")
|
|
|
|
model_schema = create_schema(
|
|
meta_conf.model,
|
|
name=name,
|
|
fields=meta_conf.fields,
|
|
exclude=meta_conf.exclude,
|
|
optional_fields=meta_conf.fields_optional,
|
|
custom_fields=custom_fields,
|
|
base_class=cls,
|
|
)
|
|
model_schema.__doc__ = cls.__doc__
|
|
return model_schema
|
|
|
|
return cls
|
|
|
|
|
|
class ModelSchema(Schema, metaclass=ModelSchemaMetaclass):
|
|
pass
|
|
|
|
|
|
_is_modelschema_class_defined = True
|