Skip to main content

A FastAPI and Strawberry GraphQL library to create CRUD APIs over MongoDB with advanced filtering capabilities.

Project description

🦥 LazyQL

PyPI version Python versions License pipeline status coverage report Code style: ruff Checked with mypy

LazyQL is a powerful Python library that instantly generates full-featured GraphQL CRUD APIs from Pydantic models with MongoDB (via Motor). Stop writing boilerplate—focus on your business logic.


✨ Key Features

  • 🚀 Instant CRUD: Generate complete GraphQL Query and Mutation types from Pydantic models in seconds
  • 🎯 Advanced Filtering: Rich, type-safe filtering system with 15+ operators (contains, gte, in, all, etc.)
  • 🔒 Built-in Security: Granular permission system for create, update, delete, and list operations
  • 📊 Audit Logging: Automatic change tracking in MongoDB Time Series collections
  • 🗑️ Soft Deletes: Native support for soft deletion and restoration
  • Async & Fast: Built on Motor for high-performance asynchronous MongoDB operations
  • 🔄 ACID Transactions: Full MongoDB transaction support
  • 🧩 Extensible: Easily customize resolvers, filters, and permissions
  • 🏗️ Nested Models: Full support for nested Pydantic models in filters and queries
  • 📅 DateTime Support: Comprehensive datetime filtering with all operators

📦 Installation

pip install lazyql

Or with Poetry:

poetry add lazyql

Requirements:

  • Python 3.11+
  • MongoDB 4.4+
  • Motor 3.7+

⚡ Quick Start

1. Define Your Model

from lazyql import BaseDBModel
from pydantic import Field
from datetime import datetime

class User(BaseDBModel):
    name: str
    email: str
    age: int
    role: str = Field(default="user")
    active: bool = True
    tags: list[str] = []
    created_at: datetime  # Automatically managed

2. Generate GraphQL API

import strawberry
from motor.motor_asyncio import AsyncIOMotorClient
from lazyql import create_crud_api

# Connect to MongoDB
client = AsyncIOMotorClient("mongodb://localhost:27017")
db = client.my_database

# Generate CRUD API
Query, Mutation = create_crud_api(
    model=User,
    collection_name="users"
)

# Create schema
schema = strawberry.Schema(query=Query, mutation=Mutation)

3. Integrate with FastAPI

from fastapi import FastAPI
from strawberry.fastapi import GraphQLRouter

app = FastAPI()

graphql_app = GraphQLRouter(schema)
app.include_router(graphql_app, prefix="/graphql")

4. Use Your API

# Query with filters
query {
  users(
    filters: {
      age: { gte: 18, lte: 65 }
      name: { contains: "John" }
      role: { in: ["admin", "user"] }
      active: { eq: true }
    }
    limit: 10
    skip: 0
  ) {
    id
    name
    email
    age
  }
}

# Create mutation
mutation {
  createUser(
    data: {
      name: "John Doe"
      email: "john@example.com"
      age: 30
    }
  ) {
    id
    name
  }
}

# Update mutation
mutation {
  updateUser(
    id: "507f1f77bcf86cd799439011"
    data: { age: 31 }
  ) {
    id
    age
  }
}

🎯 Advanced Features

Rich Filtering System

LazyQL provides a powerful, type-safe filtering system with nested operators:

query {
  users(
    filters: {
      # String operators
      name: { 
        contains: "John"
        startsWith: "J"
        endsWith: "n"
        in: ["John", "Jane"]
      }
      
      # Numeric operators
      age: { 
        gte: 18
        lte: 65
        ne: 25
      }
      
      # List operators
      tags: {
        all: ["verified", "premium"]
        size: 2
      }
      
      # Boolean
      active: { eq: true }
      
      # DateTime
      created_at: {
        gte: "2024-01-01T00:00:00Z"
        lte: "2024-12-31T23:59:59Z"
      }
      
      # Nested models
      profile: {
        address: {
          city: { eq: "Paris" }
        }
      }
    }
  ) {
    id
    name
  }
}

Available Operators:

Operator Description Types
eq Equality All
ne Not equal All
gt, gte Greater than (or equal) Int, Float, DateTime
lt, lte Less than (or equal) Int, Float, DateTime
contains String contains (case-insensitive) String
startsWith String starts with String
endsWith String ends with String
in Value in list All scalars
nin Value not in list All scalars
all List contains all elements List
size List size List
exists Field exists All

Permissions

Control access to operations with custom permission checkers:

from lazyql import create_crud_api, PermissionChecker
from strawberry.types import Info

def is_admin(info: Info) -> bool:
    """Check if user is admin."""
    user = getattr(info.context, "user", None)
    return user and user.get("role") == "admin"

def is_authenticated(info: Info) -> bool:
    """Check if user is authenticated."""
    return hasattr(info.context, "user") and info.context.user is not None

# Apply permissions
Query, Mutation = create_crud_api(
    model=User,
    collection_name="users",
    permissions={
        "create": is_admin,
        "update": is_authenticated,
        "delete": is_admin,
        "list": is_authenticated,
    }
)

Audit Logging

Automatic audit trail for all mutations:

from lazyql import init_audit_timeseries

# Initialize audit log collection (Time Series optimized)
await init_audit_timeseries(db)

# All mutations are automatically logged:
# - CREATE, UPDATE, DELETE, RESTORE operations
# - Before/after state diffs
# - User who performed the action
# - Timestamp

Soft Deletes

Built-in soft delete support:

# Soft delete
mutation {
  deleteUser(id: "507f1f77bcf86cd799439011") {
    id
    deleted_at
  }
}

# Restore
mutation {
  restoreUser(id: "507f1f77bcf86cd799439011") {
    id
    deleted_at
  }
}

# Query excludes soft-deleted by default
query {
  users { id name }  # Only active users
}

# Include deleted
query {
  users(includeDeleted: true) { id name }
}

Nested Models

Full support for nested Pydantic models:

from lazyql import BaseDBModel, register_sub_model

class Address(BaseDBModel):
    street: str
    city: str
    zip_code: str
    country: str

class Profile(BaseDBModel):
    first_name: str
    last_name: str
    address: Address

class User(BaseDBModel):
    name: str
    profile: Profile

# Register nested models
register_sub_model(Address)
register_sub_model(Profile)

# Now you can filter on nested fields:
# filters: { profile: { address: { city: { eq: "Paris" } } } }

Direct Service Usage

Use the service layer directly for custom logic:

from lazyql import LazyQL

service = LazyQL(db, "users", User)

# Get all with filters
users = await service.get_all(
    filters={"age": {"$gte": 18}},
    limit=10,
    skip=0,
    sort_by="age",
    sort_order=-1
)

# Create
user = await service.create(
    User(name="John", email="john@example.com", age=30),
    user="admin"
)

# Update
updated = await service.update(
    id=user.id,
    data={"age": 31},
    user="admin"
)

# Soft delete
await service.delete(id=user.id, user="admin")

# Restore
await service.restore(id=user.id, user="admin")

📚 Documentation


🧪 Testing

LazyQL is thoroughly tested with:

  • 571 tests covering all features
  • 91% code coverage
  • 59 integration tests with real MongoDB
  • Comprehensive filter operator testing

Run tests:

poetry run pytest

With coverage:

poetry run pytest --cov=lazyql --cov-report=html

🏗️ Architecture

LazyQL follows clean architecture principles:

  • Service Layer: LazyQL class handles all MongoDB operations
  • Factory Pattern: create_crud_api generates GraphQL types and resolvers
  • Strategy Pattern: Extensible operator handlers for filters
  • Type Safety: Full Pydantic and Strawberry type integration

🤝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.


📄 License

LazyQL is licensed under the MIT License. See LICENSE for details.


🙏 Acknowledgments

Built with:


📞 Support

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

lazyql-0.1.5.tar.gz (28.4 kB view details)

Uploaded Source

Built Distribution

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

lazyql-0.1.5-py3-none-any.whl (34.8 kB view details)

Uploaded Python 3

File details

Details for the file lazyql-0.1.5.tar.gz.

File metadata

  • Download URL: lazyql-0.1.5.tar.gz
  • Upload date:
  • Size: 28.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for lazyql-0.1.5.tar.gz
Algorithm Hash digest
SHA256 e7d615dbf1f085e160d27f32a5646ec7c2c0685a57ce0886b7992cdcc9f41fae
MD5 707e67811189340eab37951c17988689
BLAKE2b-256 9b2343b57ace3f7060ca8e259aa47cf7c60d12662461f5962aad390d65c54f33

See more details on using hashes here.

File details

Details for the file lazyql-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: lazyql-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 34.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for lazyql-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 7825e202306d0bd895567f479f0976ce32fcb2736229786c378ba0804996b6dc
MD5 8e5170f32cf3eafdd587a014f0a767d5
BLAKE2b-256 49b729907ffd1f00daa5d9fd7b9182662b35e782e48cf7321fb0ab16a80b7435

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