Skip to content

Core

Platform infrastructure: deployment settings, FastAPI dependencies, and domain errors.

HTTP routes themselves are documented in the running API's OpenAPI UI (/docs).

app.core.settings

Compass platform settings (deployment), not use-case configuration.

Settings

Bases: BaseSettings

Process-wide Compass knobs loaded from the environment at startup.

is_development property

Whether the app is running in a development environment.

Returns:

Type Description
bool

True when compass_environment is 'development' or 'dev'.

cors_origins property

Origins allowed to call the API cross-origin; empty for same-origin only.

Returns:

Type Description
list[str]

Stripped origin strings from compass_cors_origins.

reload_token property

Token expected in the X-Reload-Token header.

Returns:

Type Description
str

Configured reload secret (may be empty).

ontology_dir property

Root directory for shared ontology files (shapes, templates).

Returns:

Type Description
Path

Explicit compass_ontology_dir or the package-relative default.

use_case_dir property

Directory holding this deployment's compass.ttl and vocab.ttl.

Returns:

Type Description
Path

ontology_dir / compass_use_case.

_empty_ontology_dir_is_none(value) classmethod

Treat an empty string env override as unset.

Parameters:

Name Type Description Default
value object

Raw field value before validation.

required

Returns:

Type Description
object

None for empty input, otherwise value unchanged.

Source code in src/backend/app/core/settings.py
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@field_validator("compass_ontology_dir", mode="before")
@classmethod
def _empty_ontology_dir_is_none(cls, value: object) -> object:
    """Treat an empty string env override as unset.

    Args:
        value: Raw field value before validation.

    Returns:
        ``None`` for empty input, otherwise *value* unchanged.
    """
    if value == "" or value is None:
        return None
    return value

_strip_use_case(value) classmethod

Reject empty use-case names and strip whitespace.

Parameters:

Name Type Description Default
value object

Raw field value before validation.

required

Returns:

Type Description
object

Stripped string, or value unchanged when not a string.

Source code in src/backend/app/core/settings.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
@field_validator("compass_use_case", mode="before")
@classmethod
def _strip_use_case(cls, value: object) -> object:
    """Reject empty use-case names and strip whitespace.

    Args:
        value: Raw field value before validation.

    Returns:
        Stripped string, or *value* unchanged when not a string.
    """
    if isinstance(value, str):
        stripped = value.strip()
        if not stripped:
            raise ValueError("COMPASS_USE_CASE must be a non-empty directory name")
        return stripped
    return value

_default_ontology_dir()

Resolve the default ontology directory relative to this package.

Returns:

Type Description
Path

src/ontology when the layout matches a normal checkout.

Source code in src/backend/app/core/settings.py
11
12
13
14
15
16
17
18
19
def _default_ontology_dir() -> Path:
    """Resolve the default ontology directory relative to this package.

    Returns:
        ``src/ontology`` when the layout matches a normal checkout.
    """
    # app/core/settings.py -> app -> backend -> src -> ontology
    base = Path(__file__).resolve().parents[3]
    return base / "ontology"

_project_root_env_file()

Return the project-root .env file if it exists.

Returns:

Type Description
Path | None

Path to .env at the repository root, or None when absent.

Path | None

Docker Compose injects variables directly, so the file is optional.

Source code in src/backend/app/core/settings.py
22
23
24
25
26
27
28
29
30
31
def _project_root_env_file() -> Path | None:
    """Return the project-root .env file if it exists.

    Returns:
        Path to ``.env`` at the repository root, or ``None`` when absent.
        Docker Compose injects variables directly, so the file is optional.
    """
    # app/core/settings.py -> app -> backend -> src -> project root
    env_file = Path(__file__).resolve().parents[4] / ".env"
    return env_file if env_file.exists() else None

app.core.deps

Shared FastAPI dependencies.

get_settings()

Provide the process-wide deployment settings.

Returns:

Type Description
Settings

Singleton Settings instance.

Source code in src/backend/app/core/deps.py
15
16
17
18
19
20
21
def get_settings() -> Settings:
    """Provide the process-wide deployment settings.

    Returns:
        Singleton ``Settings`` instance.
    """
    return settings

get_config()

Provide the use-case configuration.

Returns:

Type Description
Config

Singleton Config instance.

Source code in src/backend/app/core/deps.py
24
25
26
27
28
29
30
def get_config() -> Config:
    """Provide the use-case configuration.

    Returns:
        Singleton ``Config`` instance.
    """
    return config

get_lang(cfg, lang='en')

Validate and return the lang query parameter.

Parameters:

Name Type Description Default
cfg Annotated[Config, Depends(get_config)]

Use-case config (defines supported_langs).

required
lang Annotated[str, Query(description='UI language code')]

Requested language code.

'en'

Returns:

Type Description
str

Validated language code.

Raises:

Type Description
UnsupportedLangError

When lang is not in cfg.supported_langs.

Source code in src/backend/app/core/deps.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
def get_lang(
    cfg: Annotated[Config, Depends(get_config)],
    lang: Annotated[str, Query(description="UI language code")] = "en",
) -> str:
    """Validate and return the ``lang`` query parameter.

    Args:
        cfg: Use-case config (defines ``supported_langs``).
        lang: Requested language code.

    Returns:
        Validated language code.

    Raises:
        UnsupportedLangError: When *lang* is not in ``cfg.supported_langs``.
    """
    if lang not in cfg.supported_langs:
        raise UnsupportedLangError(
            f"Unsupported lang {lang!r}; supported: {', '.join(cfg.supported_langs)}"
        )
    return lang

app.core.exceptions

Domain errors raised by the API and mapped to HTTP in handlers.

AppError(detail)

Bases: Exception

Base for application errors that become a consistent JSON response.

Attributes:

Name Type Description
detail

Human-readable error message returned to the client.

Store detail and pass it to Exception.

Parameters:

Name Type Description Default
detail str

Error message.

required
Source code in src/backend/app/core/exceptions.py
11
12
13
14
15
16
17
18
def __init__(self, detail: str):
    """Store *detail* and pass it to ``Exception``.

    Args:
        detail: Error message.
    """
    self.detail = detail
    super().__init__(detail)

ReloadNotConfiguredError(detail)

Bases: AppError

Reload was requested but no token is configured on the server.

Source code in src/backend/app/core/exceptions.py
11
12
13
14
15
16
17
18
def __init__(self, detail: str):
    """Store *detail* and pass it to ``Exception``.

    Args:
        detail: Error message.
    """
    self.detail = detail
    super().__init__(detail)

UnauthorizedError(detail)

Bases: AppError

The caller failed an authentication check.

Source code in src/backend/app/core/exceptions.py
11
12
13
14
15
16
17
18
def __init__(self, detail: str):
    """Store *detail* and pass it to ``Exception``.

    Args:
        detail: Error message.
    """
    self.detail = detail
    super().__init__(detail)

UnsupportedLangError(detail)

Bases: AppError

The lang query parameter is not in the use-case Config.

Source code in src/backend/app/core/exceptions.py
11
12
13
14
15
16
17
18
def __init__(self, detail: str):
    """Store *detail* and pass it to ``Exception``.

    Args:
        detail: Error message.
    """
    self.detail = detail
    super().__init__(detail)

ReloadError(detail)

Bases: AppError

The files on disk are not usable. The store already serving is untouched.

Source code in src/backend/app/core/exceptions.py
11
12
13
14
15
16
17
18
def __init__(self, detail: str):
    """Store *detail* and pass it to ``Exception``.

    Args:
        detail: Error message.
    """
    self.detail = detail
    super().__init__(detail)

QueryError(sparql, cause)

Bases: AppError

A SPARQL query could not be executed. Carries the query for the log.

Attributes:

Name Type Description
sparql

The query text that failed.

detail

Short summary of the underlying exception.

Attach the failing query and summarize cause.

Parameters:

Name Type Description Default
sparql str

Query that Oxigraph rejected.

required
cause Exception

Underlying exception.

required
Source code in src/backend/app/core/exceptions.py
45
46
47
48
49
50
51
52
53
def __init__(self, sparql: str, cause: Exception):
    """Attach the failing query and summarize *cause*.

    Args:
        sparql: Query that Oxigraph rejected.
        cause: Underlying exception.
    """
    self.sparql = sparql
    super().__init__(f"{type(cause).__name__}: {cause}")

app.core.handlers

Register exception handlers that emit one JSON error shape.

register_exception_handlers(app)

Attach domain-error handlers that return a uniform JSON detail body.

Parameters:

Name Type Description Default
app FastAPI

FastAPI application to mutate in place.

required
Source code in src/backend/app/core/handlers.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def register_exception_handlers(app: FastAPI) -> None:
    """Attach domain-error handlers that return a uniform JSON ``detail`` body.

    Args:
        app: FastAPI application to mutate in place.
    """

    @app.exception_handler(UnsupportedLangError)
    async def unsupported_lang(
        _request: Request, exc: UnsupportedLangError
    ) -> JSONResponse:
        """Map unsupported ``lang`` to HTTP 400."""
        return JSONResponse(status_code=400, content={"detail": exc.detail})

    @app.exception_handler(ReloadNotConfiguredError)
    async def reload_not_configured(
        _request: Request, exc: ReloadNotConfiguredError
    ) -> JSONResponse:
        """Map missing reload token configuration to HTTP 503."""
        return JSONResponse(status_code=503, content={"detail": exc.detail})

    @app.exception_handler(UnauthorizedError)
    async def unauthorized(_request: Request, exc: UnauthorizedError) -> JSONResponse:
        """Map failed auth checks to HTTP 401."""
        return JSONResponse(status_code=401, content={"detail": exc.detail})

    @app.exception_handler(AppError)
    async def app_error(_request: Request, exc: AppError) -> JSONResponse:
        """Map generic ``AppError`` subclasses to HTTP 400."""
        return JSONResponse(status_code=400, content={"detail": exc.detail})

    @app.exception_handler(InvalidTerm)
    async def invalid_term(_request: Request, exc: InvalidTerm) -> JSONResponse:
        """Map unsafe SPARQL term construction to HTTP 400."""
        return JSONResponse(status_code=400, content={"detail": str(exc)})

    @app.exception_handler(ReloadError)
    async def reload_error(_request: Request, exc: ReloadError) -> JSONResponse:
        """Map a rejected ontology reload to HTTP 409, keeping the live store."""
        logger.warning("ontology reload rejected: %s", exc)
        return JSONResponse(
            status_code=409,
            content={
                "detail": {
                    "reloaded": False,
                    "reason": str(exc),
                    "serving": "the previously loaded ontology is still being served",
                }
            },
        )

    @app.exception_handler(QueryError)
    async def query_error(_request: Request, exc: QueryError) -> JSONResponse:
        """Map SPARQL failures to HTTP 500 without leaking the query text."""
        logger.exception("SPARQL query failed:\n%s", exc.sparql)
        return JSONResponse(
            status_code=500,
            content={"detail": "SPARQL query failed"},
        )

app.core.development

Development-only FastAPI configuration.

configure_development(app)

Add development-only middleware and routes.

Parameters:

Name Type Description Default
app FastAPI

The FastAPI application to configure.

required
Source code in src/backend/app/core/development.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
def configure_development(app: FastAPI) -> None:
    """Add development-only middleware and routes.

    Args:
        app: The FastAPI application to configure.
    """

    app.add_middleware(
        CORSMiddleware,
        allow_origins=settings.cors_origins,
        allow_methods=["GET", "POST"],
        allow_headers=["*"],
    )