Skip to main content

A Python library inspired by TypeScript, featuring handy decorators for transforming Pydantic models and automatic CRUD schema generation.

Project description

Pystructor

Python Support License Pydantic Version SQLModel Version

Pystructor is a Python library inspired by TypeScript, featuring handy decorators for transforming Pydantic models and SQLModel entities, along with automatic CRUD schema generation. It aims to simplify data model manipulation and reduce boilerplate code in your Python applications.

Features

  • TypeScript-inspired Decorators: Easily transform Pydantic/SQLModel classes.
    • @partial(Model): Makes all fields in Model optional.
    • @omit(Model, 'field1', 'field2'): Excludes specified fields from Model.
    • @pick(Model, 'field1', 'field2'): Selects only specified fields from Model.
  • Automatic CRUD Schema Generation: Quickly generate Create, Read, and Update Pydantic schemas from your SQLModel table definitions using generate_crud_schemas.
  • Type Safety: Leverages Pydantic's robust type checking.
  • Extensible: Customize generated schemas by including or excluding specific fields.

Requirements

  • Python 3.8+
  • Pydantic >=2.0.0, <3.0.0
  • SQLModel >=0.0.24
  • SQLAlchemy >=2.0, <3.0

Installation

You can install Pystructor directly from its Git repository:

pip install git+https://github.com/baryber/pystructor.git

If you want to install a specific branch, tag, or commit:

# Install from 'master' branch
pip install git+https://github.com/baryber/pystructor.git@master

# Install version 'v0.1.0' (if tagged)
pip install git+https://github.com/baryber/pystructor.git@v0.1.0

Usage

Decorators for Model Transformation

Pystructor provides decorators to easily create variations of your Pydantic models or SQLModel entities.

from pydantic import BaseModel, Field as PydanticField
from sqlmodel import SQLModel, Field as SQLModelField
from pystructor import partial, omit, pick
from datetime import datetime

# Define a base model (can be Pydantic BaseModel or SQLModel)
class UserBase(SQLModel):
    id: int | None = SQLModelField(default=None, primary_key=True)
    username: str
    email: str
    full_name: str | None = None
    hashed_password: str

# 1. @partial - Make all fields optional
@partial(UserBase)
class UserUpdate(SQLModel):
    # All fields from UserBase are now optional (id, username, email, full_name, hashed_password)
    # You can add new fields or override existing ones if needed
    updated_at: datetime = PydanticField(default_factory=datetime.utcnow)

# Example:
user_update_instance = UserUpdate(username="new_username") # email, full_name, etc. are optional
print(UserUpdate.model_fields["email"].is_required()) # Output: False
print(UserUpdate.model_fields["updated_at"].is_required()) # Output: True (unless default is provided)


# 2. @omit - Exclude specified fields
@omit(UserBase, "hashed_password", "id")
class UserCreate(SQLModel):
    # "hashed_password" and "id" are excluded from UserBase
    # New fields specific to creation can be added
    password_confirmation: str = PydanticField(...)

# Example:
user_create_instance = UserCreate(
    username="testuser",
    email="test@example.com",
    password_confirmation="securepassword123"
)
assert "hashed_password" not in UserCreate.model_fields
assert "id" not in UserCreate.model_fields
assert "password_confirmation" in UserCreate.model_fields


# 3. @pick - Select only specified fields
@pick(UserBase, "id", "username", "email", "full_name")
class UserPublic(SQLModel):
    # Only "id", "username", "email", "full_name" are included from UserBase
    # "hashed_password" is excluded
    # You can add other public-specific fields
    profile_views: int = 0

# Example:
user_public_instance = UserPublic(id=1, username="testuser", email="test@example.com")
assert "hashed_password" not in UserPublic.model_fields
assert "username" in UserPublic.model_fields
assert "profile_views" in UserPublic.model_fields

Automatic CRUD Schema Generation

Generate Create, Read, and Update Pydantic schemas for your SQLModel tables with a single function call.

from sqlmodel import SQLModel, Field
from pystructor import generate_crud_schemas, IncludeFieldsType
from typing import Annotated, List

# 1. Define your SQLModel table
class Item(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    name: str = Field(index=True, description="Item name")
    description: str | None = Field(default=None)
    price: float
    secret_token: str # A field we might want to exclude from reads
    owner_id: int | None = Field(default=None) # foreign_key="user.id" (assuming User model)

# 2. Generate CRUD schemas
# By default:
# - CreateSchema: Excludes 'id'.
# - ReadSchema: Excludes fields named 'password'. Other fields are included.
# - UpdateSchema: Makes all fields optional.

ItemCreate, ItemRead, ItemUpdate = generate_crud_schemas(Item)

# Example:
new_item_data = ItemCreate(name="Awesome Gadget", price=99.99, secret_token="supersecret")
item_from_db = ItemRead(id=1, name="Awesome Gadget", price=99.99, secret_token="supersecret", owner_id=1)
update_for_item = ItemUpdate(price=89.99) # Only update price, other fields are optional

# 3. Customizing generated schemas
# You can include additional fields or exclude more fields.

# Example: Exclude 'secret_token' from ItemRead and 'owner_id' from ItemCreate
# Also, add a computed field to ItemRead.
# (Note: For related models, you'd typically use Pydantic's nested models or SQLModel's Relationship features,
# then customize the Read schema if needed. `include_to_read` is for adding/modifying fields at the schema level.)

include_extra_to_read: IncludeFieldsType = {
    "is_expensive": (bool, Field(default=False)) # Example additional field
    # For a truly computed field based on other model fields, you might use @computed_field from Pydantic
    # or pass a validator/method if Pystructor supports it for callables.
    # The current `IncludeFieldsType` supports type and FieldInfo.
}

CustomItemCreate, CustomItemRead, CustomItemUpdate = generate_crud_schemas(
    Item,
    exclude_from_create=["owner_id"],
    exclude_from_read=["secret_token"],
    include_to_read=include_extra_to_read
)

# Now:
# - CustomItemCreate will not have 'owner_id'.
# - CustomItemRead will not have 'secret_token' but will have 'is_expensive'.
# - CustomItemUpdate will have all original fields as optional.

# Example usage of custom schemas:
# custom_create_data = CustomItemCreate(name="My Item", price=10.0, secret_token="abc")
# custom_read_data = CustomItemRead(id=1, name="My Item", price=10.0, owner_id=1, is_expensive=False)
# print(hasattr(custom_read_data, "secret_token")) # False
# print(hasattr(custom_read_data, "is_expensive")) # True

Planned Features / Future Work

Pystructor is actively developing. Here are some features planned for future releases:

  • @required(Model): Make all fields in Model required.
  • @readonly(Model): Make model fields immutable (similar to Pydantic's frozen=True).
  • @non_nullable(Model): Remove Optional / None from field types.
  • @deep_partial(Model): Recursively apply partial to nested models.
  • @exclude_type(Model, TypeToExclude): Exclude a specific type from Union types in fields.
  • @merge(ModelA, ModelB): Merge fields from two models.
  • @as_form(Model): Utility to easily convert Pydantic models to FastAPI Form dependencies.
  • Enhanced customization for generate_crud_schemas, including support for callables/computed fields more directly.

Contributing

Contributions are welcome! If you have suggestions, bug reports, or want to contribute code, please feel free to:

  1. Open an issue on the project's Git repository.
  2. Fork the repository and submit a pull request.

Please ensure your code follows the existing style and includes tests for new features or bug fixes.

License

Pystructor is licensed under the MIT License. (Note: You'll need to add a LICENSE file to your repository, or remove this link if not applicable).

Project Links

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pystructor-0.2.0.tar.gz (11.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pystructor-0.2.0-py3-none-any.whl (11.7 kB view details)

Uploaded Python 3

File details

Details for the file pystructor-0.2.0.tar.gz.

File metadata

  • Download URL: pystructor-0.2.0.tar.gz
  • Upload date:
  • Size: 11.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.4

File hashes

Hashes for pystructor-0.2.0.tar.gz
Algorithm Hash digest
SHA256 d966448daa91a6601e470836cc2d18e4e06f33193ca0594460c7e1f059d0d300
MD5 58cd1c55cef826466d85230ecbb13ec7
BLAKE2b-256 6332c9e5ce4f57efa1288057b1384fb90d447ff80c4f932888826ec01e98c7a4

See more details on using hashes here.

File details

Details for the file pystructor-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pystructor-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 11.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.10.4

File hashes

Hashes for pystructor-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1c234f120678d53cb6c5996618da4614bc7b2936bfd2743e4cadb476c197025e
MD5 f9a3ae178eb0a405cc04111f61841f64
BLAKE2b-256 69d6baa7b54315b9b735d03b8b9cedfe694259aee6f7c172fca4dfac0617d3c9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page