transmutation


Nametransmutation JSON
Version 1.0.0 PyPI version JSON
download
home_pageNone
SummaryComprehensive database migration and schema alteration tool using SQLAlchemy and Alembic
upload_time2025-10-19 14:49:36
maintainerNone
docs_urlNone
authorOdos Matthews
requires_python>=3.8
licenseMIT
keywords database migration schema alembic sqlalchemy
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Transmutation

A comprehensive database migration and schema alteration tool built on SQLAlchemy and Alembic.

[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Comprehensive Operations**: Column, table, index, and constraint operations
- **Reversible Migrations**: Built-in migration system with automatic rollback
- **Type Safety**: Full type hints for Python 3.8+
- **Database Support**: SQLite, PostgreSQL, MySQL, and more via SQLAlchemy
- **Simple API**: Easy-to-use functions for common operations
- **Advanced Features**: Batch operations, transaction management, custom SQL
- **Validation**: Automatic validation of operations before execution
- **Error Handling**: Comprehensive exception hierarchy for precise error handling

## Installation

```bash
pip install transmutation
```

## Quick Start

```python
from sqlalchemy import create_engine
import transmutation as tm

# Create engine
engine = create_engine('sqlite:///mydb.db')

# Direct operations
tm.add_column('users', 'email', str, engine, nullable=False)
tm.create_index('idx_email', 'users', 'email', engine, unique=True)
tm.rename_column('users', 'name', 'full_name', engine)

# Using the migration system for rollback capability
migration = tm.Migration(engine)
migration.add_column('users', 'phone', str)
migration.create_unique_constraint('uq_email', 'users', 'email')
migration.upgrade()  # Apply changes

# If something goes wrong, rollback
migration.downgrade()
```

## Core Operations

### Column Operations

#### Add Column

```python
import transmutation as tm

# Basic column addition
tm.add_column('users', 'email', str, engine)

# With constraints
tm.add_column('users', 'age', int, engine, nullable=False, default=0)

# With server default
tm.add_column('users', 'created_at', 'datetime', engine, 
              server_default='CURRENT_TIMESTAMP')
```

#### Rename Column

```python
tm.rename_column('users', 'name', 'full_name', engine)
```

#### Drop Column

```python
tm.drop_column('users', 'middle_name', engine)
```

#### Alter Column

```python
# Change column properties
tm.alter_column('users', 'email', engine, nullable=False)

# Change type
tm.alter_column('users', 'age', engine, type_=int)

# Rename and modify
tm.alter_column('users', 'name', engine, 
                new_column_name='full_name', nullable=False)
```

### Table Operations

#### Create Table

```python
from sqlalchemy import Column, Integer, String

columns = [
    Column('id', Integer, primary_key=True),
    Column('name', String(50), nullable=False),
    Column('email', String(100), unique=True)
]

tm.create_table('users', columns, engine)
```

#### Rename Table

```python
tm.rename_table('old_users', 'users', engine)
```

#### Copy Table

```python
from fullmetalalchemy.features import get_table

source_table = get_table('users', engine)
tm.copy_table(source_table, 'users_backup', engine)

# Copy structure only (no data)
tm.copy_table(source_table, 'users_template', engine, copy_data=False)
```

#### Drop Table

```python
tm.drop_table('old_table', engine, if_exists=True)

# With cascade
tm.drop_table('parent_table', engine, cascade=True)
```

#### Truncate Table

```python
tm.truncate_table('temp_data', engine)
```

#### Create Table from SELECT

```python
from sqlalchemy import select
from fullmetalalchemy.features import get_table

users = get_table('users', engine)
query = select(users).where(users.c.active == True)

tm.create_table_as('active_users', query, engine)
```

### Index Operations

#### Create Index

```python
# Single column index
tm.create_index('idx_email', 'users', 'email', engine)

# Composite index
tm.create_index('idx_name', 'users', ['last_name', 'first_name'], engine)

# Unique index
tm.create_unique_index('idx_unique_email', 'users', 'email', engine)

# Or using create_index with unique=True
tm.create_index('idx_email', 'users', 'email', engine, unique=True)
```

#### Drop Index

```python
tm.drop_index('idx_email', 'users', engine)

# Skip if doesn't exist
tm.drop_index('idx_email', 'users', engine, if_exists=True)
```

### Constraint Operations

#### Foreign Keys

```python
# Create foreign key
tm.create_foreign_key(
    'fk_user_address',
    'addresses',      # source table
    'user_id',        # source column
    'users',          # referenced table
    'id',             # referenced column
    engine,
    ondelete='CASCADE'
)

# Composite foreign key
tm.create_foreign_key(
    'fk_user_role',
    'user_roles',
    ['user_id', 'role_id'],
    'roles',
    ['user_id', 'id'],
    engine
)
```

#### Unique Constraints

```python
# Single column
tm.create_unique_constraint('uq_email', 'users', 'email', engine)

# Multiple columns
tm.create_unique_constraint('uq_user_role', 'user_roles', 
                           ['user_id', 'role_id'], engine)
```

#### Check Constraints

```python
tm.create_check_constraint('ck_age_positive', 'users', 'age > 0', engine)

tm.create_check_constraint('ck_email_format', 'users', 
                          "email LIKE '%@%'", engine)
```

#### Primary Keys

```python
# Create primary key (on table without one)
tm.create_primary_key('users', 'id', engine)

# Composite primary key
tm.create_primary_keys('user_roles', ['user_id', 'role_id'], engine)

# Replace existing primary key
from fullmetalalchemy.features import get_table
table = get_table('users', engine)
tm.replace_primary_key(table, 'uuid', engine)

# Replace with composite key
tm.replace_primary_keys(table, ['tenant_id', 'user_id'], engine)
```

#### Drop Constraints

```python
# Drop any constraint by name and type
tm.drop_constraint('fk_user_address', 'addresses', engine, 
                  type_='foreignkey')

tm.drop_constraint('uq_email', 'users', engine, type_='unique')

tm.drop_constraint('ck_age_positive', 'users', engine, type_='check')
```

## Migration System

The Migration class provides a powerful way to manage schema changes with rollback capability.

### Basic Usage

```python
from sqlalchemy import create_engine
import transmutation as tm

engine = create_engine('sqlite:///mydb.db')
migration = tm.Migration(engine)

# Queue operations
migration.add_column('users', 'email', str, nullable=False)
migration.create_index('idx_email', 'users', 'email', unique=True)
migration.add_column('users', 'phone', str)

# Apply all changes
migration.upgrade()

# If something went wrong, rollback
migration.downgrade()
```

### Batch Operations with Auto-Rollback

```python
migration = tm.Migration(engine)

try:
    with migration.batch_operations():
        migration.add_column('users', 'email', str)
        migration.create_index('idx_email', 'users', 'email')
        migration.create_foreign_key('fk_user', 'posts', 'user_id', 
                                    'users', 'id')
    # Changes are automatically applied on successful exit
except tm.MigrationError as e:
    print(f"Migration failed and was rolled back: {e}")
```

### All Migration Methods

```python
migration = tm.Migration(engine)

# Column operations
migration.add_column('table', 'col', str)
migration.drop_column('table', 'col')
migration.rename_column('table', 'old', 'new')
migration.alter_column('table', 'col', nullable=False)

# Table operations
migration.create_table('table', columns)
migration.drop_table('table')
migration.rename_table('old', 'new')
migration.copy_table(table, 'new_table')

# Index operations
migration.create_index('idx', 'table', 'col')
migration.drop_index('idx', 'table', 'col')
migration.create_unique_index('idx', 'table', 'col')

# Constraint operations
migration.create_foreign_key('fk', 'source', 'col', 'ref', 'ref_col')
migration.create_unique_constraint('uq', 'table', 'col')
migration.create_check_constraint('ck', 'table', 'condition')

# Execute custom SQL
migration.execute_sql("UPDATE users SET active = 1")

# Apply changes
migration.upgrade()

# Check status
print(f"Pending: {migration.pending_operations()}")
print(f"Applied: {migration.applied_operations()}")

# Rollback
migration.downgrade()

# Clear queue without executing
migration.clear()
```

### Transaction Management

```python
# Auto-rollback enabled by default
migration = tm.Migration(engine, auto_transaction=True)

# Disable auto-rollback for manual control
migration = tm.Migration(engine, auto_transaction=False)
```

## Advanced Usage

### Schema Support

All operations support schema specification for databases that use schemas (PostgreSQL):

```python
# PostgreSQL schema support
tm.add_column('users', 'email', str, engine, schema='public')
tm.create_index('idx_email', 'users', 'email', engine, schema='public')
```

### Custom SQL Execution

```python
migration = tm.Migration(engine)

# Execute custom SQL
result = migration.execute_sql(
    "UPDATE users SET status = 'active' WHERE last_login > :date",
    date='2024-01-01'
)
```

### Validation

Transmutation automatically validates operations before execution:

```python
try:
    tm.add_column('nonexistent_table', 'col', str, engine)
except tm.ValidationError as e:
    print(f"Validation failed: {e}")
```

### Working with Alteration Classes

For advanced use cases, you can work directly with Alteration classes:

```python
from transmutation.alteration import AddColumn, CreateIndex

# Create alterations manually
add_col = AddColumn('users', 'email', str, engine)
create_idx = CreateIndex('idx_email', 'users', 'email', engine, unique=True)

# Apply
table = add_col.upgrade()
table = create_idx.upgrade()

# Rollback
create_idx.downgrade()
add_col.downgrade()
```

## Error Handling

Transmutation provides specific exceptions for different error types:

```python
import transmutation as tm

try:
    tm.add_column('users', 'email', str, engine)
except tm.ColumnError as e:
    print(f"Column operation failed: {e}")
except tm.ValidationError as e:
    print(f"Validation failed: {e}")
except tm.TransmutationError as e:
    print(f"General error: {e}")

# Migration-specific errors
try:
    migration.upgrade()
except tm.MigrationError as e:
    print(f"Migration failed: {e}")
except tm.RollbackError as e:
    print(f"Rollback failed: {e}")
```

### Available Exceptions

- `TransmutationError` - Base exception for all transmutation errors
- `MigrationError` - Migration operation failed
- `ColumnError` - Column operation failed
- `TableError` - Table operation failed
- `ConstraintError` - Constraint operation failed
- `IndexError` - Index operation failed
- `ValidationError` - Validation failed before operation
- `RollbackError` - Rollback operation failed

## Database Support

Transmutation works with any database supported by SQLAlchemy and Alembic:

- **SQLite**: Full support with no external dependencies
- **PostgreSQL**: Full support with schema capabilities
- **MySQL/MariaDB**: Full support
- **Oracle**: Supported via SQLAlchemy
- **Microsoft SQL Server**: Supported via SQLAlchemy

### SQLite

SQLite is the default and requires no additional setup. Foreign keys can be enabled:

```python
from sqlalchemy import create_engine, event

engine = create_engine('sqlite:///mydb.db')

@event.listens_for(engine, "connect")
def set_sqlite_pragma(dbapi_conn, connection_record):
    cursor = dbapi_conn.cursor()
    cursor.execute("PRAGMA foreign_keys=ON")
    cursor.close()
```

### PostgreSQL

Full schema support:

```python
tm.create_table('users', columns, engine, schema='myschema')
```

## API Organization

Transmutation provides a well-organized API with operations grouped by category:

```python
import transmutation as tm

# Or import specific modules
from transmutation import column, table, index, constraint
from transmutation import Migration

# Column operations
from transmutation.column import add_column, rename_column, drop_column, alter_column

# Table operations  
from transmutation.table import create_table, drop_table, rename_table, copy_table

# Index operations
from transmutation.index import create_index, drop_index, create_unique_index

# Constraint operations
from transmutation.constraint import (
    create_foreign_key,
    create_unique_constraint,
    create_check_constraint,
    drop_constraint
)
```

## Examples

### Complete Migration Example

```python
from sqlalchemy import create_engine, Column, Integer, String
import transmutation as tm

# Setup
engine = create_engine('sqlite:///myapp.db')

# Create a new table
columns = [
    Column('id', Integer, primary_key=True),
    Column('username', String(50), nullable=False),
    Column('email', String(100))
]
tm.create_table('users', columns, engine)

# Add indexes
tm.create_unique_index('idx_username', 'users', 'username', engine)
tm.create_index('idx_email', 'users', 'email', engine)

# Add constraints
tm.create_check_constraint('ck_username_length', 'users', 
                          'LENGTH(username) >= 3', engine)

# Modify existing table
tm.add_column('users', 'created_at', 'datetime', engine,
             server_default='CURRENT_TIMESTAMP')
tm.alter_column('users', 'email', engine, nullable=False)
```

### Using Migration for Complex Changes

```python
from sqlalchemy import create_engine
import transmutation as tm

engine = create_engine('sqlite:///myapp.db')
migration = tm.Migration(engine)

# Queue multiple related changes
migration.add_column('users', 'status', str, default='active')
migration.create_index('idx_status', 'users', 'status')
migration.add_column('users', 'last_login', 'datetime')

# Create posts table with foreign key to users
from sqlalchemy import Column, Integer, String, Text
posts_columns = [
    Column('id', Integer, primary_key=True),
    Column('user_id', Integer, nullable=False),
    Column('title', String(200), nullable=False),
    Column('content', Text)
]
migration.create_table('posts', posts_columns)
migration.create_foreign_key('fk_post_user', 'posts', 'user_id', 
                            'users', 'id', ondelete='CASCADE')
migration.create_index('idx_user_posts', 'posts', 'user_id')

# Apply all changes atomically
migration.upgrade()

# If anything goes wrong:
# migration.downgrade()
```

### Data Migration with Custom SQL

```python
migration = tm.Migration(engine)

# Add new column
migration.add_column('users', 'full_name', str)

# Populate it with data using custom SQL
migration.execute_sql(
    "UPDATE users SET full_name = name || ' ' || surname WHERE surname IS NOT NULL"
)
migration.execute_sql(
    "UPDATE users SET full_name = name WHERE surname IS NULL"
)

# Drop old columns
migration.drop_column('users', 'name')
migration.drop_column('users', 'surname')

# Apply all changes
migration.upgrade()
```

## Development

### Setup

```bash
# Clone repository
git clone https://github.com/odosmatthews/transmutation.git
cd transmutation

# Install development dependencies
pip install -e ".[dev]"
```

### Running Tests

```bash
# All tests
pytest

# Specific test file
pytest tests/test_alter.py

# With coverage report
pytest --cov=transmutation --cov-report=html

# Open coverage report
open htmlcov/index.html
```

### Code Quality

```bash
# Run type checking
mypy src

# Run linter
ruff check src tests

# Auto-fix linting issues
ruff check --fix src tests

# Format code
black src tests

# Sort imports
isort src tests
```

## Best Practices

### 1. Use Migration for Complex Changes

For multiple related changes, use the Migration system:

```python
# Good - atomic, reversible
migration = tm.Migration(engine)
migration.add_column('users', 'email', str)
migration.create_index('idx_email', 'users', 'email')
migration.upgrade()

# Less ideal - individual operations
tm.add_column('users', 'email', str, engine)
tm.create_index('idx_email', 'users', 'email', engine)
```

### 2. Always Handle Errors

```python
try:
    migration.upgrade()
except tm.MigrationError as e:
    logger.error(f"Migration failed: {e}")
    migration.downgrade()
    raise
```

### 3. Use Validation

Transmutation validates operations automatically, but you can also validate manually:

```python
from transmutation.utils import validate_table_exists, validate_column_exists

validate_table_exists('users', engine)
validate_column_exists('users', 'email', engine)
```

### 4. Test Your Migrations

Always test migrations in a development environment first:

```python
def test_migration():
    # Setup test database
    engine = create_engine('sqlite:///:memory:')
    
    # Run migration
    migration = tm.Migration(engine)
    migration.add_column('users', 'new_col', str)
    migration.upgrade()
    
    # Verify
    from fullmetalalchemy.features import get_table
    table = get_table('users', engine)
    assert 'new_col' in table.columns
    
    # Test rollback
    migration.downgrade()
    table = get_table('users', engine)
    assert 'new_col' not in table.columns
```

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request

### Development Guidelines

- Add tests for new features
- Maintain test coverage above 65%
- Follow type hints for all functions
- Add comprehensive docstrings
- Run linters before committing

## License

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

## Acknowledgments

Built on top of:
- [SQLAlchemy](https://www.sqlalchemy.org/) - The Python SQL toolkit
- [Alembic](https://alembic.sqlalchemy.org/) - Database migration tool
- [fullmetalalchemy](https://github.com/kajuberdut/fullmetalalchemy) - SQLAlchemy utilities

## Support

For issues, questions, or contributions, please visit the GitHub repository.

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "transmutation",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "database, migration, schema, alembic, sqlalchemy",
    "author": "Odos Matthews",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/d6/b8/02955ad0b4b186af6dac2afa9dbead86225ad43889649781c3f651649b6f/transmutation-1.0.0.tar.gz",
    "platform": "unix",
    "description": "# Transmutation\n\nA comprehensive database migration and schema alteration tool built on SQLAlchemy and Alembic.\n\n[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n## Features\n\n- **Comprehensive Operations**: Column, table, index, and constraint operations\n- **Reversible Migrations**: Built-in migration system with automatic rollback\n- **Type Safety**: Full type hints for Python 3.8+\n- **Database Support**: SQLite, PostgreSQL, MySQL, and more via SQLAlchemy\n- **Simple API**: Easy-to-use functions for common operations\n- **Advanced Features**: Batch operations, transaction management, custom SQL\n- **Validation**: Automatic validation of operations before execution\n- **Error Handling**: Comprehensive exception hierarchy for precise error handling\n\n## Installation\n\n```bash\npip install transmutation\n```\n\n## Quick Start\n\n```python\nfrom sqlalchemy import create_engine\nimport transmutation as tm\n\n# Create engine\nengine = create_engine('sqlite:///mydb.db')\n\n# Direct operations\ntm.add_column('users', 'email', str, engine, nullable=False)\ntm.create_index('idx_email', 'users', 'email', engine, unique=True)\ntm.rename_column('users', 'name', 'full_name', engine)\n\n# Using the migration system for rollback capability\nmigration = tm.Migration(engine)\nmigration.add_column('users', 'phone', str)\nmigration.create_unique_constraint('uq_email', 'users', 'email')\nmigration.upgrade()  # Apply changes\n\n# If something goes wrong, rollback\nmigration.downgrade()\n```\n\n## Core Operations\n\n### Column Operations\n\n#### Add Column\n\n```python\nimport transmutation as tm\n\n# Basic column addition\ntm.add_column('users', 'email', str, engine)\n\n# With constraints\ntm.add_column('users', 'age', int, engine, nullable=False, default=0)\n\n# With server default\ntm.add_column('users', 'created_at', 'datetime', engine, \n              server_default='CURRENT_TIMESTAMP')\n```\n\n#### Rename Column\n\n```python\ntm.rename_column('users', 'name', 'full_name', engine)\n```\n\n#### Drop Column\n\n```python\ntm.drop_column('users', 'middle_name', engine)\n```\n\n#### Alter Column\n\n```python\n# Change column properties\ntm.alter_column('users', 'email', engine, nullable=False)\n\n# Change type\ntm.alter_column('users', 'age', engine, type_=int)\n\n# Rename and modify\ntm.alter_column('users', 'name', engine, \n                new_column_name='full_name', nullable=False)\n```\n\n### Table Operations\n\n#### Create Table\n\n```python\nfrom sqlalchemy import Column, Integer, String\n\ncolumns = [\n    Column('id', Integer, primary_key=True),\n    Column('name', String(50), nullable=False),\n    Column('email', String(100), unique=True)\n]\n\ntm.create_table('users', columns, engine)\n```\n\n#### Rename Table\n\n```python\ntm.rename_table('old_users', 'users', engine)\n```\n\n#### Copy Table\n\n```python\nfrom fullmetalalchemy.features import get_table\n\nsource_table = get_table('users', engine)\ntm.copy_table(source_table, 'users_backup', engine)\n\n# Copy structure only (no data)\ntm.copy_table(source_table, 'users_template', engine, copy_data=False)\n```\n\n#### Drop Table\n\n```python\ntm.drop_table('old_table', engine, if_exists=True)\n\n# With cascade\ntm.drop_table('parent_table', engine, cascade=True)\n```\n\n#### Truncate Table\n\n```python\ntm.truncate_table('temp_data', engine)\n```\n\n#### Create Table from SELECT\n\n```python\nfrom sqlalchemy import select\nfrom fullmetalalchemy.features import get_table\n\nusers = get_table('users', engine)\nquery = select(users).where(users.c.active == True)\n\ntm.create_table_as('active_users', query, engine)\n```\n\n### Index Operations\n\n#### Create Index\n\n```python\n# Single column index\ntm.create_index('idx_email', 'users', 'email', engine)\n\n# Composite index\ntm.create_index('idx_name', 'users', ['last_name', 'first_name'], engine)\n\n# Unique index\ntm.create_unique_index('idx_unique_email', 'users', 'email', engine)\n\n# Or using create_index with unique=True\ntm.create_index('idx_email', 'users', 'email', engine, unique=True)\n```\n\n#### Drop Index\n\n```python\ntm.drop_index('idx_email', 'users', engine)\n\n# Skip if doesn't exist\ntm.drop_index('idx_email', 'users', engine, if_exists=True)\n```\n\n### Constraint Operations\n\n#### Foreign Keys\n\n```python\n# Create foreign key\ntm.create_foreign_key(\n    'fk_user_address',\n    'addresses',      # source table\n    'user_id',        # source column\n    'users',          # referenced table\n    'id',             # referenced column\n    engine,\n    ondelete='CASCADE'\n)\n\n# Composite foreign key\ntm.create_foreign_key(\n    'fk_user_role',\n    'user_roles',\n    ['user_id', 'role_id'],\n    'roles',\n    ['user_id', 'id'],\n    engine\n)\n```\n\n#### Unique Constraints\n\n```python\n# Single column\ntm.create_unique_constraint('uq_email', 'users', 'email', engine)\n\n# Multiple columns\ntm.create_unique_constraint('uq_user_role', 'user_roles', \n                           ['user_id', 'role_id'], engine)\n```\n\n#### Check Constraints\n\n```python\ntm.create_check_constraint('ck_age_positive', 'users', 'age > 0', engine)\n\ntm.create_check_constraint('ck_email_format', 'users', \n                          \"email LIKE '%@%'\", engine)\n```\n\n#### Primary Keys\n\n```python\n# Create primary key (on table without one)\ntm.create_primary_key('users', 'id', engine)\n\n# Composite primary key\ntm.create_primary_keys('user_roles', ['user_id', 'role_id'], engine)\n\n# Replace existing primary key\nfrom fullmetalalchemy.features import get_table\ntable = get_table('users', engine)\ntm.replace_primary_key(table, 'uuid', engine)\n\n# Replace with composite key\ntm.replace_primary_keys(table, ['tenant_id', 'user_id'], engine)\n```\n\n#### Drop Constraints\n\n```python\n# Drop any constraint by name and type\ntm.drop_constraint('fk_user_address', 'addresses', engine, \n                  type_='foreignkey')\n\ntm.drop_constraint('uq_email', 'users', engine, type_='unique')\n\ntm.drop_constraint('ck_age_positive', 'users', engine, type_='check')\n```\n\n## Migration System\n\nThe Migration class provides a powerful way to manage schema changes with rollback capability.\n\n### Basic Usage\n\n```python\nfrom sqlalchemy import create_engine\nimport transmutation as tm\n\nengine = create_engine('sqlite:///mydb.db')\nmigration = tm.Migration(engine)\n\n# Queue operations\nmigration.add_column('users', 'email', str, nullable=False)\nmigration.create_index('idx_email', 'users', 'email', unique=True)\nmigration.add_column('users', 'phone', str)\n\n# Apply all changes\nmigration.upgrade()\n\n# If something went wrong, rollback\nmigration.downgrade()\n```\n\n### Batch Operations with Auto-Rollback\n\n```python\nmigration = tm.Migration(engine)\n\ntry:\n    with migration.batch_operations():\n        migration.add_column('users', 'email', str)\n        migration.create_index('idx_email', 'users', 'email')\n        migration.create_foreign_key('fk_user', 'posts', 'user_id', \n                                    'users', 'id')\n    # Changes are automatically applied on successful exit\nexcept tm.MigrationError as e:\n    print(f\"Migration failed and was rolled back: {e}\")\n```\n\n### All Migration Methods\n\n```python\nmigration = tm.Migration(engine)\n\n# Column operations\nmigration.add_column('table', 'col', str)\nmigration.drop_column('table', 'col')\nmigration.rename_column('table', 'old', 'new')\nmigration.alter_column('table', 'col', nullable=False)\n\n# Table operations\nmigration.create_table('table', columns)\nmigration.drop_table('table')\nmigration.rename_table('old', 'new')\nmigration.copy_table(table, 'new_table')\n\n# Index operations\nmigration.create_index('idx', 'table', 'col')\nmigration.drop_index('idx', 'table', 'col')\nmigration.create_unique_index('idx', 'table', 'col')\n\n# Constraint operations\nmigration.create_foreign_key('fk', 'source', 'col', 'ref', 'ref_col')\nmigration.create_unique_constraint('uq', 'table', 'col')\nmigration.create_check_constraint('ck', 'table', 'condition')\n\n# Execute custom SQL\nmigration.execute_sql(\"UPDATE users SET active = 1\")\n\n# Apply changes\nmigration.upgrade()\n\n# Check status\nprint(f\"Pending: {migration.pending_operations()}\")\nprint(f\"Applied: {migration.applied_operations()}\")\n\n# Rollback\nmigration.downgrade()\n\n# Clear queue without executing\nmigration.clear()\n```\n\n### Transaction Management\n\n```python\n# Auto-rollback enabled by default\nmigration = tm.Migration(engine, auto_transaction=True)\n\n# Disable auto-rollback for manual control\nmigration = tm.Migration(engine, auto_transaction=False)\n```\n\n## Advanced Usage\n\n### Schema Support\n\nAll operations support schema specification for databases that use schemas (PostgreSQL):\n\n```python\n# PostgreSQL schema support\ntm.add_column('users', 'email', str, engine, schema='public')\ntm.create_index('idx_email', 'users', 'email', engine, schema='public')\n```\n\n### Custom SQL Execution\n\n```python\nmigration = tm.Migration(engine)\n\n# Execute custom SQL\nresult = migration.execute_sql(\n    \"UPDATE users SET status = 'active' WHERE last_login > :date\",\n    date='2024-01-01'\n)\n```\n\n### Validation\n\nTransmutation automatically validates operations before execution:\n\n```python\ntry:\n    tm.add_column('nonexistent_table', 'col', str, engine)\nexcept tm.ValidationError as e:\n    print(f\"Validation failed: {e}\")\n```\n\n### Working with Alteration Classes\n\nFor advanced use cases, you can work directly with Alteration classes:\n\n```python\nfrom transmutation.alteration import AddColumn, CreateIndex\n\n# Create alterations manually\nadd_col = AddColumn('users', 'email', str, engine)\ncreate_idx = CreateIndex('idx_email', 'users', 'email', engine, unique=True)\n\n# Apply\ntable = add_col.upgrade()\ntable = create_idx.upgrade()\n\n# Rollback\ncreate_idx.downgrade()\nadd_col.downgrade()\n```\n\n## Error Handling\n\nTransmutation provides specific exceptions for different error types:\n\n```python\nimport transmutation as tm\n\ntry:\n    tm.add_column('users', 'email', str, engine)\nexcept tm.ColumnError as e:\n    print(f\"Column operation failed: {e}\")\nexcept tm.ValidationError as e:\n    print(f\"Validation failed: {e}\")\nexcept tm.TransmutationError as e:\n    print(f\"General error: {e}\")\n\n# Migration-specific errors\ntry:\n    migration.upgrade()\nexcept tm.MigrationError as e:\n    print(f\"Migration failed: {e}\")\nexcept tm.RollbackError as e:\n    print(f\"Rollback failed: {e}\")\n```\n\n### Available Exceptions\n\n- `TransmutationError` - Base exception for all transmutation errors\n- `MigrationError` - Migration operation failed\n- `ColumnError` - Column operation failed\n- `TableError` - Table operation failed\n- `ConstraintError` - Constraint operation failed\n- `IndexError` - Index operation failed\n- `ValidationError` - Validation failed before operation\n- `RollbackError` - Rollback operation failed\n\n## Database Support\n\nTransmutation works with any database supported by SQLAlchemy and Alembic:\n\n- **SQLite**: Full support with no external dependencies\n- **PostgreSQL**: Full support with schema capabilities\n- **MySQL/MariaDB**: Full support\n- **Oracle**: Supported via SQLAlchemy\n- **Microsoft SQL Server**: Supported via SQLAlchemy\n\n### SQLite\n\nSQLite is the default and requires no additional setup. Foreign keys can be enabled:\n\n```python\nfrom sqlalchemy import create_engine, event\n\nengine = create_engine('sqlite:///mydb.db')\n\n@event.listens_for(engine, \"connect\")\ndef set_sqlite_pragma(dbapi_conn, connection_record):\n    cursor = dbapi_conn.cursor()\n    cursor.execute(\"PRAGMA foreign_keys=ON\")\n    cursor.close()\n```\n\n### PostgreSQL\n\nFull schema support:\n\n```python\ntm.create_table('users', columns, engine, schema='myschema')\n```\n\n## API Organization\n\nTransmutation provides a well-organized API with operations grouped by category:\n\n```python\nimport transmutation as tm\n\n# Or import specific modules\nfrom transmutation import column, table, index, constraint\nfrom transmutation import Migration\n\n# Column operations\nfrom transmutation.column import add_column, rename_column, drop_column, alter_column\n\n# Table operations  \nfrom transmutation.table import create_table, drop_table, rename_table, copy_table\n\n# Index operations\nfrom transmutation.index import create_index, drop_index, create_unique_index\n\n# Constraint operations\nfrom transmutation.constraint import (\n    create_foreign_key,\n    create_unique_constraint,\n    create_check_constraint,\n    drop_constraint\n)\n```\n\n## Examples\n\n### Complete Migration Example\n\n```python\nfrom sqlalchemy import create_engine, Column, Integer, String\nimport transmutation as tm\n\n# Setup\nengine = create_engine('sqlite:///myapp.db')\n\n# Create a new table\ncolumns = [\n    Column('id', Integer, primary_key=True),\n    Column('username', String(50), nullable=False),\n    Column('email', String(100))\n]\ntm.create_table('users', columns, engine)\n\n# Add indexes\ntm.create_unique_index('idx_username', 'users', 'username', engine)\ntm.create_index('idx_email', 'users', 'email', engine)\n\n# Add constraints\ntm.create_check_constraint('ck_username_length', 'users', \n                          'LENGTH(username) >= 3', engine)\n\n# Modify existing table\ntm.add_column('users', 'created_at', 'datetime', engine,\n             server_default='CURRENT_TIMESTAMP')\ntm.alter_column('users', 'email', engine, nullable=False)\n```\n\n### Using Migration for Complex Changes\n\n```python\nfrom sqlalchemy import create_engine\nimport transmutation as tm\n\nengine = create_engine('sqlite:///myapp.db')\nmigration = tm.Migration(engine)\n\n# Queue multiple related changes\nmigration.add_column('users', 'status', str, default='active')\nmigration.create_index('idx_status', 'users', 'status')\nmigration.add_column('users', 'last_login', 'datetime')\n\n# Create posts table with foreign key to users\nfrom sqlalchemy import Column, Integer, String, Text\nposts_columns = [\n    Column('id', Integer, primary_key=True),\n    Column('user_id', Integer, nullable=False),\n    Column('title', String(200), nullable=False),\n    Column('content', Text)\n]\nmigration.create_table('posts', posts_columns)\nmigration.create_foreign_key('fk_post_user', 'posts', 'user_id', \n                            'users', 'id', ondelete='CASCADE')\nmigration.create_index('idx_user_posts', 'posts', 'user_id')\n\n# Apply all changes atomically\nmigration.upgrade()\n\n# If anything goes wrong:\n# migration.downgrade()\n```\n\n### Data Migration with Custom SQL\n\n```python\nmigration = tm.Migration(engine)\n\n# Add new column\nmigration.add_column('users', 'full_name', str)\n\n# Populate it with data using custom SQL\nmigration.execute_sql(\n    \"UPDATE users SET full_name = name || ' ' || surname WHERE surname IS NOT NULL\"\n)\nmigration.execute_sql(\n    \"UPDATE users SET full_name = name WHERE surname IS NULL\"\n)\n\n# Drop old columns\nmigration.drop_column('users', 'name')\nmigration.drop_column('users', 'surname')\n\n# Apply all changes\nmigration.upgrade()\n```\n\n## Development\n\n### Setup\n\n```bash\n# Clone repository\ngit clone https://github.com/odosmatthews/transmutation.git\ncd transmutation\n\n# Install development dependencies\npip install -e \".[dev]\"\n```\n\n### Running Tests\n\n```bash\n# All tests\npytest\n\n# Specific test file\npytest tests/test_alter.py\n\n# With coverage report\npytest --cov=transmutation --cov-report=html\n\n# Open coverage report\nopen htmlcov/index.html\n```\n\n### Code Quality\n\n```bash\n# Run type checking\nmypy src\n\n# Run linter\nruff check src tests\n\n# Auto-fix linting issues\nruff check --fix src tests\n\n# Format code\nblack src tests\n\n# Sort imports\nisort src tests\n```\n\n## Best Practices\n\n### 1. Use Migration for Complex Changes\n\nFor multiple related changes, use the Migration system:\n\n```python\n# Good - atomic, reversible\nmigration = tm.Migration(engine)\nmigration.add_column('users', 'email', str)\nmigration.create_index('idx_email', 'users', 'email')\nmigration.upgrade()\n\n# Less ideal - individual operations\ntm.add_column('users', 'email', str, engine)\ntm.create_index('idx_email', 'users', 'email', engine)\n```\n\n### 2. Always Handle Errors\n\n```python\ntry:\n    migration.upgrade()\nexcept tm.MigrationError as e:\n    logger.error(f\"Migration failed: {e}\")\n    migration.downgrade()\n    raise\n```\n\n### 3. Use Validation\n\nTransmutation validates operations automatically, but you can also validate manually:\n\n```python\nfrom transmutation.utils import validate_table_exists, validate_column_exists\n\nvalidate_table_exists('users', engine)\nvalidate_column_exists('users', 'email', engine)\n```\n\n### 4. Test Your Migrations\n\nAlways test migrations in a development environment first:\n\n```python\ndef test_migration():\n    # Setup test database\n    engine = create_engine('sqlite:///:memory:')\n    \n    # Run migration\n    migration = tm.Migration(engine)\n    migration.add_column('users', 'new_col', str)\n    migration.upgrade()\n    \n    # Verify\n    from fullmetalalchemy.features import get_table\n    table = get_table('users', engine)\n    assert 'new_col' in table.columns\n    \n    # Test rollback\n    migration.downgrade()\n    table = get_table('users', engine)\n    assert 'new_col' not in table.columns\n```\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n### Development Guidelines\n\n- Add tests for new features\n- Maintain test coverage above 65%\n- Follow type hints for all functions\n- Add comprehensive docstrings\n- Run linters before committing\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Acknowledgments\n\nBuilt on top of:\n- [SQLAlchemy](https://www.sqlalchemy.org/) - The Python SQL toolkit\n- [Alembic](https://alembic.sqlalchemy.org/) - Database migration tool\n- [fullmetalalchemy](https://github.com/kajuberdut/fullmetalalchemy) - SQLAlchemy utilities\n\n## Support\n\nFor issues, questions, or contributions, please visit the GitHub repository.\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Comprehensive database migration and schema alteration tool using SQLAlchemy and Alembic",
    "version": "1.0.0",
    "project_urls": {
        "Homepage": "https://github.com/odosmatthews/transmutation",
        "Repository": "https://github.com/odosmatthews/transmutation"
    },
    "split_keywords": [
        "database",
        " migration",
        " schema",
        " alembic",
        " sqlalchemy"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f5514a464b33c931b190b2591829c1f2659e8d65337602987f4c7b0e085ae3a4",
                "md5": "d6ad72b71ea5e7832d6133e825b581fa",
                "sha256": "7362fd6dbd344ce877a803c3ab9ea9e41d11f01711697980e6802df5159dde6c"
            },
            "downloads": -1,
            "filename": "transmutation-1.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "d6ad72b71ea5e7832d6133e825b581fa",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 24134,
            "upload_time": "2025-10-19T14:49:35",
            "upload_time_iso_8601": "2025-10-19T14:49:35.689571Z",
            "url": "https://files.pythonhosted.org/packages/f5/51/4a464b33c931b190b2591829c1f2659e8d65337602987f4c7b0e085ae3a4/transmutation-1.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d6b802955ad0b4b186af6dac2afa9dbead86225ad43889649781c3f651649b6f",
                "md5": "e8c1f5f8a88a6976a397ff2cd39c2503",
                "sha256": "3c13a5e6efced25c84081f826a7078f0c1bd1392aa3fa63e726ec308cbe8ee57"
            },
            "downloads": -1,
            "filename": "transmutation-1.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "e8c1f5f8a88a6976a397ff2cd39c2503",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 28415,
            "upload_time": "2025-10-19T14:49:36",
            "upload_time_iso_8601": "2025-10-19T14:49:36.962089Z",
            "url": "https://files.pythonhosted.org/packages/d6/b8/02955ad0b4b186af6dac2afa9dbead86225ad43889649781c3f651649b6f/transmutation-1.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-19 14:49:36",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "odosmatthews",
    "github_project": "transmutation",
    "github_not_found": true,
    "lcname": "transmutation"
}
        
Elapsed time: 1.69873s