Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from fastapi_startkit.console.command import Command
from cleo.helpers import option

from fastapi_startkit.console.command import Command
from fastapi_startkit.support import Uri


class ServeCommand(Command):
name = "serve"
Expand Down Expand Up @@ -42,15 +44,40 @@ def handle(self):
from fastapi_startkit import Config
from fastapi_startkit.container import Container

# 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)
# 1. Start from hardcoded defaults
host: str = "127.0.0.1"
port: int = 8000

# 2. Override with APP_URL (lowest env-level source)
app_url: str | None = Config.get("fastapi.app_url")
if app_url:
normalized = app_url if "://" in app_url else f"http://{app_url}"
uri = Uri.of(normalized)
if uri.host():
host = uri.host()
if uri.port() is not None:
port = uri.port()

# 3. Override with APP_HOST / APP_PORT (higher than APP_URL)
cfg_host: str | None = Config.get("fastapi.host")
cfg_port: int | None = Config.get("fastapi.port")
if cfg_host:
host = cfg_host
if cfg_port is not None:
port = int(cfg_port)

# 4. Override with CLI flags (highest priority)
cli_host: str | None = self.option("host")
cli_port: str | None = self.option("port")
if cli_host:
host = cli_host
if cli_port:
port = int(cli_port)

reload = cfg_reload if self.option("reload") is None else self.option("reload")
app = self.option("app")

Expand Down
10 changes: 6 additions & 4 deletions fastapi_startkit/src/fastapi_startkit/fastapi/config/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
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.
All fields are raw environment values — no parsing or defaults applied here.
Resolution logic (APP_HOST / APP_PORT → APP_URL → 127.0.0.1 / 8000)
lives in ServeCommand.
"""

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))
host: str | None = dataclasses.field(default_factory=lambda: env("APP_HOST"))
port: int | None = dataclasses.field(default_factory=lambda: env("APP_PORT"))
app_url: str | None = dataclasses.field(default_factory=lambda: env("APP_URL"))
reload: bool = dataclasses.field(default_factory=lambda: env("APP_RELOAD", True))
reload_dirs: list | None = None
reload_excludes: list = dataclasses.field(
Expand Down
52 changes: 52 additions & 0 deletions fastapi_startkit/tests/fastapi/test_fastapi_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Tests for FastAPIConfig raw values."""

from fastapi_startkit.fastapi.config.fastapi import FastAPIConfig


class TestFastAPIConfigDefaults:
"""FastAPIConfig stores raw env values — no defaults or resolution here."""

def test_host_is_falsy_without_env(self, monkeypatch):
monkeypatch.delenv("APP_HOST", raising=False)
assert not FastAPIConfig().host

def test_port_is_falsy_without_env(self, monkeypatch):
monkeypatch.delenv("APP_PORT", raising=False)
assert not FastAPIConfig().port

def test_app_url_is_falsy_without_env(self, monkeypatch):
monkeypatch.delenv("APP_URL", raising=False)
assert not FastAPIConfig().app_url

def test_default_reload_is_true(self, monkeypatch):
monkeypatch.delenv("APP_RELOAD", raising=False)
assert FastAPIConfig().reload is True

def test_default_reload_dirs_is_none(self):
assert FastAPIConfig().reload_dirs is None

def test_default_reload_excludes(self):
excludes = FastAPIConfig().reload_excludes
assert "*.log" in excludes
assert "tests/*" in excludes
assert "node_modules/*" in excludes


class TestFastAPIConfigEnvVars:
"""FastAPIConfig reads raw env values without defaults."""

def test_app_host(self, monkeypatch):
monkeypatch.setenv("APP_HOST", "0.0.0.0")
assert FastAPIConfig().host == "0.0.0.0"

def test_app_port(self, monkeypatch):
monkeypatch.setenv("APP_PORT", "9000")
assert FastAPIConfig().port == 9000

def test_app_url(self, monkeypatch):
monkeypatch.setenv("APP_URL", "http://myapp.com:9000")
assert FastAPIConfig().app_url == "http://myapp.com:9000"

def test_app_reload_false(self, monkeypatch):
monkeypatch.setenv("APP_RELOAD", "False")
assert FastAPIConfig().reload is False
Loading