coding-client 1.21.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- coding_client/__init__.py +1 -0
- coding_client/api/__init__.py +19 -0
- coding_client/api/exception_handlers.py +158 -0
- coding_client/api/logging_setup.py +44 -0
- coding_client/api/metrics.py +40 -0
- coding_client/api/openapi_config.py +124 -0
- coding_client/api/request_logging.py +60 -0
- coding_client/api/validation.py +150 -0
- coding_client/auth/__init__.py +78 -0
- coding_client/auth/_default_tenant.py +132 -0
- coding_client/auth/_header_trust.py +98 -0
- coding_client/auth/_onbehalf.py +315 -0
- coding_client/auth/_trusted_networks.py +94 -0
- coding_client/auth/dependencies.py +61 -0
- coding_client/auth/middleware.py +445 -0
- coding_client/auth/models.py +83 -0
- coding_client/auth/outbound.py +137 -0
- coding_client/auth/rbac.py +21 -0
- coding_client/auth/rbac_coding.py +5 -0
- coding_client/auth/tenant.py +650 -0
- coding_client/auth/websocket.py +148 -0
- coding_client/clients/__init__.py +24 -0
- coding_client/clients/base.py +131 -0
- coding_client/clients/coding_ai.py +33 -0
- coding_client/clients/coding_analysis.py +36 -0
- coding_client/clients/coding_containers.py +45 -0
- coding_client/clients/coding_github.py +127 -0
- coding_client/clients/coding_realtime.py +127 -0
- coding_client/clients/coding_security.py +51 -0
- coding_client/clients/coding_testing.py +31 -0
- coding_client/clients/coding_workspaces.py +265 -0
- coding_client/config/__init__.py +12 -0
- coding_client/config/base_settings.py +61 -0
- coding_client/config/yaml_config_loader.py +172 -0
- coding_client/db/__init__.py +42 -0
- coding_client/db/db_types.py +122 -0
- coding_client/db/exceptions.py +59 -0
- coding_client/db/postgres_connection.py +572 -0
- coding_client/db/tenant_query_helper.py +417 -0
- coding_client/db_init.py +49 -0
- coding_client/exceptions/__init__.py +67 -0
- coding_client/exceptions/api_exceptions.py +395 -0
- coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
- coding_client/interfaces/__init__.py +22 -0
- coding_client/interfaces/cache_interface.py +179 -0
- coding_client/interfaces/container_interface.py +49 -0
- coding_client/interfaces/docker_interface.py +283 -0
- coding_client/interfaces/graph_interface.py +28 -0
- coding_client/interfaces/security_interface.py +72 -0
- coding_client/interfaces/webhook_interface.py +67 -0
- coding_client/llm/__init__.py +54 -0
- coding_client/llm/base_agent.py +195 -0
- coding_client/llm/context.py +121 -0
- coding_client/llm/responses.py +102 -0
- coding_client/llm/routing.py +82 -0
- coding_client/llm/settings.py +102 -0
- coding_client/models/__init__.py +62 -0
- coding_client/models/agent.py +38 -0
- coding_client/models/ai_test_failure.py +745 -0
- coding_client/models/analysis.py +330 -0
- coding_client/models/architecture.py +685 -0
- coding_client/models/bug.py +128 -0
- coding_client/models/code_graph.py +317 -0
- coding_client/models/container.py +241 -0
- coding_client/models/core.py +878 -0
- coding_client/models/docker_container.py +104 -0
- coding_client/models/github.py +293 -0
- coding_client/models/logging.py +29 -0
- coding_client/models/package_manager.py +257 -0
- coding_client/models/pr_validation.py +440 -0
- coding_client/models/pydantic_ai.py +395 -0
- coding_client/models/repo_scanner.py +249 -0
- coding_client/models/responses.py +431 -0
- coding_client/models/security.py +185 -0
- coding_client/models/static_analysis.py +508 -0
- coding_client/models/system.py +48 -0
- coding_client/models/test_executor.py +204 -0
- coding_client/models/testing.py +673 -0
- coding_client/models/testing_config.py +200 -0
- coding_client/models/validation.py +127 -0
- coding_client/models_init.py +272 -0
- coding_client/telemetry.py +195 -0
- coding_client/testing.py +180 -0
- coding_client/types/__init__.py +130 -0
- coding_client/types/constraint_types.py +276 -0
- coding_client/types/core_types.py +112 -0
- coding_client/types/state_machine.py +443 -0
- coding_client/utils/__init__.py +86 -0
- coding_client/utils/archive_utils.py +387 -0
- coding_client/utils/ast_debug.py +45 -0
- coding_client/utils/async_timeout.py +364 -0
- coding_client/utils/constants.py +518 -0
- coding_client/utils/content_processing.py +326 -0
- coding_client/utils/database.py +916 -0
- coding_client/utils/encoding_helper.py +222 -0
- coding_client/utils/enum_helpers.py +166 -0
- coding_client/utils/file_operations.py +177 -0
- coding_client/utils/filesystem_ops.py +528 -0
- coding_client/utils/http_client.py +562 -0
- coding_client/utils/path_utils.py +471 -0
- coding_client/utils/profiling.py +421 -0
- coding_client/utils/response_builder.py +240 -0
- coding_client/utils/retry_helper.py +712 -0
- coding_client/utils/security_validation.py +423 -0
- coding_client-1.21.2.dist-info/METADATA +234 -0
- coding_client-1.21.2.dist-info/RECORD +109 -0
- coding_client-1.21.2.dist-info/WHEEL +5 -0
- coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
- coding_client-1.21.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""OpenTelemetry trace setup for coding-* services.
|
|
2
|
+
|
|
3
|
+
One implementation, called once per service at **application-construction time**.
|
|
4
|
+
|
|
5
|
+
Why this lives in the SDK rather than a per-repo ``telemetry.py``: the wider fleet
|
|
6
|
+
copies a ``src/telemetry.py`` into every repo, and those copies drift. Three
|
|
7
|
+
coding services each grew their own version of this block and **all three emitted
|
|
8
|
+
nothing**, for three different reasons, for months — while logging that tracing
|
|
9
|
+
was enabled.
|
|
10
|
+
|
|
11
|
+
The trap that made them silent is the reason ``setup_tracing`` refuses rather than
|
|
12
|
+
returns:
|
|
13
|
+
|
|
14
|
+
``FastAPIInstrumentor.instrument_app(app)`` called inside a FastAPI lifespan
|
|
15
|
+
produces **zero spans and raises nothing**. By the time the lifespan runs,
|
|
16
|
+
Starlette has already built the middleware stack, so the instrumentation is
|
|
17
|
+
discarded silently.
|
|
18
|
+
|
|
19
|
+
So a late call is a hard error here, named at the call site. Every other failure
|
|
20
|
+
mode (no endpoint, SDK disabled, packages absent) is a quiet ``False`` — those are
|
|
21
|
+
legitimate local/test configurations, and none of them can be mistaken for
|
|
22
|
+
working tracing, because they emit no success log either.
|
|
23
|
+
|
|
24
|
+
Configuration, matching the fleet convention already provisioned in
|
|
25
|
+
``clusters/strato``:
|
|
26
|
+
|
|
27
|
+
- ``OTLP_TRACES_ENDPOINT`` — full URL including ``/v1/traces``. Unset ⇒ no exporter.
|
|
28
|
+
- ``OTEL_SDK_DISABLED`` — ``"true"`` ⇒ no tracing at all.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import logging
|
|
34
|
+
import os
|
|
35
|
+
from typing import TYPE_CHECKING, Any
|
|
36
|
+
|
|
37
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
38
|
+
from fastapi import FastAPI
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
#: Probe and scrape paths, excluded so kubelet traffic does not dominate the
|
|
43
|
+
#: trace store. Substring-matched by the instrumentation, not anchored.
|
|
44
|
+
#: Nine services probed every ~10s would otherwise outweigh every real request
|
|
45
|
+
#: the coding fleet serves.
|
|
46
|
+
DEFAULT_EXCLUDED_URLS = "health,healthz,metrics,ready,readyz,live,livez"
|
|
47
|
+
|
|
48
|
+
#: The single span attribute that carries the raw query string. Stripped so a
|
|
49
|
+
#: future ``?token=`` parameter cannot reach the trace store. No coding-*
|
|
50
|
+
#: route declares a sensitive query parameter today (105 scanned across the
|
|
51
|
+
#: fleet); this keeps it that way by construction rather than by review.
|
|
52
|
+
_URL_ATTRIBUTES = ("http.url", "url.full")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _strip_query(url: str) -> str:
|
|
56
|
+
"""Drop everything from the first ``?``, keeping scheme/host/path."""
|
|
57
|
+
head, sep, _ = url.partition("?")
|
|
58
|
+
return head if sep else url
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _server_request_hook(span: Any, scope: dict[str, Any]) -> None:
|
|
62
|
+
"""Redact the query string from the server span while it is still writable.
|
|
63
|
+
|
|
64
|
+
Runs inside the instrumentation with a recording span, which is the only
|
|
65
|
+
point where these attributes can still be overwritten — once the span has
|
|
66
|
+
ended its attributes are frozen.
|
|
67
|
+
"""
|
|
68
|
+
if span is None or not getattr(span, "is_recording", lambda: False)():
|
|
69
|
+
return
|
|
70
|
+
for key in _URL_ATTRIBUTES:
|
|
71
|
+
value = (span.attributes or {}).get(key)
|
|
72
|
+
if isinstance(value, str) and "?" in value:
|
|
73
|
+
span.set_attribute(key, _strip_query(value))
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _already_started(app: FastAPI) -> bool:
|
|
77
|
+
"""True once Starlette has built the middleware stack.
|
|
78
|
+
|
|
79
|
+
After that point ``instrument_app`` is a silent no-op, which is the exact
|
|
80
|
+
failure this module exists to make impossible.
|
|
81
|
+
"""
|
|
82
|
+
return getattr(app, "middleware_stack", None) is not None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _instrument_httpx(provider: Any) -> bool:
|
|
86
|
+
"""Instrument outbound httpx calls against ``provider``, best-effort.
|
|
87
|
+
|
|
88
|
+
Client spans are what Tempo's ``service_graphs`` processor turns into edges —
|
|
89
|
+
a server-span-only service appears in the graph as a node nothing points at.
|
|
90
|
+
This SDK's own outbound helpers use httpx, so instrumenting it here covers
|
|
91
|
+
every coding-to-coding call without per-service work.
|
|
92
|
+
|
|
93
|
+
``HTTPXClientInstrumentor`` is a process-wide singleton that binds its tracer
|
|
94
|
+
at ``instrument()`` time, so the provider is passed explicitly rather than
|
|
95
|
+
resolved from the global — the same reason it is passed to the FastAPI
|
|
96
|
+
instrumentation. Re-instrumenting an already-instrumented process is a no-op
|
|
97
|
+
in OpenTelemetry, so this rebinds first to guarantee spans reach *this*
|
|
98
|
+
service's provider.
|
|
99
|
+
"""
|
|
100
|
+
try:
|
|
101
|
+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
|
|
102
|
+
except ImportError:
|
|
103
|
+
logger.debug("httpx tracing skipped: opentelemetry-instrumentation-httpx not installed")
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
instrumentor = HTTPXClientInstrumentor()
|
|
107
|
+
if getattr(instrumentor, "is_instrumented_by_opentelemetry", False):
|
|
108
|
+
instrumentor.uninstrument()
|
|
109
|
+
instrumentor.instrument(tracer_provider=provider)
|
|
110
|
+
return True
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def setup_tracing(
|
|
114
|
+
app: FastAPI,
|
|
115
|
+
service_name: str,
|
|
116
|
+
*,
|
|
117
|
+
excluded_urls: str | None = None,
|
|
118
|
+
instrument_httpx: bool = True,
|
|
119
|
+
) -> bool:
|
|
120
|
+
"""Install OTLP tracing on ``app``. Returns True only if spans will be exported.
|
|
121
|
+
|
|
122
|
+
Must be called at construction time, before the app serves its first request
|
|
123
|
+
(so: module scope or an app factory — never inside ``lifespan`` or a startup
|
|
124
|
+
handler).
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
app: The FastAPI application, not yet started.
|
|
128
|
+
service_name: ``service.name`` in Tempo. Use the Kubernetes service name.
|
|
129
|
+
excluded_urls: Comma-separated substrings; defaults to probe/scrape paths.
|
|
130
|
+
instrument_httpx: Also trace outbound httpx calls, so this service becomes
|
|
131
|
+
a *source* of service-graph edges and not only a target. Best-effort:
|
|
132
|
+
a missing ``opentelemetry-instrumentation-httpx`` is not an error.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True when a tracer provider and OTLP exporter were installed. False when
|
|
136
|
+
tracing is deliberately off (``OTEL_SDK_DISABLED``), unconfigured (no
|
|
137
|
+
``OTLP_TRACES_ENDPOINT``), or the OpenTelemetry packages are absent.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
RuntimeError: if called after the app has started, because the
|
|
141
|
+
instrumentation would be discarded without any error of its own.
|
|
142
|
+
"""
|
|
143
|
+
if _already_started(app):
|
|
144
|
+
raise RuntimeError(
|
|
145
|
+
f"setup_tracing({service_name!r}) was called after the application "
|
|
146
|
+
"started — FastAPIInstrumentor.instrument_app() is a silent no-op at "
|
|
147
|
+
"that point and would export ZERO spans. Move this call to "
|
|
148
|
+
"application-construction time (module scope or the app factory), "
|
|
149
|
+
"not the lifespan or a startup handler."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
if os.getenv("OTEL_SDK_DISABLED", "false").strip().lower() == "true":
|
|
153
|
+
logger.info("tracing disabled: OTEL_SDK_DISABLED=true")
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
endpoint = (os.getenv("OTLP_TRACES_ENDPOINT") or "").strip()
|
|
157
|
+
if not endpoint:
|
|
158
|
+
logger.info("tracing disabled: OTLP_TRACES_ENDPOINT is not set")
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
from opentelemetry import trace
|
|
163
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
164
|
+
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
165
|
+
from opentelemetry.sdk.resources import Resource
|
|
166
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
167
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor
|
|
168
|
+
except ImportError as exc:
|
|
169
|
+
logger.info("tracing disabled: OpenTelemetry packages not installed (%s)", exc)
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
provider = TracerProvider(resource=Resource.create({"service.name": service_name}))
|
|
173
|
+
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter(endpoint=endpoint)))
|
|
174
|
+
|
|
175
|
+
# Register globally: FastAPIInstrumentor resolves the provider lazily through
|
|
176
|
+
# the global proxy, so an unregistered provider yields non-recording spans and
|
|
177
|
+
# nothing is ever exported.
|
|
178
|
+
trace.set_tracer_provider(provider)
|
|
179
|
+
|
|
180
|
+
FastAPIInstrumentor.instrument_app(
|
|
181
|
+
app,
|
|
182
|
+
tracer_provider=provider,
|
|
183
|
+
excluded_urls=DEFAULT_EXCLUDED_URLS if excluded_urls is None else excluded_urls,
|
|
184
|
+
server_request_hook=_server_request_hook,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
httpx_traced = _instrument_httpx(provider) if instrument_httpx else False
|
|
188
|
+
|
|
189
|
+
logger.info(
|
|
190
|
+
"tracing enabled: service=%s endpoint=%s httpx=%s",
|
|
191
|
+
service_name,
|
|
192
|
+
endpoint,
|
|
193
|
+
httpx_traced,
|
|
194
|
+
)
|
|
195
|
+
return True
|
coding_client/testing.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Test-side helpers for neutralising this SDK's identity dependencies.
|
|
2
|
+
|
|
3
|
+
This module is the supported replacement for ``SKIP_AUTH=true``, which was
|
|
4
|
+
deleted in 1.16.0. The two do the same job for a test suite and differ in the
|
|
5
|
+
only way that matters:
|
|
6
|
+
|
|
7
|
+
SKIP_AUTH ambient, global, ENV-triggered, compiled into the
|
|
8
|
+
production wheel. Anything that could set an
|
|
9
|
+
environment variable could disable authentication for
|
|
10
|
+
the whole process, and the branch sat ABOVE every
|
|
11
|
+
credential path in require_tenant.
|
|
12
|
+
|
|
13
|
+
override_tenant_auth per-app-object, explicit, CALL-triggered. There is no
|
|
14
|
+
environment variable, no import-time side effect, and
|
|
15
|
+
no way to reach it except by calling it against a
|
|
16
|
+
specific FastAPI instance.
|
|
17
|
+
|
|
18
|
+
That is the entire security delta, and it is enforced rather than asserted:
|
|
19
|
+
``tests/unit/test_auth/test_no_auth_bypass.py`` fails if this file ever gains an
|
|
20
|
+
``os.getenv``/``os.environ`` read or imports ``pytest``.
|
|
21
|
+
|
|
22
|
+
WHY THIS IS SHIPPED HERE RATHER THAN WRITTEN PER REPO
|
|
23
|
+
-----------------------------------------------------
|
|
24
|
+
Because the KEY SET drifts, and it has already drifted. Before 1.16.0,
|
|
25
|
+
``coding-testing/tests/integration/conftest.py`` hand-rolled overrides for
|
|
26
|
+
``get_current_user``, ``requires_read_permission`` and
|
|
27
|
+
``requires_write_permission`` — and NOT ``require_tenant``. That list was
|
|
28
|
+
incomplete and nobody knew, because the same conftest also set
|
|
29
|
+
``SKIP_AUTH=true`` two lines earlier and covered the gap.
|
|
30
|
+
|
|
31
|
+
Four repos maintaining that list by hand is four chances to miss the next
|
|
32
|
+
dependency this SDK adds. Centralised, adding one is a single edit to
|
|
33
|
+
``_TENANT_DEPENDENCIES`` that every consumer picks up at its next pin bump. This
|
|
34
|
+
fleet has already been burned twice by forked auth helpers; a third fork, living
|
|
35
|
+
in test code where nobody reviews it as security-relevant, is not a good trade
|
|
36
|
+
for avoiding one import.
|
|
37
|
+
|
|
38
|
+
USAGE
|
|
39
|
+
-----
|
|
40
|
+
from coding_client.testing import override_tenant_auth
|
|
41
|
+
|
|
42
|
+
@pytest.fixture
|
|
43
|
+
def client(app):
|
|
44
|
+
undo = override_tenant_auth(app)
|
|
45
|
+
yield TestClient(app)
|
|
46
|
+
undo() # or use the context manager below
|
|
47
|
+
|
|
48
|
+
# a service whose own dependency objects differ from the SDK's, or which
|
|
49
|
+
# needs a service-specific return shape:
|
|
50
|
+
undo = override_tenant_auth(
|
|
51
|
+
app,
|
|
52
|
+
tenant_id="tenant-a",
|
|
53
|
+
extra={my_module.create_tenant_context: lambda: MyContext("tenant-a")},
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
``undo()`` restores the PREVIOUS mapping exactly. It does not call
|
|
57
|
+
``.clear()`` — several suites yield a module-level ``main.app`` singleton, and
|
|
58
|
+
clearing would silently drop overrides the caller installed itself.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
from __future__ import annotations
|
|
62
|
+
|
|
63
|
+
from collections.abc import Callable, Iterator
|
|
64
|
+
from contextlib import contextmanager
|
|
65
|
+
from typing import Any
|
|
66
|
+
|
|
67
|
+
from .auth.dependencies import get_current_user
|
|
68
|
+
from .auth.models import User
|
|
69
|
+
from .auth.tenant import (
|
|
70
|
+
create_tenant_context,
|
|
71
|
+
get_current_user_with_tenant,
|
|
72
|
+
optional_tenant,
|
|
73
|
+
require_tenant,
|
|
74
|
+
require_tenant_context,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
__all__ = ["override_tenant_auth", "tenant_auth_override"]
|
|
78
|
+
|
|
79
|
+
# Every identity dependency this SDK exposes. A route that depends on ANY of
|
|
80
|
+
# these must be neutralised, or a suite bypasses auth on some routes and 401s on
|
|
81
|
+
# others — which is exactly the half-configured state that made the old
|
|
82
|
+
# coding-testing conftest look correct.
|
|
83
|
+
#
|
|
84
|
+
# ⚠️ Adding a dependency to auth/tenant.py means adding it HERE. That is the
|
|
85
|
+
# whole point of this tuple existing.
|
|
86
|
+
_TENANT_DEPENDENCIES = (
|
|
87
|
+
require_tenant,
|
|
88
|
+
optional_tenant,
|
|
89
|
+
get_current_user_with_tenant,
|
|
90
|
+
create_tenant_context,
|
|
91
|
+
require_tenant_context,
|
|
92
|
+
get_current_user,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
DEFAULT_TEST_TENANT = "test-tenant"
|
|
96
|
+
DEFAULT_TEST_USER = "test-user"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def override_tenant_auth(
|
|
100
|
+
app: Any,
|
|
101
|
+
*,
|
|
102
|
+
tenant_id: str = DEFAULT_TEST_TENANT,
|
|
103
|
+
user: User | None = None,
|
|
104
|
+
extra: dict[Callable[..., Any], Callable[..., Any]] | None = None,
|
|
105
|
+
) -> Callable[[], None]:
|
|
106
|
+
"""Neutralise this SDK's identity dependencies on ``app``.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
app: the FastAPI application whose ``dependency_overrides`` to install
|
|
110
|
+
into. Per-app, so two apps in one test session cannot interfere.
|
|
111
|
+
tenant_id: what the tenant-returning dependencies resolve to.
|
|
112
|
+
user: what the user-returning dependencies resolve to. Defaults to a
|
|
113
|
+
``User`` whose ``tenant_id`` matches ``tenant_id`` — they must agree,
|
|
114
|
+
or a route reading both sees two different tenants.
|
|
115
|
+
extra: additional ``{dependency: replacement}`` pairs, merged last so a
|
|
116
|
+
caller can override one of the defaults. Use for a service's own
|
|
117
|
+
forked dependency object (coding-ai's ``require_tenant`` is a
|
|
118
|
+
different object from this SDK's) or for a service-specific return
|
|
119
|
+
shape (coding-analysis's ``create_tenant_context`` returns its own
|
|
120
|
+
``TenantContext``, not this SDK's dict).
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
A zero-argument callable that restores the previous overrides exactly.
|
|
124
|
+
"""
|
|
125
|
+
resolved_user = user or User(
|
|
126
|
+
sub=DEFAULT_TEST_USER,
|
|
127
|
+
preferred_username=DEFAULT_TEST_USER,
|
|
128
|
+
tenant_id=tenant_id,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
replacements: dict[Callable[..., Any], Callable[..., Any]] = {
|
|
132
|
+
require_tenant: lambda: tenant_id,
|
|
133
|
+
optional_tenant: lambda: tenant_id,
|
|
134
|
+
get_current_user_with_tenant: lambda: resolved_user,
|
|
135
|
+
create_tenant_context: lambda: {"tenant_id": tenant_id},
|
|
136
|
+
require_tenant_context: lambda: {"tenant_id": tenant_id},
|
|
137
|
+
get_current_user: lambda: resolved_user,
|
|
138
|
+
}
|
|
139
|
+
if extra:
|
|
140
|
+
replacements.update(extra)
|
|
141
|
+
|
|
142
|
+
overrides = app.dependency_overrides
|
|
143
|
+
# Snapshot only the keys we touch. Restoring the whole dict would clobber
|
|
144
|
+
# anything installed between our install and undo.
|
|
145
|
+
previous: dict[Callable[..., Any], Any] = {}
|
|
146
|
+
absent: list[Callable[..., Any]] = []
|
|
147
|
+
for dependency, replacement in replacements.items():
|
|
148
|
+
if dependency in overrides:
|
|
149
|
+
previous[dependency] = overrides[dependency]
|
|
150
|
+
else:
|
|
151
|
+
absent.append(dependency)
|
|
152
|
+
overrides[dependency] = replacement
|
|
153
|
+
|
|
154
|
+
def undo() -> None:
|
|
155
|
+
for dependency, original in previous.items():
|
|
156
|
+
overrides[dependency] = original
|
|
157
|
+
for dependency in absent:
|
|
158
|
+
overrides.pop(dependency, None)
|
|
159
|
+
|
|
160
|
+
return undo
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@contextmanager
|
|
164
|
+
def tenant_auth_override(
|
|
165
|
+
app: Any,
|
|
166
|
+
*,
|
|
167
|
+
tenant_id: str = DEFAULT_TEST_TENANT,
|
|
168
|
+
user: User | None = None,
|
|
169
|
+
extra: dict[Callable[..., Any], Callable[..., Any]] | None = None,
|
|
170
|
+
) -> Iterator[None]:
|
|
171
|
+
"""Context-manager form of :func:`override_tenant_auth`.
|
|
172
|
+
|
|
173
|
+
Restores on the way out even when the body raises — which a bare
|
|
174
|
+
``undo()`` after ``yield`` in a fixture does not.
|
|
175
|
+
"""
|
|
176
|
+
undo = override_tenant_auth(app, tenant_id=tenant_id, user=user, extra=extra)
|
|
177
|
+
try:
|
|
178
|
+
yield
|
|
179
|
+
finally:
|
|
180
|
+
undo()
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Type definitions for coding-client.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- Core Literal types for type safety (SupportedLanguage, ContainerState, etc.)
|
|
5
|
+
- Constraint types with Pydantic Annotated validators (ContainerName, CodeContent, etc.)
|
|
6
|
+
- State machine implementation for lifecycle management
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .constraint_types import (
|
|
10
|
+
SAFE_STRING_PATTERN,
|
|
11
|
+
AbsolutePath,
|
|
12
|
+
AnalysisID,
|
|
13
|
+
CodeContent,
|
|
14
|
+
CodeSnippet,
|
|
15
|
+
ColumnNumber,
|
|
16
|
+
Confidence,
|
|
17
|
+
ContainerName,
|
|
18
|
+
CPUCores,
|
|
19
|
+
FileSizeBytes,
|
|
20
|
+
FutureTimestamp,
|
|
21
|
+
GitHubRepoUrl,
|
|
22
|
+
LineNumber,
|
|
23
|
+
MemoryMB,
|
|
24
|
+
Percentage,
|
|
25
|
+
PortNumber,
|
|
26
|
+
RelativePath,
|
|
27
|
+
RequestID,
|
|
28
|
+
SafeCodeString,
|
|
29
|
+
SafeString,
|
|
30
|
+
SafeStringLong,
|
|
31
|
+
SafeStringMedium,
|
|
32
|
+
SafeStringMediumOptional,
|
|
33
|
+
SafeStringOptional,
|
|
34
|
+
SafeStringShort,
|
|
35
|
+
SafeStringShortOptional,
|
|
36
|
+
Score,
|
|
37
|
+
TimeoutSeconds,
|
|
38
|
+
UserIdentifier,
|
|
39
|
+
ValidTimestamp,
|
|
40
|
+
)
|
|
41
|
+
from .core_types import (
|
|
42
|
+
AnalysisState,
|
|
43
|
+
AnalysisType,
|
|
44
|
+
CacheOperationType,
|
|
45
|
+
ContainerState,
|
|
46
|
+
DatabaseState,
|
|
47
|
+
ErrorCategory,
|
|
48
|
+
ExecutionMode,
|
|
49
|
+
ExitCode,
|
|
50
|
+
FileOperationType,
|
|
51
|
+
GitHubEventType,
|
|
52
|
+
GitHubEventTypeEnum,
|
|
53
|
+
HealthState,
|
|
54
|
+
NetworkOperationType,
|
|
55
|
+
PRState,
|
|
56
|
+
QualityLevel,
|
|
57
|
+
ResponseState,
|
|
58
|
+
SecurityLevel,
|
|
59
|
+
SupportedLanguage,
|
|
60
|
+
TestState,
|
|
61
|
+
TestType,
|
|
62
|
+
TransactionState,
|
|
63
|
+
ValidationResult,
|
|
64
|
+
)
|
|
65
|
+
from .state_machine import (
|
|
66
|
+
ContainerStateMachine,
|
|
67
|
+
StateMachine,
|
|
68
|
+
StateTransition,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
__all__ = [
|
|
72
|
+
# Core Literal types
|
|
73
|
+
"AnalysisState",
|
|
74
|
+
"AnalysisType",
|
|
75
|
+
"CacheOperationType",
|
|
76
|
+
"ContainerState",
|
|
77
|
+
"DatabaseState",
|
|
78
|
+
"ErrorCategory",
|
|
79
|
+
"ExecutionMode",
|
|
80
|
+
"ExitCode",
|
|
81
|
+
"FileOperationType",
|
|
82
|
+
"GitHubEventType",
|
|
83
|
+
"GitHubEventTypeEnum",
|
|
84
|
+
"HealthState",
|
|
85
|
+
"NetworkOperationType",
|
|
86
|
+
"PRState",
|
|
87
|
+
"QualityLevel",
|
|
88
|
+
"ResponseState",
|
|
89
|
+
"SecurityLevel",
|
|
90
|
+
"SupportedLanguage",
|
|
91
|
+
"TestState",
|
|
92
|
+
"TestType",
|
|
93
|
+
"TransactionState",
|
|
94
|
+
"ValidationResult",
|
|
95
|
+
# Constraint types
|
|
96
|
+
"AbsolutePath",
|
|
97
|
+
"AnalysisID",
|
|
98
|
+
"CodeContent",
|
|
99
|
+
"CodeSnippet",
|
|
100
|
+
"ColumnNumber",
|
|
101
|
+
"Confidence",
|
|
102
|
+
"ContainerName",
|
|
103
|
+
"CPUCores",
|
|
104
|
+
"FileSizeBytes",
|
|
105
|
+
"FutureTimestamp",
|
|
106
|
+
"GitHubRepoUrl",
|
|
107
|
+
"LineNumber",
|
|
108
|
+
"MemoryMB",
|
|
109
|
+
"Percentage",
|
|
110
|
+
"PortNumber",
|
|
111
|
+
"RelativePath",
|
|
112
|
+
"RequestID",
|
|
113
|
+
"SAFE_STRING_PATTERN",
|
|
114
|
+
"SafeCodeString",
|
|
115
|
+
"SafeString",
|
|
116
|
+
"SafeStringLong",
|
|
117
|
+
"SafeStringMedium",
|
|
118
|
+
"SafeStringMediumOptional",
|
|
119
|
+
"SafeStringOptional",
|
|
120
|
+
"SafeStringShort",
|
|
121
|
+
"SafeStringShortOptional",
|
|
122
|
+
"Score",
|
|
123
|
+
"TimeoutSeconds",
|
|
124
|
+
"UserIdentifier",
|
|
125
|
+
"ValidTimestamp",
|
|
126
|
+
# State machine
|
|
127
|
+
"ContainerStateMachine",
|
|
128
|
+
"StateMachine",
|
|
129
|
+
"StateTransition",
|
|
130
|
+
]
|