Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: MongoDB support via beanie #187

Merged
merged 21 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -82,11 +82,11 @@ 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]
--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
Expand Down
42 changes: 37 additions & 5 deletions fastapi_template/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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:7.0",
async_driver="beanie",
driver_short="mongodb",
driver="mongodb",
port=27017
),
)
],
)

Expand Down Expand Up @@ -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(
Expand All @@ -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"
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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"),
)
),
),

],
)

Expand All @@ -310,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",
Expand Down Expand Up @@ -595,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",
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ By default it runs:

You can read more about pre-commit here: https://pre-commit.com/


{%- if cookiecutter.enable_kube == 'True' %}

## Kubernetes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,18 @@
"alembic.ini",
"{{cookiecutter.project_name}}/web/api/dummy",
"{{cookiecutter.project_name}}/web/gql/dummy",
"{{cookiecutter.project_name}}/db_sa",
usefulalgorithm marked this conversation as resolved.
Show resolved Hide resolved
"{{cookiecutter.project_name}}/tests/test_dummy.py",
"deploy/kube/db.yml"
]
},
"Beanie support": {
"enabled": "{{cookiecutter.db_info.name == 'mongodb'}}",
"resources": [
"{{cookiecutter.project_name}}/db_beanie"
]
},
"Postgres and MySQL support": {
"enabled": "{{cookiecutter.db_info.name != 'sqlite'}}",
"enabled": "{{cookiecutter.db_info.name not in ['sqlite', 'mongodb']}}",
"resources": [
"deploy/kube/db.yml"
]
Expand Down Expand Up @@ -136,6 +141,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",
Expand Down Expand Up @@ -182,6 +188,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": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ services:
# Exposes application port.
- "8000:8000"
build:
context: .
target: dev
volumes:
# Adds current directory as volume.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,13 @@ services:
{{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
usefulalgorithm marked this conversation as resolved.
Show resolved Hide resolved
{%- 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 %}
Expand Down Expand Up @@ -103,6 +107,24 @@ services:
retries: 40
{%- endif %}

{%- if cookiecutter.db_info.name == "mongodb"%}
db:
image: {{cookiecutter.db_info.image}}
hostname: {{cookiecutter.project_name}}-db
restart: always
usefulalgorithm marked this conversation as resolved.
Show resolved Hide resolved
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" %}

db:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
Expand Down Expand Up @@ -213,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 %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}


Expand Down Expand Up @@ -255,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.

Expand Down Expand Up @@ -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()) # type: ignore
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' %}
Expand Down Expand Up @@ -456,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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""DAO classes."""
Loading
Loading