Skip to main content

Generate SQLAlchemy query optimization options from simplified field selection syntax

Project description

SQLAlchemy Load Generator

Generate SQLAlchemy query optimization options (selectinload + load_only) from simplified field selection syntax.

Why This Library?

SQLAlchemy's query options (selectinload, joinedload, load_only) are powerful but painful to write, especially with nested relationships.

The Problem

1. Verbose nested syntax

# Loading User -> Posts -> Comments requires deep nesting
stmt = select(User).options(
    selectinload(User.posts).options(
        load_only(Post.id, Post.title),
        selectinload(Post.comments).options(
            load_only(Comment.id, Comment.content)
        )
    )
)

2. Coupled with query logic

You must decide what to load at query time, mixing data requirements with query construction. Different API endpoints need different loading strategies, leading to duplicated query code.

3. Dynamic composition is awkward

# Conditionally adding options requires extra logic
options = []
if need_posts:
    options.append(selectinload(User.posts))
if need_comments:
    options.append(selectinload(User.posts).selectinload(Post.comments))
stmt = select(User).options(*options)

4. Easy to cause N+1 or over-fetching

  • Forget selectinload → N+1 queries
  • Load unnecessary fields → wasted memory

The Solution

This library provides a declarative syntax similar to GraphQL:

# Before: verbose, nested, error-prone
stmt = select(User).options(
    selectinload(User.posts).options(
        load_only(Post.id, Post.title),
        selectinload(Post.comments).options(
            load_only(Comment.id, Comment.content)
        )
    )
)

# After: clean, declarative, optimized
generator = LoadGenerator(Base)
options = generator.generate(User, "{ id name posts { title comments { content } } }")
stmt = select(User).options(*options)

Installation

pip install sqlalchemy-load

Usage

from sqlalchemy import select
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy_load import LoadGenerator

# Initialize with your DeclarativeBase
generator = LoadGenerator(Base)

# Generate options using simplified syntax
options = generator.generate(User, "{ id name posts { title comments { content } } }")

# Use with SQLAlchemy query
stmt = select(User).options(*options)

Syntax

The simplified syntax is similar to GraphQL but without commas:

{ field1 field2 relationship { nested_field } }
  • Fields are space-separated
  • Relationships use { } for nested selection
  • Commas are optional and ignored

Examples

# Simple fields
generator.generate(User, "{ id name email }")

# Nested relationships
generator.generate(User, "{ id posts { title content } }")

# Deeply nested
generator.generate(User, "{ id posts { title comments { content author } } }")

# Multiple relationships
generator.generate(User, "{ name posts { title } profile { bio } }")

# Different models with same generator
generator.generate(Post, "{ title content author { name } }")
generator.generate(Comment, "{ content post { title } }")

API

LoadGenerator(base_class)

Create a generator with a SQLAlchemy DeclarativeBase. Preloads metadata for all models in the registry for optimal performance.

from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

generator = LoadGenerator(Base)

generator.generate(model_class, query_string) -> list

Generate SQLAlchemy options from a query string.

options = generator.generate(User, "{ id name posts { title } }")
stmt = select(User).options(*options)

Features

  • Preloaded metadata: All model metadata is cached at initialization for fast lookups
  • Result caching: Same query returns cached result, avoiding redundant computation
  • Parse caching: Query string parsing is cached with lru_cache
  • Automatic primary key inclusion: Primary keys are always included in load_only
  • Relationship detection: Automatically detects SQLAlchemy relationships
  • Nested loading: Recursively generates selectinload with nested load_only
  • Error handling: Clear errors for invalid fields, relationships, or syntax

Error Handling

from sqlalchemy_load import (
    LoadGenerator,
    ParseError,
    FieldNotFoundError,
    RelationshipNotFoundError,
)

generator = LoadGenerator(Base)

# Syntax error
try:
    generator.generate(User, "{ id name")  # Missing closing brace
except ParseError as e:
    print(f"Syntax error: {e}")

# Field doesn't exist
try:
    generator.generate(User, "{ nonexistent }")
except FieldNotFoundError as e:
    print(f"Field not found: {e}")

# Relationship doesn't exist
try:
    generator.generate(User, "{ notarelationship { id } }")
except RelationshipNotFoundError as e:
    print(f"Relationship not found: {e}")

Requirements

  • Python >= 3.10
  • SQLAlchemy >= 2.0

License

MIT

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

sqlalchemy_load-0.3.0.tar.gz (42.0 kB view details)

Uploaded Source

Built Distribution

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

sqlalchemy_load-0.3.0-py3-none-any.whl (7.2 kB view details)

Uploaded Python 3

File details

Details for the file sqlalchemy_load-0.3.0.tar.gz.

File metadata

  • Download URL: sqlalchemy_load-0.3.0.tar.gz
  • Upload date:
  • Size: 42.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.8.13

File hashes

Hashes for sqlalchemy_load-0.3.0.tar.gz
Algorithm Hash digest
SHA256 2ad38a29b73f622ac0a4a04fb02f913d37bb4ac08f6b4868eead48b8194762c2
MD5 22bc1fcf2cccc36407f754f2f13d2fa7
BLAKE2b-256 d0b2ad884979e9e46ed531ee21631e9663e249bedaf4983372a37cb1dd4589d8

See more details on using hashes here.

File details

Details for the file sqlalchemy_load-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for sqlalchemy_load-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1af1ffd1025f2c060a1820c8665a670aefd9cc629d626eca2177407ed2f35c93
MD5 08042854078ccace7325e860d2e6a461
BLAKE2b-256 1cc6ba538992c013bff86c4d95ef6e91f919a0763f2ecda75f673ac2f60202fc

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