Skip to content

Commit 8ffddd5

Browse files
committed
新增 alembic 管理数据库架构
1 parent 2d73312 commit 8ffddd5

File tree

9 files changed

+263
-5
lines changed

9 files changed

+263
-5
lines changed

alembic.ini

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = migrations
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-dateutil library that can be
20+
# installed by adding `alembic[tz]` to the pip requirements
21+
# string value is passed to dateutil.tz.gettz()
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 migrations/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:migrations/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+
# the output encoding used when revision files
55+
# are written from script.py.mako
56+
# output_encoding = utf-8
57+
58+
#sqlalchemy.url = driver://user:pass@localhost/dbname
59+
60+
61+
[post_write_hooks]
62+
# post_write_hooks defines scripts or Python functions that are run
63+
# on newly generated revision scripts. See the documentation for further
64+
# detail and examples
65+
66+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
67+
# hooks = black
68+
# black.type = console_scripts
69+
# black.entrypoint = black
70+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
71+
72+
# Logging configuration
73+
[loggers]
74+
keys = root,sqlalchemy,alembic
75+
76+
[handlers]
77+
keys = console
78+
79+
[formatters]
80+
keys = generic
81+
82+
[logger_root]
83+
level = WARN
84+
handlers = console
85+
qualname =
86+
87+
[logger_sqlalchemy]
88+
level = WARN
89+
handlers =
90+
qualname = sqlalchemy.engine
91+
92+
[logger_alembic]
93+
level = INFO
94+
handlers =
95+
qualname = alembic
96+
97+
[handler_console]
98+
class = StreamHandler
99+
args = (sys.stderr,)
100+
level = NOTSET
101+
formatter = generic
102+
103+
[formatter_generic]
104+
format = %(levelname)-5.5s [%(name)s] %(message)s
105+
datefmt = %H:%M:%S

database.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import datetime
22

3+
from alembic import command
4+
from alembic.config import Config
35
from sqlalchemy import Boolean, Column, Integer, String, DateTime
46
from sqlalchemy.ext.declarative import declarative_base
57
from sqlalchemy.ext.asyncio import create_async_engine
68
from sqlalchemy.ext.asyncio.session import AsyncSession
79

8-
engine = create_async_engine("sqlite+aiosqlite:///database.db")
10+
import settings
11+
12+
engine = create_async_engine(f"sqlite+aiosqlite:///{settings.DATABASE_FILE}")
913

1014
Base = declarative_base()
1115

1216

1317
async def init_models():
14-
async with engine.begin() as conn:
15-
await conn.run_sync(Base.metadata.create_all)
18+
config = Config()
19+
config.set_main_option("script_location", "migrations")
20+
command.upgrade(config, "head")
1621

1722

1823
async def get_session():

main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,4 @@ async def share(background_tasks: BackgroundTasks, text: str = Form(default=None
160160
if __name__ == '__main__':
161161
import uvicorn
162162

163-
uvicorn.run('main:app', host='0.0.0.0', port=settings.PORT, debug=settings.DEBUG)
163+
uvicorn.run('main:app', host='0.0.0.0', port=settings.PORT, reload=settings.DEBUG)

migrations/README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration.

migrations/env.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from logging.config import fileConfig
2+
from sqlalchemy import create_engine
3+
from alembic import context
4+
5+
# this is the Alembic Config object, which provides
6+
# access to the values within the .ini file in use.
7+
config = context.config
8+
9+
# Interpret the config file for Python logging.
10+
# This line sets up loggers basically.
11+
if config.config_file_name is not None:
12+
fileConfig(config.config_file_name)
13+
14+
# add your model's MetaData object here
15+
# for 'autogenerate' support
16+
# from myapp import mymodel
17+
# target_metadata = mymodel.Base.metadata
18+
from database import Base
19+
target_metadata = Base.metadata
20+
21+
# other values from the config, defined by the needs of env.py,
22+
# can be acquired:
23+
# my_important_option = config.get_main_option("my_important_option")
24+
# ... etc.
25+
import settings
26+
27+
def run_migrations_offline() -> None:
28+
"""Run migrations in 'offline' mode.
29+
30+
This configures the context with just a URL
31+
and not an Engine, though an Engine is acceptable
32+
here as well. By skipping the Engine creation
33+
we don't even need a DBAPI to be available.
34+
35+
Calls to context.execute() here emit the given string to the
36+
script output.
37+
38+
"""
39+
context.configure(
40+
url=f"sqlite:///{settings.DATABASE_FILE}",
41+
target_metadata=target_metadata,
42+
literal_binds=True,
43+
dialect_opts={"paramstyle": "named"},
44+
)
45+
46+
with context.begin_transaction():
47+
context.run_migrations()
48+
49+
50+
def run_migrations_online() -> None:
51+
"""Run migrations in 'online' mode.
52+
53+
In this scenario we need to create an Engine
54+
and associate a connection with the context.
55+
56+
"""
57+
connectable = create_engine(f"sqlite:///{settings.DATABASE_FILE}")
58+
59+
with connectable.connect() as connection:
60+
context.configure(
61+
connection=connection, target_metadata=target_metadata
62+
)
63+
64+
with context.begin_transaction():
65+
context.run_migrations()
66+
67+
68+
if context.is_offline_mode():
69+
run_migrations_offline()
70+
else:
71+
run_migrations_online()

migrations/script.py.mako

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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() -> None:
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade() -> None:
24+
${downgrades if downgrades else "pass"}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""init
2+
3+
Revision ID: 5a7ea072b184
4+
Revises:
5+
Create Date: 2022-12-13 20:21:25.645856
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
import settings
12+
13+
# revision identifiers, used by Alembic.
14+
revision = '5a7ea072b184'
15+
down_revision = None
16+
branch_labels = None
17+
depends_on = None
18+
19+
engine = sa.create_engine(f"sqlite:///{settings.DATABASE_FILE}")
20+
21+
def upgrade() -> None:
22+
if sa.inspect(engine).has_table("codes"):
23+
return
24+
# ### commands auto generated by Alembic - please adjust! ###
25+
op.create_table('codes',
26+
sa.Column('id', sa.Integer(), nullable=False),
27+
sa.Column('code', sa.String(length=10), nullable=True),
28+
sa.Column('key', sa.String(length=30), nullable=True),
29+
sa.Column('name', sa.String(length=500), nullable=True),
30+
sa.Column('size', sa.Integer(), nullable=True),
31+
sa.Column('type', sa.String(length=20), nullable=True),
32+
sa.Column('text', sa.String(length=500), nullable=True),
33+
sa.Column('used', sa.Boolean(), nullable=True),
34+
sa.Column('count', sa.Integer(), nullable=True),
35+
sa.Column('use_time', sa.DateTime(), nullable=True),
36+
sa.Column('exp_time', sa.DateTime(), nullable=True),
37+
sa.PrimaryKeyConstraint('id')
38+
)
39+
op.create_index(op.f('ix_codes_code'), 'codes', ['code'], unique=True)
40+
op.create_index(op.f('ix_codes_id'), 'codes', ['id'], unique=False)
41+
op.create_index(op.f('ix_codes_key'), 'codes', ['key'], unique=True)
42+
# ### end Alembic commands ###
43+
44+
45+
def downgrade() -> None:
46+
# ### commands auto generated by Alembic - please adjust! ###
47+
op.drop_index(op.f('ix_codes_key'), table_name='codes')
48+
op.drop_index(op.f('ix_codes_id'), table_name='codes')
49+
op.drop_index(op.f('ix_codes_code'), table_name='codes')
50+
op.drop_table('codes')
51+
# ### end Alembic commands ###

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
fastapi==0.88.0
22
aiosqlite==0.17.0
3+
alembic==1.8.1
34
SQLAlchemy==1.4.44
45
python-multipart==0.0.5
56
uvicorn==0.15.0

settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# 端口
88
PORT = config('PORT', cast=int, default=12345)
99
# Sqlite数据库文件
10-
DATABASE_URL = config('DATABASE_URL', cast=str, default="sqlite+aiosqlite:///database.db")
10+
DATABASE_FILE = config('DATABASE_URL', cast=str, default="database.db")
1111
# 静态文件夹
1212
DATA_ROOT = config('DATA_ROOT', cast=str, default="./static")
1313
# 静态文件夹URL

0 commit comments

Comments
 (0)