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
3 changes: 2 additions & 1 deletion bin/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"

# ── Start MySQL via Docker Compose ────────────────────────────────────────────
echo "Starting test services..."
docker compose -f "$ROOT/docker-compose.yml" down --remove-orphans
docker compose -f "$ROOT/docker-compose.yml" up -d --wait

# Ensure MySQL is torn down on exit (even if tests fail)
trap 'echo "Stopping test services..."; docker compose -f "$ROOT/docker-compose.test.yml" down' EXIT
trap 'echo "Stopping test services..."; docker compose -f "$ROOT/docker-compose.yml" down' EXIT

# ── Common DB env vars (match docker-compose.test.yml) ───────────────────────
export DB_HOST=127.0.0.1
Expand Down
2 changes: 1 addition & 1 deletion example/config-app/uv.lock

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

2 changes: 1 addition & 1 deletion example/database-app/uv.lock

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

3 changes: 2 additions & 1 deletion example/fastapi-app/bootstrap/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
from fastapi_startkit import Application
from fastapi_startkit.logging import LogProvider
from providers.fastapi_provider import FastAPIProvider
from config.fastapi import FastAPIConfig

app: Application = Application(
base_path=str(Path.cwd()), # This always gives path relative to the execution.
providers=[
LogProvider,
FastAPIProvider,
(FastAPIProvider, FastAPIConfig),
]
)
18 changes: 18 additions & 0 deletions example/fastapi-app/config/fastapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import dataclasses

from fastapi_startkit.environment import env


@dataclasses.dataclass
class FastAPIConfig:
host: str = dataclasses.field(default_factory=lambda: env("APP_HOST", "127.0.0.1"))
port: int = dataclasses.field(default_factory=lambda: env("APP_PORT", 8000))
reload: bool = dataclasses.field(default_factory=lambda: env("APP_RELOAD", True))
reload_dirs: list | None = None
reload_excludes: list = dataclasses.field(
default_factory=lambda: [
"*.log",
"tests/*",
"node_modules/*",
]
)
5 changes: 5 additions & 0 deletions example/fastapi-app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ dependencies = [

[tool.uv.sources]
fastapi-startkit = { path = "../../fastapi_startkit", editable = true }

[dependency-groups]
dev = [
"dumpdie>=1.5.0",
]
2 changes: 1 addition & 1 deletion example/fastapi-app/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ async def index():
Logger.info("Welcome to FastAPI StartKit!")
Logger.info("Version: 1.0.0")
Logger.info("Docs: /docs")
Logger.log("debug", "Debugging the application.")
Logger.log("debug", "Debugging the application!")
return {
"message": "Welcome to FastAPI StartKit!",
"version": "1.0.0",
Expand Down
36 changes: 34 additions & 2 deletions example/fastapi-app/uv.lock

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

2 changes: 1 addition & 1 deletion fastapi_startkit/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fastapi-startkit"
version = "0.22.0"
version = "0.23.0"
description = "Fastapi Starter kit components"
authors = [
{name = "Bedram Tamang", email = "tmgbedu@gmail.com"}
Expand Down
2 changes: 1 addition & 1 deletion fastapi_startkit/src/fastapi_startkit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
from .console import ConsoleApplication
from .configuration.config import Config

__all__ = ["Application", "Config"]
__all__ = ["Application", "ConsoleApplication", "Config"]
3 changes: 2 additions & 1 deletion fastapi_startkit/src/fastapi_startkit/fastapi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .providers.fastapi_provider import FastAPIProvider
from .routers.router import Router
from .requests.model import RequestModel
from .config import FastAPIConfig

__all__ = ["FastAPIProvider", "Router", "RequestModel"]
__all__ = ["FastAPIProvider", "Router", "RequestModel", "FastAPIConfig"]
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,22 @@ class ServeCommand(Command):
"port",
"p",
flag=False,
default="8000",
description="The port to serve the application on",
default=None,
description="The port to serve the application on (overrides fastapi config)",
),
option(
"host",
None,
flag=False,
default="127.0.0.1",
description="The host to bind to",
default=None,
description="The host to bind to (overrides fastapi config)",
),
option(
"reload",
"r",
flag=False,
default=True,
description="Enable auto-reload on code changes",
default=None,
description="Enable auto-reload on code changes (overrides fastapi config)",
),
option(
"app",
Expand All @@ -39,12 +39,20 @@ class ServeCommand(Command):

def handle(self):
import uvicorn
from fastapi_startkit import Config
from fastapi_startkit.container import Container

port = int(self.option("port"))
host = self.option("host")
# Resolve server settings: CLI flag > fastapi config > uvicorn default (None)
cfg_host = Config.get("fastapi.host", "127.0.0.1")
cfg_port = Config.get("fastapi.port", 8000)
cfg_reload = Config.get("fastapi.reload", True)
cfg_reload_dirs = Config.get("fastapi.reload_dirs") or None
cfg_reload_excludes = Config.get("fastapi.reload_excludes") or None

host = self.option("host") or cfg_host
port = int(self.option("port") or cfg_port)
reload = cfg_reload if self.option("reload") is None else self.option("reload")
app = self.option("app")
reload = self.option("reload")

exist = self.is_app_exist()

Expand All @@ -59,11 +67,13 @@ def handle(self):
kwargs.update(
{
"app": app,
"reload": reload,
"factory": True,
"reload_excludes": ["*.log", "tests/*"],
}
)
if cfg_reload_dirs is not None:
kwargs["reload_dirs"] = cfg_reload_dirs
if cfg_reload_excludes is not None:
kwargs["reload_excludes"] = cfg_reload_excludes

self.line(
f"<info>Starting Uvicorn server on {host}:{port} [{app}]...</info>"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .fastapi import FastAPIConfig

__all__ = ["FastAPIConfig"]
24 changes: 24 additions & 0 deletions fastapi_startkit/src/fastapi_startkit/fastapi/config/fastapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import dataclasses

from fastapi_startkit.environment import env


@dataclasses.dataclass
class FastAPIConfig:
"""Server configuration for the uvicorn/FastAPI serve command.

All fields can be overridden via environment variables or by publishing a
``config/fastapi.py`` file in the application root.
"""

host: str = dataclasses.field(default_factory=lambda: env("APP_HOST", "127.0.0.1"))
port: int = dataclasses.field(default_factory=lambda: env("APP_PORT", 8000))
reload: bool = dataclasses.field(default_factory=lambda: env("APP_RELOAD", True))
reload_dirs: list | None = None
reload_excludes: list = dataclasses.field(
default_factory=lambda: [
"*.log",
"tests/*",
"node_modules/*",
]
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
from fastapi import FastAPI

from fastapi_startkit.fastapi.commands import ServeCommand
from fastapi_startkit.fastapi.config import FastAPIConfig
from fastapi_startkit.providers import Provider


class FastAPIProvider(Provider):
provider_key = "fastapi"

def register(self) -> None:
"""Create a FastAPI instance and register routers."""
config = self.resolve_config(FastAPIConfig)
self.merge_config_from(config, self.provider_key)

fastapi = FastAPI(
title="Jobins AI Agent (LangChain)",
version="1.0.0",
Expand All @@ -16,9 +22,16 @@ def register(self) -> None:
self.app.use_fastapi(fastapi)

def boot(self):
import os

self.commands([ServeCommand])
self._register_exception_handlers()

source = os.path.abspath(
os.path.join(os.path.dirname(__file__), "../config/fastapi.py")
)
self.publishes({source: "config/fastapi.py"})

def _register_exception_handlers(self):
"""Wire exception_manager as a catch-all handler for all exceptions."""
from fastapi import HTTPException
Expand Down
2 changes: 1 addition & 1 deletion fastapi_startkit/uv.lock

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

Loading