Skip to content

Commit

Permalink
Add sia dir
Browse files Browse the repository at this point in the history
  • Loading branch information
stvoutsin committed Oct 8, 2024
1 parent cf40335 commit a015dd4
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 0 deletions.
14 changes: 14 additions & 0 deletions src/sia/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""The sia service."""

__all__ = ["__version__"]

from importlib.metadata import PackageNotFoundError, version

__version__: str
"""The application version string (PEP 440 / SemVer compatible)."""

try:
__version__ = version("sia")
except PackageNotFoundError:
# package is not installed
__version__ = "0.0.0"
33 changes: 33 additions & 0 deletions src/sia/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Configuration definition."""

from __future__ import annotations

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from safir.logging import LogLevel, Profile

__all__ = ["Config", "config"]


class Config(BaseSettings):
"""Configuration for sia."""

name: str = Field("sia", title="Name of application")

path_prefix: str = Field("/sia", title="URL prefix for application")

profile: Profile = Field(
Profile.development, title="Application logging profile"
)

log_level: LogLevel = Field(
LogLevel.INFO, title="Log level of the application's logger"
)

model_config = SettingsConfigDict(
env_prefix="VO_SIAV2_", case_sensitive=False
)


config = Config()
"""Configuration for sia."""
Empty file added src/sia/handlers/__init__.py
Empty file.
52 changes: 52 additions & 0 deletions src/sia/handlers/external.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Handlers for the app's external root, ``/sia/``."""

from typing import Annotated

from fastapi import APIRouter, Depends
from safir.dependencies.logger import logger_dependency
from safir.metadata import get_metadata
from structlog.stdlib import BoundLogger

from ..config import config
from ..models import Index

__all__ = ["get_index", "external_router"]

external_router = APIRouter()
"""FastAPI router for all external handlers."""


@external_router.get(
"/",
description=(
"Document the top-level API here. By default it only returns metadata"
" about the application."
),
response_model=Index,
response_model_exclude_none=True,
summary="Application metadata",
)
async def get_index(
logger: Annotated[BoundLogger, Depends(logger_dependency)],
) -> Index:
"""GET ``/sia/`` (the app's external root).
Customize this handler to return whatever the top-level resource of your
application should return. For example, consider listing key API URLs.
When doing so, also change or customize the response model in
`sia.models.Index`.
By convention, the root of the external API includes a field called
``metadata`` that provides the same Safir-generated metadata as the
internal root endpoint.
"""
# There is no need to log simple requests since uvicorn will do this
# automatically, but this is included as an example of how to use the
# logger for more complex logging.
logger.info("Request for application metadata")

metadata = get_metadata(
package_name="sia",
application_name=config.name,
)
return Index(metadata=metadata)
42 changes: 42 additions & 0 deletions src/sia/handlers/internal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Internal HTTP handlers that serve relative to the root path, ``/``.
These handlers aren't externally visible since the app is available at a path,
``/sia``. See `sia.handlers.external` for
the external endpoint handlers.
These handlers should be used for monitoring, health checks, internal status,
or other information that should not be visible outside the Kubernetes cluster.
"""

from fastapi import APIRouter
from safir.metadata import Metadata, get_metadata

from ..config import config

__all__ = ["get_index", "internal_router"]

internal_router = APIRouter()
"""FastAPI router for all internal handlers."""


@internal_router.get(
"/",
description=(
"Return metadata about the running application. Can also be used as"
" a health check. This route is not exposed outside the cluster and"
" therefore cannot be used by external clients."
),
include_in_schema=False,
response_model=Metadata,
response_model_exclude_none=True,
summary="Application metadata",
)
async def get_index() -> Metadata:
"""GET ``/`` (the app's internal root).
By convention, this endpoint returns only the application's metadata.
"""
return get_metadata(
package_name="sia",
application_name=config.name,
)
60 changes: 60 additions & 0 deletions src/sia/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""The main application factory for the sia service.
Notes
-----
Be aware that, following the normal pattern for FastAPI services, the app is
constructed when this module is loaded and is not deferred until a function is
called.
"""

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from importlib.metadata import metadata, version

from fastapi import FastAPI
from safir.dependencies.http_client import http_client_dependency
from safir.logging import configure_logging, configure_uvicorn_logging
from safir.middleware.x_forwarded import XForwardedMiddleware

from .config import config
from .handlers.external import external_router
from .handlers.internal import internal_router

__all__ = ["app"]


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
"""Set up and tear down the application."""
# Any code here will be run when the application starts up.

yield

# Any code here will be run when the application shuts down.
await http_client_dependency.aclose()


configure_logging(
profile=config.profile,
log_level=config.log_level,
name="sia",
)
configure_uvicorn_logging(config.log_level)

app = FastAPI(
title="sia",
description=metadata("sia")["Summary"],
version=version("sia"),
openapi_url=f"{config.path_prefix}/openapi.json",
docs_url=f"{config.path_prefix}/docs",
redoc_url=f"{config.path_prefix}/redoc",
lifespan=lifespan,
)
"""The main FastAPI application for sia."""

# Attach the routers.
app.include_router(internal_router)
app.include_router(external_router, prefix=f"{config.path_prefix}")

# Add middleware.
app.add_middleware(XForwardedMiddleware)
20 changes: 20 additions & 0 deletions src/sia/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""Models for sia."""

from pydantic import BaseModel, Field
from safir.metadata import Metadata as SafirMetadata

__all__ = ["Index"]


class Index(BaseModel):
"""Metadata returned by the external root URL of the application.
Notes
-----
As written, this is not very useful. Add additional metadata that will be
helpful for a user exploring the application, or replace this model with
some other model that makes more sense to return from the application API
root.
"""

metadata: SafirMetadata = Field(..., title="Package metadata")

0 comments on commit a015dd4

Please sign in to comment.