refactor: read settings directly instead of getattr with a second default

About 85 call sites read a setting as getattr(settings, "NAME", fallback),
each carrying its own copy of the default. Every one of those names is a
field with a default on the model, so the fallback could never apply to
the real settings object; it only masked drift. Two had drifted:

- OPENAI_PROMPT_CACHE_KEY defaults to True on the model but the reader
  fell back to False, and two test stubs relied on that.
- SharePoint's MICROSOFT_AUTHORITY fallback to
  https://login.microsoftonline.com/<tenant> never fired, because the
  attribute always exists (as None), so MSAL got authority=None. The
  connector now derives the tenant authority when the setting is unset,
  as its test always assumed.

Four places read EMBEDDINGS_KEY straight from os.environ, skipping the
"None"/"" normalisation the model applies; they read the setting now.
Test stubs that replaced a module's settings with a SimpleNamespace list
every setting the code under test reads.
This commit is contained in:
arc53-machine committed 2026-09-17 11:14:34 +01:00
1 parent d70644743b
commit f882ef49a7
46 files changed
+111 -113

No files matched your search

+2 -2
View File
@@ -382,7 +382,7 @@ class BaseAgent(ABC):
when the conversation was compressed after that turn was produced —
the compressed local history is the context then, not the server's.
"""
if not getattr(settings, "OPENAI_RESPONSES_CHAIN_ACROSS_TURNS", True):
if not settings.OPENAI_RESPONSES_CHAIN_ACROSS_TURNS:
return None
if not self.chat_history:
return None
@@ -422,7 +422,7 @@ class BaseAgent(ABC):
# No provider-reported usage on the previous turn (older rows,
# estimate-only providers): nothing to bound against.
return meta["response_id"]
budget = getattr(settings, "OPENAI_RESPONSES_CHAIN_BUDGET_TOKENS", None)
budget = settings.OPENAI_RESPONSES_CHAIN_BUDGET_TOKENS
if not budget:
from docsgpt.core.model_utils import get_token_limit
+1 -1
View File
@@ -53,7 +53,7 @@ def _dedupable_tool_names() -> frozenset:
"""
from docsgpt.core.settings import settings
return frozenset(BUILTIN_AGENT_TOOLS) | frozenset(getattr(settings, "DEFAULT_CHAT_TOOLS", None) or [])
return frozenset(BUILTIN_AGENT_TOOLS) | frozenset(settings.DEFAULT_CHAT_TOOLS or [])
def _requires_approval(tool: Dict, action: Dict) -> bool:
+1 -1
View File
@@ -829,7 +829,7 @@ class ArtifactGeneratorTool(Tool):
spec_path = f"{token_dir}/spec.json"
out_path = f"{token_dir}/out.{_KIND_INFO[kind]['ext']}"
program = _RENDERERS[kind].format(spec_path=spec_path, out_path=out_path)
timeout = float(getattr(settings, "SANDBOX_EXEC_TIMEOUT", 60))
timeout = float(settings.SANDBOX_EXEC_TIMEOUT)
manager = SandboxCreator.get_manager()
try:
+1 -1
View File
@@ -113,7 +113,7 @@ def bridge_attachment(
# Reject oversize attachments BEFORE buffering them: the authoritative ``size``
# column lets us avoid pulling a multi-hundred-MB file fully into worker memory,
# and the bounded read below backstops a missing/lying ``size``.
max_bytes = int(getattr(settings, "ARTIFACT_MAX_BYTES", 0) or 0)
max_bytes = int(settings.ARTIFACT_MAX_BYTES or 0)
declared_size = attachment.get("size")
if max_bytes and isinstance(declared_size, (int, float)) and declared_size > max_bytes:
raise AttachmentBridgeError(
+4 -4
View File
@@ -87,9 +87,9 @@ class CodeExecutorTool(Tool):
baked in. Keep the package lists in sync with deployment/sandbox/Dockerfile
(jupyter) and scripts/build_daytona_snapshot.py (daytona snapshot).
"""
backend = str(getattr(settings, "SANDBOX_BACKEND", "jupyter") or "jupyter").lower()
backend = str(settings.SANDBOX_BACKEND or "jupyter").lower()
if backend == "daytona":
if getattr(settings, "DAYTONA_SNAPSHOT", None):
if settings.DAYTONA_SNAPSHOT:
return (
"Preinstalled beyond the stdlib: python-pptx, python-docx, openpyxl, "
"reportlab, lxml, pillow. pip install anything else from within the code "
@@ -330,7 +330,7 @@ class CodeExecutorTool(Tool):
# Reject an oversize input BEFORE buffering it: the declared ``size``
# avoids pulling a huge file into worker memory, and the bounded read
# below backstops a missing/lying size column.
max_bytes = int(getattr(settings, "SANDBOX_MAX_INPUT_BYTES", 0) or 0)
max_bytes = int(settings.SANDBOX_MAX_INPUT_BYTES or 0)
declared_size = version.get("size")
if max_bytes and isinstance(declared_size, (int, float)) and declared_size > max_bytes:
return {"error": f"input artifact {artifact_id} exceeds the {max_bytes}-byte sandbox input limit."}
@@ -484,7 +484,7 @@ class CodeExecutorTool(Tool):
@staticmethod
def _exec_timeout() -> float:
"""Return the fixed per-run wall-clock cap (SANDBOX_EXEC_TIMEOUT; not caller-adjustable)."""
return float(getattr(settings, "SANDBOX_EXEC_TIMEOUT", 60))
return float(settings.SANDBOX_EXEC_TIMEOUT)
@staticmethod
def _is_timeout(result: ExecResult) -> bool:
+2 -2
View File
@@ -108,11 +108,11 @@ class MCPTool(Tool):
if configured_redirect_uri:
return configured_redirect_uri.rstrip("/")
explicit = getattr(settings, "MCP_OAUTH_REDIRECT_URI", None)
explicit = settings.MCP_OAUTH_REDIRECT_URI
if explicit:
return explicit.rstrip("/")
connector_base = getattr(settings, "CONNECTOR_REDIRECT_BASE_URI", None)
connector_base = settings.CONNECTOR_REDIRECT_BASE_URI
if connector_base:
parsed = urlparse(connector_base)
if parsed.scheme and parsed.netloc:
+1 -1
View File
@@ -255,7 +255,7 @@ class ReadDocumentTool(Tool):
# The task's per-call time limits are raised to match the awaited window: bound to
# the base timeout at import, the worker would otherwise self-terminate a large
# parse long before this await gives up.
queue = getattr(settings, "DOCUMENT_PARSE_QUEUE", "parsing")
queue = settings.DOCUMENT_PARSE_QUEUE
try:
async_result = parse_document.apply_async(
args=[artifact_id, parent, self.user_id, options],
+1 -1
View File
@@ -331,7 +331,7 @@ class WorkflowAgent(BaseAgent):
from docsgpt.storage.storage_creator import StorageCreator
storage = StorageCreator.get_storage()
max_bytes = int(getattr(settings, "ARTIFACT_MAX_BYTES", 0) or 0)
max_bytes = int(settings.ARTIFACT_MAX_BYTES or 0)
dropped: List[str] = []
if len(self.attachments) > _MAX_INPUT_DOCUMENTS:
over = len(self.attachments) - _MAX_INPUT_DOCUMENTS
+7 -7
View File
@@ -639,7 +639,7 @@ class WorkflowEngine:
raw_ids = self._resolve_input_artifact_ids(inputs)
if not raw_ids:
return loaded
max_bytes = int(getattr(settings, "SANDBOX_MAX_INPUT_BYTES", 0) or 0)
max_bytes = int(settings.SANDBOX_MAX_INPUT_BYTES or 0)
storage = StorageCreator.get_storage()
# Two inputs whose current versions share a filename would clobber each other at the
# same ``inputs/{name}`` path; track used paths and disambiguate deterministically.
@@ -749,15 +749,15 @@ class WorkflowEngine:
supported = set(supported_types)
supports_images = any(t.startswith("image/") for t in supported)
max_files = int(getattr(settings, "WORKFLOW_NODE_NATIVE_MAX_FILES", 5))
extract_max = int(getattr(settings, "WORKFLOW_NODE_EXTRACT_MAX_FILES", 5))
max_files = int(settings.WORKFLOW_NODE_NATIVE_MAX_FILES)
extract_max = int(settings.WORKFLOW_NODE_EXTRACT_MAX_FILES)
# One wall clock for every blocking parse this node issues. The cap
# above bounds how MANY parses run; this bounds how LONG they take in
# total, so N documents cannot serialize N size-scaled windows.
parse_deadline = time.monotonic() + float(
getattr(settings, "WORKFLOW_NODE_EXTRACT_BUDGET_SECONDS", 900)
settings.WORKFLOW_NODE_EXTRACT_BUDGET_SECONDS
)
max_bytes = int(getattr(settings, "SANDBOX_MAX_INPUT_BYTES", 25 * 1024 * 1024))
max_bytes = int(settings.SANDBOX_MAX_INPUT_BYTES)
# One read-only connection for the whole batch; the resolved-version
# rows are collected, then storage reads happen outside the DB context.
@@ -976,7 +976,7 @@ class WorkflowEngine:
if not user_id:
return None
options = {"output": "markdown", "include_tables": False, "persist": False}
queue = getattr(settings, "DOCUMENT_PARSE_QUEUE", "parsing")
queue = settings.DOCUMENT_PARSE_QUEUE
# OCR cost scales with pages, so the window grows with the document's size
# (floored at DOCUMENT_PARSE_TIMEOUT); the task's per-call time limits are
# raised to match, else the worker would self-terminate mid-parse.
@@ -1084,7 +1084,7 @@ class WorkflowEngine:
"""Return the stricter of the node's requested timeout and the sandbox cap."""
from docsgpt.core.settings import settings
cap = float(getattr(settings, "SANDBOX_EXEC_TIMEOUT", 60))
cap = float(settings.SANDBOX_EXEC_TIMEOUT)
if requested is None:
return cap
try: