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 19 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 @@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""DAO classes."""
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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()

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
Loading
Loading