This repository has been archived by the owner on Sep 12, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dto) - rework of dto pattern (#139)
* ✨ feat(dto): decorator based pydantic models gen with validation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ♻️ refactor: fix mypy * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ♻️ refactor: fix pylint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * feat(dto): refactor dto.factory. Moves the core changes from dto.dto() into dto.factory() to centralize logic. * Update src/starlite_saqlalchemy/dto.py * feat(dto): more iteration. - Adds `dto.Attrib.pydantic_type` and `dto.Attrib.validators` where `pydantic_type` allows the dto field type to be explicitly set, and `validators` allows arbitrary single argument callables to be specified as dto validators for any model attribute. - `factory()` should now handle `MappedAsDataclass` sqla models - `decorator()` is for decorating pydantic models, and uses the decorated class as the base class for the model. - adds some tests. * feat(dto): more iteration. Further development of the decorator pattern. * feat!(dto): refactor of DTOs.. again. Have done away with both `factory()` and `decorator()`, as neither plays nicely with static type checkers. Using `FromMapped[Annotated[Author, dto.config("read")]]` has the same result as the old `dto.factory("DTO", Author, dto.Purpose.READ)`. In `FromMapped.__class_getitem__()` we create a new class, assign the model to it, and return it inplace of `FromMapped`. This new class can be subclassed in order to define pydantic validators and override types, just as any other pydantic model would be. * feat!(dto): more dto iteration. This PR refreshes DTO docs, gets test cov up to 100%, and adds some ergonomic improvements to the dto declaration syntax. Co-authored-by: gazorby <gazorby@pm.me> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Matthieu MN <10926130+gazorby@users.noreply.github.com>
- Loading branch information
1 parent
e929713
commit e503447
Showing
28 changed files
with
881 additions
and
475 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from __future__ import annotations | ||
|
||
from pydantic import Field, constr | ||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column | ||
|
||
from starlite_saqlalchemy import dto | ||
|
||
|
||
def check_email(email: str) -> str: | ||
"""Validate an email.""" | ||
if "@" not in email: | ||
raise ValueError("Invalid email!") | ||
return email | ||
|
||
|
||
class Base(DeclarativeBase): | ||
"""Our ORM base class.""" | ||
|
||
|
||
class Thing(Base): | ||
"""Something in our domain.""" | ||
|
||
__tablename__ = "things" | ||
# demonstrates marking a field as "read-only" and overriding the generated pydantic `FieldInfo` | ||
# for the DTO field. | ||
id = mapped_column( | ||
primary_key=True, info=dto.field("read-only", pydantic_field=Field(alias="identifier")) | ||
) | ||
# demonstrates overriding the type assigned to the field in generated DTO | ||
always_upper: Mapped[str] = mapped_column(info=dto.field(pydantic_type=constr(to_upper=True))) | ||
# demonstrates setting a field as "private" | ||
private: Mapped[str] = mapped_column(info=dto.field("private")) | ||
# demonstrates setting a validator for the field | ||
email: Mapped[str] = mapped_column(info=dto.field(validators=[check_email])) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from __future__ import annotations | ||
|
||
from datetime import date, datetime | ||
from typing import Annotated | ||
|
||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column | ||
|
||
from starlite_saqlalchemy import dto | ||
|
||
|
||
class Base(DeclarativeBase): | ||
"""ORM base class. | ||
All SQLAlchemy ORM models must inherit from DeclarativeBase. We also | ||
define some common columns that we want to be present on every model | ||
in our domain. | ||
""" | ||
|
||
id: Mapped[int] = mapped_column(primary_key=True) | ||
created: Mapped[datetime] | ||
updated: Mapped[datetime] | ||
|
||
|
||
class Author(Base): | ||
"""A domain model. | ||
In addition to the columns defined on `Base` we have "name" and | ||
"dob". | ||
""" | ||
|
||
__tablename__ = "authors" | ||
name: Mapped[str] | ||
dob: Mapped[date] | ||
|
||
|
||
# This creates a DTO, which is simply a Pydantic model that inherits | ||
# from our special `FromMapped` subclass. We call it "WriteDTO" as it | ||
# is the model that we'll use to parse client data as they try to | ||
# "write" to (that is, create or update) authors in our domain. | ||
WriteDTO = dto.FromMapped[Annotated[Author, "write"]] | ||
|
||
# we can inspect the fields that are available on the DTO | ||
print(WriteDTO.__fields__) | ||
|
||
# { | ||
# "id": ModelField(name="id", type=int, required=True), | ||
# "created": ModelField(name="created", type=datetime, required=True), | ||
# "updated": ModelField(name="updated", type=datetime, required=True), | ||
# "name": ModelField(name="name", type=str, required=True), | ||
# "dob": ModelField(name="dob", type=date, required=True), | ||
# } |
Oops, something went wrong.