Skip to content
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
2 changes: 1 addition & 1 deletion docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ When creating a new project or external app, dependency versions in `pyproject.t

```toml
[tool.poetry.dependencies]
python = "^3.11"
python = ">=3.11,<4.0"
fastapi = ">=0.120.0,<0.130" # Specific range instead of *
sqlalchemy = ">=2.0,<3.0"
alembic = ">=1.17.2,<1.18"
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Update dependency versions in `pyproject.toml` from `*` to specific ranges for p

```toml
[tool.poetry.dependencies]
python = "^3.11"
python = ">=3.11,<4.0"
fastapi = ">=0.120.0,<0.130"
sqlalchemy = ">=2.0,<3.0"
alembic = ">=1.17.2,<1.18"
Expand Down
11 changes: 10 additions & 1 deletion fastappkit/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ def new(
("app/external/__init__.py.j2", f"{name}/{name}/__init__.py"),
("app/external/models.py.j2", f"{name}/{name}/models.py"),
("app/external/router.py.j2", f"{name}/{name}/router.py"),
("app/external/config.py.j2", f"{name}/{name}/config.py"),
("app/external/pyproject.toml.j2", f"{name}/pyproject.toml"),
("app/external/README.md.j2", f"{name}/README.md"),
("app/external/main.py.j2", f"{name}/main.py"),
]

# Create fastappkit.toml INSIDE package directory (included in package when published)
Expand Down Expand Up @@ -172,6 +174,11 @@ def new(
context,
)
output.verbose("Created .gitignore")

# Create .env from env.example template
env_example_content = template_engine.render("app/external/env.example.j2", context)
(app_path / ".env").write_text(env_example_content, encoding="utf-8")
output.verbose("Created .env")
else:
# Internal app templates
# Internal apps don't have their own migrations - they use core's migrations
Expand Down Expand Up @@ -205,7 +212,9 @@ def new(
output.info("\nNext steps:")
output.info(f" cd {name}")
output.info(" pip install -e . # Install the package")
output.info(f" # Add '{name}' to fastappkit.toml apps list")
output.info(" # Configure .env file (DATABASE_URL, DEBUG, etc.)")
output.info(" # Run independently: uvicorn main:app --reload")
output.info(" # Or add to fastappkit.toml apps list in a core project")
output.info(f" fastappkit migrate app {name} makemigrations")
output.warning(
"\n⚠️ Note: Dependency versions in pyproject.toml are set to '*' (any version)."
Expand Down
67 changes: 64 additions & 3 deletions fastappkit/cli/templates/app/external/README.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,68 @@ External pluggable app for fastappkit.
pip install -e .
```

## Usage
## Configuration

Add to your `fastappkit.toml`:
This app uses a `.env` file for configuration. A `.env` file is created automatically when you create the app.

**Configure your environment:**

1. Edit the `.env` file in the app root directory:
```bash
# Database Configuration
DATABASE_URL=sqlite:///./{{ app_name }}.db

# Development Settings
DEBUG=false
```

2. For production or different databases, update `DATABASE_URL`:
```bash
DATABASE_URL=postgresql://user:password@localhost/{{ app_name }}
```

The `.env` file is automatically loaded by `config.py` using `pydantic-settings`.

## Independent Development

This external app can be developed and tested independently without requiring a full fastappkit project.

### Running the Server

Start the development server:

```bash
# From the app root directory
uvicorn main:app --reload
```

Or use Python directly:

```bash
python main.py
```

The server will start on `http://127.0.0.1:8000` with the app's routes available at `/{{ app_name }}/`.

### Project Structure

```
{{ app_name }}/
├── {{ app_name }}/ # Package directory
│ ├── __init__.py # register() function
│ ├── models.py # SQLAlchemy models
│ ├── router.py # FastAPI routes
│ ├── config.py # Settings (loads from .env)
│ └── migrations/ # Alembic migrations
├── main.py # Entry point for independent development
├── alembic.ini # Alembic configuration
├── .env # Environment variables (gitignored)
└── pyproject.toml # Package metadata
```

## Usage in a Core Project

To use this app in a fastappkit project, add it to your `fastappkit.toml`:

```toml
[tool.fastappkit]
Expand Down Expand Up @@ -53,7 +112,9 @@ alembic downgrade -1
alembic upgrade head --sql
```

**Database URL**: Set `DATABASE_URL` environment variable, or edit `alembic.ini` to configure your database.
**Database URL**: The `DATABASE_URL` is read from the `.env` file automatically. You can also:
- Set `DATABASE_URL` environment variable (takes precedence)
- Edit `alembic.ini` sqlalchemy.url (fallback)

### When Using the External App in a Core Project

Expand Down
13 changes: 11 additions & 2 deletions fastappkit/cli/templates/app/external/alembic.ini.j2
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,18 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# are written from script.py.mako
# output_encoding = utf-8

# Database URL - can be overridden via DATABASE_URL environment variable
# Database URL configuration
#
# This can be configured in three ways (in order of precedence):
# 1. DATABASE_URL environment variable (from .env file when running independently)
# 2. This sqlalchemy.url setting (fallback)
# 3. Set programmatically in migrations/env.py
#
# For independent development, create a .env file with:
# DATABASE_URL=sqlite:///./{{ app_name }}.db
#
# The migrations/env.py will automatically read DATABASE_URL from the environment.
# Default uses SQLite for development
# Edit this line or set DATABASE_URL environment variable
sqlalchemy.url = sqlite:///./{{ app_name }}.db


Expand Down
31 changes: 31 additions & 0 deletions fastappkit/cli/templates/app/external/config.py.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"""
Settings configuration for {{ app_name }}.

Uses pydantic-settings to load from .env file.
"""

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class Settings(BaseSettings):
"""
Application settings.

Loads from .env file automatically.
"""

database_url: str = Field(
default="sqlite:///./{{ app_name }}.db",
alias="DATABASE_URL"
)
debug: bool = Field(
default=False,
alias="DEBUG"
)

model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
populate_by_name=True
)
5 changes: 5 additions & 0 deletions fastappkit/cli/templates/app/external/env.example.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Database Configuration
DATABASE_URL=sqlite:///./{{ app_name }}.db

# Development Settings
DEBUG=false
44 changes: 44 additions & 0 deletions fastappkit/cli/templates/app/external/main.py.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""
Main entry point for {{ app_name }}.

Run with: uvicorn main:app --reload

This allows you to develop and test the external app independently
without requiring a full fastappkit project.
"""

import sys
from pathlib import Path

# Add current directory to sys.path so we can import the package
# This allows running without 'pip install -e .' (though that's recommended)
_current_dir = Path(__file__).parent
if str(_current_dir) not in sys.path:
sys.path.insert(0, str(_current_dir))

from fastapi import FastAPI

from {{ app_name }}.config import Settings
from {{ app_name }} import register

# Load settings from .env
settings = Settings()

# Create FastAPI app
app = FastAPI(
title="{{ app_name }}",
description="External pluggable app for fastappkit",
debug=settings.debug,
)

# Register this app's router
router = register(app)
if router is not None:
# If register() returns a router, mount it with default prefix
app.include_router(router, prefix="/{{ app_name }}", tags=["{{ app_name }}"])


if __name__ == "__main__":
import uvicorn

uvicorn.run(app, host="127.0.0.1", port=8000, reload=True)
13 changes: 9 additions & 4 deletions fastappkit/cli/templates/app/external/migrations/env.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,17 @@ target_metadata = Base.metadata

config = context.config

# Get database URL from config (set by fastappkit when running from core project)
# or from environment variable (when running Alembic directly from external app)
# or from alembic.ini (fallback for direct Alembic usage)
# Get database URL with the following precedence order:
# 1. Config (set by fastappkit when running from core project)
# 2. DATABASE_URL environment variable (from .env file when running independently)
# 3. alembic.ini sqlalchemy.url (fallback)
#
# This allows the external app to work both:
# - When used in a core project (fastappkit sets it via config)
# - When developed independently (reads from .env file)
database_url = config.get_main_option("sqlalchemy.url")
if not database_url:
# If not set in config, try environment variable
# If not set in config, try environment variable (from .env)
import os
database_url = os.getenv("DATABASE_URL")
if database_url:
Expand Down
4 changes: 3 additions & 1 deletion fastappkit/cli/templates/app/external/pyproject.toml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ license = "MIT"
packages = [{include = "{{ app_name }}"}]

[tool.poetry.dependencies]
python = "^3.11"
python = ">=3.11,<4.0"
fastapi = "*"
sqlalchemy = "*"
alembic = "*"
pydantic-settings = "*"
uvicorn = {extras = ["standard"], version = "*"}

# Note: FastAppKit metadata is in {{ app_name }}/fastappkit.toml
# This file is included in the package when published to PyPI
2 changes: 1 addition & 1 deletion fastappkit/cli/templates/project/pyproject.toml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ readme = "README.md"
license = "MIT"

[tool.poetry.dependencies]
python = "^3.11"
python = ">=3.11,<4.0"
fastapi = "*"
fastappkit = "*"
{% if use_poetry %}uvicorn = {extras = ["standard"], version = "*"}{% endif %}
Expand Down
4 changes: 2 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ include = [
]

[tool.poetry.dependencies]
python = "^3.11.6"
python = ">=3.11,<4.0"
fastapi = ">=0.120.0,<0.130"
sqlalchemy = ">=2.0,<3.0"
alembic = ">=1.17.2,<1.18"
Expand Down