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
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
MAIN_BASE_URL="http://localhost:8012"

# Infrastructure
DB_URL="postgresql+asyncpg://postgres:changethis@db:5432/app"
REDIS_URL="redis://default:changethis@redis:6379/0"
DB_URL="postgresql+asyncpg://postgres:changethis@fastid-db:5432/app"
REDIS_URL="redis://default:changethis@fastid-redis:6379/0"

# Notifications
NOTIFY_FROM_NAME="FastID"
Expand Down Expand Up @@ -41,7 +41,7 @@ NOTIFY_SMTP_PASSWORD=...

# Plugins
#OBS_ENABLED=1
#OBS_TEMPO_URL="http://host.docker.internal:4317"
#OBS_TEMPO_URL="http://tempo:4317"

# Docker environment
POSTGRES_USER="postgres"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
run: |
touch .env
docker compose down -v --remove-orphans
docker compose up -d db redis
docker compose up -d fastid-db fastid-redis
poetry run make certs

- name: Run tests
Expand Down
2 changes: 1 addition & 1 deletion docker-compose-prod.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
services:
fastapi:
fastid-app:
build:
target: prod
43 changes: 26 additions & 17 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
x-logging: &default-logging
# Uncomment below to enable default logging
driver: json-file
# Uncomment below to enable Loki logging
# driver: loki
# options:
# loki-url: 'http://localhost:3100/api/prom/push'
# loki-pipeline-stages: |
# - multiline:
# firstline: '^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2}'
# max_wait_time: 3s
# - regex:
# expression: '^(?P<time>\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2},d{3}) (?P<message>(?s:.*))$$'
# Comment below to enable Loki logging
driver: json-file
# Uncomment below to enable Loki logging
# driver: loki
# options:
# loki-url: 'http://localhost:3100/api/prom/push'
# loki-pipeline-stages: |
# - multiline:
# firstline: '^\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2}'
# max_wait_time: 3s
# - regex:
# expression: '^(?P<time>\d{4}-\d{2}-\d{2} \d{1,2}:\d{2}:\d{2},d{3}) (?P<message>(?s:.*))$$'

services:

db:
fastid-db:
image: postgres:16-alpine
env_file:
- .env
Expand All @@ -33,8 +33,10 @@ services:
retries: 5
start_period: 30s
timeout: 10s
networks:
- fastid-net

redis:
fastid-redis:
image: redis:latest
env_file:
- .env
Expand All @@ -54,8 +56,10 @@ services:
- redis_data:/var/lib/redis/data
ports:
- "6312:6379"
networks:
- fastid-net

fastapi:
fastid-app:
build:
context: .
dockerfile: docker/fastapi.Dockerfile
Expand All @@ -66,14 +70,19 @@ services:
ports:
- "8012:8000"
depends_on:
- db
- redis
- fastid-db
- fastid-redis
logging: *default-logging
volumes:
- "./fastid:/opt/fastid/fastid"
- "./templates:/opt/fastid/templates"
- "./static:/opt/fastid/static"
networks:
- fastid-net

volumes:
pg_data:
redis_data:
networks:
fastid-net:
name: fastid-net
51 changes: 34 additions & 17 deletions fastid/api/mini_app.py → fastid/api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Any

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from fastid.api.exceptions import add_exception_handlers
from fastid.api.lifespan import LifespanTasks
Expand All @@ -11,41 +12,57 @@


class APIMiniApp(MiniApp):
module_name = "api"
name = "api"

def __init__(
def __init__( # noqa: PLR0913
self,
title: str = "Unnamed App",
title: str = "FastID API",
version: str = "0.1.0",
base_url: str = "/api/v1",
allow_origins: Sequence[str] = ("*",),
allow_origin_regex: str | None = None,
plugins: Sequence[Plugin] = (),
**fastapi_kwargs: Any,
) -> None:
self.title = title
self.version = version
self.base_url = base_url
self.allow_origins = allow_origins
self.allow_origin_regex = allow_origin_regex
self.plugins = plugins
self.fastapi_kwargs = fastapi_kwargs

async def on_startup(self, _app: FastAPI) -> None:
async with LifespanTasks() as tasks:
await tasks.on_startup()

async def on_shutdown(self, _app: FastAPI) -> None:
async with LifespanTasks() as tasks:
await tasks.on_shutdown()

def install(self, app: FastAPI) -> None:
api_app = FastAPI(
def create(self) -> FastAPI:
app = FastAPI(
title=self.title,
version=self.version,
**self.fastapi_kwargs,
)
api_app.include_router(api_router)
add_exception_handlers(api_app)
app.include_router(api_router)
add_exception_handlers(app)
app.add_middleware(
CORSMiddleware,
allow_origins=self.allow_origins,
allow_origin_regex=self.allow_origin_regex,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
for plugin in self.plugins:
plugin.install(api_app)
installed = [plugin.plugin_name for plugin in self.plugins]
plugin.install(app)
installed = [plugin.name for plugin in self.plugins]
log.info("Plugins (%d): %s", len(installed), ", ".join(installed))
return app

def install(self, app: FastAPI) -> None:
api_app = self.create()
app.mount(self.base_url, api_app)
app.extra["api_app"] = api_app

async def on_startup(self, _app: FastAPI) -> None:
async with LifespanTasks() as tasks:
await tasks.on_startup()

async def on_shutdown(self, _app: FastAPI) -> None:
async with LifespanTasks() as tasks:
await tasks.on_shutdown()
70 changes: 27 additions & 43 deletions fastid/core/app.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,44 @@
from fastid.api.mini_app import APIMiniApp
from fastid.core.base import MiniApp, Plugin, app_factory
from fastid.api.app import APIMiniApp
from fastid.core.base import Plugin, app_factory
from fastid.core.config import cors_settings, main_settings
from fastid.core.middlewares.cors import CORSPlugin
from fastid.dashboard.app import AdminMiniApp
from fastid.dashboard.config import admin_settings
from fastid.dashboard.mini_app import AdminMiniApp
from fastid.database.dependencies import engine
from fastid.pages.mini_app import FrontendMiniApp
from fastid.pages.app import FrontendMiniApp
from fastid.plugins.obs.config import obs_settings
from fastid.plugins.obs.metrics import MetricsPlugin
from fastid.plugins.obs.tracing import TracingPlugin

mini_apps: list[MiniApp] = []
api_plugins: list[Plugin] = [
CORSPlugin(
origins=cors_settings.origins,
origin_regex=cors_settings.origin_regex,
)
]
plugins: list[Plugin] = []

# Must be last plugin
if obs_settings.enabled:
api_plugins.extend(
(
MetricsPlugin(app_name=main_settings.discovery_name),
TracingPlugin(
app_name=main_settings.discovery_name,
export_url=obs_settings.tempo_url,
instrument=["logger", "httpx", "sqlalchemy"],
engine=engine,
),
)
metrics_plugin = MetricsPlugin(app_name=main_settings.discovery_name)
tracing_plugin = TracingPlugin(
app_name=main_settings.discovery_name,
export_url=obs_settings.tempo_url,
instrument=["logger", "httpx", "sqlalchemy"],
engine=engine,
)
plugins += [metrics_plugin, tracing_plugin]

mini_apps.append(
APIMiniApp(
title=main_settings.title,
version=main_settings.version,
base_url=main_settings.api_path,
plugins=api_plugins,
)
api_app = APIMiniApp(
title=main_settings.title,
version=main_settings.version,
base_url=main_settings.api_path,
allow_origins=cors_settings.origins,
allow_origin_regex=cors_settings.origin_regex,
plugins=plugins,
)

if admin_settings.enabled:
mini_apps.append(
AdminMiniApp(
engine,
title=f"{main_settings.title} Admin",
favicon_url=admin_settings.favicon_url,
logo_url=admin_settings.logo_url,
)
)

# Must be last module
mini_apps.append(FrontendMiniApp(title=main_settings.title))
admin_app = AdminMiniApp(
engine,
title=f"{main_settings.title} Admin",
favicon_url=admin_settings.favicon_url,
logo_url=admin_settings.logo_url,
)
frontend_app = FrontendMiniApp(title=main_settings.title)

app = app_factory(
title=main_settings.title,
mini_apps=mini_apps,
apps=[api_app, admin_app, frontend_app],
)
31 changes: 19 additions & 12 deletions fastid/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@


class MiniApp:
module_name = "unknown_module"
name = "unknown_app"

@abstractmethod
def create(self) -> FastAPI: ...

@abstractmethod
def install(self, app: FastAPI) -> None: ...
Expand All @@ -22,34 +25,38 @@ async def on_shutdown(self, app: FastAPI) -> None:


class Plugin:
plugin_name: str = "unknown_plugin"
name: str = "unknown_plugin"
scope: Sequence[str] = ()

@abstractmethod
def install(self, app: FastAPI) -> None: ...


def app_factory(
*,
title: str = "Unknown app",
mini_apps: Sequence[MiniApp] = (),
title: str = "FastID",
apps: Sequence[MiniApp] = (),
**kwargs: Any,
) -> FastAPI:
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
# Startup tasks
for m in mini_apps:
for m in apps:
await m.on_startup(_app)
yield
# Shutdown tasks
for m in mini_apps:
for m in apps:
await m.on_shutdown(_app)

app = FastAPI(title=title, lifespan=lifespan, **kwargs)
for mini_app in mini_apps:
mini_app.install(app)
installed = [mini_app.module_name for mini_app in mini_apps]
log.info("Mini apps (%d): %s", len(installed), ", ".join(installed))
return app
master_app = FastAPI(title=title, lifespan=lifespan, **kwargs)

# Install apps
for app in apps:
app.install(master_app)
installed = [mini_app.name for mini_app in apps]
log.info("Apps (%d): %s", len(installed), ", ".join(installed))

return master_app


class UseCase:
Expand Down
2 changes: 1 addition & 1 deletion fastid/core/middlewares/cors.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@


class CORSPlugin(Plugin):
plugin_name = "cors"
name = "cors"

def __init__(
self,
Expand Down
12 changes: 8 additions & 4 deletions fastid/dashboard/mini_app.py → fastid/dashboard/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


class AdminMiniApp(MiniApp):
module_name = "admin"
name = "admin"

def __init__(
self,
Expand All @@ -23,10 +23,10 @@ def __init__(
self.base_url = base_url
self.admin_kwargs = admin_kwargs

def install(self, app: FastAPI) -> None:
admin_app = FastAPI()
def create(self) -> FastAPI:
app = FastAPI()
admin = Admin(
admin_app,
app,
self.engine,
base_url="/",
authentication_backend=admin_auth,
Expand All @@ -35,5 +35,9 @@ def install(self, app: FastAPI) -> None:
admin.add_view(UserAdmin)
admin.add_view(OAuthClientAdmin)
admin.add_view(OAuthAccountAdmin)
return app

def install(self, app: FastAPI) -> None:
admin_app = self.create()
app.mount(self.base_url, admin_app)
app.extra["admin_app"] = admin_app
Loading