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/preposition17/pystructor.git

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

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

# Install version 'v0.1.0' (if tagged)
pip install git+https://github.com/preposition17/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.1.0.tar.gz (9.4 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.1.0-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: pystructor-0.1.0.tar.gz
  • Upload date:
  • Size: 9.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.7

File hashes

Hashes for pystructor-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ed206e440d3a6863fa6d3d8e90c49919a37d75e8223a23658110494eced45c95
MD5 118a8abb341f8c79673175cf8e22dff9
BLAKE2b-256 96b83bdca6163d852db5d8e943ea083b65eea73f65f79966763fa6ceb7ae0ecf

See more details on using hashes here.

File details

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

File metadata

File hashes

Hashes for pystructor-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dd98c33c5d47e39cc35ce439e58733fb74aac823377591cece8a7442516f048f
MD5 eea60e01bdf5dacade9a36dce81b12ba
BLAKE2b-256 b3ba1d47057e7058bde50ce0f5155077ee10231183a5cb9f5593163f6d190d49

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