Coming Soon¶
This page lists features that are documented but not yet fully implemented in Ferro. These features are planned for future releases.
Work in Progress
The features listed below are referenced in the documentation but are not currently available. Check back for updates or follow the GitHub repository for progress.
Query Features¶
Case-Insensitive LIKE (ilike)¶
Status: Not Implemented
Documentation References:
- docs/guide/queries.md (lines 248-249)
Description:
Case-insensitive pattern matching with ilike() method.
Example (Not Working):
Workaround:
Use standard like() with lowercase conversion or database-specific functions.
NOT IN Operator (not_in)¶
Status: Not Implemented
Documentation References:
- docs/guide/queries.md (lines 235-236)
Description:
A not_in_() method for excluding values from a list.
Example (Not Working):
# This does not work yet
banned_users = await User.where(User.status.not_in_(["banned", "suspended"])).all()
Workaround:
Use negation with & and != operators:
Raw SQL Queries¶
Status: Implemented for execute, fetch_all, and fetch_one
Documentation References:
- docs/guide/queries.md (lines 252-266)
Description: Direct raw SQL execution with parameterization is available. Raw rows are plain dictionaries of wire-close primitive values; typed model hydration still belongs to the ORM.
Example:
from ferro import execute, fetch_all
results = await fetch_all(
"SELECT * FROM users WHERE age > $1 AND status = $2",
18,
"active",
using="app",
)
Additional raw helpers beyond these functions remain out of scope.
Eager Loading / Prefetch Related¶
Status: Not Implemented
Documentation References:
- docs/guide/queries.md (lines 211-214, 318-321)
Description: Eager loading of relationships to avoid N+1 queries.
Example (Not Working):
Workaround: Manually load relationships as needed. Be mindful of N+1 query patterns.
Select Specific Fields¶
Status: Not Implemented
Documentation References:
- docs/guide/queries.md (lines 174-181)
Description: Loading only specific fields instead of all model fields.
Example (Not Working):
Workaround: Load full models and access only the fields you need.
Aggregation Functions¶
Status: Partially Implemented
Documentation References:
- docs/guide/queries.md (lines 166-172)
Description:
Only .count() is implemented. Other aggregations like sum(), avg(), min(), max() are not available.
Example (Partially Working):
# Works
total_users = await User.count()
# Does NOT work yet
total_sales = await Order.sum(Order.amount)
avg_price = await Product.avg(Product.price)
Workaround:
Use .count() or load all records and compute aggregations in Python.
Atomic Field Updates¶
Status: Not Implemented
Documentation References:
- docs/guide/mutations.md (lines 129-139)
Description: Database-level atomic increment/decrement operations.
Example (Not Working):
# This does not work yet
await Post.where(Post.id == post_id).update(
view_count=Post.view_count + 1
)
Workaround: Load the instance, modify it, and save:
Database Connection Features¶
disconnect()¶
Status: Not Implemented
Documentation References:
- docs/guide/database.md (lines 217-232)
Description: Graceful database disconnection for shutdown hooks.
Example (Not Working):
Workaround: Connection cleanup is handled automatically on process exit.
check_connection()¶
Status: Not Implemented
Documentation References:
- docs/guide/database.md (lines 151-164)
Description: Health check function to verify database connectivity.
Example (Not Working):
Workaround: Attempt a simple query to verify connectivity:
connection_context()¶
Status: Not Implemented
Documentation References:
- docs/guide/database.md (lines 166-179)
Description: Request-scoped connection context manager.
Example (Not Working):
# This does not work yet
from ferro import connection_context
async def handle_request():
async with connection_context():
user = await User.create(username="alice")
await Post.create(title="Hello", author=user)
Workaround:
Use transaction() context manager for scoped database operations.
Connection Pool Configuration¶
Status: Partially Implemented
Documentation References:
- docs/guide/database.md (lines 76-104)
Description:
PoolConfig(max_connections=..., min_connections=...) is implemented per connection. Additional pool options like acquire timeout, idle timeout, max lifetime, and health-check toggles are still future work.
Example (Partially Working):
await ferro.connect(
"postgresql://user:password@localhost/dbname",
pool=ferro.PoolConfig(max_connections=20, min_connections=5),
)
For unsupported advanced pool options, use backend defaults.
Multiple Database Support¶
Status: Implemented for explicit named connections
Documentation References:
- docs/guide/database.md (lines 123-149)
- docs/howto/multiple-databases.md (entire file)
Description: Connecting to and querying multiple databases or roles with explicit named connections is supported. Automatic router policies, read/write splitting, and distributed transactions remain out of scope.
Example:
await ferro.connect("postgresql://localhost/main_db", name="primary", default=True)
await ferro.connect("postgresql://localhost/replica_db", name="replica")
# Query specific database
users = await User.using("replica").all()
Transaction Features¶
Nested Transactions / Savepoints¶
Status: Not Implemented
Documentation References:
- docs/guide/transactions.md (lines 91-106)
Description: True nested transaction support with savepoints.
Current Behavior:
Nested transaction() blocks participate in the outermost transaction.
Example (Not Working as Described):
async with transaction(): # Outer transaction
await User.create(username="alice")
async with transaction(): # Should be a savepoint, but isn't
await Post.create(title="Hello")
# Partial rollback not supported
Workaround: Avoid nesting transactions. Structure code to use a single transaction scope.
Migration Features¶
Alembic Integration Details¶
Status: Partially Documented
Documentation References:
- docs/guide/migrations.md (entire file)
Description:
The migration guide references ferro.migrations.get_metadata() and assumes full Alembic integration.
Verification Needed:
# Check if this import works
from ferro.migrations import get_metadata
target_metadata = get_metadata()
Note: Verify that ferro-orm[alembic] installation provides the necessary migration bridge.
Model Features¶
Model.count() Class Method¶
Status: Implemented, but Usage Unclear
Documentation References:
- docs/getting-started/tutorial.md (line 135)
Description:
The tutorial shows await Post.count() being called directly on the model class.
Verification:
# This should work
total_posts = await Post.select().count()
# Check if this shorthand works
total_posts = await Post.count()
Error Handling¶
Specific Exception Types¶
Status: Not Confirmed
Documentation References:
- docs/guide/mutations.md (lines 380-408)
- docs/guide/database.md (lines 266-276)
Description:
Documentation references IntegrityError, ValidationError, and ConnectionError without imports.
Example (Import Path Unknown):
# Import path not documented
try:
await User.create(username="alice", email="duplicate@example.com")
except IntegrityError as e: # Where does this come from?
print(f"Integrity error: {e}")
Clarification Needed:
Document the exception hierarchy and import paths:
- Are these from ferro package?
- Re-exported from Pydantic?
- Database-driver specific?
Relationship Features¶
Many-to-Many Join Table Creation¶
Status: Partially Implemented
Documentation References:
- docs/guide/relationships.md (lines 176-289)
Description:
Many-to-many relationships are defined with ManyToMany(...), but the join tables are not automatically created during auto_migrate=True.
Example (Partially Working):
from typing import Annotated
from ferro import BackRef, Field, ManyToMany, Model, Relation
class Post(Model):
id: int | None = Field(default=None, primary_key=True)
tags: Relation[list["Tag"]] = ManyToMany(related_name="posts")
class Tag(Model):
id: int | None = Field(default=None, primary_key=True)
posts: Relation[list["Post"]] = BackRef()
# Models created, but join table 'post_tags' is NOT auto-created
# This causes errors when trying to use M2M methods:
await post.tags.add(tag) # RuntimeError: no such table: post_tags
Workaround: Manual join table creation may be required, or use Alembic migrations. Further investigation needed.
Test Status: 4 tests skipped in tests/test_documentation_features.py
One-to-One Automatic Behavior¶
Status: Documented but Verify
Documentation References:
- docs/guide/relationships.md (lines 154-162)
Description: Documentation states that one-to-one reverse relations automatically return a single object instead of a Query.
Example (Verify Behavior):
class User(Model):
id: int
profile: "Profile" = BackRef()
class Profile(Model):
id: int
user: Annotated[User, ForeignKey(related_name="profile", unique=True)]
user = await User.where(User.id == 1).first()
# Does this return Profile | None directly?
# Or does it return Query[Profile]?
profile = await user.profile
Action: Verify with tests that unique ForeignKey creates this behavior.
Summary¶
Definitely Not Implemented¶
ilike()- case-insensitive LIKEnot_in_()- NOT IN operator- Additional raw SQL helper APIs beyond
execute/fetch_all/fetch_one - Eager loading (
prefetch_related) - Select specific fields (partial model loading)
- Aggregation functions (sum, avg, min, max)
- Atomic field updates (e.g.,
view_count + 1) disconnect()functioncheck_connection()functionconnection_context()context manager- Additional connection pool parameters
- Automatic routing policies for multiple databases
- Nested transactions / savepoints
Needs Verification¶
Model.count()class method shorthand- Exception types and import paths
- One-to-one automatic single object return
- Alembic integration (ferro.migrations module)
Next Steps¶
If you encounter any issues with documented features:
- Check GitHub Issues: ferro-orm/issues
- Report Missing Features: Open an issue if a documented feature doesn't work
- Use Workarounds: See the workarounds provided above for each feature
Want to contribute? Check the Contributing Guide to help implement these features.