IronHammer
A production-ready Python library that provides a drop-in replacement for Anvil Data Tables API.
Features
- 95%+ API Compatibility with Anvil Data Tables server-side API
- Multiple Storage Backends: SQLite, PostgreSQL, MySQL, and in-memory
- Flexible Media Storage: Filesystem, Cloudflare R2, Amazon S3, MinIO
- Advanced Query Engine: Full support for complex queries with boolean expressions
- Relationships: One-to-one, one-to-many, many-to-many with cascade delete
- Transactions: ACID compliance with nested transactions and savepoints
- Schema Management: Automatic migrations with versioning
- Indexing: Unique and composite indexes for performance optimization
- Serialization: JSON, CSV, YAML import/export
- Permissions: Role-based access control with custom authentication hooks
- Thread-Safe: Safe for concurrent access in multi-threaded environments
- Type Hints: Full type annotations for IDE support
Installation
pip install IronHammer
Quick Start
from IronHammer.apptables import app_tables
import IronHammer.apptables.query as q
# Add a row
user = app_tables.users.add_row(
name="John",
age=25
)
# Get a row
user = app_tables.users.get(name="John")
# Search with queries
rows = app_tables.users.search(
age=q.greater_than(18)
)
for row in rows:
print(row["name"])
# Update a row
user["age"] = 30
user.update(active=True)
# Delete a row
user.delete()
Configuration
SQLite Backend (Default)
from IronHammer.apptables import app_tables
app_tables.configure(
backend="sqlite",
database_path="my_data.db"
)
PostgreSQL Backend
app_tables.configure(
backend="postgresql",
host="localhost",
port=5432,
database="mydb",
user="postgres",
password="secret"
)
MySQL Backend
app_tables.configure(
backend="mysql",
host="localhost",
port=3306,
database="mydb",
user="root",
password="secret"
)
In-Memory Backend
app_tables.configure(
backend="memory"
)
Table Operations
# Create a table
app_tables.create_table("users", columns={
"name": "string",
"age": "number",
"active": "bool"
})
# List tables
tables = app_tables.list_tables()
# Delete a table
app_tables.delete_table("users")
# Rename a table
app_tables.rename_table("users", "people")
Query API
import IronHammer.apptables.query as q
# Basic comparisons
app_tables.users.search(age=q.equal_to(25))
app_tables.users.search(age=q.not_equal_to(25))
app_tables.users.search(age=q.greater_than(18))
app_tables.users.search(age=q.greater_than_or_equal_to(18))
app_tables.users.search(age=q.less_than(65))
app_tables.users.search(age=q.less_than_or_equal_to(65))
# String operations
app_tables.users.search(name=q.contains("John"))
app_tables.users.search(name=q.startswith("J"))
app_tables.users.search(name=q.endswith("n"))
app_tables.users.search(name=q.like("J%"))
app_tables.users.search(name=q.ilike("j%"))
app_tables.users.search(name=q.regexp(r"^J.*n$"))
# Null checks
app_tables.users.search(email=q.is_none())
app_tables.users.search(email=q.not_none())
# List operations
app_tables.users.search(age=q.in_list([18, 25, 30]))
app_tables.users.search(age=q.not_in_list([10, 15, 20]))
# Range queries
app_tables.users.search(age=q.between(18, 65))
# Boolean expressions
app_tables.users.search(
q.all_of(
age=q.greater_than(18),
active=True
)
)
app_tables.users.search(
q.any_of(
name=q.contains("John"),
name=q.contains("Jane")
)
)
app_tables.users.search(
q.not_(
age=q.less_than(18)
)
)
# Sorting
app_tables.users.search(
age=q.greater_than(18),
order_by="name",
ascending=True
)
Transactions
from IronHammer.apptables import transaction
with transaction():
user = app_tables.users.add_row(name="John", age=25)
app_tables.orders.add_row(user_id=user.get_id(), total=100.0)
# If an exception occurs, changes are rolled back automatically
Nested Transactions
with transaction() as outer:
user = app_tables.users.add_row(name="John", age=25)
with transaction(savepoint=True):
app_tables.orders.add_row(user_id=user.get_id(), total=100.0)
# Can rollback to this savepoint
Relationships
# Define relationships in schema
app_tables.create_table("users", columns={
"name": "string",
"email": "string"
})
app_tables.create_table("posts", columns={
"title": "string",
"content": "text",
"user_id": "reference:users"
})
# Access related data
user = app_tables.users.get(name="John")
posts = user["posts"] # One-to-many relationship
post = app_tables.posts.get(title="My Post")
author = post["user"] # Many-to-one relationship
Media Storage
from IronHammer.apptables.media import Media
# Configure media storage
app_tables.configure_media(
backend="filesystem",
path="/path/to/media"
)
# Cloudflare R2
app_tables.configure_media(
backend="r2",
account_id="your-account-id",
access_key="your-access-key",
secret_key="your-secret-key",
bucket="my-bucket"
)
# Amazon S3
app_tables.configure_media(
backend="s3",
access_key="your-access-key",
secret_key="your-secret-key",
bucket="my-bucket",
region="us-east-1"
)
# Use media objects
media = Media.from_file("image.png")
app_tables.users.add_row(name="John", avatar=media)
Serialization
# Export to JSON
app_tables.users.export_to_json("users.json")
# Import from JSON
app_tables.users.import_from_json("users.json")
# Export to CSV
app_tables.users.export_to_csv("users.csv")
# Export to YAML
app_tables.users.export_to_yaml("users.yaml")
Bulk Operations
# Bulk insert
data = [
{"name": "John", "age": 25},
{"name": "Jane", "age": 30},
{"name": "Bob", "age": 35}
]
app_tables.users.bulk_add(data)
# Bulk update
app_tables.users.bulk_update(
ids=[id1, id2, id3],
updates={"active": True}
)
# Bulk delete
app_tables.users.bulk_delete([id1, id2, id3])
Permissions
# Configure permissions
app_tables.configure_permissions({
"users": {
"read": ["admin", "user"],
"write": ["admin"],
"delete": ["admin"]
}
})
# Custom authentication hook
def authenticate(role, table, operation):
# Your custom authentication logic
return True
app_tables.set_auth_hook(authenticate)
Compatibility Mode
For existing Anvil projects, use the compatibility package:
# Instead of:
# from anvil.tables import app_tables
# import anvil.tables.query as q
# Use:
from IronHammer.apptables.compat import app_tables
import IronHammer.apptables.compat.query as q
API Reference
See the docs directory for complete API documentation.
Testing
# Run all tests
pytest
# Run with coverage
pytest --cov=IronHammer --cov-report=html
# Run specific test file
pytest tests/test_table.py
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
AGPL-3.0-or-later - see LICENSE for details.
Changelog
See CHANGELOG.md for version history.
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 ironhammer-1.0.2.tar.gz.
File metadata
- Download URL: ironhammer-1.0.2.tar.gz
- Upload date:
- Size: 67.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ec4cab1a59ca47a583d2b8b6fd244256b49ca2c0d33534ddd34a02601735c5f6
|
|
| MD5 |
bd1e1f20ad6e6fa92e7ebdfdf91c4783
|
|
| BLAKE2b-256 |
875e033e25245bd085c32f96b42ed005e069033c8c607ef7fa77fe122172babd
|
File details
Details for the file ironhammer-1.0.2-py3-none-any.whl.
File metadata
- Download URL: ironhammer-1.0.2-py3-none-any.whl
- Upload date:
- Size: 76.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0f33076d29a4b0ad266c7f43c6d6a6ed27a627a9faf0a1257d0f04b4ec56ebae
|
|
| MD5 |
323935511fa7350133f7875f898162cc
|
|
| BLAKE2b-256 |
29f1f8415df03258203d7db622f7107c73f108292f4bb9bfb1e99c8124266bdf
|