From 7c70f82a21bab9e9a97796d137e1658502d2cedf Mon Sep 17 00:00:00 2001 From: VinokurovVE Date: Wed, 31 Jul 2024 22:56:31 +0900 Subject: [PATCH] =?UTF-8?q?=D0=91=D0=BE=D0=BB=D1=8C=D1=88=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B5=D0=BC?= =?UTF-8?q?=20Alembic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend-app/alembic.ini | 114 +++++++++++++++ backend-app/alembic/README | 1 + backend-app/alembic/env.py | 90 ++++++++++++ backend-app/alembic/script.py.mako | 26 ++++ ...9-12fb0121c6a3_create_tables_user_login.py | 54 +++++++ backend-app/core/__init__.py | 4 + backend-app/core/models/__init__.py | 7 + backend-app/core/models/base.py | 5 + backend-app/core/models/db_helper.py | 41 ++++++ backend-app/core/settings.py | 18 ++- backend-app/database.py | 23 --- backend-app/main.py | 19 ++- backend-app/repo/__init__.py | 5 + backend-app/repo/{model.py => models.py} | 12 +- backend-app/repo/{schema.py => schemas.py} | 0 backend-app/utils/__init__.py | 0 poetry.lock | 133 +++++++++++++++++- pyproject.toml | 2 + readme.md | 9 +- 19 files changed, 526 insertions(+), 37 deletions(-) create mode 100644 backend-app/alembic.ini create mode 100644 backend-app/alembic/README create mode 100644 backend-app/alembic/env.py create mode 100644 backend-app/alembic/script.py.mako create mode 100644 backend-app/alembic/versions/2024_07_31_2249-12fb0121c6a3_create_tables_user_login.py create mode 100644 backend-app/core/models/__init__.py create mode 100644 backend-app/core/models/base.py create mode 100644 backend-app/core/models/db_helper.py delete mode 100644 backend-app/database.py rename backend-app/repo/{model.py => models.py} (80%) rename backend-app/repo/{schema.py => schemas.py} (100%) create mode 100644 backend-app/utils/__init__.py diff --git a/backend-app/alembic.ini b/backend-app/alembic.ini new file mode 100644 index 0000000..3d7d610 --- /dev/null +++ b/backend-app/alembic.ini @@ -0,0 +1,114 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# Use forward slashes (/) also on windows to provide an os agnostic path +script_location = 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 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: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 = 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 = %(here)s/.venv/bin/ruff +# ruff.options = --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +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 diff --git a/backend-app/alembic/README b/backend-app/alembic/README new file mode 100644 index 0000000..e0d0858 --- /dev/null +++ b/backend-app/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration with an async dbapi. \ No newline at end of file diff --git a/backend-app/alembic/env.py b/backend-app/alembic/env.py new file mode 100644 index 0000000..13c54c1 --- /dev/null +++ b/backend-app/alembic/env.py @@ -0,0 +1,90 @@ +import asyncio +from logging.config import fileConfig + +from sqlalchemy import pool +from sqlalchemy.engine import Connection +from sqlalchemy.ext.asyncio import async_engine_from_config +from core.settings import settings +from core.models.base import Base +from alembic import context + +# 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. +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 +config.set_main_option("sqlalchemy.url", settings.db.url) +# 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() diff --git a/backend-app/alembic/script.py.mako b/backend-app/alembic/script.py.mako new file mode 100644 index 0000000..fbc4b07 --- /dev/null +++ b/backend-app/alembic/script.py.mako @@ -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"} diff --git a/backend-app/alembic/versions/2024_07_31_2249-12fb0121c6a3_create_tables_user_login.py b/backend-app/alembic/versions/2024_07_31_2249-12fb0121c6a3_create_tables_user_login.py new file mode 100644 index 0000000..f04cc11 --- /dev/null +++ b/backend-app/alembic/versions/2024_07_31_2249-12fb0121c6a3_create_tables_user_login.py @@ -0,0 +1,54 @@ +"""create tables user, login + +Revision ID: 12fb0121c6a3 +Revises: +Create Date: 2024-07-31 22:49:23.147480 + +""" + +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = "12fb0121c6a3" +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "logins", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("username", sa.String(length=255), nullable=False), + sa.Column("password", sa.String(length=255), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("username"), + ) + op.create_table( + "users", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("firstname", sa.String(length=255), nullable=True), + sa.Column("lastname", sa.String(length=255), nullable=True), + sa.Column("age", sa.Integer(), nullable=True), + sa.Column("email", sa.String(length=255), nullable=True), + sa.Column("login_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["login_id"], + ["logins.id"], + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("email"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("users") + op.drop_table("logins") + # ### end Alembic commands ### diff --git a/backend-app/core/__init__.py b/backend-app/core/__init__.py index e69de29..13568a8 100644 --- a/backend-app/core/__init__.py +++ b/backend-app/core/__init__.py @@ -0,0 +1,4 @@ +__all__ = ( + "settings" +) +from .settings import settings \ No newline at end of file diff --git a/backend-app/core/models/__init__.py b/backend-app/core/models/__init__.py new file mode 100644 index 0000000..adb9f9e --- /dev/null +++ b/backend-app/core/models/__init__.py @@ -0,0 +1,7 @@ +__all__ = ( + "db_helper", + "Base" +) +from .db_helper import db_helper +from .base import Base +from repo.models import LoginModel, UserModel \ No newline at end of file diff --git a/backend-app/core/models/base.py b/backend-app/core/models/base.py new file mode 100644 index 0000000..d4c131f --- /dev/null +++ b/backend-app/core/models/base.py @@ -0,0 +1,5 @@ +from sqlalchemy.orm import DeclarativeBase + +class Base(DeclarativeBase): + pass + diff --git a/backend-app/core/models/db_helper.py b/backend-app/core/models/db_helper.py new file mode 100644 index 0000000..c773f30 --- /dev/null +++ b/backend-app/core/models/db_helper.py @@ -0,0 +1,41 @@ +from sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine, async_sessionmaker, AsyncSession +from typing import AsyncGenerator +from core.settings import settings +class DatabaseHelper: + def __init__( + self, + url: str, + echo:bool=False, + echo_pool:bool=False, + pool_size: int = 5, + max_overflow: int =10, + ) -> None: + self.engine: AsyncEngine = create_async_engine( + url=url, + echo=echo, + echo_pool=echo_pool, + pool_size=pool_size, + max_overflow = max_overflow + ) + self.session_factory: async_sessionmaker[AsyncSession] = async_sessionmaker( + bind= self.engine, + autoflush=False, + autocommit=False, + expire_on_commit=False + ) + + async def dispose(self) -> None: + await self.engine.dispose() + + async def session_getter(self) -> AsyncGenerator[AsyncSession,None]: + async with self.session_factory() as session: + yield session + +db_helper = DatabaseHelper( + url = settings.db.url, + echo = settings.db.echo, + echo_pool = settings.db.echo_pool, + pool_size = settings.db.pool_size, + max_overflow = settings.db.max_overflow, + +) \ No newline at end of file diff --git a/backend-app/core/settings.py b/backend-app/core/settings.py index bd68ab5..6c7550c 100644 --- a/backend-app/core/settings.py +++ b/backend-app/core/settings.py @@ -1,11 +1,25 @@ -from pydantic_settings import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import BaseModel class RunSettings(BaseModel): host: str = "0.0.0.0" port: int = 8000 +class DatabaseConfig(BaseModel): + url: str + echo:bool=False + echo_pool:bool=False + pool_size: int = 50 + max_overflow: int =10 + class Settings(BaseSettings): + model_config = SettingsConfigDict( + env_file=".env", + case_sensitive=False, + env_nested_delimiter="__", + env_prefix="APP_CONFIG__" + ) run: RunSettings = RunSettings() + db: DatabaseConfig -settings = Settings() \ No newline at end of file +settings = Settings() diff --git a/backend-app/database.py b/backend-app/database.py deleted file mode 100644 index a1a4cf2..0000000 --- a/backend-app/database.py +++ /dev/null @@ -1,23 +0,0 @@ -from sqlalchemy.ext.asyncio import async_sessionmaker, AsyncSession, create_async_engine -from sqlalchemy.orm import DeclarativeBase - -class Model(DeclarativeBase): - pass - - -async_engine=create_async_engine( - url = "mssql+aioodbc://sa:159357"\ - "@10.124.30.208:1433/test_db"\ - "?driver=ODBC+Driver+17+for+SQL+Server", - connect_args={"check_same_thread": False} - ) - -async_session = async_sessionmaker( - async_engine, - autoflush=True, - autocommit=False, - expire_on_commit =False - ) -# async def get_async_session() -> AsyncSession: -# async with async_session() as session: -# session. \ No newline at end of file diff --git a/backend-app/main.py b/backend-app/main.py index a6c86fc..157097e 100644 --- a/backend-app/main.py +++ b/backend-app/main.py @@ -4,21 +4,32 @@ from fastapi.responses import ORJSONResponse from core.settings import settings from api.main_router import router as main_router from api.another_router import router as another_router +from core.models import db_helper +from contextlib import asynccontextmanager +@asynccontextmanager +async def lifespan(app: FastAPI): + #start up + yield + #shutdown + await db_helper.dispose() -app = FastAPI(default_response_class=ORJSONResponse) +main_app = FastAPI( + lifespan=lifespan, + default_response_class=ORJSONResponse + ) #api/hello -app.include_router( +main_app.include_router( router=main_router, prefix="/api", tags=["Основной роутер"] ) -app.include_router( +main_app.include_router( router=another_router, prefix="/another", tags=["Побочный роутер"] ) if __name__ == "__main__": - uvicorn.run(app, host=settings.run.host, port=settings.run.port) \ No newline at end of file + uvicorn.run(main_app, host=settings.run.host, port=settings.run.port) \ No newline at end of file diff --git a/backend-app/repo/__init__.py b/backend-app/repo/__init__.py index e69de29..e9ec424 100644 --- a/backend-app/repo/__init__.py +++ b/backend-app/repo/__init__.py @@ -0,0 +1,5 @@ +__all__ = ( + "UserModel", + "LoginModel" +) +from .models import UserModel, LoginModel \ No newline at end of file diff --git a/backend-app/repo/model.py b/backend-app/repo/models.py similarity index 80% rename from backend-app/repo/model.py rename to backend-app/repo/models.py index 61d1adb..34566f0 100644 --- a/backend-app/repo/model.py +++ b/backend-app/repo/models.py @@ -1,21 +1,21 @@ -from database import Model +from core.models import Base from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy import String, Integer, Boolean, ForeignKey from typing import Optional, Sequence -class UserModel(Model): +class UserModel(Base): __tablename__ ="users" id: Mapped[int] = mapped_column(Integer, primary_key=True) firstname: Mapped[str | None]= mapped_column(String(255), nullable=True) lastname: Mapped[str | None]= mapped_column(String(255), nullable=True) age: Mapped[int | None] = mapped_column(Integer, nullable=True) - email: Mapped[str | None] = mapped_column(String(255), nullable=True) + email: Mapped[str | None] = mapped_column(String(255), nullable=True, unique=True) login_id: Mapped[int] = mapped_column(Integer, ForeignKey("logins.id")) -class LoginModel(Model): +class LoginModel(Base): __tablename__ ="logins" id: Mapped[int] = mapped_column(Integer, primary_key=True) - login: Mapped[str]= mapped_column(String(255), nullable=False) - password: Mapped[str]= mapped_column(String(255), nullable=False) + username: Mapped[str]= mapped_column(String(255), nullable=False, unique=True) + password: Mapped[str]= mapped_column(String(255), nullable=False) \ No newline at end of file diff --git a/backend-app/repo/schema.py b/backend-app/repo/schemas.py similarity index 100% rename from backend-app/repo/schema.py rename to backend-app/repo/schemas.py diff --git a/backend-app/utils/__init__.py b/backend-app/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock index 67682ac..26b3108 100644 --- a/poetry.lock +++ b/poetry.lock @@ -14,6 +14,25 @@ files = [ [package.dependencies] pyodbc = ">=5.0.1" +[[package]] +name = "alembic" +version = "1.13.2" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, + {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, +] + +[package.dependencies] +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + [[package]] name = "annotated-types" version = "0.7.0" @@ -45,6 +64,50 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + [[package]] name = "certifi" version = "2024.7.4" @@ -361,6 +424,25 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "mako" +version = "1.3.5" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -465,6 +547,17 @@ files = [ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + [[package]] name = "orjson" version = "3.10.6" @@ -525,6 +618,44 @@ files = [ {file = "orjson-3.10.6.tar.gz", hash = "sha256:e54b63d0a7c6c54a5f5f726bc93a2078111ef060fec4ecbf34c5db800ca3b3a7"}, ] +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + [[package]] name = "pydantic" version = "2.8.2" @@ -1222,4 +1353,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "c354748ac55aa1d7c444388106be2183e21b59f54bf1629a6b5b8c7da70eaf97" +content-hash = "c1080a2638fb671a736b742ed0b4f44863e6cd4d3f36153e4f5f20fed11cf559" diff --git a/pyproject.toml b/pyproject.toml index f031dca..084e147 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,3 +8,5 @@ pydantic-settings = "^2.4.0" sqlalchemy = {extras = ["asyncio"], version = "^2.0.31"} aioodbc = "^0.5.0" orjson = "^3.10.6" +alembic = "^1.13.2" +black = "^24.4.2" diff --git a/readme.md b/readme.md index 384edd5..3279137 100644 --- a/readme.md +++ b/readme.md @@ -2,7 +2,9 @@ 2. Установка poetry pip install poetry 3. Создание папки проекта и добавить в него файл pyproject.toml с данными +[markdown] [tool.poetry] +[markdown] package-mode = false [tool.poetry.dependencies] python = "^3.12" @@ -12,4 +14,9 @@ poetry install poetry add fastapi uvicorn[standart] pydantic-settings sqlalchemy[asyncio] aioodbc orjson 6. Создаем гит проект для нашего приложения git init -7. Добавляем файл .gitignore \ No newline at end of file +7. Добавляем файл .gitignore + +8. Alembic +alembic revision --autogenerate -m "create tables user, login" + +alembic upgrade head \ No newline at end of file