feat: добавлены файлы для миграции базы данных Alembic
- Созданы новые файлы alembic/versions/2025_01_10_1706-ccf7560dd457_create_user_table.py, alembic/env.py, alembic/README. - Добавлен новый конфигурационный файл alembic.ini для настройки Alembic. - Создан шаблон для генерации миграционных скриптов в файле script.py.mako.
This commit is contained in:
		
							
								
								
									
										115
									
								
								alembic.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								alembic.ini
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,115 @@ | ||||
| # A generic, single database configuration. | ||||
|  | ||||
| [alembic] | ||||
| # path to migration scripts. | ||||
| # Use forward slashes (/) also on windows to provide an os agnostic path | ||||
| script_location = lkeep/database/alembic | ||||
|  | ||||
| # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s | ||||
| # Uncomment the line below if you want the files to be prepended with date and time | ||||
| file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s | ||||
|  | ||||
| # sys.path path, will be prepended to sys.path if present. | ||||
| # defaults to the current working directory. | ||||
| prepend_sys_path = . | ||||
|  | ||||
| # timezone to use when rendering the date within the migration file | ||||
| # as well as the filename. | ||||
| # If specified, requires the python>=3.9 or backports.zoneinfo library. | ||||
| # Any required deps can installed by adding `alembic[tz]` to the pip requirements | ||||
| # string value is passed to ZoneInfo() | ||||
| # leave blank for localtime | ||||
| # timezone = | ||||
|  | ||||
| # 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 lkeep/database/alembic/versions.  When using multiple version | ||||
| # directories, initial revisions must be specified with --version-path. | ||||
| # The path separator used here should be the separator specified by "version_path_separator" below. | ||||
| # version_locations = %(here)s/bar:%(here)s/bat:lkeep/database/alembic/versions | ||||
|  | ||||
| # version path separator; As mentioned above, this is the character used to split | ||||
| # version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. | ||||
| # If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. | ||||
| # Valid values for version_path_separator are: | ||||
| # | ||||
| # version_path_separator = : | ||||
| # version_path_separator = ; | ||||
| # version_path_separator = space | ||||
| # version_path_separator = newline | ||||
| version_path_separator = os  # Use os.pathsep. Default configuration used for new projects. | ||||
|  | ||||
| # set to 'true' to search source files recursively | ||||
| # in each "version_locations" directory | ||||
| # new in Alembic version 1.10 | ||||
| # recursive_version_locations = false | ||||
|  | ||||
| # the output encoding used when revision files | ||||
| # are written from script.py.mako | ||||
| # output_encoding = utf-8 | ||||
|  | ||||
| # sqlalchemy.url = driver://user:pass@localhost/dbname | ||||
|  | ||||
|  | ||||
| [post_write_hooks] | ||||
| # post_write_hooks defines scripts or Python functions that are run | ||||
| # on newly generated revision scripts.  See the documentation for further | ||||
| # detail and examples | ||||
|  | ||||
| # format using "black" - use the console_scripts runner, against the "black" entrypoint | ||||
| # hooks = black | ||||
| # black.type = console_scripts | ||||
| # black.entrypoint = black | ||||
| # black.options = -l 79 REVISION_SCRIPT_FILENAME | ||||
|  | ||||
| # lint with attempts to fix using "ruff" - use the exec runner, execute a binary | ||||
| hooks = ruff | ||||
| ruff.type = exec | ||||
| ruff.executable = poetry | ||||
| ruff.options = run ruff format REVISION_SCRIPT_FILENAME | ||||
|  | ||||
| # Logging configuration | ||||
| [loggers] | ||||
| keys = root,sqlalchemy,alembic | ||||
|  | ||||
| [handlers] | ||||
| keys = console | ||||
|  | ||||
| [formatters] | ||||
| keys = generic | ||||
|  | ||||
| [logger_root] | ||||
| level = WARNING | ||||
| handlers = console | ||||
| qualname = | ||||
|  | ||||
| [logger_sqlalchemy] | ||||
| level = WARNING | ||||
| 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 | ||||
							
								
								
									
										1
									
								
								lkeep/database/alembic/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								lkeep/database/alembic/README
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| Generic single-database configuration with an async dbapi. | ||||
							
								
								
									
										92
									
								
								lkeep/database/alembic/env.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								lkeep/database/alembic/env.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | ||||
| import asyncio | ||||
| from logging.config import fileConfig | ||||
|  | ||||
| from alembic import context | ||||
| from sqlalchemy import pool | ||||
| from sqlalchemy.engine import Connection | ||||
| from sqlalchemy.ext.asyncio import async_engine_from_config | ||||
|  | ||||
| from lkeep.core.settings import settings | ||||
| from lkeep.database.models import Base | ||||
|  | ||||
| # this is the Alembic Config object, which provides | ||||
| # access to the values within the .ini file in use. | ||||
| config = context.config | ||||
| config.set_main_option("sqlalchemy.url", settings.db_settings.db_url) | ||||
|  | ||||
| # Interpret the config file for Python logging. | ||||
| # This line sets up loggers basically. | ||||
| if config.config_file_name is not None: | ||||
|     fileConfig(config.config_file_name) | ||||
|  | ||||
| # add your model's MetaData object here | ||||
| # for 'autogenerate' support | ||||
| # from myapp import mymodel | ||||
| # target_metadata = mymodel.Base.metadata | ||||
| 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. | ||||
|  | ||||
|  | ||||
| def run_migrations_offline() -> None: | ||||
|     """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, | ||||
|         dialect_opts={"paramstyle": "named"}, | ||||
|     ) | ||||
|  | ||||
|     with context.begin_transaction(): | ||||
|         context.run_migrations() | ||||
|  | ||||
|  | ||||
| def do_run_migrations(connection: Connection) -> None: | ||||
|     context.configure(connection=connection, target_metadata=target_metadata) | ||||
|  | ||||
|     with context.begin_transaction(): | ||||
|         context.run_migrations() | ||||
|  | ||||
|  | ||||
| async def run_async_migrations() -> None: | ||||
|     """In this scenario we need to create an Engine | ||||
|     and associate a connection with the context. | ||||
|  | ||||
|     """ | ||||
|  | ||||
|     connectable = async_engine_from_config( | ||||
|         config.get_section(config.config_ini_section, {}), | ||||
|         prefix="sqlalchemy.", | ||||
|         poolclass=pool.NullPool, | ||||
|     ) | ||||
|  | ||||
|     async with connectable.connect() as connection: | ||||
|         await connection.run_sync(do_run_migrations) | ||||
|  | ||||
|     await connectable.dispose() | ||||
|  | ||||
|  | ||||
| def run_migrations_online() -> None: | ||||
|     """Run migrations in 'online' mode.""" | ||||
|  | ||||
|     asyncio.run(run_async_migrations()) | ||||
|  | ||||
|  | ||||
| if context.is_offline_mode(): | ||||
|     run_migrations_offline() | ||||
| else: | ||||
|     run_migrations_online() | ||||
							
								
								
									
										26
									
								
								lkeep/database/alembic/script.py.mako
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								lkeep/database/alembic/script.py.mako
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| """${message} | ||||
|  | ||||
| Revision ID: ${up_revision} | ||||
| Revises: ${down_revision | comma,n} | ||||
| Create Date: ${create_date} | ||||
|  | ||||
| """ | ||||
| from typing import Sequence, Union | ||||
|  | ||||
| from alembic import op | ||||
| import sqlalchemy as sa | ||||
| ${imports if imports else ""} | ||||
|  | ||||
| # revision identifiers, used by Alembic. | ||||
| revision: str = ${repr(up_revision)} | ||||
| down_revision: Union[str, None] = ${repr(down_revision)} | ||||
| branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} | ||||
| depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} | ||||
|  | ||||
|  | ||||
| def upgrade() -> None: | ||||
|     ${upgrades if upgrades else "pass"} | ||||
|  | ||||
|  | ||||
| def downgrade() -> None: | ||||
|     ${downgrades if downgrades else "pass"} | ||||
| @@ -0,0 +1,52 @@ | ||||
| """Create User Table | ||||
|  | ||||
| Revision ID: ccf7560dd457 | ||||
| Revises: | ||||
| Create Date: 2025-01-10 17:06:05.585011 | ||||
|  | ||||
| """ | ||||
|  | ||||
| from collections.abc import Sequence | ||||
|  | ||||
| import sqlalchemy as sa | ||||
| from alembic import op | ||||
|  | ||||
| # revision identifiers, used by Alembic. | ||||
| revision: str = "ccf7560dd457" | ||||
| down_revision: str | None = None | ||||
| branch_labels: str | Sequence[str] | None = None | ||||
| depends_on: str | Sequence[str] | None = None | ||||
|  | ||||
|  | ||||
| def upgrade() -> None: | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.create_table( | ||||
|         "user", | ||||
|         sa.Column("email", sa.String(length=100), nullable=False), | ||||
|         sa.Column("hashed_password", sa.Text(), nullable=False), | ||||
|         sa.Column("is_active", sa.Boolean(), nullable=False), | ||||
|         sa.Column("is_superuser", sa.Boolean(), nullable=False), | ||||
|         sa.Column("is_verified", sa.Boolean(), nullable=False), | ||||
|         sa.Column("id", sa.UUID(), nullable=False), | ||||
|         sa.Column( | ||||
|             "created_at", | ||||
|             sa.DateTime(timezone=True), | ||||
|             server_default=sa.text("now()"), | ||||
|             nullable=False, | ||||
|         ), | ||||
|         sa.Column( | ||||
|             "updated_at", | ||||
|             sa.DateTime(timezone=True), | ||||
|             server_default=sa.text("now()"), | ||||
|             nullable=False, | ||||
|         ), | ||||
|         sa.PrimaryKeyConstraint("id"), | ||||
|         sa.UniqueConstraint("email"), | ||||
|     ) | ||||
|     # ### end Alembic commands ### | ||||
|  | ||||
|  | ||||
| def downgrade() -> None: | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.drop_table("user") | ||||
|     # ### end Alembic commands ### | ||||
		Reference in New Issue
	
	Block a user