-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
221 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |