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.
Metadata
Release files for IronHammer 1.0.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ironhammer-1.0.5.tar.gz | 67.4 kB | Without provenance | Details for ironhammer-1.0.5.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ironhammer-1.0.5-py3-none-any.whl | Python 3 | none | any | Details for ironhammer-1.0.5-py3-none-any.whl |
Total release size: 144.4 kB
Release files / ironhammer-1.0.5.tar.gz
| Download URL | ironhammer-1.0.5.tar.gz |
|---|---|
| Size | 67.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5d2aad8302ad60e4307e98aebc96d482d83982912c8358a304bb65480f87c5d0
|
|
BLAKE2b-256 checksum How to use checksums |
9e1ac05b260e060cfe8561798f1e992b0615f62fd67f6afb2347faafda58caee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ironhammer-1.0.5-py3-none-any.whl
| Download URL | ironhammer-1.0.5-py3-none-any.whl |
|---|---|
| Size | 77.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a4e8ccb6f84b36d16d21b3ea2264ef3d7ef74c16270ea5402eca5113cd948c93
|
|
BLAKE2b-256 checksum How to use checksums |
a04029f3e1f0459fcdddf1243e0c5a98f8e8b2e6af577a5900d96db85780b8d4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for IronHammer 1.0.5
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.