Frequently Asked Questions¶
Common questions and answers about Oxyde.
General¶
What is Oxyde?¶
Oxyde is a high-performance async Python ORM with a Rust core. It combines:
- Python API: Pydantic v2 models with Django-style query syntax
- Rust Core: High-performance SQL generation and execution via sqlx
- MessagePack Protocol: Efficient binary communication (~2KB payloads)
Why Rust?¶
The Rust core provides:
- Performance: SQL generation and connection pooling are 5-10x faster than pure Python
- Memory Safety: No GC pauses during I/O operations
- Concurrency: True async I/O that releases Python's GIL
Which databases are supported?¶
| Database | Status | Features |
|---|---|---|
| PostgreSQL | Full support | RETURNING, JSONB, Arrays, UPSERT |
| SQLite | Full support | RETURNING, WAL mode, connection pooling |
| MySQL | Full support | No RETURNING (uses LAST_INSERT_ID), UPSERT via ON DUPLICATE KEY |
Is Oxyde production-ready?¶
Oxyde is suitable for production use with these considerations:
- Core CRUD operations are stable
- Advanced features (migrations, M2M) are still maturing
- Thoroughly test your specific use case
Installation¶
How do I install Oxyde?¶
What are the system requirements?¶
- Python 3.10+
- No Rust compiler needed (wheels are pre-built)
How do I install from source?¶
# Clone repository
git clone https://github.com/mr-fatalyst/oxyde.git
cd oxyde
# Install Rust (if not installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Build and install
cd crates/oxyde-core-py
maturin develop --release
cd ../../python
pip install -e .
Models¶
How do I define a model?¶
from oxyde import OxydeModel, Field
class User(OxydeModel):
class Meta:
is_table = True
id: int | None = Field(default=None, db_pk=True)
name: str
email: str = Field(db_unique=True)
Why isn't my model creating a table?¶
Ensure you have is_table = True in the Meta class:
How do I use UUIDs as primary keys?¶
from uuid import UUID
class User(OxydeModel):
class Meta:
is_table = True
id: UUID = Field(
db_pk=True,
db_default="gen_random_uuid()" # PostgreSQL
)
How do I add Pydantic validation?¶
Oxyde fields support all Pydantic validation:
class User(OxydeModel):
age: int = Field(ge=0, le=150)
email: str = Field(pattern=r"^[\w.-]+@[\w.-]+\.\w+$")
name: str = Field(min_length=1, max_length=100)
Queries¶
How do I filter with OR conditions?¶
Use Q expressions:
How do I do case-insensitive search?¶
Use __icontains, __iexact, etc.:
users = await User.objects.filter(name__icontains="alice").all()
users = await User.objects.filter(email__iexact="ALICE@EXAMPLE.COM").all()
How do I paginate results?¶
# Page 1 (first 20)
page1 = await User.objects.limit(20).all()
# Page 2
page2 = await User.objects.offset(20).limit(20).all()
# With ordering
users = await User.objects.order_by("-created_at").limit(20).offset(40).all()
How do I count rows efficiently?¶
# Don't do this (loads all rows)
count = len(await User.objects.all())
# Do this instead
count = await User.objects.filter(status="active").count()
How do I load related objects?¶
Use join() for FK relations:
# Without join - author not loaded
posts = await Post.objects.all()
for post in posts:
print(post.author) # None - not loaded
# With join - author loaded in same query
posts = await Post.objects.join("author").all()
for post in posts:
print(post.author.name) # Data available
How do I do atomic updates?¶
Use F expressions:
from oxyde import F
# Atomic increment
await Post.objects.filter(id=1).update(views=F("views") + 1)
# Atomic decrement
await User.objects.filter(id=1).update(balance=F("balance") - 100)
Connections¶
How do I connect to the database?¶
How do I configure connection pooling?¶
from oxyde import db, PoolSettings
await db.init(
"postgresql://localhost/mydb",
settings=PoolSettings(
max_connections=20,
min_connections=5,
acquire_timeout=30,
)
)
How do I use multiple databases?¶
await db.init(
default="postgresql://localhost/main",
analytics="postgresql://localhost/analytics",
)
# Use specific database
events = await Event.objects.all(using="analytics")
How do I close connections properly?¶
from oxyde import db
# In application shutdown
await db.close()
# With FastAPI lifespan
app = FastAPI(lifespan=db.lifespan("postgresql://localhost/mydb"))
Transactions¶
How do I use transactions?¶
from oxyde.db import transaction
async with transaction.atomic():
user = await User.objects.create(name="Alice")
await Account.objects.create(user_id=user.id, balance=0)
What happens on exception?¶
The transaction is automatically rolled back:
async with transaction.atomic():
await User.objects.create(name="Alice")
raise ValueError("Oops!") # Transaction rolled back
How do I use savepoints?¶
Nest transaction.atomic() blocks:
async with transaction.atomic():
await User.objects.create(name="Alice")
try:
async with transaction.atomic():
await User.objects.create(name="Bob")
raise ValueError() # Inner transaction rolled back
except ValueError:
pass # Alice is still committed
await User.objects.create(name="Charlie")
How do I lock rows?¶
Use for_update() or for_share():
async with atomic():
user = await User.objects.filter(id=1).for_update().first()
user.balance -= 100
await user.save()
Performance¶
Why are my queries slow?¶
Common causes:
- Missing indexes: Add
db_index=Trueto filtered fields - Queries in loops: Use
join()orprefetch()to load related objects - Large result sets: Use
limit()and pagination - Missing connection pool: Configure
min_connections > 0
How do I analyze query performance?¶
# Get query plan
plan = await User.objects.filter(age__gte=18).explain()
print(plan)
# With execution times
plan = await User.objects.filter(age__gte=18).explain(analyze=True)
How do I see the generated SQL?¶
sql, params = User.objects.filter(age__gte=18).sql()
print(sql) # SELECT ... WHERE age >= $1
print(params) # [18]
What are the best SQLite settings?¶
Oxyde applies optimized defaults automatically:
- WAL journal mode (10-20x faster writes)
- NORMAL synchronous mode
- 10MB cache size
- 5 second busy timeout
Errors¶
NotFoundError¶
Raised when get() finds no rows:
try:
user = await User.objects.get(id=999)
except NotFoundError:
print("User not found")
# Or use get_or_none
user = await User.objects.get_or_none(id=999)
MultipleObjectsReturned¶
Raised when get() finds multiple rows:
# Wrong - status is not unique
user = await User.objects.get(status="active")
# Correct - use unique field
user = await User.objects.get(email="alice@example.com")
# Or use first() for non-unique queries
user = await User.objects.filter(status="active").first()
IntegrityError¶
Raised on constraint violations:
try:
await User.objects.create(email="duplicate@example.com")
except IntegrityError:
print("Email already exists")
# Better: use get_or_create
user, created = await User.objects.get_or_create(
email="alice@example.com",
defaults={"name": "Alice"}
)