Source code for manna.config

from functools import lru_cache
from typing import Literal

from pydantic import Field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


[docs] class Settings(BaseSettings): model_config = SettingsConfigDict( env_prefix="MANNA_", env_file=".env", env_file_encoding="utf-8", extra="ignore", ) host: str = "0.0.0.0" port: int = 8000 # Optional comma-separated allow-list of archive short_names # (e.g. "datalab,alma"). Unset/empty => every archive physically present in # the `archives/` package is active. See archives/__init__.py and # docs/archives-spec.md. archives: str | None = None log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR"] = "INFO" # Optional comma-separated allow-list of hostnames the server may fetch # (e.g. "almascience.eso.org,eso.org"). Matches exactly or as a subdomain. # Unset => any *public* host is reachable, which keeps search_ivoa_registry # discovery working; private/loopback/link-local space is refused either # way. See _url_guard.py. allowed_hosts: str | None = None # Slice 5: async TAP family. tap_sync_timeout_seconds: float = 20.0 # count_observations_near_target: bounded budget for polling an async count job # (an archive whose count_target runs async). A COUNT returns one row and # completes fast once scheduled; on budget exhaustion the tool returns a # pending envelope with the job_url. count_async_budget_seconds: float = Field(default=15.0, gt=0) count_async_poll_interval_seconds: float = Field(default=1.0, gt=0) # Bounded server-side wait on async TAP jobs (tools/tap.py::_wait_for_phase). # get_async_job_status blocks up to async_wait_seconds when the call omits # wait_seconds; a caller-supplied value is clamped to async_wait_max_seconds. # The run_adql_query async promotion waits the default before returning its # envelope. Keep the max under the MCP SDK's 60 s client request timeout — # the worst single call is a 20 s sync attempt plus the promotion wait. async_wait_seconds: float = Field(default=20.0, ge=0) async_wait_max_seconds: float = Field(default=30.0, ge=0) # Inline response caps (results.py). A TAP result larger than EITHER limit # is routed to an async job whose result the client fetches itself (the # server never holds the bytes); discovery tools (cone / SIA search) # truncate inline instead. Defaults are sized for small-context backends # (e.g. a 131072-token local vLLM), where a single fat inline result can # overflow the model window. Raise them for frontier models with large # context windows. inline_row_limit: int = 200 inline_byte_limit: int = 48 * 1024 # describe_ivoa_service degrades from full per-column detail to a table # catalog (names + descriptions + column counts) once the full introspection # payload would exceed this many bytes. Prevents a large service (e.g. Gaia, # ~127k tokens of tables × columns) from overflowing the model context. See # shape_registry_describe_result. registry_describe_byte_limit: int = 48 * 1024 @model_validator(mode="after") def _async_wait_default_within_max(self) -> "Settings": if self.async_wait_seconds > self.async_wait_max_seconds: raise ValueError( "MANNA_ASYNC_WAIT_SECONDS must not exceed MANNA_ASYNC_WAIT_MAX_SECONDS" ) return self
[docs] @lru_cache(maxsize=1) def get_settings() -> Settings: """Process-wide Settings singleton. Cached so runtime consumers (the lazy backend accessors, the URL guard) read environment / .env once rather than re-parsing per call. Tests that mutate the environment must call ``get_settings.cache_clear()`` to force a re-read. """ return Settings()