CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-marshmallow-sqlalchemy

SQLAlchemy integration with the marshmallow (de)serialization library

Pending
Overview
Eval results
Files

relationship-fields.mddocs/

Relationship Fields

Specialized marshmallow fields for handling SQLAlchemy relationships with automatic serialization and deserialization of related model instances, supporting both single relationships and collections.

Capabilities

Related Field

Field for representing SQLAlchemy relationships, handling serialization of related model instances and deserialization back to instances with session-aware loading.

class Related(fields.Field):
    """Field for SQLAlchemy relationships.
    
    Handles serialization/deserialization of related model instances.
    Must be attached to a Schema with a SQLAlchemy model.
    """
    
    def __init__(
        self,
        columns: list[str] | str | None = None,
        column: str | None = None,  # Deprecated, use columns
        **kwargs,
    ):
        """Initialize Related field.
        
        Parameters:
        - columns: Column names on related model for serialization.
                  If None, uses primary key(s) of related model.
        - column: (Deprecated) Single column name, use columns instead.
        - **kwargs: Additional field arguments
        """
    
    @property
    def model(self) -> type[DeclarativeMeta] | None:
        """The SQLAlchemy model of the parent schema."""
    
    @property
    def related_model(self) -> type[DeclarativeMeta]:
        """The SQLAlchemy model of the related object."""
    
    @property
    def related_keys(self) -> list[MapperProperty]:
        """Properties used for serialization/deserialization."""
    
    @property
    def session(self) -> Session:
        """SQLAlchemy session from parent schema."""
    
    @property
    def transient(self) -> bool:
        """Whether parent schema loads transient instances."""

RelatedList Field

List field that extends marshmallow's List field with special handling for collections of related objects in SQLAlchemy relationships.

class RelatedList(fields.List):
    """List field for one-to-many and many-to-many relationships.
    
    Extends marshmallow List field with proper handling for
    SQLAlchemy relationship collections.
    """
    
    def get_value(self, obj, attr, accessor=None):
        """Get value with special handling for relationship collections."""

Nested Field

Nested field that automatically inherits session and transient state from the parent schema for proper relationship handling.

class Nested(fields.Nested):
    """Nested field that inherits session from parent schema.
    
    Ensures nested schemas have access to the same SQLAlchemy
    session for proper relationship loading.
    """
    
    def _deserialize(self, *args, **kwargs):
        """Deserialize with session inheritance."""

Utility Functions

def get_primary_keys(model: type[DeclarativeMeta]) -> list[MapperProperty]:
    """Get primary key properties for a SQLAlchemy model.
    
    Parameters:
    - model: SQLAlchemy model class
    
    Returns:
    List of primary key mapper properties
    """

def ensure_list(value: Any) -> list:
    """Ensure value is a list.
    
    Converts iterables to lists, wraps non-iterables in lists.
    """

Usage Examples

Basic Related Field

from marshmallow_sqlalchemy import SQLAlchemyAutoSchema, Related
from mymodels import Author, Book

class BookSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Book
        load_instance = True
        include_relationships = True
    
    # Related field automatically created for relationships
    # when include_relationships=True
    author = Related()  # Uses primary key of Author model

class AuthorSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Author
        load_instance = True
    
    # Manual Related field with custom columns
    books = Related(columns=["id", "title"])

Related Field Serialization

# Serialization - converts model instances to dictionaries
book = session.get(Book, 1)
schema = BookSchema()
serialized = schema.dump(book)
# Result: {
#     "id": 1,
#     "title": "Python Guide", 
#     "author": {"id": 5, "name": "John Doe"}  # Related field serialized
# }

# For multiple columns, returns dict
author_with_email = Related(columns=["id", "email"])
# Serializes to: {"id": 5, "email": "john@example.com"}

# For single column (primary key), returns scalar value
author_id_only = Related(columns=["id"])  
# Serializes to: 5

Related Field Deserialization

# Deserialization - converts dictionaries back to model instances
book_data = {
    "title": "New Book",
    "author": {"id": 5}  # Reference to existing author
}

schema = BookSchema()
book_instance = schema.load(book_data, session=session)
# Loads existing Author with id=5 from database
# Creates new Book instance with that author

# Scalar value deserialization (for single primary key)
book_data_scalar = {
    "title": "Another Book", 
    "author": 5  # Direct primary key value
}
book_instance = schema.load(book_data_scalar, session=session)

Transient Mode

# Load related objects as new instances (not from database)
schema = BookSchema()
book_data = {
    "title": "New Book",
    "author": {"name": "Jane Doe", "email": "jane@example.com"}
}

# Transient mode - creates new Author instance
book_instance = schema.load(book_data, session=session, transient=True)
# book_instance.author is a new Author instance, not loaded from DB

RelatedList for Collections

from marshmallow_sqlalchemy import RelatedList

class AuthorSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Author
        load_instance = True
    
    # Automatically created when include_relationships=True
    books = RelatedList(Related(columns=["id", "title"]))

# Serialization of collections
author = session.get(Author, 1)
schema = AuthorSchema()
serialized = schema.dump(author)
# Result: {
#     "id": 1,
#     "name": "John Doe",
#     "books": [
#         {"id": 1, "title": "First Book"},
#         {"id": 2, "title": "Second Book"}
#     ]
# }

# Deserialization of collections
author_data = {
    "name": "New Author",
    "books": [
        {"id": 1},  # Load existing book
        {"title": "Brand New Book"}  # Create new book
    ]
}
author_instance = schema.load(author_data, session=session)

Nested Schemas

from marshmallow_sqlalchemy import Nested

class BookDetailSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Book
        load_instance = True

class AuthorDetailSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Author  
        load_instance = True
    
    # Use nested schema for detailed book information
    books = Nested(BookDetailSchema, many=True)

# Session automatically inherited by nested schema
schema = AuthorDetailSchema()
author_data = {
    "name": "Author Name",
    "books": [
        {"title": "Book Title", "isbn": "978-1234567890"}
    ]
}
author_instance = schema.load(author_data, session=session)
# Nested BookDetailSchema automatically gets the session

Custom Column Specifications

class BookSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Book
        load_instance = True
    
    # Use specific columns for serialization
    author = Related(columns=["id", "name", "email"])
    
    # Use single column (returns scalar)
    category_id = Related(columns=["id"])
    
    # Use all columns (default behavior)
    publisher = Related()  # Uses primary key columns

# Multiple primary key handling
class CompositeKeyModel(Base):
    __tablename__ = "composite"
    key1 = sa.Column(sa.Integer, primary_key=True)
    key2 = sa.Column(sa.String, primary_key=True)

class ParentSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = Parent
        load_instance = True
    
    # Automatically handles multiple primary keys
    composite_ref = Related()
    # Serializes to: {"key1": 1, "key2": "abc"}
    # Deserializes from: {"key1": 1, "key2": "abc"}

Error Handling

# Related field validation errors
try:
    schema = BookSchema()
    result = schema.load({
        "title": "Test Book",
        "author": "invalid_value"  # Should be dict or primary key
    }, session=session)
except ValidationError as e:
    # e.messages contains field-specific errors
    print(e.messages["author"])
    # ["Could not deserialize related value 'invalid_value'; expected a dictionary with keys ['id']"]

# Missing related object
try:
    result = schema.load({
        "title": "Test Book", 
        "author": {"id": 999}  # Non-existent author
    }, session=session)
    # Creates new Author instance with id=999 if not found in DB
except ValidationError as e:
    # Handle validation errors
    pass

Association Proxies

# Related fields work with SQLAlchemy association proxies
class User(Base):
    __tablename__ = "users"
    id = sa.Column(sa.Integer, primary_key=True)
    name = sa.Column(sa.String)
    
    # Association proxy
    role_names = association_proxy("user_roles", "role_name")

class UserSchema(SQLAlchemyAutoSchema):
    class Meta:
        model = User
        load_instance = True
        include_relationships = True
    
    # Related field handles association proxy relationships
    role_names = Related()  # Automatically detected and handled

Install with Tessl CLI

npx tessl i tessl/pypi-marshmallow-sqlalchemy

docs

field-conversion.md

index.md

relationship-fields.md

schemas.md

tile.json