Skip to main content

A lightweight multitenancy library for FastAPI applications

Project description

Hidra: Lightweight Multi-Tenancy for Python

Hidra is a lightweight, framework-agnostic library for building multi-tenant applications in Python. It provides a simple and flexible way to manage tenants, allowing you to isolate tenant data using different strategies.

PyPI version License: MIT

Key Features

  • FastAPI-Focused: Specifically designed and optimized for FastAPI applications.
  • Multiple Tenancy Strategies:
    • DATABASE_PER_TENANT: Each tenant has a separate database.
    • SCHEMA_PER_TENANT: Each tenant has a separate schema within the same database.
    • ROW_LEVEL: All tenants share the same database and tables, with data isolated by a tenant identifier column.
  • Context-Aware: Uses contextvars to safely manage the current tenant's context, making it suitable for asynchronous applications.
  • FastAPI Integration: Provides ready-to-use middlewares and decorators specifically for FastAPI.
  • Easy Setup: Simple configuration with quick_start() and initialize_hidra_fastapi() functions
  • Enhanced Decorators: Improved requires_tenant() decorator with more flexible options
  • Simplified Database Access: HidraDB class for easier database session management
  • Schema Management: Advanced schema management tools to ensure proper tenant schema creation and naming compatibility with PostgreSQL (e.g., replacing hyphens with underscores).
  • Developer-Controlled Tenants Table: The structure of the tenants table is defined by the developer to match business requirements, with optional convenience functions for common use cases.
  • Automatic Schema Validation: Built-in validation and cleaning of tenant names for PostgreSQL compatibility.
  • Diagnostic Tools: Built-in functions to diagnose configuration issues
  • Helpful Error Messages: Clear error messages with suggestions for resolution

Installation

Install the library using pip:

# Core library
pip install hidra

# To include optional dependencies for FastAPI
pip install hidra[fastapi]

# For development (includes testing and linting tools)
pip install hidra[dev]

Enhanced Usage Examples

Quick Start Configuration (With Predefined Tenants)

from hidra import quick_start

# Simple configuration with predefined tenants
config = quick_start(
    db_config={
        "db_driver": "postgresql",
        "db_host": "localhost",
        "db_port": "5432",
        "db_username": "postgres",
        "db_password": "password",
        "db_name": "multitenant"
    },
    tenants={
        "company1": {"plan": "premium"},
        "company2": {"plan": "basic"}
    }
)

Minimal Configuration for FastAPI (No Predefined Tenants Required)

from fastapi import FastAPI
from hidra import create_hidra_app

# Create FastAPI app with minimal configuration
app = create_hidra_app(
    db_config={
        "db_driver": "postgresql",
        "db_host": "localhost",
        "db_port": "5432",
        "db_username": "postgres",
        "db_password": "password",
        "db_name": "multitenant"
    },
    # Tenants are loaded automatically when requested
    enable_auto_loading=True,
    auto_tenant_validation=True  # Validate tenants exist before processing
)

# Protected endpoint - no need to define tenants in code
@app.get("/data")
async def get_data():
    from hidra import get_current_tenant_id
    tenant_id = get_current_tenant_id()
    return {"tenant_id": tenant_id, "message": "Data retrieved"}

Enhanced Decorators

from hidra import requires_tenant

# Any tenant with access
@app.get("/data")
@requires_tenant()
async def get_data():
    pass

# Specific tenant only
@app.get("/premium-data")
@requires_tenant("premium_company")
async def get_premium_data():
    pass

# Multiple tenants allowed
@app.get("/shared-data")
@requires_tenant(["company1", "company2"])
async def get_shared_data():
    pass

Simplified Database Access

from hidra import HidraDB

# Create database access
hidra_db = HidraDB({
    "db_driver": "postgresql",
    "db_host": "localhost",
    "db_port": "5432",
    "db_username": "postgres",
    "db_password": "password",
    "db_name": "multitenant"
})

# Use with FastAPI Depends
@app.get("/users")
async def get_users(db: Session = Depends(hidra_db.get_tenant_db())):
    pass

Basic Usage (with FastAPI)

Here's a quick example of how to use Hidra with FastAPI.

1. Configure Tenants

First, configure your tenants and the tenancy strategy.

# main.py
from hidra import tenant_context, TenancyStrategy, MultiTenantManager

# Initialize the tenant manager
manager = MultiTenantManager()

# Configure individual tenants
manager.configure_tenant("tenant1", {"db_connection": "postgresql://user:pass@host/db1"})
manager.configure_tenant("tenant2", {"db_connection": "postgresql://user:pass@host/db2"})

# Set the default strategy
manager.set_default_strategy(TenancyStrategy.DATABASE_PER_TENANT)

# Set the manager in the global tenant context
tenant_context.tenant_manager = manager

2. Add the Middleware

The middleware identifies the tenant from the request (e.g., using a header) and sets it in the context.

# main.py
from fastapi import FastAPI
from hidra.middleware import TenantMiddleware
from hidra.decorators import tenant_required

app = FastAPI()

# Add the middleware to your application
app.add_middleware(TenantMiddleware)

@app.get("/items")
@tenant_required
async def get_items():
    # The tenant is now available in the context
    current_tenant = tenant_context.require_tenant()
    
    # You can get tenant-specific configuration
    config = tenant_context.tenant_manager.get_tenant_config(current_tenant)
    
    return {"tenant_id": current_tenant, "db_connection": config.get("db_connection")}

3. Run the Application

To run this example, you would make a request with the X-Tenant-ID header:

curl -X GET "http://127.0.0.1:8000/items" -H "X-Tenant-ID: tenant1"

The response would be:

{
  "tenant_id": "tenant1",
  "db_connection": "postgresql://user:pass@host/db1"
}

Complete FastAPI Integration

from fastapi import FastAPI, Depends
from sqlalchemy.orm import Session
from hidra import create_hidra_app, requires_tenant

# Create FastAPI app with Hidra integration
app = create_hidra_app(
    db_config={
        "db_driver": "postgresql",
        "db_host": "localhost",
        "db_port": "5432",
        "db_username": "postgres",
        "db_password": "password",
        "db_name": "multitenant"
    }
)

# Protected endpoint
@app.get("/users")
@requires_tenant()  # Requires tenant
async def get_users():
    from hidra import get_current_tenant_id
    tenant_id = get_current_tenant_id()
    return {"tenant_id": tenant_id, "message": "Users retrieved"}

Diagnostic Tools

from hidra import diagnose_setup, print_diagnosis

# Get diagnosis as dictionary
diagnosis = diagnose_setup()

# Print formatted diagnosis
print_diagnosis()

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue. If you want to contribute code, please follow these steps:

  1. Fork the repository.
  2. Create a new branch for your feature or bug fix.
  3. Write your code and add tests.
  4. Ensure all tests pass and the code is formatted with black and linted with ruff.
  5. Submit a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

hidra-0.2.0b1.tar.gz (26.4 kB view details)

Uploaded Source

Built Distribution

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

hidra-0.2.0b1-py3-none-any.whl (25.2 kB view details)

Uploaded Python 3

File details

Details for the file hidra-0.2.0b1.tar.gz.

File metadata

  • Download URL: hidra-0.2.0b1.tar.gz
  • Upload date:
  • Size: 26.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for hidra-0.2.0b1.tar.gz
Algorithm Hash digest
SHA256 831c0e2d26bee941a92ad39a1d23e7648010216d81515f18b9ae892893f64a5d
MD5 72051f63406170cd8f5f21298bfb3bd0
BLAKE2b-256 901d957788317628edcd1ebd1049f7263012d98e2d90e0275cf5958859889788

See more details on using hashes here.

File details

Details for the file hidra-0.2.0b1-py3-none-any.whl.

File metadata

  • Download URL: hidra-0.2.0b1-py3-none-any.whl
  • Upload date:
  • Size: 25.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for hidra-0.2.0b1-py3-none-any.whl
Algorithm Hash digest
SHA256 f6d0a238b75b520ea7ce9a0090b8a0c4ee4c07581dddfcd0a48faee9b1dcacc4
MD5 f5a8bf84f315d2271b421d499ecb73f5
BLAKE2b-256 2078ee5f641047b6063d6fa6ba1358aa4f953d7421d465b403b186a7e2277a28

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page