A FastAPI and Strawberry GraphQL library to create CRUD APIs over MongoDB with advanced filtering capabilities.
Project description
🦥 LazyQL
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
QueryandMutationtypes 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
- Full Documentation - Complete API reference and guides
- Filtering Guide - Deep dive into the filtering system
- Permissions Guide - Security and access control
- Examples - Real-world examples and patterns
🧪 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:
LazyQLclass handles all MongoDB operations - Factory Pattern:
create_crud_apigenerates 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:
- Strawberry GraphQL - GraphQL library
- Pydantic - Data validation
- Motor - Async MongoDB driver
- MongoDB - Database
📞 Support
- Issues: GitLab Issues
- Documentation: Full Docs
- Repository: GitLab
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lazyql-0.1.15.tar.gz.
File metadata
- Download URL: lazyql-0.1.15.tar.gz
- Upload date:
- Size: 32.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87b797642866790aa987c9e3fc86952a8ef7941e2514e75f20c79925ab34d63e
|
|
| MD5 |
c9c40c07d3d5b736b68fe6cd263e2264
|
|
| BLAKE2b-256 |
abe0bc2a8b4c977c8ce3e30d583760521f3e351a2eeeb3b51b8ec6c6e3161aaf
|
File details
Details for the file lazyql-0.1.15-py3-none-any.whl.
File metadata
- Download URL: lazyql-0.1.15-py3-none-any.whl
- Upload date:
- Size: 40.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
22d308fae5e81d38386fb1167a29f0b59cdc10a3063cdd764c1fb92c3c607540
|
|
| MD5 |
e1db0c2b52b4ba768880e195e4facc48
|
|
| BLAKE2b-256 |
b4b9144760cfcbdb9da860f0375f1e82cf59cf98e5a884c0c9bcc441cb4321bd
|