Django ORM-like interface for external REST APIs with Pydantic validation and httpx
Project description
django-api-orm
A Django ORM-like interface for external REST APIs with Pydantic validation and httpx.
Overview
django-api-orm provides a familiar Django ORM interface for interacting with REST APIs, making it easy to work with external services using the same patterns you already know. Built with modern Python features:
- Pydantic 2.0 for robust data validation and serialization
- httpx for modern HTTP client support (both sync and async)
- Full type safety with comprehensive type hints
- Django-like API for intuitive, familiar usage
- 91% test coverage with 152 comprehensive tests
Features
- Sync and Async Support - Use synchronous or asynchronous clients based on your needs
- Type Safety - Full type hints and Pydantic validation throughout
- Nested Schemas - Full support for nested Pydantic models with dot notation access
- Django-like QuerySet API - Familiar filter(), exclude(), get(), first(), last(), etc.
- HTTP/2 Support - Optional HTTP/2 for better performance (async only)
- Connection Pooling - Built-in connection pooling with httpx
- Automatic Retries - Configurable retry logic for failed requests
- Lazy Evaluation - QuerySets only execute when needed
- Chainable Queries - Build complex queries with method chaining
- CRUD Operations - Full create, read, update, delete support
- Concurrent Operations - Easy async/await with asyncio.gather()
- Test Server Included - FastAPI test server with insurance domain for development and testing
Installation
# Basic installation
pip install django-api-orm
# With async and HTTP/2 support
pip install django-api-orm[async]
# Development installation
pip install django-api-orm[dev]
Using uv (recommended):
uv add django-api-orm
# or with async support
uv add "django-api-orm[async]"
Quick Start
Define Your Models
from pydantic import BaseModel
from django_api_orm import APIModel, ServiceClient, register_models
# Define your Pydantic schema
class UserSchema(BaseModel):
id: int | None = None # Optional for creation
name: str
email: str
active: bool = True
# Define your API model
class User(APIModel):
_schema_class = UserSchema
_endpoint = "/api/v1/users/"
Synchronous Usage
# Create a client and register models
with ServiceClient(
base_url="https://api.example.com",
auth_token="your-token-here"
) as client:
register_models(client, User)
# Query users (Django-like!)
active_users = User.objects.filter(active=True)
for user in active_users:
print(f"{user.name} - {user.email}")
# Get a single user
user = User.objects.get(id=1)
# Create a new user
new_user = User.objects.create(
name="Alice Smith",
email="alice@example.com"
)
# Update a user
user.email = "newemail@example.com"
user.save(update_fields=["email"])
# Delete a user
user.delete()
# Chain queries
recent_active = (User.objects
.filter(active=True)
.order_by("-created_at")
.first())
# Get or create
user, created = User.objects.get_or_create(
email="bob@example.com",
defaults={"name": "Bob Jones"}
)
Asynchronous Usage
import asyncio
from django_api_orm import AsyncAPIModel, AsyncServiceClient, register_async_models
class User(AsyncAPIModel):
_schema_class = UserSchema
_endpoint = "/api/v1/users/"
async def main():
async with AsyncServiceClient(
base_url="https://api.example.com",
auth_token="your-token-here",
http2=True # Enable HTTP/2
) as client:
register_async_models(client, User)
# Async iteration
async for user in User.objects.filter(active=True):
print(f"{user.name} - {user.email}")
# Async retrieval
user = await User.objects.get(id=1)
# Async creation
new_user = await User.objects.create(
name="Charlie Brown",
email="charlie@example.com"
)
# Async update
user.email = "updated@example.com"
await user.save()
# Concurrent operations
users_count, posts_count = await asyncio.gather(
User.objects.filter(active=True).count(),
Post.objects.filter(published=True).count()
)
asyncio.run(main())
Nested Schemas
django-api-orm fully supports nested Pydantic models with dot notation access:
from pydantic import BaseModel, EmailStr
from datetime import date
# Define nested schema
class AddressSchema(BaseModel):
street: str
city: str
state: str
zip_code: str
country: str = "USA"
# Use in parent schema
class PolicyHolderSchema(BaseModel):
id: int | None = None
first_name: str
last_name: str
email: EmailStr
date_of_birth: date
address: AddressSchema # Nested schema
active: bool = True
class PolicyHolder(APIModel):
_schema_class = PolicyHolderSchema
_endpoint = "/api/v1/policyholders/"
# Access nested fields with dot notation
with ServiceClient(base_url="https://api.example.com") as client:
register_models(client, PolicyHolder)
# Create with nested data
address = AddressSchema(
street="123 Main St",
city="Chicago",
state="IL",
zip_code="60601"
)
holder = PolicyHolder.objects.create(
first_name="John",
last_name="Doe",
email="john@example.com",
date_of_birth=date(1980, 1, 1),
address=address, # Nested schema
active=True
)
# Access nested fields
print(holder.address.city) # "Chicago"
print(holder.address.state) # "IL"
print(holder.address.zip_code) # "60601"
# Update nested fields
holder.address.street = "456 Oak Ave"
holder.save()
See examples/INSURANCE_DOMAIN.md for a complete insurance domain example with nested schemas.
Test Server
django-api-orm includes a comprehensive FastAPI test server for development and testing. The test server implements a complete insurance domain API with PolicyHolder, Policy, and Claim models.
Starting the Test Server
# Install development dependencies
uv sync --all-extras
# Start the server
uvicorn examples.test_server:app --host 127.0.0.1 --port 8700 --reload
The server will be available at http://localhost:8700 with interactive API docs at http://localhost:8700/docs.
Running Test Scripts
# Run synchronous test script
uv run python examples/test_with_server.py
# Run asynchronous test script
uv run python examples/test_with_server_async.py
The test scripts demonstrate all features including:
- Creating, reading, updating, and deleting records
- Filtering and querying with nested schemas
- Relationship-based filtering
- Concurrent async operations
- And much more!
See examples/README.md for complete test server documentation.
Advanced Features
Query Methods
# Filtering
User.objects.filter(active=True, role="admin")
User.objects.exclude(status="banned")
# Ordering
User.objects.order_by("-created_at")
User.objects.order_by("name", "-email")
# Slicing
User.objects.all()[10:20] # Offset and limit
User.objects.all()[0] # Get first by index
# Retrieval
User.objects.get(id=123) # Get single (raises DoesNotExist)
User.objects.first() # Get first or None
User.objects.last() # Get last or None
User.objects.exists() # Check if any exist
User.objects.count() # Get count
# Value extraction
User.objects.all().values("id", "name") # List of dicts
User.objects.all().values_list("id", flat=True) # Flat list
Manager Methods
# Creation
User.objects.create(name="Alice", email="alice@example.com")
User.objects.bulk_create([
{"name": "Bob", "email": "bob@example.com"},
{"name": "Charlie", "email": "charlie@example.com"}
])
# Get or create
user, created = User.objects.get_or_create(
email="alice@example.com",
defaults={"name": "Alice"}
)
# Update or create
user, created = User.objects.update_or_create(
email="alice@example.com",
defaults={"name": "Alice Updated"}
)
Model Methods
# Create
user = User(name="Alice", email="alice@example.com", _client=client)
user.save()
# Update
user.email = "newemail@example.com"
user.save() # Updates all fields
user.save(update_fields=["email"]) # Partial update
# Refresh
user.refresh_from_api() # Re-fetch from API
# Delete
user.delete()
# Convert to dict
data = user.to_dict()
data = user.to_dict(exclude_unset=True)
Testing
The library has comprehensive test coverage (91%):
# Run all tests
uv run pytest
# Run with coverage
uv run pytest --cov=src/django_api_orm
# Run specific tests
uv run pytest tests/test_orm_integration.py
uv run pytest tests/test_async_orm_integration.py
Type Checking and Linting
# Type checking with mypy
uv run mypy src/
# Linting with ruff
uv run ruff check src/ tests/
# Auto-fix linting issues
uv run ruff check --fix src/ tests/
# Format code
uv run ruff format src/ tests/
Project Status
Version: 0.1.0 (Beta)
Test Coverage: 91% (152 tests)
Implemented Features
- Complete exception system
- Synchronous HTTP client (ServiceClient)
- Asynchronous HTTP client (AsyncServiceClient)
- QuerySet implementation (sync and async)
- Manager implementation (sync and async)
- APIModel base class (sync and async)
- Model registration system
- Nested Pydantic schema support with dot notation
- Manager delegation methods (values, values_list)
- AsyncQuerySet slicing support
- Comprehensive test suite (152 tests, 91% coverage)
- FastAPI test server with insurance domain examples
- Full type safety with mypy
- CI/CD with GitHub Actions
- Connection pooling and retries
- HTTP/2 support (async)
Roadmap
- Advanced caching layer
- Streaming support for large responses
- Related field support (select_related, prefetch_related)
- Query optimization and request batching
- More example projects
- Performance benchmarks
Why httpx over requests?
We chose httpx for several important reasons:
- Native async/sync support - Single codebase for both patterns
- HTTP/2 support - Better performance with multiplexing
- Better connection pooling - Optimized and built-in
- Modern API - Cleaner, more Pythonic interface
- Active development - Better maintained with more features
- Type hints - Better IDE support and type safety
- Better timeout control - More granular timeout configuration
Development
Setup
# Clone the repository
git clone https://github.com/mrb101/django-api-orm.git
cd django-api-orm
# Install dependencies
uv sync --all-extras
# Run tests
uv run pytest
# Type checking
uv run mypy src/
# Linting
uv run ruff check src/ tests/
Project Structure
src/django_api_orm/
├── __init__.py # Public API exports
├── py.typed # PEP 561 type marker
├── exceptions.py # Custom exceptions
├── client.py # Sync HTTP client (httpx)
├── async_client.py # Async HTTP client (httpx)
├── base.py # APIModel, QuerySet, Manager
├── async_base.py # Async versions
├── utils.py # Helper functions
└── typing.py # Type hints and protocols
tests/
├── test_exceptions.py # Exception tests
├── test_client.py # Sync client tests
├── test_async_client.py # Async client tests
├── test_orm_integration.py # Sync ORM tests
├── test_async_orm_integration.py # Async ORM tests
└── test_utils.py # Utility tests
examples/
├── basic_usage.py # Basic sync example
├── async_usage.py # Basic async example
├── test_server.py # FastAPI test server (insurance domain)
├── test_with_server.py # Comprehensive sync test script
├── test_with_server_async.py # Comprehensive async test script
├── README.md # Test server documentation
├── INSURANCE_DOMAIN.md # Insurance domain guide
└── QUICKSTART.md # Quick start guide
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for your changes
- Ensure tests pass (
uv run pytest) - Ensure type checking passes (
uv run mypy src/) - Ensure linting passes (
uv run ruff check src/ tests/) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
MIT License - see LICENSE file for details.
Credits
Created by Bassel J. Hamadeh at TigerLab.
Inspired by Django ORM, Pydantic, and httpx.
Support
- Issues: https://github.com/mrb101/django-api-orm/issues
- Discussions: https://github.com/mrb101/django-api-orm/discussions
- Documentation: https://django-api-orm.readthedocs.io/en/latest/
Changelog
See CHANGELOG.md for detailed version history.
Recent Updates
- Nested Pydantic schema support - Full support for nested models with dot notation access
- FastAPI test server - Comprehensive insurance domain test server for development
- Enhanced Manager methods - Added values() and values_list() delegation methods
- Bug fixes - Fixed nested schema preservation, attribute syncing, and JSON serialization
0.1.0 (Beta)
- Initial release with full sync and async support
- Complete QuerySet, Manager, and APIModel implementations
- 91% test coverage with 152 tests
- Full type safety with mypy
- CI/CD with GitHub Actions
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 django_api_orm-0.1.0.tar.gz.
File metadata
- Download URL: django_api_orm-0.1.0.tar.gz
- Upload date:
- Size: 169.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
da04c8986323e2b02f17ce9068024c41b608fc05f77a107f6a939c0a0b2d8537
|
|
| MD5 |
1f2eb0a63767e22ae310c86bf2c6140b
|
|
| BLAKE2b-256 |
f4ccef08069befd9fc12cee8cfd48a2cbde1149f1fe712851218701621ef042a
|
File details
Details for the file django_api_orm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: django_api_orm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 25.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
461547e5a90aa9fd1f2ab8921f1716a31400dbc04919890fa36720bf63d81a27
|
|
| MD5 |
f8157016ee4051c94de5acef5c0ce5e4
|
|
| BLAKE2b-256 |
46a05319607f9810e656fd54709226425a7cfb939ea026a2ddfa3264d645bdaf
|