From e44bbb8176a2d91c48d1edd6209dbb90c0a3b265 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 23 Aug 2023 22:12:24 +0800 Subject: [PATCH 01/21] feature: add MongoDB support Signed-off-by: Tsung-Ju Lii --- fastapi_template/cli.py | 34 ++++++++++++++++++- .../conditional_files.json | 9 ++++- .../deploy/docker-compose.dev.yml | 1 + .../deploy/docker-compose.yml | 12 +++++++ .../pyproject.toml | 5 +++ .../db_beanie/models/__init__.py | 1 + .../db_beanie/models/dummy_model.py | 5 +++ .../web/api/dummy/schema.py | 13 +++++++ .../web/lifetime.py | 20 ++++++++++- 9 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/dummy_model.py diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py index 58059f4..08a015d 100644 --- a/fastapi_template/cli.py +++ b/fastapi_template/cli.py @@ -176,6 +176,23 @@ def checker(ctx: BuilderContext) -> bool: port=5432, ), ), + MenuEntry( + code="mongodb", + user_view="MongoDB", + description=( + "{name} is one of the most popular NoSQL databases out there.".format( + name=colored("MongoDB", color="green"), + ) + ), + additional_info=Database( + name="mongodb", + image="mongo:4.2", + async_driver="beanie", + driver_short="pymongo", + driver="pymongo", + port=27017 + ), + ) ], ) @@ -234,7 +251,7 @@ def checker(ctx: BuilderContext) -> bool: entries=[ MenuEntry( code="none", - user_view="Whithout ORMs", + user_view="Without ORMs", description=( "If you select this option, you will get only {what}.\n" "The rest {warn}.".format( @@ -246,6 +263,7 @@ def checker(ctx: BuilderContext) -> bool: MenuEntry( code="ormar", user_view="Ormar", + is_hidden=check_db(["sqlite", "mysql", "postgresql"]), pydantic_v1=True, description=( "{what} is a great {feature} ORM.\n" @@ -258,6 +276,7 @@ def checker(ctx: BuilderContext) -> bool: MenuEntry( code="sqlalchemy", user_view="SQLAlchemy", + is_hidden=check_db(["sqlite", "mysql", "postgresql"]), description=( "{what} is the most popular python ORM.\n" "It has a {feature} and a big community around it.".format( @@ -269,6 +288,7 @@ def checker(ctx: BuilderContext) -> bool: MenuEntry( code="tortoise", user_view="Tortoise", + is_hidden=check_db(["sqlite", "mysql", "postgresql"]), description=( "{what} is a great {feature} ORM.\n" "It's easy to use, it has it's own migration tooling.".format( @@ -302,6 +322,18 @@ def checker(ctx: BuilderContext) -> bool: ) ), ), + MenuEntry( + code="beanie", + user_view="Beanie", + is_hidden=check_db(["mongodb"]), + description=( + "{what} is an asynchronous object-document mapper (ODM) for MongoDB.\n" + "Data models are based on Pydantic.".format( + what=colored("Beanie", color="green"), + ) + ), + ), + ], ) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json index 54b3875..c3925b4 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json +++ b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json @@ -62,7 +62,7 @@ ] }, "Postgres and MySQL support": { - "enabled": "{{cookiecutter.db_info.name != 'sqlite'}}", + "enabled": "{{cookiecutter.db_info.name not in ['sqlite', 'mongodb']}}", "resources": [ "deploy/kube/db.yml" ] @@ -136,6 +136,7 @@ "{{cookiecutter.project_name}}/tests/test_dummy.py", "{{cookiecutter.project_name}}/db_piccolo/dao", "{{cookiecutter.project_name}}/db_piccolo/models/dummy_model.py", + "{{cookiecutter.project_name}}/db_beanie/models/dummy_model.py", "{{cookiecutter.project_name}}/db_sa/migrations/versions/2021-08-16-16-55_2b7380507a71.py", "{{cookiecutter.project_name}}/db_ormar/migrations/versions/2021-08-16-16-55_2b7380507a71.py", "{{cookiecutter.project_name}}/db_tortoise/migrations/models/1_20210928165300_init_dummy_pg.sql", @@ -182,6 +183,12 @@ "{{cookiecutter.project_name}}/piccolo_conf.py" ] }, + "Beanie": { + "enabled": "{{cookiecutter.orm == 'beanie'}}", + "resources": [ + "{{cookiecutter.project_name}}/db_beanie" + ] + } "Postgresql DB": { "enabled": "{{cookiecutter.db_info.name == 'postgresql'}}", "resources": [ diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.dev.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.dev.yml index 3a2c37c..9a80fb8 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.dev.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.dev.yml @@ -6,6 +6,7 @@ services: # Exposes application port. - "8000:8000" build: + context: . target: dev volumes: # Adds current directory as volume. diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml index 36270cf..3f143db 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml @@ -103,6 +103,18 @@ services: retries: 40 {%- endif %} + {%- if cookiecutter.db_info.name == "mongodb"%} + db: + image: {{cookiecutter.db_info.image}} + port: + - "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" + restart: always + environment: + MONGO_INITDB_ROOT_USERNAME: "{{cookiecutter.project_name}}" + MONGO_INITDB_ROOT_PASSWORD: "{{cookiecutter.project_name}}" + command: "mongod" + {%- endif %} + {%- if cookiecutter.db_info.name == "mysql" %} db: diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml index 1f3e922..c403168 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml @@ -86,6 +86,11 @@ aiofiles = "^23.1.0" psycopg = { version = "^3.1.9", extras = ["binary", "pool"] } {%- endif %} httptools = "^0.6.0" +{%- if cookiecutter.orm == "beanie" %} +beanie = "^1.21.0" +{%- else %} +pymongo = "^4.5.0" +{%- endif %} {%- if cookiecutter.api_type == "graphql" %} strawberry-graphql = { version = "^0.194.4", extras = ["fastapi"] } {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py new file mode 100644 index 0000000..8ebc7f6 --- /dev/null +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py @@ -0,0 +1 @@ +"""{{cookiecutter.project_name}} models.""" \ No newline at end of file diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/dummy_model.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/dummy_model.py new file mode 100644 index 0000000..ea18756 --- /dev/null +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/dummy_model.py @@ -0,0 +1,5 @@ +from beanie import Document + +class DummyModel(Document): + """Model for demo purpose.""" + name: str \ No newline at end of file diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index a6553e7..f34adf8 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -4,6 +4,10 @@ from pydantic import ConfigDict {%- endif %} +{%- if cookiecutter.db_info.name == "mongodb" %} +from pydantic import field_validator +from bson import ObjectId +{%- endif %} class DummyModelDTO(BaseModel): @@ -13,7 +17,16 @@ class DummyModelDTO(BaseModel): It returned when accessing dummy models from the API. """ + {%- if cookiecutter.dbinfo.name != "mongodb" %} id: int + {%- else %} + id: str + + @field_validator("id", mode="before") + @classmethod + def parse_object_id(cls, document_id: ObjectId) -> str: + return str(document_id) + {%- endif %} name: str 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 f99fa2e..45823bd 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 @@ -120,6 +120,24 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover app.state.db_session_factory = session_factory {%- endif %} +{%- if cookiecutter.orm == "beanie" %} +import beanie +from motor.motor_asyncio import AsyncIOMotorClient +from {{cookiecutter.projecT_name}}.db.models.dummy_model import DummyModel +async def _setup_db(app: FastAPI) -> None: + client = AsyncIOMotorClient( + f"mongodb://{settings.db_user}:{settings.db_pass}" + + f"@{settings.db_host}:{settings.db_port}/{settings.db_base}" + ) + app.state.db_client = client + await beanie.init_beanie( + database=client[settings.db_base], + document_models=[ + DummyModel, # type: ignore + ] + ) +{%- endif %} + {%- if cookiecutter.enable_migrations != "True" %} {%- if cookiecutter.orm in ["ormar", "sqlalchemy"] %} async def _create_tables() -> None: # pragma: no cover @@ -276,7 +294,7 @@ async def _startup() -> None: # noqa: WPS430 _setup_db(app) {%- elif cookiecutter.orm == "ormar" %} await database.connect() - {%- elif cookiecutter.orm == "psycopg" %} + {%- elif cookiecutter.orm in ["beanie", "psycopg"] %} await _setup_db(app) {%- endif %} {%- if cookiecutter.db_info.name != "none" and cookiecutter.enable_migrations != "True" %} From 2ff120d964b94fbba10a53c6fe8ff9511fc8145e Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 23 Aug 2023 22:33:53 +0800 Subject: [PATCH 02/21] make cookiecutter recognize beanie stuff --- .../conditional_files.json | 15 +- .../replaceable_files.json | 3 +- .../web/api/dummy/schema.py | 2 +- .../web/lifetime.py | 2 +- poetry.lock | 165 +++++++++++++++++- pyproject.toml | 1 + 6 files changed, 182 insertions(+), 6 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json index c3925b4..e3648b2 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json +++ b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json @@ -56,11 +56,22 @@ "alembic.ini", "{{cookiecutter.project_name}}/web/api/dummy", "{{cookiecutter.project_name}}/web/gql/dummy", - "{{cookiecutter.project_name}}/db_sa", "{{cookiecutter.project_name}}/tests/test_dummy.py", "deploy/kube/db.yml" ] }, + "SQLAlchemy support": { + "enabled": "{{cookiecutter.db_info.name not in ['none', 'mongodb']}}", + "resources": [ + "{{cookiecutter.project_name}}/db_sa" + ] + }, + "Beanie support": { + "enabled": "{{cookiecutter.db_info.name == 'mongodb'}}", + "resources": [ + "{{cookiecutter.project_name}}/db_beanie" + ] + }, "Postgres and MySQL support": { "enabled": "{{cookiecutter.db_info.name not in ['sqlite', 'mongodb']}}", "resources": [ @@ -188,7 +199,7 @@ "resources": [ "{{cookiecutter.project_name}}/db_beanie" ] - } + }, "Postgresql DB": { "enabled": "{{cookiecutter.db_info.name == 'postgresql'}}", "resources": [ diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json index 58edafb..29d2c07 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json +++ b/fastapi_template/template/{{cookiecutter.project_name}}/replaceable_files.json @@ -4,6 +4,7 @@ "{{cookiecutter.project_name}}/db_ormar", "{{cookiecutter.project_name}}/db_tortoise", "{{cookiecutter.project_name}}/db_psycopg", - "{{cookiecutter.project_name}}/db_piccolo" + "{{cookiecutter.project_name}}/db_piccolo", + "{{cookiecutter.project_name}}/db_beanie" ] } diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index f34adf8..1122cf0 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -17,7 +17,7 @@ class DummyModelDTO(BaseModel): It returned when accessing dummy models from the API. """ - {%- if cookiecutter.dbinfo.name != "mongodb" %} + {%- if cookiecutter.db_info.name != "mongodb" %} id: int {%- else %} id: str 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 45823bd..3068743 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 @@ -123,7 +123,7 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover {%- if cookiecutter.orm == "beanie" %} import beanie from motor.motor_asyncio import AsyncIOMotorClient -from {{cookiecutter.projecT_name}}.db.models.dummy_model import DummyModel +from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel async def _setup_db(app: FastAPI) -> None: client = AsyncIOMotorClient( f"mongodb://{settings.db_user}:{settings.db_pass}" diff --git a/poetry.lock b/poetry.lock index da7c968..039017b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -302,6 +302,79 @@ files = [ docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +[[package]] +name = "greenlet" +version = "2.0.2" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" +files = [ + {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, + {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, + {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, + {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, + {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, + {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, + {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, + {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, + {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, + {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, + {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, + {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, + {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, + {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, + {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, + {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, + {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, + {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, + {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, + {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, + {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, + {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, + {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, + {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, + {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, + {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, + {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, + {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, + {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, + {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, + {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, + {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, + {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, + {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, +] + +[package.extras] +docs = ["Sphinx", "docutils (<0.18)"] +test = ["objgraph", "psutil"] + [[package]] name = "identify" version = "2.5.24" @@ -429,6 +502,78 @@ files = [ {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, ] +[[package]] +name = "msgpack" +version = "1.0.5" +description = "MessagePack serializer" +optional = false +python-versions = "*" +files = [ + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, + {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, + {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, + {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, + {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, + {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, + {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, + {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, + {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, + {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, + {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, + {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, + {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, + {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, + {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, + {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, + {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, + {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, + {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, + {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, + {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, + {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, + {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, + {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, + {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, + {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, + {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, + {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, + {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, + {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, + {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, + {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, +] + [[package]] name = "nodeenv" version = "1.8.0" @@ -710,6 +855,24 @@ files = [ [package.extras] plugins = ["importlib-metadata"] +[[package]] +name = "pynvim" +version = "0.4.3" +description = "Python client to neovim" +optional = false +python-versions = "*" +files = [ + {file = "pynvim-0.4.3.tar.gz", hash = "sha256:3a795378bde5e8092fbeb3a1a99be9c613d2685542f1db0e5c6fd467eed56dff"}, +] + +[package.dependencies] +greenlet = "*" +msgpack = ">=0.5.0" + +[package.extras] +pyuv = ["pyuv (>=1.0.0)"] +test = ["pytest (>=3.4.0)"] + [[package]] name = "pytest" version = "6.2.5" @@ -1070,4 +1233,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ca51e2183f25683e533c30357685e4bcf8c0d75d92586fca2dbb0dfc20833908" +content-hash = "0a368636e28b463e5fc8e4935db409a8e1870996c19f185585305ade47beee84" diff --git a/pyproject.toml b/pyproject.toml index 5373380..855c81c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ pytest-env = "^0.6.2" Faker = "^8.14.0" pytest-xdist = {version = "^2.5.0", extras = ["psutil"]} requests = "^2.28.1" +pynvim = "^0.4.3" [tool.pytest.ini_options] minversion = "6.0" From 82b02a3077729799a311a9db65cb84ee4c357a96 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 23 Aug 2023 23:02:14 +0800 Subject: [PATCH 03/21] fix things --- .../template/{{cookiecutter.project_name}}/README.md | 11 +++++++++++ .../deploy/docker-compose.yml | 2 +- .../{{cookiecutter.project_name}}/settings.py | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/README.md b/fastapi_template/template/{{cookiecutter.project_name}}/README.md index c1d06fe..8b53672 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/README.md +++ b/fastapi_template/template/{{cookiecutter.project_name}}/README.md @@ -51,7 +51,9 @@ $ tree "{{cookiecutter.project_name}}" ├── conftest.py # Fixtures for all tests. {%- if cookiecutter.db_info.name != "none" %} ├── db # module contains db configurations +{%- if cookiecutter.db_info.name != "mongodb" %} │   ├── dao # Data Access Objects. Contains different classes to interact with database. +{%- endif %} │   └── models # Package contains different models for ORMs. {%- endif %} ├── __main__.py # Startup script. Starts uvicorn. @@ -130,6 +132,15 @@ By default it runs: You can read more about pre-commit here: https://pre-commit.com/ +{%- if cookiecutter.db_info.name == 'mongodb' %} +## MongoDB + +Start a MongoDB container via docker compose by: +```shell +docker-compose -f deploy/docker-compose.yml --project-directory . up -d db +``` +{%- endif %} + {%- if cookiecutter.enable_kube == 'True' %} ## Kubernetes diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml index 3f143db..67e02db 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml @@ -106,7 +106,7 @@ services: {%- if cookiecutter.db_info.name == "mongodb"%} db: image: {{cookiecutter.db_info.image}} - port: + ports: - "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" restart: always environment: diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py index 534045c..8e54067 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/settings.py @@ -56,8 +56,12 @@ class Settings(BaseSettings): db_port: int = {{cookiecutter.db_info.port}} db_user: str = "{{cookiecutter.project_name}}" db_pass: str = "{{cookiecutter.project_name}}" + {%- if cookiecutter.db_info.name != "sqlite" %} + db_base: str = "admin" + {%- else %} db_base: str = "{{cookiecutter.project_name}}" {%- endif %} + {%- endif %} db_echo: bool = False {%- endif %} From 762a7d7b62f9bff33af10f1d0c3b448af574d920 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 23 Aug 2023 23:16:35 +0800 Subject: [PATCH 04/21] fix test imports and docstring --- .../tests/test_dummy.py | 13 +++++++++++++ .../web/api/dummy/schema.py | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py index 868035e..d2716f4 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py @@ -14,7 +14,10 @@ {%- endif %} from starlette import status +{%- if cookiecutter.orm != 'beanie' %} from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO + +{%- endif %} from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel @@ -56,8 +59,14 @@ async def test_creation( {%- elif cookiecutter.orm in ["tortoise", "ormar", "piccolo"] %} dao = DummyDAO() {%- endif %} + + {%- if cookiecutter.orm == "beanie" %} + instance = await DummyModel.find(DummyModel.name == test_name).first_or_none() + assert instance.name == test_name + {%- else %} instances = await dao.filter(name=test_name) assert instances[0].name == test_name + {%- endif %} @pytest.mark.anyio @@ -79,7 +88,11 @@ async def test_getting( dao = DummyDAO() {%- endif %} test_name = uuid.uuid4().hex + {%- if cookiecutter.orm == "beanie" %} + await DummyModel.insert_one(DummyModel(name=test_name)) + {%- else %} await dao.create_dummy_model(name=test_name) + {%- endif %} {%- if cookiecutter.api_type == 'rest' %} url = fastapi_app.url_path_for('get_dummy_models') diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index 1122cf0..d8ace70 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -25,6 +25,10 @@ class DummyModelDTO(BaseModel): @field_validator("id", mode="before") @classmethod def parse_object_id(cls, document_id: ObjectId) -> str: + """ + Validator for turning an incoming `ObjectId` into a + json serializable `str`. + """ return str(document_id) {%- endif %} name: str From a1c24ba77747c7b80d007eab7ed7eaddb56699e8 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 23 Aug 2023 23:21:20 +0800 Subject: [PATCH 05/21] test and validator --- .../{{cookiecutter.project_name}}/tests/test_dummy.py | 2 +- .../{{cookiecutter.project_name}}/web/api/dummy/schema.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py index d2716f4..3ab85de 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py @@ -62,7 +62,7 @@ async def test_creation( {%- if cookiecutter.orm == "beanie" %} instance = await DummyModel.find(DummyModel.name == test_name).first_or_none() - assert instance.name == test_name + assert instance is not None and instance.name == test_name {%- else %} instances = await dao.filter(name=test_name) assert instances[0].name == test_name diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index d8ace70..7dcd8ee 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -21,7 +21,10 @@ class DummyModelDTO(BaseModel): id: int {%- else %} id: str + {%- endif %} + name: str + {%- if cookiecutter.db_info.name == "mongodb" %} @field_validator("id", mode="before") @classmethod def parse_object_id(cls, document_id: ObjectId) -> str: @@ -31,7 +34,6 @@ def parse_object_id(cls, document_id: ObjectId) -> str: """ return str(document_id) {%- endif %} - name: str {%- if cookiecutter.pydanticv1 == "True" %} From 6ad4f3a361bf4ee92350dae60ae2f13537d71f2e Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 23 Aug 2023 23:36:15 +0800 Subject: [PATCH 06/21] fix dummy view --- .../web/api/dummy/views.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py index 365caed..5ec243c 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py @@ -2,7 +2,9 @@ from fastapi import APIRouter from fastapi.param_functions import Depends +{%- if cookiecutter.db_info.name != "mongodb" %} from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO +{%- endif %} from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel from {{cookiecutter.project_name}}.web.api.dummy.schema import (DummyModelDTO, DummyModelInputDTO) @@ -14,28 +16,44 @@ async def get_dummy_models( limit: int = 10, offset: int = 0, +{%- if cookiecutter.db_info.name != "mongodb" %} dummy_dao: DummyDAO = Depends(), +{%- endif %} ) -> List[DummyModel]: """ Retrieve all dummy objects from the database. :param limit: limit of dummy objects, defaults to 10. :param offset: offset of dummy objects, defaults to 0. +{%- if cookiecutter.db_info.name != "mongodb" %} :param dummy_dao: DAO for dummy models. +{%- endif %} :return: list of dummy objects from database. """ +{%- if cookiecutter.db_info.name != "mongodb" %} return await dummy_dao.get_all_dummies(limit=limit, offset=offset) +{%- else %} + return await DummyModel.find_all(skip=offset, limit=limit).to_list() +{%- endif %} @router.put("/") async def create_dummy_model( new_dummy_object: DummyModelInputDTO, +{%- if cookiecutter.db_info.name != "mongodb" %} dummy_dao: DummyDAO = Depends(), +{%- endif %} ) -> None: """ Creates dummy model in the database. :param new_dummy_object: new dummy model item. +{%- if cookiecutter.db_info.name != "mongodb" %} :param dummy_dao: DAO for dummy models. +{%- endif %} """ +{%- if cookiecutter.db_info.name != "mongodb" %} await dummy_dao.create_dummy_model(name=new_dummy_object.name) +{%- else %} + await DummyModel.insert_one(DummyModel(name=new_dummy_object.name)) +{%- endif %} From 9c98443dbc33a5efb1c3be137ac5a4af33f01e8c Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 10:39:08 +0800 Subject: [PATCH 07/21] make flake happy --- .../{{cookiecutter.project_name}}/web/api/dummy/schema.py | 6 ++++-- pyproject.toml | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index 7dcd8ee..adb6d52 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -29,8 +29,10 @@ class DummyModelDTO(BaseModel): @classmethod def parse_object_id(cls, document_id: ObjectId) -> str: """ - Validator for turning an incoming `ObjectId` into a - json serializable `str`. + Validator that converts `ObjectId` to json serializable `str`. + + :param document_id: Bson Id for this document. + :return: The converted str. """ return str(document_id) {%- endif %} diff --git a/pyproject.toml b/pyproject.toml index 855c81c..5373380 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,6 @@ pytest-env = "^0.6.2" Faker = "^8.14.0" pytest-xdist = {version = "^2.5.0", extras = ["psutil"]} requests = "^2.28.1" -pynvim = "^0.4.3" [tool.pytest.ini_options] minversion = "6.0" From d7c3207e108215b8340dfb2557ca16953513560b Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 14:37:47 +0800 Subject: [PATCH 08/21] use db_url to access mongodb Signed-off-by: Tsung-Ju Lii --- fastapi_template/cli.py | 6 +++--- .../deploy/docker-compose.yml | 15 ++++++++++++--- .../{{cookiecutter.project_name}}/web/lifetime.py | 5 +---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py index 08a015d..64d8128 100644 --- a/fastapi_template/cli.py +++ b/fastapi_template/cli.py @@ -186,10 +186,10 @@ def checker(ctx: BuilderContext) -> bool: ), additional_info=Database( name="mongodb", - image="mongo:4.2", + image="mongo:7.0", async_driver="beanie", - driver_short="pymongo", - driver="pymongo", + driver_short="mongodb", + driver="mongodb", port=27017 ), ) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml index 67e02db..f349b14 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml @@ -52,13 +52,17 @@ services: {%- if cookiecutter.db_info.name == "sqlite" %} {{cookiecutter.project_name | upper }}_DB_FILE: /db_data/db.sqlite3 {%- else %} - {{cookiecutter.project_name | upper}}_DB_HOST: {{cookiecutter.project_name}}-db + {{cookiecutter.project_name | upper}}_DB_HOST: {{cookiecutter.project_name}}_db_1 {{cookiecutter.project_name | upper}}_DB_PORT: {{cookiecutter.db_info.port}} {{cookiecutter.project_name | upper}}_DB_USER: {{cookiecutter.project_name}} {{cookiecutter.project_name | upper}}_DB_PASS: {{cookiecutter.project_name}} + {%- if cookiecutter.db_info.name == "mongodb" %} + {{cookiecutter.project_name | upper}}_DB_BASE: admin + {%- else %} {{cookiecutter.project_name | upper}}_DB_BASE: {{cookiecutter.project_name}} {%- endif %} {%- endif %} + {%- endif %} {%- if cookiecutter.enable_rmq == 'True' %} {{cookiecutter.project_name | upper }}_RABBIT_HOST: {{cookiecutter.project_name}}-rmq {%- endif %} @@ -106,13 +110,18 @@ services: {%- if cookiecutter.db_info.name == "mongodb"%} db: image: {{cookiecutter.db_info.image}} - ports: - - "{{cookiecutter.db_info.port}}:{{cookiecutter.db_info.port}}" restart: always environment: MONGO_INITDB_ROOT_USERNAME: "{{cookiecutter.project_name}}" MONGO_INITDB_ROOT_PASSWORD: "{{cookiecutter.project_name}}" command: "mongod" + volumes: + - {{cookiecutter.project_name}}-db-data:/data/db + healthcheck: + test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet + interval: 10s + timeout: 5s + retries: 40 {%- endif %} {%- if cookiecutter.db_info.name == "mysql" %} 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 3068743..9ee9994 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 @@ -125,10 +125,7 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover from motor.motor_asyncio import AsyncIOMotorClient from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel async def _setup_db(app: FastAPI) -> None: - client = AsyncIOMotorClient( - f"mongodb://{settings.db_user}:{settings.db_pass}" - + f"@{settings.db_host}:{settings.db_port}/{settings.db_base}" - ) + client = AsyncIOMotorClient(settings.db_url.human_repr()) app.state.db_client = client await beanie.init_beanie( database=client[settings.db_base], From 5da8c725ce3e3023f19f5d8b8c4d27e494a59fc9 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 14:57:53 +0800 Subject: [PATCH 09/21] dao for mongodb --- .../db_beanie/dao/__init__.py | 1 + .../db_beanie/dao/dummy_dao.py | 39 +++++++++++++++++++ .../tests/test_dummy.py | 16 +------- .../web/api/dummy/views.py | 14 ------- 4 files changed, 42 insertions(+), 28 deletions(-) create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/__init__.py create mode 100644 fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/__init__.py new file mode 100644 index 0000000..db62a0a --- /dev/null +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/__init__.py @@ -0,0 +1 @@ +"""DAO classes.""" diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py new file mode 100644 index 0000000..bb3239f --- /dev/null +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py @@ -0,0 +1,39 @@ +from typing import List, Optional + +from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel + + +class DummyDAO: + """Class for accessing dummy table.""" + + async def create_dummy_model(self, name: str) -> None: + """ + Add single dummy to session. + + :param name: name of a dummy. + """ + await DummyModel.insert_one(DummyModel(name=name)) + + async def get_all_dummies(self, limit: int, offset: int) -> List[DummyModel]: + """ + Get all dummy models with limit/offset pagination. + + :param limit: limit of dummies. + :param offset: offset of dummies. + :return: stream of dummies. + """ + return await DummyModel.find_all(skip=offset, limit=limit).to_list() + + async def filter( + self, + name: Optional[str] = None + ) -> List[DummyModel]: + """ + Get specific dummy model. + + :param name: name of dummy instance. + :return: dummy models. + """ + if name is None: + return [] + return await DummyModel.find(DummyModel.name == name).to_list() diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py index 3ab85de..63cf2cb 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py @@ -14,10 +14,7 @@ {%- endif %} from starlette import status -{%- if cookiecutter.orm != 'beanie' %} from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO - -{%- endif %} from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel @@ -56,17 +53,12 @@ async def test_creation( dao = DummyDAO(dbsession) {%- elif cookiecutter.orm == "psycopg" %} dao = DummyDAO(dbpool) - {%- elif cookiecutter.orm in ["tortoise", "ormar", "piccolo"] %} + {%- else %} dao = DummyDAO() {%- endif %} - {%- if cookiecutter.orm == "beanie" %} - instance = await DummyModel.find(DummyModel.name == test_name).first_or_none() - assert instance is not None and instance.name == test_name - {%- else %} instances = await dao.filter(name=test_name) assert instances[0].name == test_name - {%- endif %} @pytest.mark.anyio @@ -84,15 +76,11 @@ async def test_getting( dao = DummyDAO(dbsession) {%- elif cookiecutter.orm == "psycopg" %} dao = DummyDAO(dbpool) - {%- elif cookiecutter.orm in ["tortoise", "ormar", "piccolo"] %} + {%- else %} dao = DummyDAO() {%- endif %} test_name = uuid.uuid4().hex - {%- if cookiecutter.orm == "beanie" %} - await DummyModel.insert_one(DummyModel(name=test_name)) - {%- else %} await dao.create_dummy_model(name=test_name) - {%- endif %} {%- if cookiecutter.api_type == 'rest' %} url = fastapi_app.url_path_for('get_dummy_models') diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py index 5ec243c..c77e7b4 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py @@ -2,9 +2,7 @@ from fastapi import APIRouter from fastapi.param_functions import Depends -{%- if cookiecutter.db_info.name != "mongodb" %} from {{cookiecutter.project_name}}.db.dao.dummy_dao import DummyDAO -{%- endif %} from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel from {{cookiecutter.project_name}}.web.api.dummy.schema import (DummyModelDTO, DummyModelInputDTO) @@ -16,9 +14,7 @@ async def get_dummy_models( limit: int = 10, offset: int = 0, -{%- if cookiecutter.db_info.name != "mongodb" %} dummy_dao: DummyDAO = Depends(), -{%- endif %} ) -> List[DummyModel]: """ Retrieve all dummy objects from the database. @@ -30,19 +26,13 @@ async def get_dummy_models( {%- endif %} :return: list of dummy objects from database. """ -{%- if cookiecutter.db_info.name != "mongodb" %} return await dummy_dao.get_all_dummies(limit=limit, offset=offset) -{%- else %} - return await DummyModel.find_all(skip=offset, limit=limit).to_list() -{%- endif %} @router.put("/") async def create_dummy_model( new_dummy_object: DummyModelInputDTO, -{%- if cookiecutter.db_info.name != "mongodb" %} dummy_dao: DummyDAO = Depends(), -{%- endif %} ) -> None: """ Creates dummy model in the database. @@ -52,8 +42,4 @@ async def create_dummy_model( :param dummy_dao: DAO for dummy models. {%- endif %} """ -{%- if cookiecutter.db_info.name != "mongodb" %} await dummy_dao.create_dummy_model(name=new_dummy_object.name) -{%- else %} - await DummyModel.insert_one(DummyModel(name=new_dummy_object.name)) -{%- endif %} From 893dde3c488b280baa5b8df37be904ea131836d0 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 15:40:44 +0800 Subject: [PATCH 10/21] fix generated tests Signed-off-by: Tsung-Ju Lii --- .../{{cookiecutter.project_name}}/README.md | 10 --------- .../{{cookiecutter.project_name}}/conftest.py | 21 +++++++++++++++++++ .../db_beanie/models/__init__.py | 11 +++++++++- .../web/api/dummy/views.py | 4 ---- .../web/lifetime.py | 6 ++---- 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/README.md b/fastapi_template/template/{{cookiecutter.project_name}}/README.md index 8b53672..7033146 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/README.md +++ b/fastapi_template/template/{{cookiecutter.project_name}}/README.md @@ -51,9 +51,7 @@ $ tree "{{cookiecutter.project_name}}" ├── conftest.py # Fixtures for all tests. {%- if cookiecutter.db_info.name != "none" %} ├── db # module contains db configurations -{%- if cookiecutter.db_info.name != "mongodb" %} │   ├── dao # Data Access Objects. Contains different classes to interact with database. -{%- endif %} │   └── models # Package contains different models for ORMs. {%- endif %} ├── __main__.py # Startup script. Starts uvicorn. @@ -132,14 +130,6 @@ By default it runs: You can read more about pre-commit here: https://pre-commit.com/ -{%- if cookiecutter.db_info.name == 'mongodb' %} -## MongoDB - -Start a MongoDB container via docker compose by: -```shell -docker-compose -f deploy/docker-compose.yml --project-directory . up -d db -``` -{%- endif %} {%- if cookiecutter.enable_kube == 'True' %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py index 848f1fa..7d93783 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py @@ -68,6 +68,10 @@ from piccolo.conf.apps import Finder from piccolo.table import create_tables, drop_tables +{%- elif cookiecutter.orm == "beanie" %} +import beanie +from motor.motor_asyncio import AsyncIOMotorClient + {%- endif %} @@ -332,6 +336,23 @@ async def setup_db() -> AsyncGenerator[None, None]: await drop_database(engine) {%- endif %} +{%- elif cookiecutter.orm == "beanie" %} +@pytest.fixture(autouse=True) +async def setup_db() -> AsyncGenerator[None, None]: + """ + Fixture to create database connection. + + :yield: nothing. + """ + client = AsyncIOMotorClient(settings.db_url.human_repr()) + from {{cookiecutter.project_name}}.db.models import load_all_models # noqa: WPS433 + await beanie.init_beanie( + database=client[settings.db_base], + document_models=load_all_models(), + ) + yield + + {%- endif %} {%- if cookiecutter.enable_rmq == 'True' %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py index 8ebc7f6..46d60c1 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py @@ -1 +1,10 @@ -"""{{cookiecutter.project_name}} models.""" \ No newline at end of file +"""{{cookiecutter.project_name}} models.""" + +from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel + + +def load_all_models(): # type: ignore + """Load all models from this folder.""" # noqa: DAR201 + return [ + DummyModel, # type: ignore + ] diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py index c77e7b4..365caed 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/views.py @@ -21,9 +21,7 @@ async def get_dummy_models( :param limit: limit of dummy objects, defaults to 10. :param offset: offset of dummy objects, defaults to 0. -{%- if cookiecutter.db_info.name != "mongodb" %} :param dummy_dao: DAO for dummy models. -{%- endif %} :return: list of dummy objects from database. """ return await dummy_dao.get_all_dummies(limit=limit, offset=offset) @@ -38,8 +36,6 @@ async def create_dummy_model( Creates dummy model in the database. :param new_dummy_object: new dummy model item. -{%- if cookiecutter.db_info.name != "mongodb" %} :param dummy_dao: DAO for dummy models. -{%- endif %} """ await dummy_dao.create_dummy_model(name=new_dummy_object.name) 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 9ee9994..192ef65 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 @@ -123,15 +123,13 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover {%- if cookiecutter.orm == "beanie" %} import beanie from motor.motor_asyncio import AsyncIOMotorClient -from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel +from {{cookiecutter.project_name}}.db.models import load_all_models async def _setup_db(app: FastAPI) -> None: client = AsyncIOMotorClient(settings.db_url.human_repr()) app.state.db_client = client await beanie.init_beanie( database=client[settings.db_base], - document_models=[ - DummyModel, # type: ignore - ] + document_models=load_all_models(), ) {%- endif %} From dccf4254c4c499f0c40f5b6cdd73d9231d15278e Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 15:59:15 +0800 Subject: [PATCH 11/21] add tests in test generator --- fastapi_template/tests/test_generator.py | 45 ++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/fastapi_template/tests/test_generator.py b/fastapi_template/tests/test_generator.py index 37da491..ffdbf18 100644 --- a/fastapi_template/tests/test_generator.py +++ b/fastapi_template/tests/test_generator.py @@ -68,6 +68,22 @@ def test_default_with_db(default_context: BuilderContext, db: str, orm: str): run_default_check(init_context(default_context, db, orm)) +@pytest.mark.parametrize( + "db", + [ + "mongodb", + ], +) +@pytest.mark.parametrize( + "orm", + [ + "beanie", + ], +) +def test_default_with_nosql_db(default_context: BuilderContext, db: str, orm: str): + run_default_check(init_context(default_context, db, orm)) + + @pytest.mark.parametrize("api", ["rest", "graphql"]) @pytest.mark.parametrize( "orm", @@ -82,6 +98,17 @@ def test_default_for_apis(default_context: BuilderContext, orm: str, api: str): run_default_check(init_context(default_context, "postgresql", orm, api)) +@pytest.mark.parametrize("api", ["rest", "graphql"]) +@pytest.mark.parametrize( + "orm", + [ + "beanie", + ] +) +def test_default_for_apis_with_nosql_db(default_context: BuilderContext, orm: str, api: str): + run_default_check(init_context(default_context, "mongodb", orm, api)) + + @pytest.mark.parametrize( "orm", [ @@ -108,6 +135,12 @@ def test_without_routers(default_context: BuilderContext, orm: str): run_default_check(context) +def test_without_routers_with_nosql_db(default_context: BuilderContext): + context = init_context(default_context, "mongodb", "beanie") + context.enable_routers = False + run_default_check(context) + + @pytest.mark.parametrize( "orm", [ @@ -123,6 +156,12 @@ def test_without_migrations(default_context: BuilderContext, orm: str): run_default_check(context) +def test_without_migrations_with_nosql_db(default_context: BuilderContext): + context = init_context(default_context, "mongodb", "beanie") + context.enable_migrations = False + run_default_check(context) + + def test_with_selfhosted_swagger(default_context: BuilderContext): default_context.self_hosted_swagger = True run_default_check(default_context) @@ -144,6 +183,12 @@ def test_without_dummy(default_context: BuilderContext, orm: str): run_default_check(context) +def test_without_dummy_with_nosql_db(default_context: BuilderContext): + context = init_context(default_context, "mongodb", "beanie") + context.add_dummy = False + run_default_check(context) + + @pytest.mark.parametrize( "api", [ From e8c36ac84cb4cc758b627d4bac8fb024ab02769e Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 16:01:14 +0800 Subject: [PATCH 12/21] gql/dummy --- .../{{cookiecutter.project_name}}/web/gql/dummy/schema.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/dummy/schema.py index 3a02ed2..db26bf5 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/dummy/schema.py @@ -9,5 +9,9 @@ class DummyModelDTO: It returned when accessing dummy models from the API. """ + {%- if cookiecutter.db_info.name != "mongodb" %} id: int + {%- else %} + id: str + {%- endif %} name: str From 3e4f095e7b4b6fa5d271d14e5b3a6a89d1108b6b Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Thu, 24 Aug 2023 16:16:22 +0800 Subject: [PATCH 13/21] fix all actions --- .../.github/workflows/tests.yml | 9 ++++++++- .../{{cookiecutter.project_name}}/.gitlab-ci.yml | 10 ++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml b/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml index 8974496..af81543 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/.github/workflows/tests.yml @@ -67,7 +67,11 @@ jobs: MYSQL_DATABASE: "{{ cookiecutter.project_name }}" MYSQL_AUTHENTICATION_PLUGIN: "mysql_native_password" {%- endif %} - {%- if cookiecutter.db_info.name == "mysql" %} + {%- if cookiecutter.db_info.name == "mongodb" %} + MONGO_INITDB_ROOT_USERNAME: "{{ cookiecutter.project_name }}" + MONGO_INITDB_ROOT_PASSWORD: "{{ cookiecutter.project_name }}" + {%- endif %} + {%- if cookiecutter.db_info.name == "mysql" %} options: >- --health-cmd="mysqladmin ping -u root" --health-interval=15s @@ -148,6 +152,9 @@ jobs: {%- if cookiecutter.db_info.name != "sqlite" %} {{ cookiecutter.project_name | upper }}_DB_HOST: localhost {%- endif %} + {%- if cookiecutter.db_info.name == "mongodb" %} + {{ cookiecutter.project_name | upper }}_DB_BASE: admin + {%- endif %} {%- endif %} {%- if cookiecutter.enable_rmq == "True" %} {{ cookiecutter.project_name | upper }}_RABBIT_HOST: localhost diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml b/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml index 9fc0c30..f05bfa1 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/.gitlab-ci.yml @@ -69,6 +69,16 @@ pytest: MYSQL_DATABASE: {{ cookiecutter.project_name }} ALLOW_EMPTY_PASSWORD: yes {%- endif %} + + {%- if cookiecutter.db_info.name == "mongodb" %} + + # MongoDB variables + {{ cookiecutter.project_name | upper }}_DB_HOST: database + {{ cookiecutter.project_name | upper }}_DB_BASE: admin + MONGO_INITDB_ROOT_USERNAME: {{ cookiecutter.project_name }} + MONGO_INITDB_ROOT_PASSWORD: {{ cookiecutter.project_name }} + {%- endif %} + {%- if cookiecutter.enable_rmq == "True" %} # Rabbitmq variables From b5f288f9db3de9d729afcbfeeaf93909482dfb5a Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Fri, 20 Oct 2023 18:03:27 +0800 Subject: [PATCH 14/21] fix tests --- .../{{cookiecutter.project_name}}/conditional_files.json | 6 ------ .../{{cookiecutter.project_name}}/deploy/docker-compose.yml | 3 ++- .../template/{{cookiecutter.project_name}}/pyproject.toml | 2 ++ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json index e3648b2..2d25b9f 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json +++ b/fastapi_template/template/{{cookiecutter.project_name}}/conditional_files.json @@ -60,12 +60,6 @@ "deploy/kube/db.yml" ] }, - "SQLAlchemy support": { - "enabled": "{{cookiecutter.db_info.name not in ['none', 'mongodb']}}", - "resources": [ - "{{cookiecutter.project_name}}/db_sa" - ] - }, "Beanie support": { "enabled": "{{cookiecutter.db_info.name == 'mongodb'}}", "resources": [ diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml index f349b14..fbd74e1 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/deploy/docker-compose.yml @@ -52,7 +52,7 @@ services: {%- if cookiecutter.db_info.name == "sqlite" %} {{cookiecutter.project_name | upper }}_DB_FILE: /db_data/db.sqlite3 {%- else %} - {{cookiecutter.project_name | upper}}_DB_HOST: {{cookiecutter.project_name}}_db_1 + {{cookiecutter.project_name | upper}}_DB_HOST: {{cookiecutter.project_name}}-db {{cookiecutter.project_name | upper}}_DB_PORT: {{cookiecutter.db_info.port}} {{cookiecutter.project_name | upper}}_DB_USER: {{cookiecutter.project_name}} {{cookiecutter.project_name | upper}}_DB_PASS: {{cookiecutter.project_name}} @@ -110,6 +110,7 @@ services: {%- if cookiecutter.db_info.name == "mongodb"%} db: image: {{cookiecutter.db_info.image}} + hostname: {{cookiecutter.project_name}}-db restart: always environment: MONGO_INITDB_ROOT_USERNAME: "{{cookiecutter.project_name}}" diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml index c403168..2816560 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml +++ b/fastapi_template/template/{{cookiecutter.project_name}}/pyproject.toml @@ -218,6 +218,8 @@ env = [ "{{cookiecutter.project_name | upper}}_ENVIRONMENT=pytest", {%- if cookiecutter.db_info.name == "sqlite" %} "{{cookiecutter.project_name | upper}}_DB_FILE=test_db.sqlite3", + {%- elif cookiecutter.db_info.name == "mongodb" %} + "{{cookiecutter.project_name | upper}}_DB_BASE=admin", {%- else %} "{{cookiecutter.project_name | upper}}_DB_BASE={{cookiecutter.project_name}}_test", {%- endif %} From cfef8de89519bde3277c8a8c0bbd03e1512b1c91 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Sat, 21 Oct 2023 15:06:54 +0800 Subject: [PATCH 15/21] fix all tests --- .../db_beanie/dao/dummy_dao.py | 16 ++++++++++++++++ .../db_beanie/models/__init__.py | 4 ++++ .../tests/test_dummy.py | 5 +++++ 3 files changed, 25 insertions(+) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py index bb3239f..1f9ccac 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/dao/dummy_dao.py @@ -37,3 +37,19 @@ async def filter( if name is None: return [] return await DummyModel.find(DummyModel.name == name).to_list() + + async def delete_dummy_model_by_name( + self, + name: str, + ) -> Optional[DummyModel]: + """ + Delete a dummy model by name. + + :param name: name of dummy instance. + :return: option of a dummy model. + """ + res = await DummyModel.find_one(DummyModel.name == name) + if res is None: + return res + await res.delete() + return res diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py index 46d60c1..50c4bdc 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_beanie/models/__init__.py @@ -1,10 +1,14 @@ """{{cookiecutter.project_name}} models.""" +{%- if cookiecutter.add_dummy == "True" %} from {{cookiecutter.project_name}}.db.models.dummy_model import DummyModel +{%- endif %} def load_all_models(): # type: ignore """Load all models from this folder.""" # noqa: DAR201 return [ +{%- if cookiecutter.add_dummy == "True" %} DummyModel, # type: ignore +{%- endif %} ] diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py index 63cf2cb..cb18575 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py @@ -60,6 +60,11 @@ async def test_creation( instances = await dao.filter(name=test_name) assert instances[0].name == test_name + {%- if cookiecutter.orm == "beanie" %} + # Clean up the object we just inserted + await dao.delete_dummy_model_by_name(name=test_name) + {%- endif %} + @pytest.mark.anyio async def test_getting( From dc714267bd663d5cbcf87fdbab2fbb3ce63f1670 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Sat, 21 Oct 2023 15:14:28 +0800 Subject: [PATCH 16/21] add beanie in readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 33ac340..10d374a 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ docker run --rm -it -v "$(pwd):/projects" s3rius/fastapi_template One of the coolest features is that this project is extremely configurable. You can choose between different databases and even ORMs, or you can even generate a project without a database! -Currently SQLAlchemy 2.0, TortoiseORM, Piccolo and Ormar are supported. +Currently SQLAlchemy 2.0, TortoiseORM, Piccolo, Ormar and Beanie are supported. This project can run as TUI or CLI and has excellent code documentation. @@ -84,9 +84,9 @@ Options: --force Owerrite directory if it exists --quite Do not ask for features during generation --api-type [rest|graphql] Select API type for your application - --db [none|sqlite|mysql|postgresql] + --db [none|sqlite|mysql|postgresql|mongodb] Select a database for your app - --orm [none|ormar|sqlalchemy|tortoise|psycopg|piccolo] + --orm [none|ormar|sqlalchemy|tortoise|psycopg|piccolo|beanie] Choose Object–Relational Mapper lib --ci [none|gitlab_ci|github] Select a CI for your app --redis Add redis support From 023214efe74887eb9db8d486c2fc2a81e02deaa2 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Mon, 13 Nov 2023 14:00:12 +0800 Subject: [PATCH 17/21] Update fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/lifetime.py Co-authored-by: Pavel Kirilin --- .../{{cookiecutter.project_name}}/web/lifetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 192ef65..06753bc 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 @@ -125,7 +125,7 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover from motor.motor_asyncio import AsyncIOMotorClient from {{cookiecutter.project_name}}.db.models import load_all_models async def _setup_db(app: FastAPI) -> None: - client = AsyncIOMotorClient(settings.db_url.human_repr()) + client = AsyncIOMotorClient(str(settings.db_url)) app.state.db_client = client await beanie.init_beanie( database=client[settings.db_base], From 8bd22d1305dd18bd3c58d9a2a13de5329f19d241 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 15 Nov 2023 14:00:29 +0800 Subject: [PATCH 18/21] support pydantic v1 --- README.md | 2 +- fastapi_template/cli.py | 8 ++++---- .../{{cookiecutter.project_name}}/conftest.py | 2 +- .../{{cookiecutter.project_name}}/web/api/dummy/schema.py | 8 ++++++++ .../{{cookiecutter.project_name}}/web/lifetime.py | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 10d374a..551c124 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ Options: -n, --name TEXT Name of your awesome project -V, --version Prints current version --force Owerrite directory if it exists - --quite Do not ask for features during generation + --quiet Do not ask for features during generation --api-type [rest|graphql] Select API type for your application --db [none|sqlite|mysql|postgresql|mongodb] Select a database for your app diff --git a/fastapi_template/cli.py b/fastapi_template/cli.py index 64d8128..58f6a17 100644 --- a/fastapi_template/cli.py +++ b/fastapi_template/cli.py @@ -41,8 +41,8 @@ def disable_orm(ctx: BuilderContext) -> MenuEntry: return None -def do_not_ask_features_if_quite(ctx: BuilderContext) -> Optional[List[MenuEntry]]: - if ctx.quite: +def do_not_ask_features_if_quiet(ctx: BuilderContext) -> Optional[List[MenuEntry]]: + if ctx.quiet: return [SKIP_ENTRY] return None @@ -342,7 +342,7 @@ def checker(ctx: BuilderContext) -> bool: code="features", description="Additional project features", multiselect=True, - before_ask=do_not_ask_features_if_quite, + before_ask=do_not_ask_features_if_quiet, entries=[ MenuEntry( code="pydanticv1", @@ -627,7 +627,7 @@ def run_command(callback: Callable[[BuilderContext], None]) -> None: help="Owerrite directory if it exists", ), Option( - ["--quite"], + ["--quiet"], is_flag=True, help="Do not ask for features during generation", ), diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py index 7d93783..5bbdfd3 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py @@ -344,7 +344,7 @@ async def setup_db() -> AsyncGenerator[None, None]: :yield: nothing. """ - client = AsyncIOMotorClient(settings.db_url.human_repr()) + client = AsyncIOMotorClient(settings.db_url.human_repr()) # type: ignore from {{cookiecutter.project_name}}.db.models import load_all_models # noqa: WPS433 await beanie.init_beanie( database=client[settings.db_base], diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index adb6d52..bc2eef8 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -5,7 +5,11 @@ {%- endif %} {%- if cookiecutter.db_info.name == "mongodb" %} +{%- if cookiecutter.pydanticv1 == "True" %} +from pydantic import validator +{%- else %} from pydantic import field_validator +{%- endif %} from bson import ObjectId {%- endif %} @@ -25,8 +29,12 @@ class DummyModelDTO(BaseModel): name: str {%- if cookiecutter.db_info.name == "mongodb" %} + {%- if cookiecutter.pydanticv1 == "True" %} + @validator("id") + {%- else %} @field_validator("id", mode="before") @classmethod + {%- endif %} def parse_object_id(cls, document_id: ObjectId) -> str: """ Validator that converts `ObjectId` to json serializable `str`. 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 06753bc..bda7725 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 @@ -125,7 +125,7 @@ def _setup_db(app: FastAPI) -> None: # pragma: no cover from motor.motor_asyncio import AsyncIOMotorClient from {{cookiecutter.project_name}}.db.models import load_all_models async def _setup_db(app: FastAPI) -> None: - client = AsyncIOMotorClient(str(settings.db_url)) + client = AsyncIOMotorClient(str(settings.db_url)) # type: ignore app.state.db_client = client await beanie.init_beanie( database=client[settings.db_base], From 35471f19a2e1f9683f5ccf1c89ac4146b9c496dc Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 15 Nov 2023 15:00:50 +0800 Subject: [PATCH 19/21] fix pydantic v1 bug --- .../{{cookiecutter.project_name}}/tests/test_dummy.py | 5 +++++ .../web/api/dummy/schema.py | 10 +++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py index cb18575..6065435 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py @@ -107,3 +107,8 @@ async def test_getting( assert response.status_code == status.HTTP_200_OK assert len(dummies) == 1 assert dummies[0]['name'] == test_name + + {%- if cookiecutter.orm == "beanie" %} + # Clean up the object we just inserted + await dao.delete_dummy_model_by_name(name=test_name) + {%- endif %} diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py index bc2eef8..82238eb 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/api/dummy/schema.py @@ -6,7 +6,7 @@ {%- endif %} {%- if cookiecutter.db_info.name == "mongodb" %} {%- if cookiecutter.pydanticv1 == "True" %} -from pydantic import validator +from pydantic import validator, Field {%- else %} from pydantic import field_validator {%- endif %} @@ -24,17 +24,21 @@ class DummyModelDTO(BaseModel): {%- if cookiecutter.db_info.name != "mongodb" %} id: int {%- else %} + {%- if cookiecutter.pydanticv1 == "True" %} + id: str = Field(alias="_id") + {%- else %} id: str {%- endif %} + {%- endif %} name: str {%- if cookiecutter.db_info.name == "mongodb" %} {%- if cookiecutter.pydanticv1 == "True" %} - @validator("id") + @validator("id", pre=True) {%- else %} @field_validator("id", mode="before") - @classmethod {%- endif %} + @classmethod def parse_object_id(cls, document_id: ObjectId) -> str: """ Validator that converts `ObjectId` to json serializable `str`. From da14491583de87747a3f082bbc973bd503c8d956 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Wed, 22 Nov 2023 01:45:18 +0800 Subject: [PATCH 20/21] add [Any] to AsyncConnectionPool generic type --- .../{{cookiecutter.project_name}}/conftest.py | 4 ++-- .../{{cookiecutter.project_name}}/db_psycopg/dependencies.py | 3 ++- .../{{cookiecutter.project_name}}/tests/test_dummy.py | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py index 5bbdfd3..c150815 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/conftest.py @@ -259,7 +259,7 @@ async def create_tables(connection: AsyncConnection[Any]) -> None: @pytest.fixture -async def dbpool() -> AsyncGenerator[AsyncConnectionPool, None]: +async def dbpool() -> AsyncGenerator[AsyncConnectionPool[Any], None]: """ Creates database connections pool to test database. @@ -477,7 +477,7 @@ def fastapi_app( {%- if cookiecutter.orm == "sqlalchemy" %} dbsession: AsyncSession, {%- elif cookiecutter.orm == "psycopg" %} - dbpool: AsyncConnectionPool, + dbpool: AsyncConnectionPool[Any], {%- endif %} {% if cookiecutter.enable_redis == "True" -%} fake_redis_pool: ConnectionPool, diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dependencies.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dependencies.py index ecfd2b6..149a839 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dependencies.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dependencies.py @@ -1,4 +1,5 @@ from psycopg_pool import AsyncConnectionPool +from typing import Any from starlette.requests import Request {%- if cookiecutter.enable_taskiq == "True" %} @@ -6,7 +7,7 @@ {%- endif %} -async def get_db_pool(request: Request {%- if cookiecutter.enable_taskiq == "True" %} = TaskiqDepends(){%- endif %}) -> AsyncConnectionPool: +async def get_db_pool(request: Request {%- if cookiecutter.enable_taskiq == "True" %} = TaskiqDepends(){%- endif %}) -> AsyncConnectionPool[Any]: """ Return database connections pool. diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py index 6065435..417e702 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/tests/test_dummy.py @@ -25,7 +25,7 @@ async def test_creation( {%- if cookiecutter.orm == "sqlalchemy" %} dbsession: AsyncSession, {%- elif cookiecutter.orm == "psycopg" %} - dbpool: AsyncConnectionPool, + dbpool: AsyncConnectionPool[Any], {%- endif %} ) -> None: """Tests dummy instance creation.""" @@ -73,7 +73,7 @@ async def test_getting( {%- if cookiecutter.orm == "sqlalchemy" %} dbsession: AsyncSession, {%- elif cookiecutter.orm == "psycopg" %} - dbpool: AsyncConnectionPool, + dbpool: AsyncConnectionPool[Any], {%- endif %} ) -> None: """Tests dummy instance retrieval.""" From 5ccaed538c5b592f13e403f8f1426e7ab0b00468 Mon Sep 17 00:00:00 2001 From: Tsung-Ju Lii Date: Sat, 25 Nov 2023 19:10:16 +0800 Subject: [PATCH 21/21] missed a spot --- .../{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py | 2 +- .../{{cookiecutter.project_name}}/web/gql/context.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py index eb3e6b6..fee89ed 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/db_psycopg/dao/dummy_dao.py @@ -13,7 +13,7 @@ class DummyDAO: def __init__( self, - db_pool: AsyncConnectionPool = Depends(get_db_pool), + db_pool: AsyncConnectionPool[Any] = Depends(get_db_pool), ): self.db_pool = db_pool diff --git a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/context.py b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/context.py index 23e1ddf..df5a23a 100644 --- a/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/context.py +++ b/fastapi_template/template/{{cookiecutter.project_name}}/{{cookiecutter.project_name}}/web/gql/context.py @@ -27,6 +27,7 @@ from {{cookiecutter.project_name}}.db.dependencies import get_db_session {%- elif cookiecutter.orm == "psycopg" %} +from typing import Any from psycopg_pool import AsyncConnectionPool from {{cookiecutter.project_name}}.db.dependencies import get_db_pool @@ -47,7 +48,7 @@ def __init__( {%- if cookiecutter.orm == "sqlalchemy" %} db_connection: AsyncSession = Depends(get_db_session), {%- elif cookiecutter.orm == "psycopg" %} - db_pool: AsyncConnectionPool = Depends(get_db_pool), + db_pool: AsyncConnectionPool[Any] = Depends(get_db_pool), {%- endif %} {%- if cookiecutter.enable_kafka == "True" %} kafka_producer: AIOKafkaProducer = Depends(get_kafka_producer),