Files
Inventarsystem/Legal/MULTITENANT_PYTHON_API.md
T
Aiirondev_dev 0c27d7ac86 feat: Add Excel, PDF export, and user generation modules
- Implemented `excel_export.py` for generating library item exports in Excel format.
- Created `pdf_export.py` for generating audit reports compliant with DIN 5008 standards, including detailed event tables and signature blocks.
- Developed `generate_user.py` for interactive user creation with validation for usernames and passwords.
- Introduced `module_registry.py` for managing module states and path matching.
- Added a basic `__init__.py` in the `terminplaner` module for initialization.
2026-05-20 15:30:23 +02:00

129 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Multi-Tenant Python Management API
This document explains how the multi-tenant architecture isolates data within Python, what the return values are, and how developers can build internal administrative scripts using native Python instead of the Docker CLI.
## 1. Architectural Concept
In the system, each "tenant" is essentially a dedicated MongoDB database identified by a dynamically generated string based on a subdomain or header (`inventar_<tenant_id>`).
App containers share a connection pool using `pymongo.MongoClient`, and requests are routed to specific databases dynamically based on the current Flask `g.tenant_context`.
All MongoDB administrative tasks (creating tenants, restarting apps, fetching lists) are done via standard MongoDB Python drivers because the core multi-tenancy happens at the **database level**.
## 2. Managing Tenants via Python
If you want to perform multi-tenant administrative operations without traversing through `manage-tenant.sh`, you can execute native Python scripts connecting to the system's `MongoClient`.
### Basic Connection Boilerplate
Whenever automating an administrative task in Python, you simply need to connect to MongoDB using the properties defined in `settings.py`.
```python
import sys
import os
# Append Web folder so we can access configuration
sys.path.insert(0, '/app/Web')
import settings
from pymongo import MongoClient
# Establish connection pooling
client = MongoClient(settings.MONGODB_HOST, int(settings.MONGODB_PORT))
```
### A. Adding a New Tenant (Database Initialization)
A new tenant database isnt provisioned until the first actual data insert happens. We trigger this manually by creating an `admin` user for them.
**Operation:**
```python
def create_tenant(tenant_id, admin_password="hashed_password_here"):
db_name = f"{settings.MONGODB_DB}_{tenant_id}"
db = client[db_name]
# MongoDB creates the DB automatically on first insert
result = db.users.insert_one({
'username': 'admin',
'password': admin_password,
'role': 'admin'
})
return result.inserted_id # Returns the BSON ObjectId of the new user
```
### B. List Active Tenants
To find out how many isolated tenants have active databases, you query the raw `MongoClient` for all databases and search for your configured MongoDB prefix (default: `inventar_`).
**Operation:**
```python
def list_tenants():
prefix = f"{settings.MONGODB_DB}_"
# Returns a Python list of string database names
all_dbs = client.list_database_names()
# Filter and strip the prefix to return just the tenant_ids
active_tenants = [d.replace(prefix, "") for d in all_dbs if d.startswith(prefix)]
return active_tenants # e.g., ['schule1', 'schule2', 'test']
```
### C. Soft-Restarting a Tenant (Invalidating Sessions)
"Restarting" a single tenant means signing out all of their users and forcing an application refresh. Because Session data is coupled to the tenant database, dropping their `sessions` collection achieves an instant sign-out.
**Operation:**
```python
def restart_tenant(tenant_id):
db_name = f"{settings.MONGODB_DB}_{tenant_id}"
db = client[db_name]
# Drops the collection. All active user cookies immediately become invalid.
result = db.sessions.drop()
return result # Returns None. Raises PyMongoError if connection fails.
```
### D. Removing a Tenant Completely (Wipe Data)
If a tenant is removed from the service or their lease expires, you can permanently obliterate their data container footprint.
**Operation:**
```python
def remove_tenant(tenant_id):
db_name = f"{settings.MONGODB_DB}_{tenant_id}"
# Erases the isolated database. Can't be undone.
client.drop_database(db_name)
return True # Returns True. Raises PyMongoError if connection fails.
```
## 3. Resolving Context Inside Flask (app.py)
If you are building custom application endpoints inside `Web/app.py`, you shouldn't use the direct MongoDB `client` manually. Instead, you rely on the built-in Flask context manager (`Web/tenant.py`) to give you the correct isolated scope.
### The `get_tenant_db()` function
Every route must use `get_tenant_db(client)` to ensure users can only ever access their own school/domain's database.
```python
from pymongo import MongoClient
import settings
from tenant import get_tenant_db
# Example Route
@app.route('/api/items')
def get_items():
# 1. Establish/reuse pooling connection
client = MongoClient(settings.MONGODB_HOST, settings.MONGODB_PORT)
# 2. Get the dynamically routed DB for THIS user
# (Based on Nginx Subdomain or X-Tenant-Id header)
db = get_tenant_db(client)
# 3. Runs query solely on `inventar_schule1.items`
items = list(db.items.find())
return items # List of BSON Dictionaries
```
**What it returns internally:**
The `get_tenant_db` function queries `g.tenant_context` inside Flask, calculates the database name from the subdomain, and returns a live `pymongo.database.Database` object.
This ensures that scaling is extremely cheap on resources because 1 Application Container connects to 100 separate Tenant Databases using just 1 shared `MongoClient` pool.