Skip to main content

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

ironhammer-1.0.4.tar.gz (67.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ironhammer-1.0.4-py3-none-any.whl (76.9 kB view details)

Uploaded Python 3

File details

Details for the file ironhammer-1.0.4.tar.gz.

File metadata

  • Download URL: ironhammer-1.0.4.tar.gz
  • Upload date:
  • Size: 67.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ironhammer-1.0.4.tar.gz
Algorithm Hash digest
SHA256 c1d20744bb213413ccd981711cd8bd1134eba877fbd851d8628deaffbd05793b
MD5 2920c88ff49848ae6e111704d8e03499
BLAKE2b-256 b936f4e8e762fb7df9f1c795f1fb919cb8c88658da5c1195cc463170fac679b5

See more details on using hashes here.

File details

Details for the file ironhammer-1.0.4-py3-none-any.whl.

File metadata

  • Download URL: ironhammer-1.0.4-py3-none-any.whl
  • Upload date:
  • Size: 76.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ironhammer-1.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 b09bab3568e1f9cc25d32922e2a97f87523ba85aef11355fc0b792e137cb1225
MD5 68ddd7dc25c5eee3f9d685ef229acb69
BLAKE2b-256 69b72b5fb5b7c1564c0f89781995fd61a0c0b26a27369fc71f232adbab68e8dd

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.5

2 files

This release

1.0.4 This release

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

1.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page