# 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.
[](https://badge.fury.io/py/hidra)
[](https://opensource.org/licenses/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`:
```bash
# 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)
```python
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)
```python
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
```python
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
```python
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.
```python
# 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.
```python
# 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:
```bash
curl -X GET "http://127.0.0.1:8000/items" -H "X-Tenant-ID: tenant1"
```
The response would be:
```json
{
"tenant_id": "tenant1",
"db_connection": "postgresql://user:pass@host/db1"
}
```
### Complete FastAPI Integration
```python
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
```python
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](LICENSE) file for details.
Raw data
{
"_id": null,
"home_page": null,
"name": "hidra",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "multitenancy, fastapi, postgresql, sqlalchemy, tenancy",
"author": null,
"author_email": "Hamilton Pati\u00f1o Solano <hamiltonpatinosolano@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/90/1d/957788317628edcd1ebd1049f7263012d98e2d90e0275cf5958859889788/hidra-0.2.0b1.tar.gz",
"platform": null,
"description": "# Hidra: Lightweight Multi-Tenancy for Python\r\n\r\nHidra 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.\r\n\r\n[](https://badge.fury.io/py/hidra)\r\n[](https://opensource.org/licenses/MIT)\r\n\r\n## Key Features\r\n\r\n- **FastAPI-Focused:** Specifically designed and optimized for FastAPI applications.\r\n- **Multiple Tenancy Strategies:**\r\n - `DATABASE_PER_TENANT`: Each tenant has a separate database.\r\n - `SCHEMA_PER_TENANT`: Each tenant has a separate schema within the same database.\r\n - `ROW_LEVEL`: All tenants share the same database and tables, with data isolated by a tenant identifier column.\r\n- **Context-Aware:** Uses `contextvars` to safely manage the current tenant's context, making it suitable for asynchronous applications.\r\n- **FastAPI Integration:** Provides ready-to-use middlewares and decorators specifically for FastAPI.\r\n- **Easy Setup:** Simple configuration with `quick_start()` and `initialize_hidra_fastapi()` functions\r\n- **Enhanced Decorators:** Improved `requires_tenant()` decorator with more flexible options\r\n- **Simplified Database Access:** `HidraDB` class for easier database session management\r\n- **Schema Management:** Advanced schema management tools to ensure proper tenant schema creation and naming compatibility with PostgreSQL (e.g., replacing hyphens with underscores).\r\n- **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.\r\n- **Automatic Schema Validation:** Built-in validation and cleaning of tenant names for PostgreSQL compatibility.\r\n- **Diagnostic Tools:** Built-in functions to diagnose configuration issues\r\n- **Helpful Error Messages:** Clear error messages with suggestions for resolution\r\n\r\n## Installation\r\n\r\nInstall the library using `pip`:\r\n\r\n```bash\r\n# Core library\r\npip install hidra\r\n\r\n# To include optional dependencies for FastAPI\r\npip install hidra[fastapi]\r\n\r\n# For development (includes testing and linting tools)\r\npip install hidra[dev]\r\n```\r\n\r\n## Enhanced Usage Examples\r\n\r\n### Quick Start Configuration (With Predefined Tenants)\r\n\r\n```python\r\nfrom hidra import quick_start\r\n\r\n# Simple configuration with predefined tenants\r\nconfig = quick_start(\r\n db_config={\r\n \"db_driver\": \"postgresql\",\r\n \"db_host\": \"localhost\",\r\n \"db_port\": \"5432\",\r\n \"db_username\": \"postgres\",\r\n \"db_password\": \"password\",\r\n \"db_name\": \"multitenant\"\r\n },\r\n tenants={\r\n \"company1\": {\"plan\": \"premium\"},\r\n \"company2\": {\"plan\": \"basic\"}\r\n }\r\n)\r\n```\r\n\r\n### Minimal Configuration for FastAPI (No Predefined Tenants Required)\r\n\r\n```python\r\nfrom fastapi import FastAPI\r\nfrom hidra import create_hidra_app\r\n\r\n# Create FastAPI app with minimal configuration\r\napp = create_hidra_app(\r\n db_config={\r\n \"db_driver\": \"postgresql\",\r\n \"db_host\": \"localhost\",\r\n \"db_port\": \"5432\",\r\n \"db_username\": \"postgres\",\r\n \"db_password\": \"password\",\r\n \"db_name\": \"multitenant\"\r\n },\r\n # Tenants are loaded automatically when requested\r\n enable_auto_loading=True,\r\n auto_tenant_validation=True # Validate tenants exist before processing\r\n)\r\n\r\n# Protected endpoint - no need to define tenants in code\r\n@app.get(\"/data\")\r\nasync def get_data():\r\n from hidra import get_current_tenant_id\r\n tenant_id = get_current_tenant_id()\r\n return {\"tenant_id\": tenant_id, \"message\": \"Data retrieved\"}\r\n```\r\n\r\n### Enhanced Decorators\r\n\r\n```python\r\nfrom hidra import requires_tenant\r\n\r\n# Any tenant with access\r\n@app.get(\"/data\")\r\n@requires_tenant()\r\nasync def get_data():\r\n pass\r\n\r\n# Specific tenant only\r\n@app.get(\"/premium-data\")\r\n@requires_tenant(\"premium_company\")\r\nasync def get_premium_data():\r\n pass\r\n\r\n# Multiple tenants allowed\r\n@app.get(\"/shared-data\")\r\n@requires_tenant([\"company1\", \"company2\"])\r\nasync def get_shared_data():\r\n pass\r\n```\r\n\r\n### Simplified Database Access\r\n\r\n```python\r\nfrom hidra import HidraDB\r\n\r\n# Create database access\r\nhidra_db = HidraDB({\r\n \"db_driver\": \"postgresql\",\r\n \"db_host\": \"localhost\",\r\n \"db_port\": \"5432\",\r\n \"db_username\": \"postgres\",\r\n \"db_password\": \"password\",\r\n \"db_name\": \"multitenant\"\r\n})\r\n\r\n# Use with FastAPI Depends\r\n@app.get(\"/users\")\r\nasync def get_users(db: Session = Depends(hidra_db.get_tenant_db())):\r\n pass\r\n```\r\n\r\n### Basic Usage (with FastAPI)\r\n\r\nHere's a quick example of how to use Hidra with FastAPI.\r\n\r\n#### 1. Configure Tenants\r\n\r\nFirst, configure your tenants and the tenancy strategy.\r\n\r\n```python\r\n# main.py\r\nfrom hidra import tenant_context, TenancyStrategy, MultiTenantManager\r\n\r\n# Initialize the tenant manager\r\nmanager = MultiTenantManager()\r\n\r\n# Configure individual tenants\r\nmanager.configure_tenant(\"tenant1\", {\"db_connection\": \"postgresql://user:pass@host/db1\"})\r\nmanager.configure_tenant(\"tenant2\", {\"db_connection\": \"postgresql://user:pass@host/db2\"})\r\n\r\n# Set the default strategy\r\nmanager.set_default_strategy(TenancyStrategy.DATABASE_PER_TENANT)\r\n\r\n# Set the manager in the global tenant context\r\ntenant_context.tenant_manager = manager\r\n```\r\n\r\n#### 2. Add the Middleware\r\n\r\nThe middleware identifies the tenant from the request (e.g., using a header) and sets it in the context.\r\n\r\n```python\r\n# main.py\r\nfrom fastapi import FastAPI\r\nfrom hidra.middleware import TenantMiddleware\r\nfrom hidra.decorators import tenant_required\r\n\r\napp = FastAPI()\r\n\r\n# Add the middleware to your application\r\napp.add_middleware(TenantMiddleware)\r\n\r\n@app.get(\"/items\")\r\n@tenant_required\r\nasync def get_items():\r\n # The tenant is now available in the context\r\n current_tenant = tenant_context.require_tenant()\r\n \r\n # You can get tenant-specific configuration\r\n config = tenant_context.tenant_manager.get_tenant_config(current_tenant)\r\n \r\n return {\"tenant_id\": current_tenant, \"db_connection\": config.get(\"db_connection\")}\r\n```\r\n\r\n### 3. Run the Application\r\n\r\nTo run this example, you would make a request with the `X-Tenant-ID` header:\r\n\r\n```bash\r\ncurl -X GET \"http://127.0.0.1:8000/items\" -H \"X-Tenant-ID: tenant1\"\r\n```\r\n\r\nThe response would be:\r\n\r\n```json\r\n{\r\n \"tenant_id\": \"tenant1\",\r\n \"db_connection\": \"postgresql://user:pass@host/db1\"\r\n}\r\n```\r\n\r\n### Complete FastAPI Integration\r\n\r\n```python\r\nfrom fastapi import FastAPI, Depends\r\nfrom sqlalchemy.orm import Session\r\nfrom hidra import create_hidra_app, requires_tenant\r\n\r\n# Create FastAPI app with Hidra integration\r\napp = create_hidra_app(\r\n db_config={\r\n \"db_driver\": \"postgresql\",\r\n \"db_host\": \"localhost\",\r\n \"db_port\": \"5432\",\r\n \"db_username\": \"postgres\",\r\n \"db_password\": \"password\",\r\n \"db_name\": \"multitenant\"\r\n }\r\n)\r\n\r\n# Protected endpoint\r\n@app.get(\"/users\")\r\n@requires_tenant() # Requires tenant\r\nasync def get_users():\r\n from hidra import get_current_tenant_id\r\n tenant_id = get_current_tenant_id()\r\n return {\"tenant_id\": tenant_id, \"message\": \"Users retrieved\"}\r\n```\r\n\r\n## Diagnostic Tools\r\n\r\n```python\r\nfrom hidra import diagnose_setup, print_diagnosis\r\n\r\n# Get diagnosis as dictionary\r\ndiagnosis = diagnose_setup()\r\n\r\n# Print formatted diagnosis\r\nprint_diagnosis()\r\n```\r\n\r\n## Contributing\r\n\r\nContributions 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:\r\n\r\n1. Fork the repository.\r\n2. Create a new branch for your feature or bug fix.\r\n3. Write your code and add tests.\r\n4. Ensure all tests pass and the code is formatted with `black` and linted with `ruff`.\r\n5. Submit a pull request.\r\n\r\n## License\r\n\r\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\r\n",
"bugtrack_url": null,
"license": null,
"summary": "A lightweight multitenancy library for FastAPI applications",
"version": "0.2.0b1",
"project_urls": null,
"split_keywords": [
"multitenancy",
" fastapi",
" postgresql",
" sqlalchemy",
" tenancy"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "2078ee5f641047b6063d6fa6ba1358aa4f953d7421d465b403b186a7e2277a28",
"md5": "f5a8bf84f315d2271b421d499ecb73f5",
"sha256": "f6d0a238b75b520ea7ce9a0090b8a0c4ee4c07581dddfcd0a48faee9b1dcacc4"
},
"downloads": -1,
"filename": "hidra-0.2.0b1-py3-none-any.whl",
"has_sig": false,
"md5_digest": "f5a8bf84f315d2271b421d499ecb73f5",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 25210,
"upload_time": "2025-11-01T14:44:14",
"upload_time_iso_8601": "2025-11-01T14:44:14.659842Z",
"url": "https://files.pythonhosted.org/packages/20/78/ee5f641047b6063d6fa6ba1358aa4f953d7421d465b403b186a7e2277a28/hidra-0.2.0b1-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "901d957788317628edcd1ebd1049f7263012d98e2d90e0275cf5958859889788",
"md5": "72051f63406170cd8f5f21298bfb3bd0",
"sha256": "831c0e2d26bee941a92ad39a1d23e7648010216d81515f18b9ae892893f64a5d"
},
"downloads": -1,
"filename": "hidra-0.2.0b1.tar.gz",
"has_sig": false,
"md5_digest": "72051f63406170cd8f5f21298bfb3bd0",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 26426,
"upload_time": "2025-11-01T14:44:15",
"upload_time_iso_8601": "2025-11-01T14:44:15.472591Z",
"url": "https://files.pythonhosted.org/packages/90/1d/957788317628edcd1ebd1049f7263012d98e2d90e0275cf5958859889788/hidra-0.2.0b1.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-11-01 14:44:15",
"github": false,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"lcname": "hidra"
}