Skip to content

Commit 0769d3e

Browse files
authored
feat: add MongoDB database as a part of the CLI (#89)
* feat: mongodb setup with beanie ODM * feat: add MongoDB support with configuration and testing setup
1 parent 02c8a23 commit 0769d3e

File tree

16 files changed

+175
-15
lines changed

16 files changed

+175
-15
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ scaffoldr --no-banner generate my-project
7272
When you generate a FastAPI project, you get:
7373

7474
- **Complete FastAPI Application**: Pre-configured with proper structure
75-
- **Database Integration**: SQLAlchemy with Alembic migrations
75+
- **Database Integration**: SQLAlchemy with Alembic migrations or MongoDB with Beanie ODM
7676
- **File Storage**: Built-in file upload/download endpoints
7777
- **API Documentation**: Auto-generated OpenAPI/Swagger docs
7878
- **Development Tools**: Pre-configured with ruff, mypy, pytest
@@ -117,7 +117,9 @@ my-project/
117117
│ ├── core/ # Core components (config, utils, etc.)
118118
│ ├── features/ # Feature modules (business logic)
119119
│ ├── services/ # External service integrations
120-
│ └── database/ # Data access layer
120+
{% if database %}
121+
│ └── database/ # Data access layer (SQLAlchemy or MongoDB)
122+
{% endif %}
121123
├── tests/ # Comprehensive test suite
122124
├── scripts/ # Development scripts
123125
├── .github/ # GitHub workflows and templates

src/scaffoldr/cli/generate.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ def generate(
3838
if use_cloud:
3939
cloud_type = Helper.cloud_type()
4040

41+
database_type = None
42+
if use_database:
43+
database_type = Helper.database_type()
44+
4145
project_name = project_name.replace(" ", "-").lower()
4246
# Check if directory already exists
4347
if destination == ".":
@@ -71,6 +75,7 @@ def generate(
7175
"use_docker": docker,
7276
"cloud_type": cloud_type,
7377
"database": use_database,
78+
"database_type": database_type,
7479
"framework": framework,
7580
}
7681

src/scaffoldr/core/constants/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from .art import ascii_art
2-
from .const import CloudTypes, Frameworks, console
2+
from .const import CloudTypes, DatabaseTypes, Frameworks, console
33

44
__all__ = [
55
"CloudTypes",
6+
"DatabaseTypes",
67
"Frameworks",
78
"ascii_art",
89
"console",

src/scaffoldr/core/constants/const.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ class CloudTypes(str, Enum):
1818
GCP = "gcp"
1919
AZURE = "azure"
2020
NONE = "none"
21+
22+
23+
class DatabaseTypes(str, Enum):
24+
SQLALCHEMY = "sqlalchemy"
25+
MONGODB = "mongodb"

src/scaffoldr/core/utils/helper.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import typer
44
from copier import subprocess
55

6-
from scaffoldr.core.constants.const import CloudTypes
6+
from scaffoldr.core.constants.const import CloudTypes, DatabaseTypes, console
77

88
if TYPE_CHECKING:
99
from collections.abc import Iterable
@@ -40,6 +40,20 @@ def cloud_type() -> str:
4040
cloud_type: str = cast("str", typer.prompt(f"Cloud type: {' | '.join(cloud_types)}"))
4141
if cloud_type in cloud_types:
4242
return cloud_type
43-
raise ValueError(
44-
f"Invalid cloud type: {cloud_type}. Must be one of: {' | '.join(cloud_types)}",
43+
console.print(f"[red]Error:[/red] Invalid cloud type: {cloud_type}")
44+
raise typer.Exit(1)
45+
46+
@staticmethod
47+
def database_type() -> str:
48+
"""
49+
Prompt for database type selection.
50+
"""
51+
database_types = list(DatabaseTypes)
52+
database_type: str = cast(
53+
"str",
54+
typer.prompt(f"Database type: {' | '.join(database_types)}"),
4555
)
56+
if database_type in database_types:
57+
return database_type
58+
console.print(f"[red]Error:[/red] Invalid database type: {database_type}")
59+
raise typer.Exit(1)

templates/fastapi_template/{{ project_name }}/.env.jinja

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,15 @@
1313
{{ project_slug|upper }}_LOG_GRANIAN_ERROR_LEVEL=40
1414

1515
{% if database %}
16+
{% if database_ype == 'postgresql' %}
17+
{{ project_slug|upper }}_DB_URL=postgresql+asyncpg://user:password@localhost:5432/{{ project_slug }}
18+
{{ project_slug|upper }}_DB_ECHO=false
19+
{% elif database_type == 'sqlite' %}
1620
{{ project_slug|upper }}_DB_URL=sqlite+aiosqlite:///./{{ project_name|upper }}.db
1721
{{ project_slug|upper }}_DB_ECHO=false
22+
{% elif database_type == 'mongodb' %}
23+
{{ project_slug|upper }}_DB_URL=mongodb://localhost:27017
24+
{% endif %}
1825
{% endif %}
1926

2027
{% if cloud_type %}

templates/fastapi_template/{{ project_name }}/README.md.jinja

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@
66

77
- **FastAPI**: Modern, fast web framework for building APIs
88
- **Pydantic**: Data validation and serialization
9+
{% if database %}
10+
{% if database_type == 'sqlalchemy' %}
911
- **SQLAlchemy**: SQL toolkit and ORM
1012
- **Alembic**: Database migration tool
13+
{% elif database_type == 'mongodb' %}
14+
- **Beanie**: MongoDB ODM for Python
15+
- **Motor**: Asynchronous MongoDB driver
16+
{% endif %}
17+
{% endif %}
1118
- **Uvicorn**: ASGI web server
1219
- **Granian**: High-performance web server
1320
{% if cloud_type == 'aws' %}

templates/fastapi_template/{{ project_name }}/pyproject.toml.jinja

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@ dependencies = [
3939
"azure-identity>=1.25.0",
4040
{% endif %}
4141
{% if database %}
42+
{% if database_type == 'sqlalchemy' %}
4243
"aiosqlite>=0.21.0",
4344
"sqlalchemy[asyncio]>=2.0.0",
45+
{% elif database_type == 'mongodb' %}
46+
"beanie>=2.0.0",
47+
"motor>=3.7.1",
48+
"pymongo>=4.15.1",
49+
{% endif %}
4450
{% endif %}
4551
"fastapi>=0.117.1",
4652
"granian[reload]>=2.5.4",

templates/fastapi_template/{{ project_name }}/src/{{ project_slug }}/api/lifespan.py.jinja

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,31 @@ from contextlib import asynccontextmanager
44
from fastapi import FastAPI
55

66
{% if database %}
7+
{% if database_type == 'sqlalchemy' %}
78
from {{ project_slug }}.connection import Base, database
9+
{% elif database_type == 'mongodb' %}
10+
from {{ project_slug }}.connection import mongodb
11+
{% endif %}
812
{% endif %}
913

1014
{% if framework == 'fastapi' %}
1115

1216
@asynccontextmanager
1317
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
1418
{% if database %}
19+
{% if database_type == 'sqlalchemy' %}
1520
async with database.engine.begin() as conn:
1621
# run_sync executes the given function in a sync context using the connection
1722
await conn.run_sync(Base.metadata.create_all)
18-
{% endif %}
23+
{% elif database_type == 'mongodb' %}
24+
await mongodb.connect()
25+
{% endif %}
26+
{% endif %}
1927
yield
28+
{% if database %}
29+
{% if database_type == 'mongodb' %}
30+
await mongodb.close()
31+
{% endif %}
32+
{% endif %}
2033

2134
{% endif %}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
{% if database %}
1+
{% if database_type == 'sqlalchemy' %}
22
from .database import database, inject_session, Base
33
__all__ = ["Base", "database", "inject_session"]
4+
{% elif database_type == 'mongodb' %}
5+
from .database import mongodb
6+
__all__ = ["mongodb"]
47
{% endif %}

0 commit comments

Comments
 (0)