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.8.tar.gz (30.1 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.8-py3-none-any.whl (37.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: lazyql-0.1.8.tar.gz
  • Upload date:
  • Size: 30.1 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.8.tar.gz
Algorithm Hash digest
SHA256 b30a6e8e32a1ca4f01834f9faa9eaeb585e7c14216f347e549a7f6b4d5e66e4b
MD5 5f6ea73a4ddc2156748316f63ebff29f
BLAKE2b-256 9e8eb764e86a90600ee650c53cdff8b658642031723b11d899b0f264c80a165c

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lazyql-0.1.8-py3-none-any.whl
  • Upload date:
  • Size: 37.0 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.8-py3-none-any.whl
Algorithm Hash digest
SHA256 f20bec77460bd621fb394b146936fd0acf02cee6fe2d5edb66753e393d609795
MD5 730192abf63e9ccd6edfcb0a5484abcc
BLAKE2b-256 864c7a76093074c59c0d1ceeb284b82f93ac354cdf16cd8fe2569cf9d2854171

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