argus-obs-sdk 1.0.0a3__tar.gz

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.
@@ -0,0 +1,30 @@
1
+ .env
2
+ .env.*
3
+ !.env.example
4
+ !.env.*.example
5
+ *.env.local
6
+ __pycache__/
7
+ *.py[cod]
8
+ .venv/
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ dist/
12
+ build/
13
+ *.egg-info/
14
+ .DS_Store
15
+
16
+ # Configuracion del dead man's switch: lleva credenciales de aviso.
17
+ deadman/deadman.json
18
+
19
+ # Secretos montados en contenedores. Nunca al repositorio.
20
+ platform/secrets/
21
+
22
+ # El índice se GENERA desde dist/ con scripts/construir_indice.py.
23
+ # Versionar ruedas es versionar binarios que ya sabemos reproducir.
24
+ platform/indice/simple/
25
+
26
+ # La coordinacion con otros equipos vive FUERA del repositorio, en
27
+ # ~/Documents/Victor/<equipo>_argus/. Aqui hay incidentes suyos, rutas de su
28
+ # codigo y sus identificadores de backlog: es informacion de ellos y este
29
+ # repositorio es publico. Ver docs/coordinacion.md.
30
+ docs/solicitudes/
@@ -0,0 +1,132 @@
1
+ Metadata-Version: 2.5
2
+ Name: argus-obs-sdk
3
+ Version: 1.0.0a3
4
+ Summary: SDK de observabilidad de Argus. Para APLICACIONES: configura trazas, metricas y logs con una linea.
5
+ Project-URL: Homepage, https://github.com/Root1V/argus-obs
6
+ Project-URL: Repository, https://github.com/Root1V/argus-obs
7
+ Project-URL: Issues, https://github.com/Root1V/argus-obs/issues
8
+ Author: Emeric Espiritu
9
+ License: MIT
10
+ Keywords: genai,llm,logs,metrics,observability,opentelemetry,tracing
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: System :: Monitoring
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: argus-obs-semconv<2,>=1.0.0a3
21
+ Requires-Dist: opentelemetry-exporter-otlp-proto-grpc<2,>=1.30
22
+ Requires-Dist: opentelemetry-sdk<2,>=1.30
23
+ Provides-Extra: all
24
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.30; extra == 'all'
25
+ Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.51b0; extra == 'all'
26
+ Requires-Dist: opentelemetry-instrumentation-asgi>=0.51b0; extra == 'all'
27
+ Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.51b0; extra == 'all'
28
+ Requires-Dist: opentelemetry-instrumentation-celery>=0.51b0; extra == 'all'
29
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.51b0; extra == 'all'
30
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.51b0; extra == 'all'
31
+ Requires-Dist: opentelemetry-instrumentation-redis>=0.51b0; extra == 'all'
32
+ Requires-Dist: opentelemetry-instrumentation-requests>=0.51b0; extra == 'all'
33
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.51b0; extra == 'all'
34
+ Requires-Dist: structlog>=25.1; extra == 'all'
35
+ Provides-Extra: asgi
36
+ Requires-Dist: opentelemetry-instrumentation-asgi>=0.51b0; extra == 'asgi'
37
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.51b0; extra == 'asgi'
38
+ Provides-Extra: celery
39
+ Requires-Dist: opentelemetry-instrumentation-celery>=0.51b0; extra == 'celery'
40
+ Provides-Extra: client
41
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.51b0; extra == 'client'
42
+ Requires-Dist: opentelemetry-instrumentation-requests>=0.51b0; extra == 'client'
43
+ Provides-Extra: dev
44
+ Requires-Dist: httpx>=0.27; extra == 'dev'
45
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
46
+ Requires-Dist: pytest>=8; extra == 'dev'
47
+ Requires-Dist: pyyaml>=6; extra == 'dev'
48
+ Requires-Dist: starlette>=0.37; extra == 'dev'
49
+ Provides-Extra: genai
50
+ Requires-Dist: openlit>=1.34; extra == 'genai'
51
+ Provides-Extra: http
52
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.30; extra == 'http'
53
+ Provides-Extra: kafka
54
+ Requires-Dist: opentelemetry-instrumentation-aiokafka>=0.51b0; extra == 'kafka'
55
+ Provides-Extra: logging
56
+ Requires-Dist: structlog>=25.1; extra == 'logging'
57
+ Provides-Extra: redis
58
+ Requires-Dist: opentelemetry-instrumentation-redis>=0.51b0; extra == 'redis'
59
+ Provides-Extra: sql
60
+ Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.51b0; extra == 'sql'
61
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.51b0; extra == 'sql'
62
+ Description-Content-Type: text/markdown
63
+
64
+ # argus-sdk
65
+
66
+ SDK de observabilidad de Argus para **aplicaciones**.
67
+
68
+ ```python
69
+ import argus
70
+ argus.init()
71
+ ```
72
+
73
+ Eso configura resource, trazas, métricas, logs, propagadores y todas las
74
+ auto-instrumentaciones disponibles, leyendo la configuración del entorno.
75
+
76
+ Para servicios ASGI, una línea más:
77
+
78
+ ```python
79
+ app.add_middleware(argus.middleware(app).__class__) # o ASGIMiddleware directo
80
+ ```
81
+
82
+ ## Si escribes una librería, no uses este paquete
83
+
84
+ Usa [`argus-semconv`](../argus-semconv), que depende solo de
85
+ `opentelemetry-api`. Una librería que depende del SDK impone en silencio su
86
+ versión del SDK y sus opiniones sobre exportadores a todo el que dependa de
87
+ ella.
88
+
89
+ ## El contrato
90
+
91
+ Verificado por tests en `tests/test_contract.py`, no por buenas intenciones:
92
+
93
+ 1. **Nunca tumba la aplicación.** Si el Collector está caído, la app pierde
94
+ telemetría, nunca latencia ni memoria.
95
+ 2. **Idempotente.** `init()` dos veces es no-op la segunda.
96
+ 3. **No-op sin configurar.** Los decoradores funcionan con coste cero.
97
+ 4. **Cero configuración en el caso normal.** Todo viene del entorno.
98
+ 5. **Superficie pública mínima**, fijada por test.
99
+
100
+ ## Variables de entorno
101
+
102
+ El contrato es de entorno, no de API de Python: un componente Go y uno Python
103
+ se configuran copiando el mismo bloque.
104
+
105
+ | Variable | Significado | Por defecto |
106
+ |---|---|---|
107
+ | `ARGUS_SERVICE` | El sub-componente (`service.name`) | `unknown-service` |
108
+ | `ARGUS_NAMESPACE` | La aplicación (`service.namespace`) | = servicio |
109
+ | `ARGUS_ROLE` | `api`/`worker`/`scheduler`/`cli`/`model-server`/`frontend` | `api` |
110
+ | `ARGUS_VERSION` | Versión del componente | — |
111
+ | `ARGUS_ENVIRONMENT` | `mac-dev`, `imac`, `server-1`, `ci` | `local` |
112
+ | `ARGUS_ENDPOINT` | Collector **agente local** | `http://localhost:4317` |
113
+ | `ARGUS_PROTOCOL` | `grpc` \| `http/protobuf` | `grpc` |
114
+ | `ARGUS_PROPAGATE` | `never` \| `trusted` \| `always` | `never` |
115
+ | `ARGUS_TRUSTED_CIDRS` | CIDRs de confianza, separados por coma | — |
116
+ | `ARGUS_CAPTURE_CONTENT` | Capturar prompts y respuestas | `false` |
117
+ | `ARGUS_SLO_MS` | Umbral de latencia del componente | `0` (sin umbral) |
118
+ | `ARGUS_DISABLED` | Apagar toda la telemetría | `false` |
119
+
120
+ Las estándar de OpenTelemetry (`OTEL_SERVICE_NAME`,
121
+ `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_RESOURCE_ATTRIBUTES`) también se respetan,
122
+ para que una app que ya tiene OTel migre sin tocar código.
123
+
124
+ ## Por qué `localhost` y no el plano central
125
+
126
+ Las aplicaciones exportan **siempre** al Collector agente de su propia máquina.
127
+ Nunca conocen la dirección del plano central. Eso da tres propiedades:
128
+
129
+ - Mover el plano central de una máquina a otra no toca ni una aplicación.
130
+ - Si el central está suspendido, las apps no se enteran ni se ralentizan: el
131
+ agente local escribe a disco y envía cuando vuelve la conexión.
132
+ - Añadir una máquina es desplegar un agente, no reconfigurar apps.
@@ -0,0 +1,69 @@
1
+ # argus-sdk
2
+
3
+ SDK de observabilidad de Argus para **aplicaciones**.
4
+
5
+ ```python
6
+ import argus
7
+ argus.init()
8
+ ```
9
+
10
+ Eso configura resource, trazas, métricas, logs, propagadores y todas las
11
+ auto-instrumentaciones disponibles, leyendo la configuración del entorno.
12
+
13
+ Para servicios ASGI, una línea más:
14
+
15
+ ```python
16
+ app.add_middleware(argus.middleware(app).__class__) # o ASGIMiddleware directo
17
+ ```
18
+
19
+ ## Si escribes una librería, no uses este paquete
20
+
21
+ Usa [`argus-semconv`](../argus-semconv), que depende solo de
22
+ `opentelemetry-api`. Una librería que depende del SDK impone en silencio su
23
+ versión del SDK y sus opiniones sobre exportadores a todo el que dependa de
24
+ ella.
25
+
26
+ ## El contrato
27
+
28
+ Verificado por tests en `tests/test_contract.py`, no por buenas intenciones:
29
+
30
+ 1. **Nunca tumba la aplicación.** Si el Collector está caído, la app pierde
31
+ telemetría, nunca latencia ni memoria.
32
+ 2. **Idempotente.** `init()` dos veces es no-op la segunda.
33
+ 3. **No-op sin configurar.** Los decoradores funcionan con coste cero.
34
+ 4. **Cero configuración en el caso normal.** Todo viene del entorno.
35
+ 5. **Superficie pública mínima**, fijada por test.
36
+
37
+ ## Variables de entorno
38
+
39
+ El contrato es de entorno, no de API de Python: un componente Go y uno Python
40
+ se configuran copiando el mismo bloque.
41
+
42
+ | Variable | Significado | Por defecto |
43
+ |---|---|---|
44
+ | `ARGUS_SERVICE` | El sub-componente (`service.name`) | `unknown-service` |
45
+ | `ARGUS_NAMESPACE` | La aplicación (`service.namespace`) | = servicio |
46
+ | `ARGUS_ROLE` | `api`/`worker`/`scheduler`/`cli`/`model-server`/`frontend` | `api` |
47
+ | `ARGUS_VERSION` | Versión del componente | — |
48
+ | `ARGUS_ENVIRONMENT` | `mac-dev`, `imac`, `server-1`, `ci` | `local` |
49
+ | `ARGUS_ENDPOINT` | Collector **agente local** | `http://localhost:4317` |
50
+ | `ARGUS_PROTOCOL` | `grpc` \| `http/protobuf` | `grpc` |
51
+ | `ARGUS_PROPAGATE` | `never` \| `trusted` \| `always` | `never` |
52
+ | `ARGUS_TRUSTED_CIDRS` | CIDRs de confianza, separados por coma | — |
53
+ | `ARGUS_CAPTURE_CONTENT` | Capturar prompts y respuestas | `false` |
54
+ | `ARGUS_SLO_MS` | Umbral de latencia del componente | `0` (sin umbral) |
55
+ | `ARGUS_DISABLED` | Apagar toda la telemetría | `false` |
56
+
57
+ Las estándar de OpenTelemetry (`OTEL_SERVICE_NAME`,
58
+ `OTEL_EXPORTER_OTLP_ENDPOINT`, `OTEL_RESOURCE_ATTRIBUTES`) también se respetan,
59
+ para que una app que ya tiene OTel migre sin tocar código.
60
+
61
+ ## Por qué `localhost` y no el plano central
62
+
63
+ Las aplicaciones exportan **siempre** al Collector agente de su propia máquina.
64
+ Nunca conocen la dirección del plano central. Eso da tres propiedades:
65
+
66
+ - Mover el plano central de una máquina a otra no toca ni una aplicación.
67
+ - Si el central está suspendido, las apps no se enteran ni se ralentizan: el
68
+ agente local escribe a disco y envía cuando vuelve la conexión.
69
+ - Añadir una máquina es desplegar un agente, no reconfigurar apps.
@@ -0,0 +1,59 @@
1
+ [project]
2
+ name = "argus-obs-sdk"
3
+ version = "1.0.0a3"
4
+ description = "SDK de observabilidad de Argus. Para APLICACIONES: configura trazas, metricas y logs con una linea."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = { text = "MIT" }
8
+ authors = [{ name = "Emeric Espiritu" }]
9
+ keywords = ["observability", "opentelemetry", "tracing", "metrics", "logs", "genai", "llm"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Intended Audience :: Developers",
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Topic :: System :: Monitoring",
18
+ "Typing :: Typed",
19
+ ]
20
+
21
+ # Nucleo deliberadamente pequeño. Un Temporal activity o un CLI no deben
22
+ # arrastrar Starlette, y una app que no habla con Kafka no debe instalar su
23
+ # instrumentacion. Todo lo demas va en extras.
24
+ dependencies = [
25
+ "argus-obs-semconv>=1.0.0a3,<2",
26
+ "opentelemetry-sdk>=1.30,<2",
27
+ "opentelemetry-exporter-otlp-proto-grpc>=1.30,<2",
28
+ ]
29
+
30
+ [project.optional-dependencies]
31
+ http = ["opentelemetry-exporter-otlp-proto-http>=1.30,<2"]
32
+ asgi = ["opentelemetry-instrumentation-asgi>=0.51b0", "opentelemetry-instrumentation-fastapi>=0.51b0"]
33
+ client = ["opentelemetry-instrumentation-httpx>=0.51b0", "opentelemetry-instrumentation-requests>=0.51b0"]
34
+ sql = ["opentelemetry-instrumentation-sqlalchemy>=0.51b0", "opentelemetry-instrumentation-asyncpg>=0.51b0"]
35
+ redis = ["opentelemetry-instrumentation-redis>=0.51b0"]
36
+ celery = ["opentelemetry-instrumentation-celery>=0.51b0"]
37
+ kafka = ["opentelemetry-instrumentation-aiokafka>=0.51b0"]
38
+ logging = ["structlog>=25.1"]
39
+ genai = ["openlit>=1.34"]
40
+ all = ["argus-obs-sdk[http,asgi,client,sql,redis,celery,kafka,logging]"]
41
+ dev = ["pytest>=8", "pytest-asyncio>=0.24", "httpx>=0.27", "starlette>=0.37", "pyyaml>=6"]
42
+
43
+ [build-system]
44
+ requires = ["hatchling"]
45
+ build-backend = "hatchling.build"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["src/argus"]
49
+
50
+ [tool.hatch.metadata]
51
+ allow-direct-references = true
52
+
53
+ # Quien encuentre esto en PyPI tiene derecho a saber de donde sale y donde
54
+ # reportar. Un paquete sin origen publico es la misma opacidad de cadena de
55
+ # suministro que nos objeto un equipo consumidor (D-075).
56
+ [project.urls]
57
+ Homepage = "https://github.com/Root1V/argus-obs"
58
+ Repository = "https://github.com/Root1V/argus-obs"
59
+ Issues = "https://github.com/Root1V/argus-obs/issues"
@@ -0,0 +1,251 @@
1
+ """Argus SDK — observabilidad para APLICACIONES.
2
+
3
+ import argus
4
+ argus.init()
5
+
6
+ Eso configura resource, trazas, metricas, logs, propagadores y todas las
7
+ auto-instrumentaciones disponibles, leyendo la configuracion del entorno.
8
+
9
+ Si escribes una LIBRERIA y no una aplicacion, no uses este paquete: usa
10
+ `argus-semconv`, que depende solo de la API de OpenTelemetry y por tanto no
11
+ impone un SDK ni exportadores a quien te importe.
12
+
13
+ CONTRATO DE DISENO (verificado por tests, no por buenas intenciones):
14
+
15
+ 1. Nunca tumba la aplicacion. Si algo falla al inicializar, se degrada a no-op
16
+ con un aviso. Las colas son acotadas y descartan al llenarse: si el Collector
17
+ cae, la app pierde telemetria, nunca latencia ni memoria.
18
+ 2. Idempotente. `init()` dos veces es no-op la segunda.
19
+ 3. No-op si no esta configurada. Los decoradores funcionan con coste cero.
20
+ 4. Cero configuracion en el caso normal. Los argumentos son para sobrescribir.
21
+ 5. Superficie publica minima, fijada por test.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import atexit
27
+ import logging
28
+ import warnings
29
+ from dataclasses import dataclass
30
+ from typing import Any
31
+
32
+ # Re-exportamos la superficie de convenciones para que una aplicacion solo
33
+ # tenga que importar `argus`.
34
+ from argus_semconv import (
35
+ GenAISpan,
36
+ Step,
37
+ agent,
38
+ attributes,
39
+ capture_enabled,
40
+ documents,
41
+ genai,
42
+ instrument,
43
+ mask,
44
+ retrieval,
45
+ step,
46
+ tool,
47
+ )
48
+
49
+ from . import autoinst, propagate
50
+ from ._config import Config, TrustMode
51
+ from ._logging import configure_logging, emit_wide_event
52
+ from ._metrics import GenAIMetrics, configure_metrics
53
+ from ._resource import build_resource
54
+ from ._tracing import configure_tracing
55
+ from .asgi import ASGIMiddleware
56
+
57
+ # De los metadatos del paquete instalado, no de una constante: una constante se
58
+ # queda obsoleta en cuanto se publica una version y nadie se entera.
59
+ try:
60
+ from importlib.metadata import version as _version
61
+
62
+ __version__ = _version("argus-obs-sdk")
63
+ except Exception: # noqa: BLE001 - sin instalar (ejecucion desde el arbol)
64
+ __version__ = "0.0.0.dev0"
65
+
66
+ _HANDLE: Argus | None = None
67
+ _log = logging.getLogger("argus")
68
+
69
+
70
+ @dataclass(slots=True)
71
+ class Argus:
72
+ """Handle devuelto por `init()`.
73
+
74
+ Devolver un handle en vez de `None` no es un capricho: los procesos cortos
75
+ (CLIs, activities, trabajos por lotes) necesitan `force_flush()` antes de
76
+ salir, o la telemetria muere con el proceso sin exportarse.
77
+ """
78
+
79
+ config: Config
80
+ tracer_provider: Any = None
81
+ meter_provider: Any = None
82
+ instrumented: tuple[str, ...] = ()
83
+ genai_metrics: GenAIMetrics | None = None
84
+
85
+ def force_flush(self, timeout_millis: int = 5_000) -> None:
86
+ for provider in (self.tracer_provider, self.meter_provider):
87
+ if provider is None:
88
+ continue
89
+ try:
90
+ provider.force_flush(timeout_millis)
91
+ except Exception: # noqa: BLE001
92
+ pass
93
+
94
+ def shutdown(self) -> None:
95
+ self.force_flush()
96
+ for provider in (self.tracer_provider, self.meter_provider):
97
+ if provider is None:
98
+ continue
99
+ try:
100
+ provider.shutdown()
101
+ except Exception: # noqa: BLE001
102
+ pass
103
+
104
+
105
+ def init(
106
+ service: str | None = None,
107
+ *,
108
+ namespace: str | None = None,
109
+ version: str | None = None,
110
+ role: str | None = None,
111
+ environment: str | None = None,
112
+ endpoint: str | None = None,
113
+ propagate_mode: TrustMode | None = None,
114
+ slo_ms: int | None = None,
115
+ resource_attributes: dict[str, object] | None = None,
116
+ logging_level: int | str | None = None,
117
+ json_logs: bool = True,
118
+ auto_instrument: bool = True,
119
+ skip_instrumentation: frozenset[str] = frozenset(),
120
+ disabled: bool | None = None,
121
+ ) -> Argus:
122
+ """Inicializa la telemetria. Idempotente y a prueba de fallos.
123
+
124
+ Todos los argumentos son opcionales: el caso normal es `argus.init()` y que
125
+ la configuracion venga del entorno, que es lo que permite configurar un
126
+ componente Python y uno Go copiando el mismo bloque de variables.
127
+ """
128
+ global _HANDLE
129
+
130
+ if _HANDLE is not None:
131
+ warnings.warn(
132
+ "argus.init() ya se habia llamado en este proceso. La segunda "
133
+ "llamada es un no-op.",
134
+ RuntimeWarning,
135
+ stacklevel=2,
136
+ )
137
+ return _HANDLE
138
+
139
+ try:
140
+ cfg = Config.from_env(
141
+ service,
142
+ namespace=namespace,
143
+ version=version,
144
+ role=role,
145
+ environment=environment,
146
+ endpoint=endpoint,
147
+ propagate=propagate_mode,
148
+ slo_ms=slo_ms,
149
+ disabled=disabled,
150
+ )
151
+ except Exception as exc: # noqa: BLE001
152
+ warnings.warn(f"Argus: configuracion invalida ({exc}). Telemetria desactivada.", RuntimeWarning, stacklevel=2)
153
+ _HANDLE = Argus(config=Config(service=service or "unknown-service", disabled=True))
154
+ return _HANDLE
155
+
156
+ handle = Argus(config=cfg)
157
+
158
+ try:
159
+ resource = build_resource(cfg, resource_attributes)
160
+ handle.tracer_provider = configure_tracing(cfg, resource)
161
+ handle.meter_provider = configure_metrics(cfg, resource)
162
+ configure_logging(cfg, level=logging_level, force_json=json_logs)
163
+
164
+ if handle.meter_provider is not None:
165
+ handle.genai_metrics = GenAIMetrics()
166
+
167
+ if auto_instrument:
168
+ activated = autoinst.activate(skip=skip_instrumentation)
169
+ activated += autoinst.activate_genai(skip=skip_instrumentation)
170
+ autoinst.run_hooks()
171
+ handle.instrumented = tuple(activated)
172
+
173
+ except Exception as exc: # noqa: BLE001 - regla 1: nunca tumbar la app
174
+ warnings.warn(
175
+ f"Argus: la inicializacion fallo ({exc}). La aplicacion continua "
176
+ "sin telemetria.",
177
+ RuntimeWarning,
178
+ stacklevel=2,
179
+ )
180
+
181
+ atexit.register(handle.shutdown)
182
+ _HANDLE = handle
183
+
184
+ _log.info(
185
+ "argus.init",
186
+ extra={
187
+ "app": cfg.namespace,
188
+ "service": cfg.service,
189
+ "role": cfg.role,
190
+ "environment": cfg.environment,
191
+ "endpoint": cfg.endpoint if not cfg.disabled else "disabled",
192
+ "propagate": cfg.propagate,
193
+ "instrumented": ",".join(handle.instrumented),
194
+ },
195
+ )
196
+ return handle
197
+
198
+
199
+ def handle() -> Argus | None:
200
+ """El handle actual, si `init()` ya se llamo."""
201
+ return _HANDLE
202
+
203
+
204
+ def middleware(app: Any, **kwargs: Any) -> ASGIMiddleware:
205
+ """Envuelve una app ASGI heredando la configuracion de `init()`."""
206
+ cfg = _HANDLE.config if _HANDLE else Config.from_env()
207
+ kwargs.setdefault("service", cfg.service)
208
+ kwargs.setdefault("propagate_mode", cfg.propagate)
209
+ kwargs.setdefault("trusted_cidrs", cfg.trusted_cidrs)
210
+ kwargs.setdefault("slo_ms", cfg.slo_ms)
211
+ return ASGIMiddleware(app, **kwargs)
212
+
213
+
214
+ def _reset_for_tests() -> None:
215
+ global _HANDLE
216
+ from . import _logging
217
+
218
+ _logging.reset_for_tests()
219
+ _HANDLE = None
220
+
221
+
222
+ __all__ = [
223
+ # Arranque
224
+ "init",
225
+ "handle",
226
+ "Argus",
227
+ "Config",
228
+ # ASGI
229
+ "ASGIMiddleware",
230
+ "middleware",
231
+ # Convenciones (re-exportadas de argus-semconv)
232
+ "genai",
233
+ "retrieval",
234
+ "tool",
235
+ "agent",
236
+ "documents",
237
+ "step",
238
+ "instrument",
239
+ "GenAISpan",
240
+ "Step",
241
+ "attributes",
242
+ "capture_enabled",
243
+ "mask",
244
+ # Propagacion fuera de HTTP
245
+ "propagate",
246
+ "autoinst",
247
+ # Utilidades
248
+ "emit_wide_event",
249
+ "GenAIMetrics",
250
+ "__version__",
251
+ ]