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.12.tar.gz (30.5 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.12-py3-none-any.whl (37.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: lazyql-0.1.12.tar.gz
  • Upload date:
  • Size: 30.5 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.12.tar.gz
Algorithm Hash digest
SHA256 b2ba079a41c515412de00f625c12749457cd938dd4e86eb3d7502fe80f51e8e1
MD5 e67e7360b2e341626d08a9e96fffb12a
BLAKE2b-256 59bed57eabd09814f2a4a0ed606cd33e063e1956ae79679a571468b68e59cb46

See more details on using hashes here.

File details

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

File metadata

  • Download URL: lazyql-0.1.12-py3-none-any.whl
  • Upload date:
  • Size: 37.4 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.12-py3-none-any.whl
Algorithm Hash digest
SHA256 902cd38b2896b21ad15b6d777f41d4dab8c7ba7444f216138eab9ebef324b5b7
MD5 658dcca5caede449926e25518dc3791b
BLAKE2b-256 9e1d8655f1c7fd644f546211bb2fc5cd2c6e6abeeea17a64cf90e302f1de40cc

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