♻️Support fine-grained Model.Schema configuration
- simplify Model.Schema - introduce metaclass in favor of __sub_init__ - use metaclass to populate effective Model.Schema
This commit is contained in:
parent
6f85f24232
commit
925a3b3911
2 changed files with 44 additions and 72 deletions
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Optional, List
|
||||
from ohmyapi.db import Model, field
|
||||
from passlib.context import CryptContext
|
||||
from tortoise.contrib.pydantic import pydantic_queryset_creator
|
||||
|
||||
pwd_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
||||
|
||||
|
|
@ -18,6 +19,11 @@ class User(Model):
|
|||
is_staff = field.BooleanField(default=False)
|
||||
groups: Optional[List[Group]] = field.ManyToManyField("ohmyapi_auth.Group", related_name="users")
|
||||
|
||||
|
||||
class Schema:
|
||||
exclude = 'password_hash',
|
||||
|
||||
|
||||
def set_password(self, raw_password: str) -> None:
|
||||
"""Hash and store the password."""
|
||||
self.password_hash = pwd_context.hash(raw_password)
|
||||
|
|
|
|||
|
|
@ -3,78 +3,44 @@ from tortoise.models import Model as TortoiseModel
|
|||
from tortoise.contrib.pydantic import pydantic_model_creator, pydantic_queryset_creator
|
||||
|
||||
|
||||
class Model(TortoiseModel):
|
||||
"""
|
||||
Base Tortoise model with attached Pydantic schema generators via .Schema
|
||||
"""
|
||||
class ModelMeta(type(TortoiseModel)):
|
||||
def __new__(cls, name, bases, attrs):
|
||||
new_cls = super().__new__(cls, name, bases, attrs)
|
||||
|
||||
schema_opts = getattr(new_cls, "Schema", None)
|
||||
|
||||
class BoundSchema:
|
||||
@property
|
||||
def one(self):
|
||||
"""Return a Pydantic model class for 'one' results."""
|
||||
include = getattr(schema_opts, "include", None)
|
||||
exclude = getattr(schema_opts, "exclude", None)
|
||||
return pydantic_model_creator(
|
||||
new_cls,
|
||||
name=f"{new_cls.__name__}SchemaOne",
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
exclude_readonly=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def many(self):
|
||||
"""Return a Pydantic queryset class for 'many' results."""
|
||||
include = getattr(schema_opts, "include", None)
|
||||
exclude = getattr(schema_opts, "exclude", None)
|
||||
return pydantic_queryset_creator(
|
||||
new_cls,
|
||||
name=f"{new_cls.__name__}SchemaMany",
|
||||
include=include,
|
||||
exclude=exclude,
|
||||
)
|
||||
|
||||
new_cls.Schema = BoundSchema()
|
||||
return new_cls
|
||||
|
||||
|
||||
class Model(TortoiseModel, metaclass=ModelMeta):
|
||||
class Schema:
|
||||
"""
|
||||
Provides convenient access to auto-generated Pydantic schemas.
|
||||
"""
|
||||
|
||||
def __init__(self, model_cls):
|
||||
self.model_cls = model_cls
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
# Minimal schema with just the primary key field
|
||||
pk_field = self.model_cls._meta.pk_attr
|
||||
return pydantic_model_creator(
|
||||
self.model_cls, name=f"{self.model_cls.__name__}SchemaId", include=(pk_field,)
|
||||
)
|
||||
|
||||
@property
|
||||
def get(self):
|
||||
# Full schema for reading
|
||||
return pydantic_model_creator(
|
||||
self.model_cls, name=f"{self.model_cls.__name__}SchemaGet"
|
||||
)
|
||||
|
||||
@property
|
||||
def post(self):
|
||||
# Input schema for creation (no readonly fields like ID/PK)
|
||||
return pydantic_model_creator(
|
||||
self.model_cls,
|
||||
name=f"{self.model_cls.__name__}SchemaPost",
|
||||
exclude_readonly=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def put(self):
|
||||
# Input schema for updating
|
||||
return pydantic_model_creator(
|
||||
self.model_cls,
|
||||
name=f"{self.model_cls.__name__}SchemaPut",
|
||||
exclude_readonly=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def delete(self):
|
||||
# Schema for delete operations (just PK)
|
||||
pk_field = self.model_cls._meta.pk_attr
|
||||
return pydantic_model_creator(
|
||||
self.model_cls, name=f"{self.model_cls.__name__}SchemaDelete", include=(pk_field,)
|
||||
)
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
# Schema for list endpoints
|
||||
return pydantic_queryset_creator(self.model_cls)
|
||||
|
||||
def from_fields(self, *fields: str):
|
||||
# Generate schema restricted to given fields
|
||||
valid = [f for f in fields if f in self.model_cls._meta.fields_map]
|
||||
return pydantic_model_creator(
|
||||
self.model_cls,
|
||||
name=f"{self.model_cls.__name__}SchemaFields",
|
||||
include=valid,
|
||||
)
|
||||
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
"""
|
||||
Automatically attach .Schema to all subclasses
|
||||
"""
|
||||
super().__init_subclass__(**kwargs)
|
||||
cls.Schema = cls.Schema(cls)
|
||||
include = None
|
||||
exclude = None
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue