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.3.tar.gz (67.3 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.3-py3-none-any.whl (76.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ironhammer-1.0.3.tar.gz
  • Upload date:
  • Size: 67.3 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.3.tar.gz
Algorithm Hash digest
SHA256 c3a8f397b6c766c9132f4af929b6a5f74d80f3add13ea503fea8a016757b464b
MD5 16a4c427ffef2036ec13b92836c2ec96
BLAKE2b-256 e36880e90b99cd1def217e2c25218c1810ad0343baf2af66b665be444eea9be8

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ironhammer-1.0.3-py3-none-any.whl
  • Upload date:
  • Size: 76.7 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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 a4338fa02dd10dbd31dfe9360b8cad93a415f13634ea2b3d796203969d60d63a
MD5 d4b4c9e534347964f19c2cd19c884f31
BLAKE2b-256 b7f79178da2c1f4e4a1439b15c0ba9d482f6c747e1d28ea45faf55bce9d5c471

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.5

2 files

1.0.4

2 files

This release

1.0.3 This release

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