diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c76da2982249392009c04fc1aeb5fc051cdd0698..220fc31823662b6611b520addf1dfb2b6a7b7dec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,6 +21,7 @@ jobs: # All of these options are optional, so you can remove them if you are happy with the defaults concurrent_skipping: 'same_content' skip_after_successful_duplicate: 'true' + paths_ignore: '["**/README.md"]' pytest: needs: pre_job if: ${{ needs.pre_job.outputs.should_skip != 'true' }} diff --git a/README.md b/README.md index 3b58385d83ede6c2e6887a9397a6fe12551558ad..cdd5bb5d57c8fbb5b0605ddb2b36ee9e9a43c4eb 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ python3 -m fastapi_template One of the coolest features is that this project is extremely small and handy. You can choose between different databases and even ORMs. -Currently SQLAlchemy1.4 and TortoiseORM are supported. +Currently SQLAlchemy1.4, TortoiseORM and Ormar are supported. TUI and CLI and excellent code documentation. @@ -56,7 +56,7 @@ $ python -m fastapi_template --help usage: FastAPI template [-h] [--version] [--name PROJECT_NAME] [--description PROJECT_DESCRIPTION] [--db {none,sqlite,mysql,postgresql}] - [--orm {sqlalchemy,tortoise}] + [--orm {ormar,sqlalchemy,tortoise}] [--ci {none,gitlab,github}] [--redis] [--migrations] [--kube] [--dummy] [--routers] [--swagger] [--force] @@ -68,7 +68,7 @@ optional arguments: Project description --db {none,sqlite,mysql,postgresql} Database - --orm {sqlalchemy,tortoise} + --orm {ormar,sqlalchemy,tortoise} ORM --ci {none,gitlab,github} Choose CI support diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py index c0f0fded043ce8033684c7891eaca9fc5250de77..b0933e1890ab604c6171e33a587f3dd49fff204d 100644 --- a/fastapi_template/cli.py +++ b/fastapi_template/cli.py @@ -56,7 +56,7 @@ def parse_args(): "--orm", help="ORM", type=str, - choices=list(map(attrgetter("value"), ORM)), + choices=[orm.value for orm in ORM if orm != ORM.none], default=None, dest="orm", ) @@ -65,7 +65,7 @@ def parse_args(): help="Choose CI support", default=None, type=str, - choices=list(map(attrgetter("value"), CIType)), + choices=[ci.value for ci in CIType], dest="ci_type", ) parser.add_argument( @@ -192,7 +192,7 @@ def read_user_input(current_context: BuilderContext) -> BuilderContext: current_context.orm = radiolist_dialog( "ORM", text="Which ORM do you want?", - values=[(orm, orm.value) for orm in list(ORM)], + values=[(orm, orm.value) for orm in list(ORM) if orm != ORM.none], ).run() if current_context.orm is None: raise KeyboardInterrupt() diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml index f0d843b1d537bc00e7688f02574eeb2dad9f7131..1046b7898bf084bd158356d8efb60a38228942df 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/app.yml @@ -28,11 +28,13 @@ spec: args: - -c - >- + {%- if cookiecutter.enable_migrations == "True" %} {%- if cookiecutter.orm in ['sqlalchemy', 'ormar'] %} alembic upgrade head && {%- elif cookiecutter.orm == 'tortoise' %} aerich upgrade && {%- endif %} + {%- endif %} python -m {{cookiecutter.project_name }} {%- endif %} env: @@ -40,12 +42,10 @@ spec: value: "0.0.0.0" - name: {{cookiecutter.project_name | upper }}_WORKERS_COUNT value: "10" - {%- if cookiecutter.db_info.name != "none" %} - {%- if cookiecutter.db_info.name != "sqlite" %} + {%- if cookiecutter.db_info.name not in ["none", "sqlite"] %} - name: {{cookiecutter.project_name | upper }}_DB_HOST value: "{{cookiecutter.kube_name}}-db-service" {%- endif %} - {%- endif %} {%- if cookiecutter.enable_redis == 'True' %} - name: {{cookiecutter.project_name | upper }}_REDIS_HOST value: "{{cookiecutter.kube_name}}-redis-service" diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml index 32af3d7af557e0947dacd643b890f6029cadd09c..4bd8efe248c8ea77c83ee58bd07bdcee86f88e4a 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/kube/db.yml @@ -53,6 +53,7 @@ spec: - port: {{cookiecutter.db_info.port}} targetPort: {{cookiecutter.db_info.port}} --- +{%- if cookiecutter.enable_migrations == "True" %} apiVersion: batch/v1 kind: Job metadata: @@ -87,5 +88,5 @@ spec: command: ["./wait-for-it.sh", "-t", "60", "{{cookiecutter.kube_name}}-db-service:{{cookiecutter.db_info.port}}"] restartPolicy: Never - --- +{%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py index e7e37372c4c579e61873687728bebc2d38156d0c..791eedbd2b02cfcd94974e81e6dacbb80e93a8b9 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_tortoise/config.py @@ -9,7 +9,7 @@ TORTOISE_CONFIG = { # noqa: WPS407 }, "apps": { "models": { - "models": ["aerich.models"] + MODELS_MODULES, + "models": MODELS_MODULES {%- if cookiecutter.enable_migrations == "True" %} + ["aerich.models"] {%- endif %} , "default_connection": "default", }, }, diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py index cc5e51b634ab1b7e13206eb7f2f2c0f9fd2ec6d8..9a176ff819e7b2d8799f5d73d85a20e4f9942cca 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/application.py @@ -57,7 +57,14 @@ def get_app() -> FastAPI: {% endif %} {%- if cookiecutter.orm == 'tortoise' %} - register_tortoise(app, config=TORTOISE_CONFIG, add_exception_handlers=True) + register_tortoise( + app, + config=TORTOISE_CONFIG, + add_exception_handlers=True, + {%- if cookiecutter.enable_migrations == "False" %} + generate_schemas=True, + {%- endif %} + ) {%- endif %} return app diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py index 13a189ff9c96a484893f40e9c11de27fbd5a9c7e..958c8e8e9f8bbb30e327da2c89e70787c0e301a2 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py @@ -10,6 +10,11 @@ import aioredis {%- if cookiecutter.orm == "ormar" %} from {{cookiecutter.project_name}}.db.config import database +{%- if cookiecutter.db_info.name != "none" and cookiecutter.enable_migrations == "False" %} +from sqlalchemy.engine import create_engine +from {{cookiecutter.project_name}}.db.meta import meta +from {{cookiecutter.project_name}}.db.models import load_all_models +{%- endif %} {%- endif %} {%- if cookiecutter.orm == "sqlalchemy" %} @@ -21,6 +26,11 @@ from sqlalchemy.ext.asyncio import ( ) from sqlalchemy.orm import sessionmaker +{%- if cookiecutter.db_info.name != "none" and cookiecutter.enable_migrations == "False" %} +from {{cookiecutter.project_name}}.db.meta import meta +from {{cookiecutter.project_name}}.db.models import load_all_models +{%- endif %} + def _setup_db(app: FastAPI) -> None: """ @@ -47,11 +57,34 @@ def _setup_db(app: FastAPI) -> None: {% if cookiecutter.enable_redis == "True" %} def _setup_redis(app: FastAPI) -> None: + """ + Initialize redis connection. + + :param app: current FastAPI app. + """ app.state.redis_pool = aioredis.ConnectionPool.from_url( str(settings.redis_url), ) {%- endif %} +{%- if cookiecutter.db_info.name != "none" and cookiecutter.enable_migrations == "False" %} +{%- if cookiecutter.orm in ["ormar", "sqlalchemy"] %} +async def _create_tables() -> None: + """Populates tables in the database.""" + load_all_models() + {%- if cookiecutter.orm == "ormar" %} + engine = create_engine(str(settings.db_url)) + with engine.connect() as connection: + meta.create_all(connection) + engine.dispose() + {%- elif cookiecutter.orm == "sqlalchemy" %} + engine = create_async_engine(str(settings.db_url)) + async with engine.begin() as connection: + await connection.run_sync(meta.create_all) + await engine.dispose() + {%- endif %} +{%- endif %} +{%- endif %} def startup(app: FastAPI) -> Callable[[], Awaitable[None]]: """ @@ -67,9 +100,14 @@ def startup(app: FastAPI) -> Callable[[], Awaitable[None]]: async def _startup() -> None: # noqa: WPS430 {%- if cookiecutter.orm == "sqlalchemy" %} _setup_db(app) - {% elif cookiecutter.orm == "ormar" %} + {%- elif cookiecutter.orm == "ormar" %} await database.connect() {%- endif %} + {%- if cookiecutter.db_info.name != "none" and cookiecutter.enable_migrations == "False" %} + {%- if cookiecutter.orm in ["ormar", "sqlalchemy"] %} + await _create_tables() + {%- endif %} + {%- endif %} {%- if cookiecutter.enable_redis == "True" %} _setup_redis(app) {%- endif %} diff --git a/pyproject.toml b/pyproject.toml index 180b2979215fc6ad37fe57a1012a42cceb49417f..b6d18981f3e52c17e607b326839ca71cc2492d82 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "fastapi_template" -version = "3.2.0" +version = "3.2.1" description = "Feature-rich robust FastAPI template" authors = ["Pavel Kirilin <win10@list.ru>"] packages = [{ include = "fastapi_template" }]