
Security News
Open Source Maintainers Feeling the Weight of the EUโs Cyber Resilience Act
The EU Cyber Resilience Act is prompting compliance requests that open source maintainers may not be obligated or equipped to handle.
fastapi-querybuilder
Advanced tools
Python 3.8+ | License: MIT | Tests
A powerful, flexible query builder for FastAPI applications with SQLAlchemy. Easily add filtering, sorting, and searching capabilities to your API endpoints with minimal code.
๐ Documentation โ
user.role.department.name
)deleted_at
field existspip install fastapi-querybuilder
Requirements:
from fastapi import FastAPI, Depends
from fastapi-querybuilder.dependencies import QueryBuilder
from sqlalchemy.ext.asyncio import AsyncSession
app = FastAPI()
@app.get("/users")
async def get_users(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db)
):
result = await session.execute(query)
return result.scalars().all()
Your endpoint now automatically supports:
# Advanced filtering
GET /users?filters={"name": {"$eq": "John"}, "age": {"$gte": 18}}
# Dynamic sorting
GET /users?sort=name:asc
# Global search
GET /users?search=john
# Combined usage
GET /users?filters={"is_active": {"$eq": true}}&search=admin&sort=created_at:desc
```plaintext
## ๐ Complete Usage Guide
### Basic Setup
#### 1. Define Your Models
```python
from sqlalchemy import String, ForeignKey, DateTime, Boolean, Integer, Enum
from sqlalchemy.orm import Mapped, mapped_column, relationship, declarative_base
from datetime import datetime, timezone
from enum import Enum as PyEnum
Base = declarative_base()
class StatusEnum(str, PyEnum):
ACTIVE = "active"
INACTIVE = "inactive"
SUSPENDED = "suspended"
class Role(Base):
__tablename__ = "roles"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
description: Mapped[str] = mapped_column(String(200), nullable=True)
# Relationships
users: Mapped[list["User"]] = relationship("User", back_populates="role")
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, index=True)
name: Mapped[str] = mapped_column(String(100), index=True)
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
age: Mapped[int] = mapped_column(Integer, nullable=True)
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
status: Mapped[StatusEnum] = mapped_column(String(20), default=StatusEnum.ACTIVE)
created_at: Mapped[datetime] = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc))
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
deleted_at: Mapped[datetime] = mapped_column(DateTime, nullable=True) # Soft delete
# Foreign Keys
role_id: Mapped[int] = mapped_column(ForeignKey("roles.id"))
# Relationships
role: Mapped["Role"] = relationship("Role", back_populates="users", lazy="selectin")
from fastapi import FastAPI, Depends, HTTPException
from fastapi-querybuilder.dependencies import QueryBuilder
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
app = FastAPI(title="User Management API")
# Basic endpoint with QueryBuilder
@app.get("/users", response_model=List[UserResponse])
async def get_users(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db)
):
"""
Get users with advanced filtering, sorting, and searching.
Query Parameters:
- filters: JSON string for filtering (e.g., {"name": {"$eq": "John"}})
- sort: Sort field and direction (e.g., "name:asc" or "role.name:desc")
- search: Global search term across all searchable fields
"""
try:
result = await session.execute(query)
return result.scalars().all()
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
# Endpoint with custom parameters
@app.get("/users/advanced")
async def get_users_advanced(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db),
include_inactive: bool = False
):
"""Advanced endpoint with custom business logic"""
if not include_inactive:
# Add custom filter for active users only
query = query.where(User.is_active == True)
result = await session.execute(query)
return result.scalars().all()
If your model has a deleted_at
field, QueryBuilder automatically excludes soft-deleted records:
class User(Base):
# ... other fields ...
deleted_at: Mapped[datetime] = mapped_column(DateTime, nullable=True)
# QueryBuilder automatically adds: WHERE deleted_at IS NULL
QueryBuilder automatically detects and searches across:
# This search will look in: name, email, status (enum), age (if numeric), is_active (if "true"/"false")
GET /users?search=john
# Single condition
GET /users?filters={"name": {"$eq": "John Doe"}}
# Multiple conditions (implicit AND)
GET /users?filters={"age": {"$gte": 18}, "is_active": {"$eq": true}}
# Array values
GET /users?filters={"status": {"$in": ["active", "pending"]}}
# OR condition
GET /users?filters={"$or": [{"name": {"$contains": "john"}}, {"email": {"$contains": "john"}}]}
# Complex AND/OR combinations
GET /users?filters={
"$and": [
{"age": {"$gte": 18}},
{
"$or": [
{"status": {"$eq": "active"}},
{"status": {"$eq": "pending"}}
]
}
]
}
# Filter by relationship field
GET /users?filters={"role.name": {"$eq": "admin"}}
# Deep nesting (if you have department -> company relationship)
GET /users?filters={"role.department.company.name": {"$contains": "Tech"}}
# Multiple relationship conditions
GET /users?filters={
"role.name": {"$eq": "admin"},
"role.description": {"$contains": "management"}
}
# Date-only string (matches entire day)
GET /users?filters={"created_at": {"$eq": "2023-12-01"}}
# Equivalent to: created_at >= '2023-12-01 00:00:00' AND created_at < '2023-12-02 00:00:00'
# Exact datetime
GET /users?filters={"created_at": {"$eq": "2023-12-01T10:30:00"}}
# Date ranges
GET /users?filters={"created_at": {"$gte": "2023-01-01", "$lt": "2024-01-01"}}
# Supported date formats:
# - "2023-12-01" (YYYY-MM-DD)
# - "2023-12-01T10:30:00" (ISO format)
# - "2023-12-01 10:30:00" (Space separated)
# - "2023-12-01T10:30:00Z" (UTC)
# Ascending order (default)
GET /users?sort=name:asc
GET /users?sort=name # :asc is optional
# Descending order
GET /users?sort=created_at:desc
# Multiple fields (comma-separated)
GET /users?sort=role.name:asc,created_at:desc
# Sort by relationship field
GET /users?sort=role.name:asc
# Deep relationship sorting
GET /users?sort=role.department.name:desc
Global search automatically searches across all compatible fields:
# Simple search
GET /users?search=john
# Search with other parameters
GET /users?search=admin&filters={"is_active": {"$eq": true}}&sort=name:asc
Search Behavior:
from fastapi_pagination import Page, add_pagination, Params
from fastapi_pagination.ext.sqlalchemy import paginate
@app.get("/users/paginated", response_model=Page[UserResponse])
async def get_users_paginated(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db),
params: Params = Depends()
):
return await paginate(session, query, params)
# Don't forget to add pagination to your app
add_pagination(app)
# Basic pagination
GET /users/paginated?page=1&size=10
# With filtering and sorting
GET /users/paginated?page=2&size=20&filters={"is_active": {"$eq": true}}&sort=name:asc
# With search
GET /users/paginated?page=1&size=50&search=john&sort=created_at:desc
Operator | Description | Example | SQL Equivalent |
---|---|---|---|
$eq | Equal to | {"age": {"$eq": 25}} | age = 25 |
$ne | Not equal to | {"status": {"$ne": "inactive"}} | status != 'inactive' |
$gt | Greater than | {"age": {"$gt": 18}} | age > 18 |
$gte | Greater than or equal | {"age": {"$gte": 21}} | age >= 21 |
$lt | Less than | {"age": {"$lt": 65}} | age < 65 |
$lte | Less than or equal | {"age": {"$lte": 64}} | age <= 64 |
$in | In array | {"status": {"$in": ["active", "pending"]}} | status IN ('active', 'pending') |
$isanyof | Is any of (alias for $in) | {"role": {"$isanyof": ["admin", "user"]}} | role IN ('admin', 'user') |
Operator | Description | Example | SQL Equivalent |
---|---|---|---|
$contains | Contains substring (case-insensitive) | {"name": {"$contains": "john"}} | name ILIKE '%john%' |
$ncontains | Does not contain substring | {"name": {"$ncontains": "test"}} | name NOT ILIKE '%test%' |
$startswith | Starts with | {"email": {"$startswith": "admin"}} | email ILIKE 'admin%' |
$endswith | Ends with | {"email": {"$endswith": ".com"}} | email ILIKE '%.com' |
Operator | Description | Example | SQL Equivalent |
---|---|---|---|
$isempty | Is null or empty | {"description": {"$isempty": true}} | description IS NULL |
$isnotempty | Is not null or empty | {"description": {"$isnotempty": true}} | description IS NOT NULL |
| Operator | Description | Example
|-----|-----|-----|-----
| $and
| Logical AND | {"$and": [{"age": {"$gte": 18}}, {"is_active": {"$eq": true}}]}
| $or
| Logical OR | {"$or": [{"name": {"$contains": "john"}}, {"email": {"$contains": "john"}}]}
# Empty string is treated as NULL
GET /users?filters={"description": {"$eq": ""}}
# Equivalent to: description IS NULL
# Date-only strings automatically expand to day ranges
GET /users?filters={"created_at": {"$eq": "2023-12-01"}}
# Becomes: created_at >= '2023-12-01 00:00:00' AND created_at < '2023-12-02 00:00:00'
# Time-specific dates are exact matches
GET /users?filters={"created_at": {"$eq": "2023-12-01T10:30:00"}}
# Becomes: created_at = '2023-12-01 10:30:00'
Create custom parameter classes for specialized endpoints:
from fastapi-querybuilder.params import QueryParams
from fastapi import Query
from typing import Optional
class AdminQueryParams(QueryParams):
def __init__(
self,
filters: Optional[str] = Query(None, description="JSON filter string"),
sort: Optional[str] = Query(None, description="Sort field:direction"),
search: Optional[str] = Query(None, description="Global search term"),
include_deleted: bool = Query(False, description="Include soft-deleted records"),
admin_only: bool = Query(False, description="Show only admin users")
):
super().__init__(filters, sort, search)
self.include_deleted = include_deleted
self.admin_only = admin_only
@app.get("/admin/users")
async def get_admin_users(
params: AdminQueryParams = Depends(),
session: AsyncSession = Depends(get_db)
):
query = build_query(User, params)
# Custom logic based on additional parameters
if params.admin_only:
query = query.join(Role).where(Role.name == "admin")
if not params.include_deleted:
query = query.where(User.deleted_at.is_(None))
result = await session.execute(query)
return result.scalars().all()
# Multi-level relationship filtering
GET /users?filters={
"role.department.company.name": {"$eq": "TechCorp"},
"role.department.budget": {"$gte": 100000},
"role.permissions.name": {"$contains": "admin"}
}
# Combining relationship and direct field filters
GET /users?filters={
"$and": [
{"age": {"$gte": 25}},
{"role.name": {"$in": ["admin", "manager"]}},
{
"$or": [
{"email": {"$endswith": "@company.com"}},
{"is_active": {"$eq": true}}
]
}
]
}
class User(Base):
# ... other fields ...
role: Mapped["Role"] = relationship("Role", back_populates="users", lazy="selectin")
# lazy="selectin" prevents N+1 queries when accessing role data
class User(Base):
# ... other fields ...
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime, index=True) # For sorting
is_active: Mapped[bool] = mapped_column(Boolean, index=True) # For filtering
from fastapi import HTTPException
from fastapi-querybuilder.dependencies import QueryBuilder
@app.get("/users")
async def get_users_with_error_handling(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db)
):
try:
result = await session.execute(query)
return result.scalars().all()
except ValueError as e:
# Invalid JSON in filters
raise HTTPException(status_code=400, detail=f"Invalid filter format: {str(e)}")
except AttributeError as e:
# Invalid field name
raise HTTPException(status_code=400, detail=f"Invalid field: {str(e)}")
except Exception as e:
# Other errors
raise HTTPException(status_code=500, detail="Internal server error")
# Find active premium customers who made purchases in the last month
GET /users?filters={
"$and": [
{"is_active": {"$eq": true}},
{"subscription.type": {"$eq": "premium"}},
{"orders.created_at": {"$gte": "2023-11-01"}},
{"orders.status": {"$eq": "completed"}}
]
}&sort=orders.total_amount:desc
# Search for users by email domain and sort by registration date
GET /users?filters={"email": {"$endswith": "@company.com"}}&sort=created_at:desc
# Find users with specific roles and activity status
GET /users?filters={
"$or": [
{"role.name": {"$eq": "admin"}},
{"role.name": {"$eq": "moderator"}}
],
"last_login": {"$gte": "2023-12-01"}
}&search=john
# Find published articles by specific authors in certain categories
GET /articles?filters={
"status": {"$eq": "published"},
"author.role.name": {"$in": ["editor", "admin"]},
"categories.name": {"$contains": "technology"},
"published_at": {"$gte": "2023-01-01"}
}&sort=published_at:desc
# Search for articles with specific tags and minimum view count
GET /articles?filters={
"tags.name": {"$isanyof": ["python", "fastapi", "tutorial"]},
"view_count": {"$gte": 1000}
}&search=beginner&sort=view_count:desc
# Find employees eligible for promotion
GET /employees?filters={
"$and": [
{"years_of_experience": {"$gte": 3}},
{"performance_rating": {"$gte": 4.0}},
{"department.budget_status": {"$eq": "approved"}},
{"last_promotion_date": {"$lt": "2022-01-01"}}
]
}&sort=performance_rating:desc,years_of_experience:desc
# Search for employees in specific locations with certain skills
GET /employees?filters={
"office.city": {"$in": ["New York", "San Francisco", "Austin"]},
"skills.name": {"$contains": "python"}
}&search=senior&sort=hire_date:asc
# Find users within a tenant with specific permissions
GET /users?filters={
"tenant_id": {"$eq": 123},
"roles.permissions.name": {"$contains": "billing"},
"subscription.status": {"$eq": "active"}
}&sort=last_login:desc
# Search across multiple tenants (admin view)
GET /admin/users?filters={
"tenant.plan": {"$in": ["enterprise", "professional"]},
"created_at": {"$gte": "2023-01-01"}
}&search=admin&sort=tenant.name:asc,created_at:desc
# Request
GET /users?filters={"name": {"$eq": "John"} # Missing closing brace
# Response
{
"detail": "Invalid filter JSON: Expecting ',' delimiter: line 1 column 25 (char 24)"
}
# Request
GET /users?filters={"nonexistent_field": {"$eq": "value"}}
# Response
{
"detail": "Invalid filter key: nonexistent_field. Could not resolve attribute 'nonexistent_field' in model 'User'."
}
# Request
GET /users?filters={"name": {"$invalid": "John"}}
# Response
{
"detail": "Invalid operator '$invalid' for field 'name'"
}
# Request
GET /users?sort=invalid_field:asc
# Response
{
"detail": "Invalid sort field: invalid_field"
}
# Request
GET /users?filters={"created_at": {"$eq": "invalid-date"}}
# Response
{
"detail": "Invalid date format: invalid-date"
}
from fastapi import HTTPException
from sqlalchemy.exc import SQLAlchemyError
import logging
logger = logging.getLogger(__name__)
@app.get("/users")
async def get_users_with_comprehensive_error_handling(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db)
):
try:
result = await session.execute(query)
return result.scalars().all()
except ValueError as e:
# JSON parsing or validation errors
logger.warning(f"Invalid query parameters: {str(e)}")
raise HTTPException(
status_code=400,
detail=f"Invalid query format: {str(e)}"
)
except AttributeError as e:
# Invalid field or relationship errors
logger.warning(f"Invalid field access: {str(e)}")
raise HTTPException(
status_code=400,
detail=f"Invalid field or relationship: {str(e)}"
)
except SQLAlchemyError as e:
# Database-related errors
logger.error(f"Database error: {str(e)}")
raise HTTPException(
status_code=500,
detail="Database error occurred"
)
except Exception as e:
# Unexpected errors
logger.error(f"Unexpected error: {str(e)}")
raise HTTPException(
status_code=500,
detail="An unexpected error occurred"
)
# Add indexes for frequently filtered/sorted fields
class User(Base):
__tablename__ = "users"
# Primary key (automatically indexed)
id: Mapped[int] = mapped_column(primary_key=True)
# Frequently filtered fields
email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
is_active: Mapped[bool] = mapped_column(Boolean, index=True)
status: Mapped[str] = mapped_column(String(20), index=True)
# Frequently sorted fields
created_at: Mapped[datetime] = mapped_column(DateTime, index=True)
name: Mapped[str] = mapped_column(String(100), index=True)
# Foreign keys (automatically indexed in most databases)
role_id: Mapped[int] = mapped_column(ForeignKey("roles.id"))
# Use selectin loading for relationships you'll always access
class User(Base):
role: Mapped["Role"] = relationship("Role", lazy="selectin") # Prevents N+1 queries
# Use joined loading for one-to-one relationships
class User(Base):
profile: Mapped["UserProfile"] = relationship("UserProfile", lazy="joined")
# Use lazy loading (default) for relationships you rarely access
class User(Base):
orders: Mapped[list["Order"]] = relationship("Order", lazy="select") # Default
# Limit result sets when possible
@app.get("/users")
async def get_users(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db),
limit: int = Query(100, le=1000) # Prevent excessive results
):
query = query.limit(limit)
result = await session.execute(query)
return result.scalars().all()
# Use pagination for large datasets
from fastapi_pagination import Page, Params
from fastapi_pagination.ext.sqlalchemy import paginate
@app.get("/users/paginated", response_model=Page[UserResponse])
async def get_users_paginated(
query = QueryBuilder(User),
session: AsyncSession = Depends(get_db),
params: Params = Depends()
):
return await paginate(session, query, params)
from functools import lru_cache
import hashlib
import json
# Cache expensive queries
@lru_cache(maxsize=100)
def get_cached_query_result(query_hash: str):
# Implement your caching logic here
pass
@app.get("/users")
async def get_users_with_cache(
filters: str = Query(None),
sort: str = Query(None),
search: str = Query(None),
session: AsyncSession = Depends(get_db)
):
# Create cache key from parameters
cache_key = hashlib.md5(
json.dumps({"filters": filters, "sort": sort, "search": search}).encode()
).hexdigest()
# Try to get from cache first
cached_result = get_cached_query_result(cache_key)
if cached_result:
return cached_result
# Execute query if not cached
query = QueryBuilder(User)(filters=filters, sort=sort, search=search)
result = await session.execute(query)
users = result.scalars().all()
# Cache the result
# ... caching logic ...
return users
import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from your_app import app, get_db, Base
# Test database setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_db] = override_get_db
@pytest.fixture
def client():
Base.metadata.create_all(bind=engine)
with TestClient(app) as c:
yield c
Base.metadata.drop_all(bind=engine)
def test_basic_filtering(client):
# Create test data
response = client.post("/users", json={
"name": "John Doe",
"email": "john@example.com",
"age": 30
})
# Test filtering
response = client.get('/users?filters={"name": {"$eq": "John Doe"}}')
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["name"] == "John Doe"
def test_sorting(client):
# Create test data
users = [
{"name": "Alice", "email": "alice@example.com", "age": 25},
{"name": "Bob", "email": "bob@example.com", "age": 35}
]
for user in users:
client.post("/users", json=user)
# Test sorting
response = client.get("/users?sort=age:asc")
assert response.status_code == 200
data = response.json()
assert data[0]["age"] == 25
assert data[1]["age"] == 35
def test_search(client):
# Create test data
client.post("/users", json={
"name": "John Smith",
"email": "john.smith@example.com"
})
# Test search
response = client.get("/users?search=john")
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert "john" in data[0]["name"].lower()
def test_error_handling(client):
# Test invalid JSON
response = client.get('/users?filters={"invalid": json}')
assert response.status_code == 400
# Test invalid field
response = client.get('/users?filters={"nonexistent": {"$eq": "value"}}')
assert response.status_code == 400
def test_complex_query(client):
# Create test data with relationships
role_response = client.post("/roles", json={"name": "admin"})
role_id = role_response.json()["id"]
client.post("/users", json={
"name": "Admin User",
"email": "admin@example.com",
"role_id": role_id,
"is_active": True
})
# Test complex filtering
response = client.get(f'/users?filters={{"role.name": {{"$eq": "admin"}}, "is_active": {{"$eq": true}}}}')
assert response.status_code == 200
data = response.json()
assert len(data) == 1
assert data[0]["role"]["name"] == "admin"
def test_pagination(client):
# Create multiple users
for i in range(25):
client.post("/users", json={
"name": f"User {i}",
"email": f"user{i}@example.com"
})
# Test pagination
response = client.get("/users/paginated?page=1&size=10")
assert response.status_code == 200
data = response.json()
assert len(data["items"]) == 10
assert data["total"] == 25
assert data["page"] == 1
assert data["size"] == 10
We welcome contributions! Here's how to get started:
# Clone the repository
git clone [https://github.com/yourusername/fastapi-querybuilder.git](https://github.com/yourusername/fastapi-querybuilder.git)
cd fastapi-querybuilder
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -e ".[dev]"
# Install pre-commit hooks
pre-commit install
```plaintext
### Development Dependencies
\`\`\`bash
pip install -e ".[dev]"
# This installs:
# - pytest
# - pytest-cov
# - black
# - isort
# - flake8
# - mypy
# - pre-commit
# Run all tests
pytest
# Run with coverage
pytest --cov=fastapi-querybuilder --cov-report=html
# Run specific test file
pytest tests/test_filtering.py
# Run with verbose output
pytest -v
```plaintext
### Code Quality
\`\`\`bash
# Format code
black fastapi-querybuilder/
isort fastapi-querybuilder/
# Lint code
flake8 fastapi-querybuilder/
# Type checking
mypy fastapi-querybuilder/
# Run all quality checks
pre-commit run --all-files
# Navigate to examples directory
cd examples/
# Install example dependencies
pip install -r requirements.txt
# Run the example server
python main.py
# Visit [http://localhost:8000/docs](http://localhost:8000/docs) for interactive API documentation
```plaintext
### Contribution Guidelines
1. **Fork the repository** and create a feature branch
2. **Write tests** for new functionality
3. **Ensure all tests pass** and maintain 100% coverage
4. **Follow code style** (black, isort, flake8)
5. **Add type hints** for all new code
6. **Update documentation** for new features
7. **Submit a pull request** with a clear description
### Reporting Issues
When reporting issues, please include:
- Python version
- FastAPI version
- SQLAlchemy version
- Complete error traceback
- Minimal code example to reproduce the issue
- Expected vs actual behavior
## ๐ Changelog
### v1.2.0 (Latest)
- โจ Added support for deep nested relationships (unlimited depth)
- ๐ Performance improvements for complex queries
- ๐ Fixed date range handling edge cases
- ๐ Comprehensive documentation updates
- ๐งช Expanded test coverage to 100%
### v1.1.0
- โจ Added `$isanyof` operator (alias for `$in`)
- โจ Added `$ncontains` operator for negative string matching
- ๐ Improved query optimization for relationship joins
- ๐ Fixed issue with enum field searching
- ๐ Added more real-world examples
### v1.0.0
- ๐ Initial release
- โจ Basic filtering with comparison operators
- โจ Dynamic sorting with relationship support
- โจ Global search functionality
- โจ Soft delete support
- โจ Date range handling
- โจ Pagination integration
## ๐ License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
MIT License
Copyright (c) 2024 FastAPI QueryBuilder
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## ๐ Acknowledgments
- **[FastAPI](https://fastapi.tiangolo.com/)** - The amazing web framework that makes this possible
- **[SQLAlchemy](https://www.sqlalchemy.org/)** - The powerful and flexible ORM
- **[Pydantic](https://pydantic-docs.helpmanual.io/)** - Data validation and settings management
- **[fastapi-pagination](https://github.com/uriyyo/fastapi-pagination)** - Seamless pagination integration
## ๐ Support & Community
- ๐ **Bug Reports**: [GitHub Issues](https://github.com/yourusername/fastapi-querybuilder/issues)
- ๐ฌ **Discussions**: [GitHub Discussions](https://github.com/yourusername/fastapi-querybuilder/discussions)
- ๐ง **Email**: support@fastapi-querybuilder.com
- ๐ผ **LinkedIn**: [FastAPI QueryBuilder](https://linkedin.com/company/fastapi-querybuilder)
- ๐ฆ **Twitter**: [@FastAPIQueryBuilder](https://twitter.com/FastAPIQueryBuilder)
## ๐ Show Your Support
If you find FastAPI QueryBuilder helpful, please consider:
- โญ **Starring the repository** on GitHub
- ๐ฆ **Sharing on social media** with #FastAPIQueryBuilder
- ๐ **Writing a blog post** about your experience
- ๐ฃ๏ธ **Speaking at conferences** about the project
- ๐ฐ **Sponsoring the project** for continued development
---
**Made with โค๏ธ for the FastAPI community**
*FastAPI QueryBuilder - Simplifying complex queries, one endpoint at a time.*
This comprehensive README.md includes everything a developer needs to know:
The README is now completely self-contained and provides everything developers need to understand, implement, and contribute to your FastAPI QueryBuilder package without needing to visit external documentation sites.
FAQs
Smart filtering, sorting, and searching for FastAPI with SQLAlchemy
We found that fastapi-querybuilder demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago.ย It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
The EU Cyber Resilience Act is prompting compliance requests that open source maintainers may not be obligated or equipped to handle.
Security News
Crates.io adds Trusted Publishing support, enabling secure GitHub Actions-based crate releases without long-lived API tokens.
Research
/Security News
Undocumented protestware found in 28 npm packages disrupts UI for Russian-language users visiting Russian and Belarusian domains.