sqlalchemy-tenant-wiper


Namesqlalchemy-tenant-wiper JSON
Version 0.1.8 PyPI version JSON
download
home_pageNone
SummaryA flexible SQLAlchemy-based library for tenant data deletion in multi-tenant applications
upload_time2025-07-14 17:51:34
maintainerNone
docs_urlNone
authorYour Name
requires_python<4.0,>=3.8
licenseMIT
keywords sqlalchemy tenant multi-tenant database deletion cleanup
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # SQLAlchemy Tenant Wiper

A flexible SQLAlchemy-based library for tenant data deletion in multi-tenant applications. Supports custom dynamic tenant column filtering using lambda expressions, robust validations and handles complex relationship paths for tables without explicit tenant knowledge

[![PyPI version](https://badge.fury.io/py/sqlalchemy-tenant-wiper.svg)](https://badge.fury.io/py/sqlalchemy-tenant-wiper)
[![Python Support](https://img.shields.io/pypi/pyversions/sqlalchemy-tenant-wiper.svg)](https://pypi.org/project/sqlalchemy-tenant-wiper/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Dynamic Tenant Filtering**: Lambda expressions adapt to each table's columns
- **Relationship Path Support**: Declare relationships for tables that indirectly belong to a tenants (See below)
- **Composite Primary Key Support**: Properly handles tables with composite primary keys
- **Configuration-Time Validation**: Comprehensive runtime validation at table & column level
- **Two-Phase Deletion**: Safe deletion order respecting foreign key constraints
- **Dry Run Mode**: Preview what would be deleted before execution
- **Batched Deletions**: Efficient deletion in configurable batch sizes

## Installation

### Using pip

```bash
pip install sqlalchemy-tenant-wiper
```

### Using Poetry

```bash
poetry add sqlalchemy-tenant-wiper
```

## Quick Start

```python
from sqlalchemy_tenant_wiper import TenantWiperConfig, TenantDeleter
from uuid import UUID

# Configure your tenant deletion settings
config = TenantWiperConfig(
    base=YourSQLAlchemyBase,
    tenant_filters=[
        # A record is targeted for deletion if it matches *any* of these simple conditions you provide.
        lambda table: table.c.tenant_id == your_tenant_uuid,
        lambda table: table.c.org_id.in_(your_org_uuids)
    ],
    tenant_join_paths=[
        # tables with 'tenant_id' or 'org_id' column doesn't require declaration 
        # and explicitly added for removal

        # <table_name>__<from_key>=<to_key>__<to_table_name>...
        'products__id=product_id__order_items__order_id=id__orders'
    ],
    excluded_tables=['audit_logs', 'system_logs'],
    batch_size=500
)

# Create deleter and execute
deleter = TenantDeleter(config)
deleter.delete(session, dry_run=True)  # Preview first
deleter.delete(session, commit=True)   # Execute deletion
```

## Usage Examples

### Basic Tenant Filtering

```python
from sqlalchemy_tenant_wiper import TenantWiperConfig, TenantDeleter
from uuid import UUID

# Simple tenant filtering by tenant_id
tenant_uuid = UUID('12345678-1234-5678-9012-123456789abc')

config = TenantWiperConfig(
    base=Base,
    tenant_filters=[
        lambda table: table.c.tenant_id == str(tenant_uuid)
    ]
)

deleter = TenantDeleter(config)
deleter.delete(session, dry_run=True, commit=False)
```

### Multiple Tenant Filters

```python
# Multiple ways to identify tenant data
config = TenantWiperConfig(
    base=Base,
    tenant_filters=[
        lambda table: table.c.tenant_id == str(tenant_uuid),
        lambda table: table.c.organization_id.in_(org_uuids),
        lambda table: table.c.customer_code == 'ACME_CORP'
    ]
)
```

### Relationship Paths for Indirect Tables

```python
# Handle tables without direct tenant columns
config = TenantWiperConfig(
    base=Base,
    tenant_filters=[
        lambda table: table.c.tenant_id == str(tenant_uuid)
    ],
    tenant_join_paths=[
        # <table_name>__<from_key>=<to_key>__<to_table_name>...
        # Products -> OrderItems -> Orders (with tenant_id)
        'products__id=product_id__order_items__order_id=id__orders',
        
        # Audit logs -> Users (with tenant_id)  
        'audit_logs__user_id=id__users',
        
        # Complex multi-hop relationship
        'categories__id=category_id__products__id=product_id__order_items__order_id=id__orders'
    ]
)
```

### Configuration Validation

```python
# Validate configuration at startup/in tests
config = TenantWiperConfig(
    base=Base,
    tenant_filters=[...],
    tenant_join_paths=[...],
    validate_on_init=True  # Default: validates on creation
)

# Or validate explicitly
config = TenantWiperConfig(base=Base, validate_on_init=False)
config.validate()  # Call when ready
```

### Excluding Tables

```python
# Skip certain tables from deletion
config = TenantWiperConfig(
    base=Base,
    tenant_filters=[...],
    excluded_tables=[
        'audit_logs',
        'system_configs', 
        'migration_history'
    ]
)
```

## Configuration Reference

### TenantWiperConfig

| Parameter | Type | Description |
|-----------|------|-------------|
| `base` | SQLAlchemy Base | Your declarative base class |
| `tenant_filters` | `List[Callable[[Table], Any]]` | Lambda functions for tenant filtering |
| `tenant_join_paths` | `List[str]` | Relationship path strings for indirect tables |
| `excluded_tables` | `List[str]` | Table names to exclude from deletion |
| `validate_on_init` | `bool` | Whether to validate config on creation (default: True) |

### TenantDeleter.delete()

| Parameter | Type | Description |
|-----------|------|-------------|
| `session` | SQLAlchemy Session | Database session for operations |
| `dry_run` | `bool` | If True, only report what would be deleted |
| `commit` | `bool` | If True, commit the transaction |

## Relationship Path Syntax

Relationship paths use double underscore (`__`) separators:

```
table1__from_key=to_key__table2__from_key2=to_key2__table3
```

Example:
```
products__id=product_id__order_items__order_id=id__orders
```

This creates joins:
1. `products.id = order_items.product_id`
2. `order_items.order_id = orders.id`

The final table (`orders`) must have tenant filter columns.

## Requirements

- Python 3.8+
- SQLAlchemy 1.4+

## Development

### Setup

```bash
git clone https://github.com/yourusername/sqlalchemy-tenant-wiper.git
cd sqlalchemy-tenant-wiper
poetry install
```

### Testing

```bash
poetry run pytest
```

## License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.


## TODO
[ ] Make enforcing listing of all tables as optional 

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "sqlalchemy-tenant-wiper",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.8",
    "maintainer_email": null,
    "keywords": "sqlalchemy, tenant, multi-tenant, database, deletion, cleanup",
    "author": "Your Name",
    "author_email": "your.email@example.com",
    "download_url": "https://files.pythonhosted.org/packages/69/38/6ee59f97693c6ecd2bea0dc90b839c1a02829e9c3116ef5ff1ca1cf26fe6/sqlalchemy_tenant_wiper-0.1.8.tar.gz",
    "platform": null,
    "description": "# SQLAlchemy Tenant Wiper\n\nA flexible SQLAlchemy-based library for tenant data deletion in multi-tenant applications. Supports custom dynamic tenant column filtering using lambda expressions, robust validations and handles complex relationship paths for tables without explicit tenant knowledge\n\n[![PyPI version](https://badge.fury.io/py/sqlalchemy-tenant-wiper.svg)](https://badge.fury.io/py/sqlalchemy-tenant-wiper)\n[![Python Support](https://img.shields.io/pypi/pyversions/sqlalchemy-tenant-wiper.svg)](https://pypi.org/project/sqlalchemy-tenant-wiper/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Features\n\n- **Dynamic Tenant Filtering**: Lambda expressions adapt to each table's columns\n- **Relationship Path Support**: Declare relationships for tables that indirectly belong to a tenants (See below)\n- **Composite Primary Key Support**: Properly handles tables with composite primary keys\n- **Configuration-Time Validation**: Comprehensive runtime validation at table & column level\n- **Two-Phase Deletion**: Safe deletion order respecting foreign key constraints\n- **Dry Run Mode**: Preview what would be deleted before execution\n- **Batched Deletions**: Efficient deletion in configurable batch sizes\n\n## Installation\n\n### Using pip\n\n```bash\npip install sqlalchemy-tenant-wiper\n```\n\n### Using Poetry\n\n```bash\npoetry add sqlalchemy-tenant-wiper\n```\n\n## Quick Start\n\n```python\nfrom sqlalchemy_tenant_wiper import TenantWiperConfig, TenantDeleter\nfrom uuid import UUID\n\n# Configure your tenant deletion settings\nconfig = TenantWiperConfig(\n    base=YourSQLAlchemyBase,\n    tenant_filters=[\n        # A record is targeted for deletion if it matches *any* of these simple conditions you provide.\n        lambda table: table.c.tenant_id == your_tenant_uuid,\n        lambda table: table.c.org_id.in_(your_org_uuids)\n    ],\n    tenant_join_paths=[\n        # tables with 'tenant_id' or 'org_id' column doesn't require declaration \n        # and explicitly added for removal\n\n        # <table_name>__<from_key>=<to_key>__<to_table_name>...\n        'products__id=product_id__order_items__order_id=id__orders'\n    ],\n    excluded_tables=['audit_logs', 'system_logs'],\n    batch_size=500\n)\n\n# Create deleter and execute\ndeleter = TenantDeleter(config)\ndeleter.delete(session, dry_run=True)  # Preview first\ndeleter.delete(session, commit=True)   # Execute deletion\n```\n\n## Usage Examples\n\n### Basic Tenant Filtering\n\n```python\nfrom sqlalchemy_tenant_wiper import TenantWiperConfig, TenantDeleter\nfrom uuid import UUID\n\n# Simple tenant filtering by tenant_id\ntenant_uuid = UUID('12345678-1234-5678-9012-123456789abc')\n\nconfig = TenantWiperConfig(\n    base=Base,\n    tenant_filters=[\n        lambda table: table.c.tenant_id == str(tenant_uuid)\n    ]\n)\n\ndeleter = TenantDeleter(config)\ndeleter.delete(session, dry_run=True, commit=False)\n```\n\n### Multiple Tenant Filters\n\n```python\n# Multiple ways to identify tenant data\nconfig = TenantWiperConfig(\n    base=Base,\n    tenant_filters=[\n        lambda table: table.c.tenant_id == str(tenant_uuid),\n        lambda table: table.c.organization_id.in_(org_uuids),\n        lambda table: table.c.customer_code == 'ACME_CORP'\n    ]\n)\n```\n\n### Relationship Paths for Indirect Tables\n\n```python\n# Handle tables without direct tenant columns\nconfig = TenantWiperConfig(\n    base=Base,\n    tenant_filters=[\n        lambda table: table.c.tenant_id == str(tenant_uuid)\n    ],\n    tenant_join_paths=[\n        # <table_name>__<from_key>=<to_key>__<to_table_name>...\n        # Products -> OrderItems -> Orders (with tenant_id)\n        'products__id=product_id__order_items__order_id=id__orders',\n        \n        # Audit logs -> Users (with tenant_id)  \n        'audit_logs__user_id=id__users',\n        \n        # Complex multi-hop relationship\n        'categories__id=category_id__products__id=product_id__order_items__order_id=id__orders'\n    ]\n)\n```\n\n### Configuration Validation\n\n```python\n# Validate configuration at startup/in tests\nconfig = TenantWiperConfig(\n    base=Base,\n    tenant_filters=[...],\n    tenant_join_paths=[...],\n    validate_on_init=True  # Default: validates on creation\n)\n\n# Or validate explicitly\nconfig = TenantWiperConfig(base=Base, validate_on_init=False)\nconfig.validate()  # Call when ready\n```\n\n### Excluding Tables\n\n```python\n# Skip certain tables from deletion\nconfig = TenantWiperConfig(\n    base=Base,\n    tenant_filters=[...],\n    excluded_tables=[\n        'audit_logs',\n        'system_configs', \n        'migration_history'\n    ]\n)\n```\n\n## Configuration Reference\n\n### TenantWiperConfig\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `base` | SQLAlchemy Base | Your declarative base class |\n| `tenant_filters` | `List[Callable[[Table], Any]]` | Lambda functions for tenant filtering |\n| `tenant_join_paths` | `List[str]` | Relationship path strings for indirect tables |\n| `excluded_tables` | `List[str]` | Table names to exclude from deletion |\n| `validate_on_init` | `bool` | Whether to validate config on creation (default: True) |\n\n### TenantDeleter.delete()\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `session` | SQLAlchemy Session | Database session for operations |\n| `dry_run` | `bool` | If True, only report what would be deleted |\n| `commit` | `bool` | If True, commit the transaction |\n\n## Relationship Path Syntax\n\nRelationship paths use double underscore (`__`) separators:\n\n```\ntable1__from_key=to_key__table2__from_key2=to_key2__table3\n```\n\nExample:\n```\nproducts__id=product_id__order_items__order_id=id__orders\n```\n\nThis creates joins:\n1. `products.id = order_items.product_id`\n2. `order_items.order_id = orders.id`\n\nThe final table (`orders`) must have tenant filter columns.\n\n## Requirements\n\n- Python 3.8+\n- SQLAlchemy 1.4+\n\n## Development\n\n### Setup\n\n```bash\ngit clone https://github.com/yourusername/sqlalchemy-tenant-wiper.git\ncd sqlalchemy-tenant-wiper\npoetry install\n```\n\n### Testing\n\n```bash\npoetry run pytest\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n\n## TODO\n[ ] Make enforcing listing of all tables as optional \n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A flexible SQLAlchemy-based library for tenant data deletion in multi-tenant applications",
    "version": "0.1.8",
    "project_urls": {
        "Documentation": "https://github.com/timabilov/sqlalchemy-tenant-wiper#readme",
        "Homepage": "https://github.com/timabilov/sqlalchemy-tenant-wiper",
        "Repository": "https://github.com/timabilov/sqlalchemy-tenant-wiper"
    },
    "split_keywords": [
        "sqlalchemy",
        " tenant",
        " multi-tenant",
        " database",
        " deletion",
        " cleanup"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "6b7219a94d8bd32c728019ae42675123a43aafcdf10eda568aedff4a729dec30",
                "md5": "3ec790eba9c88b49e07cea4260c4cafd",
                "sha256": "70754c5ab8f3a629920bff4d030f49eb3ec1068c208c377aea75c08ded7dd723"
            },
            "downloads": -1,
            "filename": "sqlalchemy_tenant_wiper-0.1.8-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "3ec790eba9c88b49e07cea4260c4cafd",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.8",
            "size": 11054,
            "upload_time": "2025-07-14T17:51:33",
            "upload_time_iso_8601": "2025-07-14T17:51:33.432079Z",
            "url": "https://files.pythonhosted.org/packages/6b/72/19a94d8bd32c728019ae42675123a43aafcdf10eda568aedff4a729dec30/sqlalchemy_tenant_wiper-0.1.8-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "69386ee59f97693c6ecd2bea0dc90b839c1a02829e9c3116ef5ff1ca1cf26fe6",
                "md5": "b63892e89e3f49fc47f9fead432308de",
                "sha256": "ea6729797d3834fb4c35ff7288a8166be60bf237930af0c6ef5727bf1af22fbc"
            },
            "downloads": -1,
            "filename": "sqlalchemy_tenant_wiper-0.1.8.tar.gz",
            "has_sig": false,
            "md5_digest": "b63892e89e3f49fc47f9fead432308de",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.8",
            "size": 12405,
            "upload_time": "2025-07-14T17:51:34",
            "upload_time_iso_8601": "2025-07-14T17:51:34.687069Z",
            "url": "https://files.pythonhosted.org/packages/69/38/6ee59f97693c6ecd2bea0dc90b839c1a02829e9c3116ef5ff1ca1cf26fe6/sqlalchemy_tenant_wiper-0.1.8.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-14 17:51:34",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "timabilov",
    "github_project": "sqlalchemy-tenant-wiper#readme",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "sqlalchemy-tenant-wiper"
}
        
Elapsed time: 0.66978s