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.5.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.5-py3-none-any.whl (77.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: ironhammer-1.0.5.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.5.tar.gz
Algorithm Hash digest
SHA256 5d2aad8302ad60e4307e98aebc96d482d83982912c8358a304bb65480f87c5d0
MD5 28d69484bbea041b2126e0621597cd30
BLAKE2b-256 9e1ac05b260e060cfe8561798f1e992b0615f62fd67f6afb2347faafda58caee

See more details on using hashes here.

File details

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

File metadata

  • Download URL: ironhammer-1.0.5-py3-none-any.whl
  • Upload date:
  • Size: 77.0 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.5-py3-none-any.whl
Algorithm Hash digest
SHA256 a4e8ccb6f84b36d16d21b3ea2264ef3d7ef74c16270ea5402eca5113cd948c93
MD5 e1e33882242c95f97ed15e8cfa9e04b8
BLAKE2b-256 a04029f3e1f0459fcdddf1243e0c5a98f8e8b2e6af577a5900d96db85780b8d4

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.0.5 This release

2 files

1.0.4

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