From 81a31edb445a8a43d28323ec65dba0714f23b3c0 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 12 Sep 2026 17:21:28 -0700 Subject: [PATCH 1/3] docs: document typed ORM fields and ModelField deprecation --- docs/database/casts.md | 20 ++++++++++++-------- docs/database/models.md | 42 +++++++++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/docs/database/casts.md b/docs/database/casts.md index ae9ee1a..8daad03 100644 --- a/docs/database/casts.md +++ b/docs/database/casts.md @@ -7,7 +7,7 @@ keywords: casts, serialization, model attributes, pydantic, json, orm # Casts -Casts automatically transform model attribute values when reading from or writing to the database. Annotate a field with the desired type and the ORM handles the conversion transparently. +Casts automatically transform model attribute values when reading from or writing to the database. Declare `Field[T]()` or annotate a field with the desired type and the ORM handles the conversion transparently. ## Built-in Casts @@ -152,17 +152,17 @@ class User(Model): A `NULL` database value will be returned as `time(9, 0, 0)` instead of `None`. -## Custom Casts with `Attribute` +## Nested Pydantic Models -For complex types — such as embedded value objects — extend `Attribute`. It is a Pydantic `BaseModel` subclass, so fields are validated and the instance is serialized to JSON automatically. +For embedded value objects, extend Pydantic's `BaseModel` and declare the ORM field with `Field[Address]()`. Values are reconstructed as Pydantic models when read and serialized to JSON when written. ### Defining a custom cast ```python from typing import Optional -from fastapi_startkit.masoniteorm import Attribute +from pydantic import BaseModel -class Address(Attribute): +class Address(BaseModel): street: Optional[str] = None city: Optional[str] = None state: Optional[str] = None @@ -171,18 +171,22 @@ class Address(Attribute): ### Wiring it to a model -Annotate the field with your `Attribute` subclass: +Use your Pydantic model as the field type: ```python -from fastapi_startkit.masoniteorm import Model +from fastapi_startkit.masoniteorm import Field, Model from app.casts import Address class User(Model): id: int name: str - address: Address + address = Field[Address]() ``` +The legacy declaration `address: Address = ModelField()` remains supported. +`ModelField` is publicly importable from `fastapi_startkit.masoniteorm`, but +emits a `DeprecationWarning` and is scheduled for removal in **2.x**. + The column should be a `text` or `json` column in your migration: ```python diff --git a/docs/database/models.md b/docs/database/models.md index b213aad..5770364 100644 --- a/docs/database/models.md +++ b/docs/database/models.md @@ -11,19 +11,49 @@ Models represent database tables and are the primary interface for reading and w ## Defining a Model -Extend `Model` from `fastapi_startkit.masoniteorm` and annotate your columns as class-level type hints: +Extend `Model` from `fastapi_startkit.masoniteorm` and declare columns with `Field[T]()`: ```python -from fastapi_startkit.masoniteorm import Model +from fastapi_startkit.masoniteorm import Field, Model class User(Model): __table__ = "users" - id: int - name: str - email: str + id = Field[int]() + name = Field[str]() + email = Field[str]() + is_admin = Field(default=False) +``` + +### Field types and compatibility + +`Field[int]()` supplies the type for instance access and runtime casting. +`Field(default=False)` infers `bool` from its default. Existing annotated fields, +such as `name: str`, remain supported. + +The base `Model` uses `@dataclass_transform` with `Field` and the legacy +`ModelField` registered as field specifiers. This provides static typing metadata; +it does not generate a runtime constructor or validate that every field was +supplied. Descriptor-only fields also participate in `fill()` and `update()`. + +For embedded Pydantic models, use `address = Field[Address]()`. The ORM stores +the value as JSON and reconstructs an `Address` on access. See +[nested model casts](./casts#nested-pydantic-models) for a complete example. + +`ModelField` is still defined and exported for compatibility: + +```python +from fastapi_startkit.masoniteorm import Model, ModelField +from app.casts import Address + +class LegacyUser(Model): + address: Address = ModelField() ``` +`ModelField()` emits a `DeprecationWarning` and is scheduled for removal in +**2.x**. Replace `address: Address = ModelField()` with +`address = Field[Address]()`. + ### `__table__` By default the ORM infers the table name from the class name (pluralized, snake_cased). Set `__table__` explicitly to override: @@ -234,4 +264,4 @@ class Post(Model): Relationship declarations are covered in the [Relationships](./relationships) section. -Automatic type coercion and custom value objects are covered in the [Casts](./casts) section. \ No newline at end of file +Automatic type coercion and custom value objects are covered in the [Casts](./casts) section. From 65fde79f2a489ff56f6e73199bc7e2a6b145b6a1 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 12 Sep 2026 17:24:39 -0700 Subject: [PATCH 2/3] docs: prefer plain annotations in model examples --- docs/database/casts.md | 12 +++++++----- docs/database/models.md | 38 +++++++++++++++++++++++++------------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/docs/database/casts.md b/docs/database/casts.md index 8daad03..032ef71 100644 --- a/docs/database/casts.md +++ b/docs/database/casts.md @@ -7,7 +7,7 @@ keywords: casts, serialization, model attributes, pydantic, json, orm # Casts -Casts automatically transform model attribute values when reading from or writing to the database. Declare `Field[T]()` or annotate a field with the desired type and the ORM handles the conversion transparently. +Casts automatically transform model attribute values when reading from or writing to the database. Annotate a field with the desired type and the ORM handles the conversion transparently. The optional `Field[T]()` syntax also supports explicit field types. ## Built-in Casts @@ -154,7 +154,7 @@ A `NULL` database value will be returned as `time(9, 0, 0)` instead of `None`. ## Nested Pydantic Models -For embedded value objects, extend Pydantic's `BaseModel` and declare the ORM field with `Field[Address]()`. Values are reconstructed as Pydantic models when read and serialized to JSON when written. +For embedded value objects, extend Pydantic's `BaseModel` and annotate the ORM field as `address: Address`. Values are reconstructed as Pydantic models when read and serialized to JSON when written. ### Defining a custom cast @@ -174,18 +174,20 @@ class Address(BaseModel): Use your Pydantic model as the field type: ```python -from fastapi_startkit.masoniteorm import Field, Model +from fastapi_startkit.masoniteorm import Model from app.casts import Address class User(Model): id: int name: str - address = Field[Address]() + address: Address ``` The legacy declaration `address: Address = ModelField()` remains supported. `ModelField` is publicly importable from `fastapi_startkit.masoniteorm`, but -emits a `DeprecationWarning` and is scheduled for removal in **2.x**. +emits a `DeprecationWarning` and is scheduled for removal in **2.x**. Migrate to +`address: Address`. If you prefer explicit descriptors, import `Field` and use +`address = Field[Address]()` instead. The column should be a `text` or `json` column in your migration: diff --git a/docs/database/models.md b/docs/database/models.md index 5770364..8c32d39 100644 --- a/docs/database/models.md +++ b/docs/database/models.md @@ -11,32 +11,44 @@ Models represent database tables and are the primary interface for reading and w ## Defining a Model -Extend `Model` from `fastapi_startkit.masoniteorm` and declare columns with `Field[T]()`: +Extend `Model` from `fastapi_startkit.masoniteorm` and annotate your columns with Python types: ```python -from fastapi_startkit.masoniteorm import Field, Model +from fastapi_startkit.masoniteorm import Model class User(Model): __table__ = "users" - id = Field[int]() - name = Field[str]() - email = Field[str]() - is_admin = Field(default=False) + id: int + name: str + email: str ``` -### Field types and compatibility +### Optional field configuration + +Plain annotations supply the types for attribute access and runtime casting. +Import `Field` when you need a default or field metadata: + +```python +from fastapi_startkit.masoniteorm import Field, Model + +class User(Model): + id: int + name: str + email: str + is_admin: bool = Field(default=False) +``` -`Field[int]()` supplies the type for instance access and runtime casting. -`Field(default=False)` infers `bool` from its default. Existing annotated fields, -such as `name: str`, remain supported. +Explicit descriptors such as `id = Field[int]()` and inferred defaults such as +`is_admin = Field(default=False)` are also supported. You can mix these with +plain annotations; descriptors participate in `fill()` and `update()` too. The base `Model` uses `@dataclass_transform` with `Field` and the legacy `ModelField` registered as field specifiers. This provides static typing metadata; it does not generate a runtime constructor or validate that every field was -supplied. Descriptor-only fields also participate in `fill()` and `update()`. +supplied. -For embedded Pydantic models, use `address = Field[Address]()`. The ORM stores +For embedded Pydantic models, annotate the field as `address: Address`. The ORM stores the value as JSON and reconstructs an `Address` on access. See [nested model casts](./casts#nested-pydantic-models) for a complete example. @@ -52,7 +64,7 @@ class LegacyUser(Model): `ModelField()` emits a `DeprecationWarning` and is scheduled for removal in **2.x**. Replace `address: Address = ModelField()` with -`address = Field[Address]()`. +`address: Address`. The optional `address = Field[Address]()` syntax works too. ### `__table__` From ea64da5e9ade98499ef1734bd5f9e8edf78f8a48 Mon Sep 17 00:00:00 2001 From: Bedram Tamang Date: Sat, 12 Sep 2026 17:25:18 -0700 Subject: [PATCH 3/3] docs: remove field compatibility section from models guide --- docs/database/models.md | 42 ----------------------------------------- 1 file changed, 42 deletions(-) diff --git a/docs/database/models.md b/docs/database/models.md index 8c32d39..71eedc3 100644 --- a/docs/database/models.md +++ b/docs/database/models.md @@ -24,48 +24,6 @@ class User(Model): email: str ``` -### Optional field configuration - -Plain annotations supply the types for attribute access and runtime casting. -Import `Field` when you need a default or field metadata: - -```python -from fastapi_startkit.masoniteorm import Field, Model - -class User(Model): - id: int - name: str - email: str - is_admin: bool = Field(default=False) -``` - -Explicit descriptors such as `id = Field[int]()` and inferred defaults such as -`is_admin = Field(default=False)` are also supported. You can mix these with -plain annotations; descriptors participate in `fill()` and `update()` too. - -The base `Model` uses `@dataclass_transform` with `Field` and the legacy -`ModelField` registered as field specifiers. This provides static typing metadata; -it does not generate a runtime constructor or validate that every field was -supplied. - -For embedded Pydantic models, annotate the field as `address: Address`. The ORM stores -the value as JSON and reconstructs an `Address` on access. See -[nested model casts](./casts#nested-pydantic-models) for a complete example. - -`ModelField` is still defined and exported for compatibility: - -```python -from fastapi_startkit.masoniteorm import Model, ModelField -from app.casts import Address - -class LegacyUser(Model): - address: Address = ModelField() -``` - -`ModelField()` emits a `DeprecationWarning` and is scheduled for removal in -**2.x**. Replace `address: Address = ModelField()` with -`address: Address`. The optional `address = Field[Address]()` syntax works too. - ### `__table__` By default the ORM infers the table name from the class name (pluralized, snake_cased). Set `__table__` explicitly to override: