Skip to content

Commit 5fefca5

Browse files
authored
add alembic (#1)
2 parents 2568e34 + b860cce commit 5fefca5

10 files changed

+311
-4
lines changed

.pre-commit-config.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ repos:
99
rev: v1.10.0
1010
hooks:
1111
- id: mypy
12+
additional_dependencies:
13+
- alembic==1.13.1

alembic.ini

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = alembic
6+
7+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
8+
# Uncomment the line below if you want the files to be prepended with date and time
9+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
10+
# for all available tokens
11+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
12+
13+
# sys.path path, will be prepended to sys.path if present.
14+
# defaults to the current working directory.
15+
prepend_sys_path = .
16+
17+
# timezone to use when rendering the date within the migration file
18+
# as well as the filename.
19+
# If specified, requires the python>=3.9 or backports.zoneinfo library.
20+
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
21+
# string value is passed to ZoneInfo()
22+
# leave blank for localtime
23+
# timezone =
24+
25+
# max length of characters to apply to the
26+
# "slug" field
27+
# truncate_slug_length = 40
28+
29+
# set to 'true' to run the environment during
30+
# the 'revision' command, regardless of autogenerate
31+
# revision_environment = false
32+
33+
# set to 'true' to allow .pyc and .pyo files without
34+
# a source .py file to be detected as revisions in the
35+
# versions/ directory
36+
# sourceless = false
37+
38+
# version location specification; This defaults
39+
# to alembic/versions. When using multiple version
40+
# directories, initial revisions must be specified with --version-path.
41+
# The path separator used here should be the separator specified by "version_path_separator" below.
42+
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
43+
44+
# version path separator; As mentioned above, this is the character used to split
45+
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
46+
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
47+
# Valid values for version_path_separator are:
48+
#
49+
# version_path_separator = :
50+
# version_path_separator = ;
51+
# version_path_separator = space
52+
version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
53+
54+
# set to 'true' to search source files recursively
55+
# in each "version_locations" directory
56+
# new in Alembic version 1.10
57+
# recursive_version_locations = false
58+
59+
# the output encoding used when revision files
60+
# are written from script.py.mako
61+
# output_encoding = utf-8
62+
63+
sqlalchemy.url = driver://user:pass@localhost/dbname
64+
65+
66+
[post_write_hooks]
67+
# post_write_hooks defines scripts or Python functions that are run
68+
# on newly generated revision scripts. See the documentation for further
69+
# detail and examples
70+
71+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
72+
# hooks = black
73+
# black.type = console_scripts
74+
# black.entrypoint = black
75+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
76+
77+
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
78+
# hooks = ruff
79+
# ruff.type = exec
80+
# ruff.executable = %(here)s/.venv/bin/ruff
81+
# ruff.options = --fix REVISION_SCRIPT_FILENAME
82+
83+
# Logging configuration
84+
[loggers]
85+
keys = root,sqlalchemy,alembic
86+
87+
[handlers]
88+
keys = console
89+
90+
[formatters]
91+
keys = generic
92+
93+
[logger_root]
94+
level = WARN
95+
handlers = console
96+
qualname =
97+
98+
[logger_sqlalchemy]
99+
level = WARN
100+
handlers =
101+
qualname = sqlalchemy.engine
102+
103+
[logger_alembic]
104+
level = INFO
105+
handlers =
106+
qualname = alembic
107+
108+
[handler_console]
109+
class = StreamHandler
110+
args = (sys.stderr,)
111+
level = NOTSET
112+
formatter = generic
113+
114+
[formatter_generic]
115+
format = %(levelname)-5.5s [%(name)s] %(message)s
116+
datefmt = %H:%M:%S

alembic/README

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration.

alembic/env.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import os
2+
from logging.config import fileConfig
3+
4+
from sqlalchemy import engine_from_config, pool
5+
6+
from alembic import context
7+
from teufa import db
8+
9+
config = context.config
10+
11+
if config.config_file_name is not None:
12+
fileConfig(config.config_file_name)
13+
14+
target_metadata = db.Base.metadata
15+
16+
17+
def run_migrations_offline() -> None:
18+
"""Run migrations in 'offline' mode.
19+
20+
This configures the context with just a URL
21+
and not an Engine, though an Engine is acceptable
22+
here as well. By skipping the Engine creation
23+
we don't even need a DBAPI to be available.
24+
25+
Calls to context.execute() here emit the given string to the
26+
script output.
27+
28+
"""
29+
url = os.environ["DATABASE_URL"]
30+
context.configure(
31+
url=url,
32+
target_metadata=target_metadata,
33+
literal_binds=True,
34+
dialect_opts={"paramstyle": "named"},
35+
)
36+
37+
with context.begin_transaction():
38+
context.run_migrations()
39+
40+
41+
def run_migrations_online() -> None:
42+
"""Run migrations in 'online' mode.
43+
44+
In this scenario we need to create an Engine
45+
and associate a connection with the context.
46+
47+
"""
48+
connectable = engine_from_config(
49+
{"sqlalchemy.url": os.environ["DATABASE_URL"]},
50+
poolclass=pool.NullPool,
51+
)
52+
53+
with connectable.connect() as connection:
54+
context.configure(connection=connection, target_metadata=target_metadata)
55+
56+
with context.begin_transaction():
57+
context.run_migrations()
58+
59+
60+
if context.is_offline_mode():
61+
run_migrations_offline()
62+
else:
63+
run_migrations_online()

alembic/script.py.mako

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
${imports if imports else ""}
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = ${repr(up_revision)}
16+
down_revision: Union[str, None] = ${repr(down_revision)}
17+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19+
20+
21+
def upgrade() -> None:
22+
${upgrades if upgrades else "pass"}
23+
24+
25+
def downgrade() -> None:
26+
${downgrades if downgrades else "pass"}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""add user
2+
3+
Revision ID: d487ed348c4c
4+
Revises:
5+
Create Date: 2024-05-23 22:06:52.412454
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
13+
from alembic import op
14+
15+
# revision identifiers, used by Alembic.
16+
revision: str = "d487ed348c4c"
17+
down_revision: Union[str, None] = None
18+
branch_labels: Union[str, Sequence[str], None] = None
19+
depends_on: Union[str, Sequence[str], None] = None
20+
21+
22+
def upgrade() -> None:
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_table(
25+
"users",
26+
sa.Column("id", sa.Integer(), nullable=False),
27+
sa.Column("name", sa.String(), nullable=False),
28+
sa.Column("email", sa.String(), nullable=False),
29+
sa.PrimaryKeyConstraint("id"),
30+
)
31+
# ### end Alembic commands ###
32+
33+
34+
def downgrade() -> None:
35+
# ### commands auto generated by Alembic - please adjust! ###
36+
op.drop_table("users")
37+
# ### end Alembic commands ###

poetry.lock

+39-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ gunicorn = "^22.0.0"
1616
flask = "^3.0.3"
1717
flask-sqlalchemy = "^3.1.1"
1818
psycopg = "^3.1.19"
19+
alembic = "^1.13.1"
1920

2021
[tool.poetry.scripts]
2122
teufa = "teufa.cli:cli"

teufa/cli.py

+13-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import sys
2+
13
import click
24

5+
import alembic.config
36
import teufa.server
47

58

@@ -18,6 +21,13 @@ def server(dev):
1821
teufa.server.Application(cfg).run()
1922

2023

21-
@cli.command()
22-
def initdb():
23-
click.echo("Initialized the database")
24+
@cli.command(
25+
"alembic",
26+
context_settings={
27+
"ignore_unknown_options": True,
28+
"allow_extra_args": True,
29+
},
30+
add_help_option=False,
31+
)
32+
def run_alembic(*args, **kwargs):
33+
alembic.config.main(prog="teufa alembic", argv=sys.argv[2:])

teufa/db.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
2+
3+
4+
class Base(DeclarativeBase):
5+
pass
6+
7+
8+
class User(Base):
9+
__tablename__ = "users"
10+
11+
id: Mapped[int] = mapped_column(primary_key=True)
12+
name: Mapped[str]
13+
email: Mapped[str]

0 commit comments

Comments
 (0)