Skip to content

Commit

Permalink
alembic added
Browse files Browse the repository at this point in the history
  • Loading branch information
ph4r05 committed Sep 5, 2017
1 parent b2f2a9c commit d9c4816
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,5 @@ db.config
db.json
config.json

alembic.ini

77 changes: 77 additions & 0 deletions alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# A generic, single database configuration.

[alembic:exclude]
tables =

[alembic]
# path to migration scripts
script_location = alembic

# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s

# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
timezone = UTC

# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = mysql://zeman:[email protected]/zeman


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
77 changes: 77 additions & 0 deletions alembic.ini.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# A generic, single database configuration.

[alembic:exclude]
tables =

[alembic]
# path to migration scripts
script_location = alembic

# template used to generate migration files
file_template = %%(year)d%%(month).2d%%(day).2d_%%(hour).2d%%(minute).2d_%%(rev)s_%%(slug)s

# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
timezone = UTC

# max length of characters to apply to the
# "slug" field
#truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat alembic/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8

sqlalchemy.url = driver://user:pass@localhost/dbname


# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = INFO
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
65 changes: 65 additions & 0 deletions alembic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
Generic single-database configuration.

## Installation & setup

1. Install alembic

```
pip install alembic
```

2. Create `alembic.ini` in the root project directory.
You can use `alembic.ini.example` file. Mainly set `sqlalchemy.url` property so alembic can connect
to the DB server and do the stuff - more on that later.

You can also exclude some tables from auto-detection.


## Usage

Alembic is typically run locally, connecting to a remote dev DB server.

1. Local forward remote MySQL server:

```
ssh -L 3306:localhost:3306 server
```

2. Generate auto detected upgrade file. Prefer numbered prefix so the patch ordering is clear even after tens of
patches generated.

```
alembic revision --autogenerate -m "boot"
```

Now you can manually inspect newly generated patch file, change something or add data migration logic e.g., from
converting from older to newer format.

3. Upgrade remote database schema:

```
alembic upgrade head
```

If something goes wrong or you want to be sure the upgrade does what you want do a dry run with printing SQL dump with
changes.

```
alembic upgrade head --sql
```

### Default values

Alembic autodetection does not work well with default values - they are not detected.
Add default values manually for tables used by PHP

```
op.add_column('users', sa.Column('magiccc', sa.SmallInteger(), nullable=False, server_default='0'))
```

### Foreign key conversion

```
sa.ForeignKeyConstraint\(\['([\w]+)'\],\s*\['([\w]+)\.([\w]+)'\],\s*name='([\w]+)',\s*ondelete='([\w\s]+)'\)(,?)
op.create_foreign_key('$4', SRC_TABLE, '$2', ['$1'], ['$3'], ondelete='$5' )$6
```
83 changes: 83 additions & 0 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)

# add your model's MetaData object here
# for 'autogenerate' support
import sys, os, inspect
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))))

from zemanfeed.database import Base
target_metadata = Base.metadata

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


exclude_tables = [x.strip() for x in config.get_section('alembic:exclude').get('tables', '').split(',')]


def include_object(object, name, type_, reflected, compare_to):
if type_ == "table" and name in exclude_tables:
return False
else:
return True


def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True, include_object=include_object)

with context.begin_transaction():
context.run_migrations()


def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)

with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
include_object=include_object
)

with context.begin_transaction():
context.run_migrations()

if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
24 changes: 24 additions & 0 deletions alembic/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}


def upgrade():
${upgrades if upgrades else "pass"}


def downgrade():
${downgrades if downgrades else "pass"}

0 comments on commit d9c4816

Please sign in to comment.