SpreadSheets commited on
Commit
75ff6be
·
1 Parent(s): 0e16885

feat: db migrations

Browse files
migrations/README ADDED
@@ -0,0 +1 @@
 
 
1
+ Single-database configuration for Flask.
migrations/alembic.ini ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # A generic, single database configuration.
2
+
3
+ [alembic]
4
+ # template used to generate migration files
5
+ # file_template = %%(rev)s_%%(slug)s
6
+
7
+ # set to 'true' to run the environment during
8
+ # the 'revision' command, regardless of autogenerate
9
+ # revision_environment = false
10
+
11
+
12
+ # Logging configuration
13
+ [loggers]
14
+ keys = root,sqlalchemy,alembic,flask_migrate
15
+
16
+ [handlers]
17
+ keys = console
18
+
19
+ [formatters]
20
+ keys = generic
21
+
22
+ [logger_root]
23
+ level = WARN
24
+ handlers = console
25
+ qualname =
26
+
27
+ [logger_sqlalchemy]
28
+ level = WARN
29
+ handlers =
30
+ qualname = sqlalchemy.engine
31
+
32
+ [logger_alembic]
33
+ level = INFO
34
+ handlers =
35
+ qualname = alembic
36
+
37
+ [logger_flask_migrate]
38
+ level = INFO
39
+ handlers =
40
+ qualname = flask_migrate
41
+
42
+ [handler_console]
43
+ class = StreamHandler
44
+ args = (sys.stderr,)
45
+ level = NOTSET
46
+ formatter = generic
47
+
48
+ [formatter_generic]
49
+ format = %(levelname)-5.5s [%(name)s] %(message)s
50
+ datefmt = %H:%M:%S
migrations/env.py ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from logging.config import fileConfig
3
+
4
+ from flask import current_app
5
+
6
+ from alembic import context
7
+
8
+ # this is the Alembic Config object, which provides
9
+ # access to the values within the .ini file in use.
10
+ config = context.config
11
+
12
+ # Interpret the config file for Python logging.
13
+ # This line sets up loggers basically.
14
+ fileConfig(config.config_file_name)
15
+ logger = logging.getLogger('alembic.env')
16
+
17
+
18
+ def get_engine():
19
+ try:
20
+ # this works with Flask-SQLAlchemy<3 and Alchemical
21
+ return current_app.extensions['migrate'].db.get_engine()
22
+ except (TypeError, AttributeError):
23
+ # this works with Flask-SQLAlchemy>=3
24
+ return current_app.extensions['migrate'].db.engine
25
+
26
+
27
+ def get_engine_url():
28
+ try:
29
+ return get_engine().url.render_as_string(hide_password=False).replace(
30
+ '%', '%%')
31
+ except AttributeError:
32
+ return str(get_engine().url).replace('%', '%%')
33
+
34
+
35
+ # add your model's MetaData object here
36
+ # for 'autogenerate' support
37
+ # from myapp import mymodel
38
+ # target_metadata = mymodel.Base.metadata
39
+ config.set_main_option('sqlalchemy.url', get_engine_url())
40
+ target_db = current_app.extensions['migrate'].db
41
+
42
+ # other values from the config, defined by the needs of env.py,
43
+ # can be acquired:
44
+ # my_important_option = config.get_main_option("my_important_option")
45
+ # ... etc.
46
+
47
+
48
+ def get_metadata():
49
+ if hasattr(target_db, 'metadatas'):
50
+ return target_db.metadatas[None]
51
+ return target_db.metadata
52
+
53
+
54
+ def run_migrations_offline():
55
+ """Run migrations in 'offline' mode.
56
+
57
+ This configures the context with just a URL
58
+ and not an Engine, though an Engine is acceptable
59
+ here as well. By skipping the Engine creation
60
+ we don't even need a DBAPI to be available.
61
+
62
+ Calls to context.execute() here emit the given string to the
63
+ script output.
64
+
65
+ """
66
+ url = config.get_main_option("sqlalchemy.url")
67
+ context.configure(
68
+ url=url, target_metadata=get_metadata(), literal_binds=True
69
+ )
70
+
71
+ with context.begin_transaction():
72
+ context.run_migrations()
73
+
74
+
75
+ def run_migrations_online():
76
+ """Run migrations in 'online' mode.
77
+
78
+ In this scenario we need to create an Engine
79
+ and associate a connection with the context.
80
+
81
+ """
82
+
83
+ # this callback is used to prevent an auto-migration from being generated
84
+ # when there are no changes to the schema
85
+ # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
86
+ def process_revision_directives(context, revision, directives):
87
+ if getattr(config.cmd_opts, 'autogenerate', False):
88
+ script = directives[0]
89
+ if script.upgrade_ops.is_empty():
90
+ directives[:] = []
91
+ logger.info('No changes in schema detected.')
92
+
93
+ conf_args = current_app.extensions['migrate'].configure_args
94
+ if conf_args.get("process_revision_directives") is None:
95
+ conf_args["process_revision_directives"] = process_revision_directives
96
+
97
+ connectable = get_engine()
98
+
99
+ with connectable.connect() as connection:
100
+ context.configure(
101
+ connection=connection,
102
+ target_metadata=get_metadata(),
103
+ **conf_args
104
+ )
105
+
106
+ with context.begin_transaction():
107
+ context.run_migrations()
108
+
109
+
110
+ if context.is_offline_mode():
111
+ run_migrations_offline()
112
+ else:
113
+ run_migrations_online()
migrations/script.py.mako ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+ ${imports if imports else ""}
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = ${repr(up_revision)}
14
+ down_revision = ${repr(down_revision)}
15
+ branch_labels = ${repr(branch_labels)}
16
+ depends_on = ${repr(depends_on)}
17
+
18
+
19
+ def upgrade():
20
+ ${upgrades if upgrades else "pass"}
21
+
22
+
23
+ def downgrade():
24
+ ${downgrades if downgrades else "pass"}
migrations/versions/247b23aeed89_initial_migration.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Initial migration
2
+
3
+ Revision ID: 247b23aeed89
4
+ Revises:
5
+ Create Date: 2025-09-24 20:27:37.396145
6
+
7
+ """
8
+ from alembic import op
9
+ import sqlalchemy as sa
10
+
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = '247b23aeed89'
14
+ down_revision = None
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ # ### commands auto generated by Alembic - please adjust! ###
21
+ op.create_table('user',
22
+ sa.Column('id', sa.Integer(), nullable=False),
23
+ sa.Column('name', sa.String(length=100), nullable=False),
24
+ sa.Column('email', sa.String(length=100), nullable=False),
25
+ sa.PrimaryKeyConstraint('id'),
26
+ sa.UniqueConstraint('email')
27
+ )
28
+ op.create_table('flask_dance_oauth',
29
+ sa.Column('user_id', sa.Integer(), nullable=True),
30
+ sa.Column('id', sa.Integer(), nullable=False),
31
+ sa.Column('provider', sa.String(length=50), nullable=False),
32
+ sa.Column('created_at', sa.DateTime(), nullable=False),
33
+ sa.Column('token', sa.JSON(), nullable=False),
34
+ sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
35
+ sa.PrimaryKeyConstraint('id')
36
+ )
37
+ # ### end Alembic commands ###
38
+
39
+
40
+ def downgrade():
41
+ # ### commands auto generated by Alembic - please adjust! ###
42
+ op.drop_table('flask_dance_oauth')
43
+ op.drop_table('user')
44
+ # ### end Alembic commands ###