getopentoken 0.1.0__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.
Files changed (35) hide show
  1. getopentoken-0.1.0/PKG-INFO +72 -0
  2. getopentoken-0.1.0/README.md +25 -0
  3. getopentoken-0.1.0/getopentoken.egg-info/PKG-INFO +72 -0
  4. getopentoken-0.1.0/getopentoken.egg-info/SOURCES.txt +33 -0
  5. getopentoken-0.1.0/getopentoken.egg-info/dependency_links.txt +1 -0
  6. getopentoken-0.1.0/getopentoken.egg-info/entry_points.txt +2 -0
  7. getopentoken-0.1.0/getopentoken.egg-info/requires.txt +42 -0
  8. getopentoken-0.1.0/getopentoken.egg-info/top_level.txt +1 -0
  9. getopentoken-0.1.0/opentoken/__init__.py +204 -0
  10. getopentoken-0.1.0/opentoken/_auto/sitecustomize.py +37 -0
  11. getopentoken-0.1.0/opentoken/_bedrock.py +250 -0
  12. getopentoken-0.1.0/opentoken/_bedrock_wrapper.py +262 -0
  13. getopentoken-0.1.0/opentoken/_breaker.py +120 -0
  14. getopentoken-0.1.0/opentoken/_client.py +424 -0
  15. getopentoken-0.1.0/opentoken/_context.py +50 -0
  16. getopentoken-0.1.0/opentoken/_dryrun.py +98 -0
  17. getopentoken-0.1.0/opentoken/_exporter.py +212 -0
  18. getopentoken-0.1.0/opentoken/_gateway.py +215 -0
  19. getopentoken-0.1.0/opentoken/_instrumentation.py +405 -0
  20. getopentoken-0.1.0/opentoken/_routing.py +186 -0
  21. getopentoken-0.1.0/opentoken/_transport.py +139 -0
  22. getopentoken-0.1.0/opentoken/_version.py +1 -0
  23. getopentoken-0.1.0/opentoken/adapters/__init__.py +1 -0
  24. getopentoken-0.1.0/opentoken/adapters/langchain.py +17 -0
  25. getopentoken-0.1.0/opentoken/adapters/llamaindex.py +19 -0
  26. getopentoken-0.1.0/opentoken/adapters/strands.py +38 -0
  27. getopentoken-0.1.0/opentoken/instrument.py +75 -0
  28. getopentoken-0.1.0/opentoken/models.json +66 -0
  29. getopentoken-0.1.0/opentoken/py.typed +0 -0
  30. getopentoken-0.1.0/pyproject.toml +53 -0
  31. getopentoken-0.1.0/setup.cfg +4 -0
  32. getopentoken-0.1.0/tests/test_failopen.py +350 -0
  33. getopentoken-0.1.0/tests/test_instrumentation.py +462 -0
  34. getopentoken-0.1.0/tests/test_integrations.py +281 -0
  35. getopentoken-0.1.0/tests/test_routing_dryrun.py +196 -0
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: getopentoken
3
+ Version: 0.1.0
4
+ Summary: Open Token SDK: fail-open routing to the Open Token gateway with direct Amazon Bedrock fallback, cost spans, shadow dry-runs and one-line OpenTelemetry auto-instrumentation.
5
+ Author-email: Open Token <support@getopentoken.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://getopentoken.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development :: Libraries
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Provides-Extra: otel
14
+ Requires-Dist: opentelemetry-sdk>=1.25; extra == "otel"
15
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.25; extra == "otel"
16
+ Provides-Extra: instrument
17
+ Requires-Dist: getopentoken[otel]; extra == "instrument"
18
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "instrument"
19
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "instrument"
20
+ Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "instrument"
21
+ Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "instrument"
22
+ Provides-Extra: bedrock
23
+ Requires-Dist: boto3>=1.34; extra == "bedrock"
24
+ Requires-Dist: getopentoken[otel]; extra == "bedrock"
25
+ Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "bedrock"
26
+ Provides-Extra: openai
27
+ Requires-Dist: openai>=1.40; extra == "openai"
28
+ Requires-Dist: httpx>=0.27; extra == "openai"
29
+ Requires-Dist: getopentoken[otel]; extra == "openai"
30
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "openai"
31
+ Provides-Extra: langchain
32
+ Requires-Dist: langchain-openai>=0.2; extra == "langchain"
33
+ Requires-Dist: httpx>=0.27; extra == "langchain"
34
+ Requires-Dist: getopentoken[otel]; extra == "langchain"
35
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "langchain"
36
+ Provides-Extra: llamaindex
37
+ Requires-Dist: llama-index-llms-openai-like>=0.2; extra == "llamaindex"
38
+ Requires-Dist: httpx>=0.27; extra == "llamaindex"
39
+ Requires-Dist: getopentoken[otel]; extra == "llamaindex"
40
+ Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "llamaindex"
41
+ Provides-Extra: strands
42
+ Requires-Dist: strands-agents>=1.0; extra == "strands"
43
+ Requires-Dist: httpx>=0.27; extra == "strands"
44
+ Requires-Dist: getopentoken[otel]; extra == "strands"
45
+ Provides-Extra: all
46
+ Requires-Dist: getopentoken[bedrock,instrument,openai,otel]; extra == "all"
47
+
48
+ # getopentoken
49
+
50
+ Python SDK for [Open Token](https://getopentoken.com): cost, attribution and fail-open governance for LLM calls. Installed as `getopentoken`, imported as `opentoken`.
51
+
52
+ ```bash
53
+ pip install "getopentoken[all]"
54
+ ```
55
+
56
+ ```python
57
+ import opentoken
58
+
59
+ opentoken.init()
60
+ ```
61
+
62
+ Or without code changes: `opentoken-instrument python app.py`.
63
+
64
+ Setup guides for each framework are in the Open Token dashboard under **Docs**.
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ cd sdk/python
70
+ uv venv && uv pip install -e ".[all]" langgraph langchain-openai strands-agents
71
+ PYTHONPATH=. python -m unittest discover -s tests -t tests
72
+ ```
@@ -0,0 +1,25 @@
1
+ # getopentoken
2
+
3
+ Python SDK for [Open Token](https://getopentoken.com): cost, attribution and fail-open governance for LLM calls. Installed as `getopentoken`, imported as `opentoken`.
4
+
5
+ ```bash
6
+ pip install "getopentoken[all]"
7
+ ```
8
+
9
+ ```python
10
+ import opentoken
11
+
12
+ opentoken.init()
13
+ ```
14
+
15
+ Or without code changes: `opentoken-instrument python app.py`.
16
+
17
+ Setup guides for each framework are in the Open Token dashboard under **Docs**.
18
+
19
+ ## Development
20
+
21
+ ```bash
22
+ cd sdk/python
23
+ uv venv && uv pip install -e ".[all]" langgraph langchain-openai strands-agents
24
+ PYTHONPATH=. python -m unittest discover -s tests -t tests
25
+ ```
@@ -0,0 +1,72 @@
1
+ Metadata-Version: 2.4
2
+ Name: getopentoken
3
+ Version: 0.1.0
4
+ Summary: Open Token SDK: fail-open routing to the Open Token gateway with direct Amazon Bedrock fallback, cost spans, shadow dry-runs and one-line OpenTelemetry auto-instrumentation.
5
+ Author-email: Open Token <support@getopentoken.com>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://getopentoken.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Topic :: Software Development :: Libraries
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+ Provides-Extra: otel
14
+ Requires-Dist: opentelemetry-sdk>=1.25; extra == "otel"
15
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.25; extra == "otel"
16
+ Provides-Extra: instrument
17
+ Requires-Dist: getopentoken[otel]; extra == "instrument"
18
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "instrument"
19
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "instrument"
20
+ Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "instrument"
21
+ Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "instrument"
22
+ Provides-Extra: bedrock
23
+ Requires-Dist: boto3>=1.34; extra == "bedrock"
24
+ Requires-Dist: getopentoken[otel]; extra == "bedrock"
25
+ Requires-Dist: opentelemetry-instrumentation-bedrock>=0.40; extra == "bedrock"
26
+ Provides-Extra: openai
27
+ Requires-Dist: openai>=1.40; extra == "openai"
28
+ Requires-Dist: httpx>=0.27; extra == "openai"
29
+ Requires-Dist: getopentoken[otel]; extra == "openai"
30
+ Requires-Dist: opentelemetry-instrumentation-openai>=0.40; extra == "openai"
31
+ Provides-Extra: langchain
32
+ Requires-Dist: langchain-openai>=0.2; extra == "langchain"
33
+ Requires-Dist: httpx>=0.27; extra == "langchain"
34
+ Requires-Dist: getopentoken[otel]; extra == "langchain"
35
+ Requires-Dist: opentelemetry-instrumentation-langchain>=0.40; extra == "langchain"
36
+ Provides-Extra: llamaindex
37
+ Requires-Dist: llama-index-llms-openai-like>=0.2; extra == "llamaindex"
38
+ Requires-Dist: httpx>=0.27; extra == "llamaindex"
39
+ Requires-Dist: getopentoken[otel]; extra == "llamaindex"
40
+ Requires-Dist: opentelemetry-instrumentation-llamaindex>=0.40; extra == "llamaindex"
41
+ Provides-Extra: strands
42
+ Requires-Dist: strands-agents>=1.0; extra == "strands"
43
+ Requires-Dist: httpx>=0.27; extra == "strands"
44
+ Requires-Dist: getopentoken[otel]; extra == "strands"
45
+ Provides-Extra: all
46
+ Requires-Dist: getopentoken[bedrock,instrument,openai,otel]; extra == "all"
47
+
48
+ # getopentoken
49
+
50
+ Python SDK for [Open Token](https://getopentoken.com): cost, attribution and fail-open governance for LLM calls. Installed as `getopentoken`, imported as `opentoken`.
51
+
52
+ ```bash
53
+ pip install "getopentoken[all]"
54
+ ```
55
+
56
+ ```python
57
+ import opentoken
58
+
59
+ opentoken.init()
60
+ ```
61
+
62
+ Or without code changes: `opentoken-instrument python app.py`.
63
+
64
+ Setup guides for each framework are in the Open Token dashboard under **Docs**.
65
+
66
+ ## Development
67
+
68
+ ```bash
69
+ cd sdk/python
70
+ uv venv && uv pip install -e ".[all]" langgraph langchain-openai strands-agents
71
+ PYTHONPATH=. python -m unittest discover -s tests -t tests
72
+ ```
@@ -0,0 +1,33 @@
1
+ README.md
2
+ pyproject.toml
3
+ getopentoken.egg-info/PKG-INFO
4
+ getopentoken.egg-info/SOURCES.txt
5
+ getopentoken.egg-info/dependency_links.txt
6
+ getopentoken.egg-info/entry_points.txt
7
+ getopentoken.egg-info/requires.txt
8
+ getopentoken.egg-info/top_level.txt
9
+ opentoken/__init__.py
10
+ opentoken/_bedrock.py
11
+ opentoken/_bedrock_wrapper.py
12
+ opentoken/_breaker.py
13
+ opentoken/_client.py
14
+ opentoken/_context.py
15
+ opentoken/_dryrun.py
16
+ opentoken/_exporter.py
17
+ opentoken/_gateway.py
18
+ opentoken/_instrumentation.py
19
+ opentoken/_routing.py
20
+ opentoken/_transport.py
21
+ opentoken/_version.py
22
+ opentoken/instrument.py
23
+ opentoken/models.json
24
+ opentoken/py.typed
25
+ opentoken/_auto/sitecustomize.py
26
+ opentoken/adapters/__init__.py
27
+ opentoken/adapters/langchain.py
28
+ opentoken/adapters/llamaindex.py
29
+ opentoken/adapters/strands.py
30
+ tests/test_failopen.py
31
+ tests/test_instrumentation.py
32
+ tests/test_integrations.py
33
+ tests/test_routing_dryrun.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ opentoken-instrument = opentoken.instrument:main
@@ -0,0 +1,42 @@
1
+
2
+ [all]
3
+ getopentoken[bedrock,instrument,openai,otel]
4
+
5
+ [bedrock]
6
+ boto3>=1.34
7
+ getopentoken[otel]
8
+ opentelemetry-instrumentation-bedrock>=0.40
9
+
10
+ [instrument]
11
+ getopentoken[otel]
12
+ opentelemetry-instrumentation-openai>=0.40
13
+ opentelemetry-instrumentation-langchain>=0.40
14
+ opentelemetry-instrumentation-llamaindex>=0.40
15
+ opentelemetry-instrumentation-bedrock>=0.40
16
+
17
+ [langchain]
18
+ langchain-openai>=0.2
19
+ httpx>=0.27
20
+ getopentoken[otel]
21
+ opentelemetry-instrumentation-langchain>=0.40
22
+
23
+ [llamaindex]
24
+ llama-index-llms-openai-like>=0.2
25
+ httpx>=0.27
26
+ getopentoken[otel]
27
+ opentelemetry-instrumentation-llamaindex>=0.40
28
+
29
+ [openai]
30
+ openai>=1.40
31
+ httpx>=0.27
32
+ getopentoken[otel]
33
+ opentelemetry-instrumentation-openai>=0.40
34
+
35
+ [otel]
36
+ opentelemetry-sdk>=1.25
37
+ opentelemetry-exporter-otlp-proto-http>=1.25
38
+
39
+ [strands]
40
+ strands-agents>=1.0
41
+ httpx>=0.27
42
+ getopentoken[otel]
@@ -0,0 +1 @@
1
+ opentoken
@@ -0,0 +1,204 @@
1
+ """
2
+ Open Token SDK: cost and governance for LLM calls, without ever standing in the way of your traffic.
3
+
4
+ import opentoken
5
+ opentoken.init() # reads OT_API_KEY / OT_INGEST_KEY
6
+ client = opentoken.openai_client() # OpenAI SDK client with fail-open routing to Bedrock
7
+
8
+ Customer implementation docs live in the Open Token dashboard (Docs).
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import logging
14
+ import os
15
+ import threading
16
+ from typing import Any, Optional, Sequence, Union
17
+
18
+ from ._breaker import CircuitBreaker
19
+ from ._client import OpenToken, aggregate
20
+ from ._context import Attribution, context, current
21
+ from ._exporter import FanoutExporter, SpanExporter, get_exporter, set_exporter
22
+ from ._instrumentation import (
23
+ ExportTarget,
24
+ TargetSpec,
25
+ TracingState,
26
+ force_flush,
27
+ langfuse_target,
28
+ parse_headers,
29
+ resolve_targets,
30
+ selected_frameworks,
31
+ setup_tracing,
32
+ )
33
+ from ._instrumentation import state as tracing_state
34
+ from ._gateway import GatewayRejected, GatewayUnavailable, MidStreamError
35
+ from ._routing import RemoteRouting, RoutingConfig
36
+ from ._version import __version__
37
+
38
+ __all__ = [
39
+ "Attribution",
40
+ "CircuitBreaker",
41
+ "ExportTarget",
42
+ "GatewayRejected",
43
+ "GatewayUnavailable",
44
+ "MidStreamError",
45
+ "OpenToken",
46
+ "RemoteRouting",
47
+ "RoutingConfig",
48
+ "SpanExporter",
49
+ "__version__",
50
+ "aggregate",
51
+ "async_openai_client",
52
+ "context",
53
+ "current",
54
+ "default_client",
55
+ "init",
56
+ "langfuse_target",
57
+ "openai_client",
58
+ "shutdown",
59
+ "tracing",
60
+ "wrap_bedrock",
61
+ "wrap_openai",
62
+ ]
63
+
64
+ log = logging.getLogger("opentoken")
65
+
66
+ _lock = threading.Lock()
67
+ _client: Optional[OpenToken] = None
68
+
69
+
70
+ def default_otlp_endpoint(base_url: str) -> str:
71
+ """Open Token's OTLP/HTTP traces endpoint on the same host as the API base URL."""
72
+ root = base_url.rstrip("/")
73
+ root = root[: -len("/v1")] if root.endswith("/v1") else root
74
+ return root + "/api/otel/v1/traces"
75
+
76
+
77
+ def _otlp_settings(base_url: str, endpoint: Optional[str], ingest_key: Optional[str]) -> tuple:
78
+ endpoint = endpoint or os.environ.get("OT_OTLP_ENDPOINT") or os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT")
79
+ if not endpoint and os.environ.get("OTEL_EXPORTER_OTLP_ENDPOINT"):
80
+ endpoint = os.environ["OTEL_EXPORTER_OTLP_ENDPOINT"].rstrip("/") + "/v1/traces"
81
+ if not endpoint:
82
+ endpoint = default_otlp_endpoint(base_url)
83
+ headers = parse_headers(os.environ.get("OTEL_EXPORTER_OTLP_HEADERS"))
84
+ key = ingest_key or os.environ.get("OT_INGEST_KEY")
85
+ if key:
86
+ headers["Authorization"] = f"Bearer {key}"
87
+ project = os.environ.get("OT_PROJECT")
88
+ if project and not any(k.lower() == "x-ot-project" for k in headers):
89
+ headers["X-OT-Project"] = project
90
+ return endpoint, headers
91
+
92
+
93
+ def init(
94
+ api_key: Optional[str] = None,
95
+ *,
96
+ ingest_key: Optional[str] = None,
97
+ base_url: Optional[str] = None,
98
+ otlp_endpoint: Optional[str] = None,
99
+ service_name: Optional[str] = None,
100
+ instrument: Union[bool, str, Sequence[str], None] = None,
101
+ also_export: Union[None, str, Sequence[TargetSpec]] = None,
102
+ **client_options: Any,
103
+ ) -> OpenToken:
104
+ """
105
+ One line to observe and govern LLM calls:
106
+
107
+ - the SDK's span exporter (bounded queue that drops, never blocks) and the default fail-open client;
108
+ - with `getopentoken[otel]` installed, OpenTelemetry tracing to Open Token plus auto-instrumentation of the installed
109
+ frameworks (OpenAI, LangChain/LangGraph, LlamaIndex, boto3 Bedrock; Strands and AgentCore emit natively).
110
+ `instrument`: True/"all" (default, or OT_INSTRUMENT), False/"none", or a list such as ["openai", "langchain"];
111
+ - `also_export` (or OT_ALSO_EXPORT): send the same traces to more OTLP backends, e.g. "langfuse".
112
+
113
+ `client_options` go to `OpenToken(...)` (routing overrides, bedrock_client, region, model_map, ...). Never raises
114
+ because of telemetry: a misconfigured exporter or instrumentor is logged at debug level and skipped.
115
+ """
116
+ global _client
117
+ ot = OpenToken(api_key, base_url, **client_options)
118
+ endpoint, headers = _otlp_settings(ot.base_url, otlp_endpoint, ingest_key)
119
+ service = service_name or os.environ.get("OT_SERVICE_NAME") or os.environ.get("OTEL_SERVICE_NAME") or "unknown_service"
120
+ targets = []
121
+ try:
122
+ targets = resolve_targets(also_export)
123
+ except Exception as err:
124
+ log.debug("opentoken: extra export targets skipped: %s", err)
125
+ primary = SpanExporter(endpoint, headers, service_name=service)
126
+ extra = [SpanExporter(t.endpoint, t.headers, service_name=service) for t in targets]
127
+ set_exporter(FanoutExporter([primary, *extra]) if extra else primary)
128
+ frameworks = selected_frameworks(instrument)
129
+ try:
130
+ setup_tracing(endpoint, headers, service, targets, frameworks)
131
+ except Exception as err:
132
+ log.debug("opentoken: auto-instrumentation skipped: %s", err)
133
+ with _lock:
134
+ _client = ot
135
+ return ot
136
+
137
+
138
+ def tracing() -> TracingState:
139
+ """What init() set up: whether OTel tracing is on, which frameworks are instrumented, and the export targets."""
140
+ return tracing_state()
141
+
142
+
143
+ def default_client() -> OpenToken:
144
+ global _client
145
+ with _lock:
146
+ if _client is None:
147
+ _client = OpenToken()
148
+ return _client
149
+
150
+
151
+ def shutdown(timeout_s: float = 2.0) -> None:
152
+ """Flushes pending spans (call before a short-lived process such as a Lambda handler returns)."""
153
+ exporter = get_exporter()
154
+ if exporter is not None:
155
+ exporter.flush(timeout_s)
156
+ force_flush(timeout_s)
157
+
158
+
159
+ def _transports(ot: OpenToken):
160
+ import httpx
161
+
162
+ from ._transport import AsyncOpenTokenTransport, OpenTokenTransport
163
+
164
+ return httpx.Client(transport=OpenTokenTransport(ot), timeout=None), httpx.AsyncClient(
165
+ transport=AsyncOpenTokenTransport(ot), timeout=None
166
+ )
167
+
168
+
169
+ def openai_client(ot: Optional[OpenToken] = None, **kwargs: Any) -> Any:
170
+ """`openai.OpenAI` whose chat completions use the fail-open router (gateway or Bedrock direct)."""
171
+ import openai # type: ignore
172
+
173
+ ot = ot or default_client()
174
+ sync, _ = _transports(ot)
175
+ kwargs.setdefault("base_url", ot.base_url)
176
+ kwargs.setdefault("api_key", ot.api_key or "ot-unset")
177
+ return openai.OpenAI(http_client=sync, **kwargs)
178
+
179
+
180
+ def async_openai_client(ot: Optional[OpenToken] = None, **kwargs: Any) -> Any:
181
+ import openai # type: ignore
182
+
183
+ ot = ot or default_client()
184
+ _, async_client = _transports(ot)
185
+ kwargs.setdefault("base_url", ot.base_url)
186
+ kwargs.setdefault("api_key", ot.api_key or "ot-unset")
187
+ return openai.AsyncOpenAI(http_client=async_client, **kwargs)
188
+
189
+
190
+ def wrap_openai(client: Any, ot: Optional[OpenToken] = None) -> Any:
191
+ """Copy of an existing OpenAI / AsyncOpenAI client that routes chat completions through Open Token."""
192
+ import openai # type: ignore
193
+
194
+ ot = ot or default_client()
195
+ sync, async_client = _transports(ot)
196
+ http_client = async_client if isinstance(client, openai.AsyncOpenAI) else sync
197
+ return client.with_options(http_client=http_client, base_url=ot.base_url, api_key=ot.api_key or client.api_key)
198
+
199
+
200
+ def wrap_bedrock(client: Any, ot: Optional[OpenToken] = None, model: Optional[str] = None) -> Any:
201
+ """boto3 `bedrock-runtime` client whose converse / converse_stream go through Open Token with fail-open."""
202
+ from ._bedrock_wrapper import BedrockClientWrapper
203
+
204
+ return BedrockClientWrapper(client, ot or default_client(), model)
@@ -0,0 +1,37 @@
1
+ # Loaded at interpreter startup by `opentoken-instrument` (this directory is prepended to PYTHONPATH).
2
+ import importlib.util
3
+ import os
4
+ import sys
5
+
6
+
7
+ def _chain_existing_sitecustomize():
8
+ """Runs the sitecustomize this one shadows (the app's or the virtualenv's), as opentelemetry-instrument does."""
9
+ here = os.path.dirname(os.path.abspath(__file__))
10
+ for entry in sys.path:
11
+ if not entry or os.path.abspath(entry) == here:
12
+ continue
13
+ candidate = os.path.join(entry, "sitecustomize.py")
14
+ if os.path.isfile(candidate):
15
+ spec = importlib.util.spec_from_file_location("_opentoken_chained_sitecustomize", candidate)
16
+ if spec and spec.loader:
17
+ try:
18
+ spec.loader.exec_module(importlib.util.module_from_spec(spec))
19
+ except Exception:
20
+ pass
21
+ return
22
+
23
+
24
+ def _auto_init():
25
+ if os.environ.get("OPENTOKEN_AUTO_INIT") != "1":
26
+ return
27
+ try:
28
+ import opentoken
29
+
30
+ opentoken.init()
31
+ except Exception as err: # telemetry must never stop the app from starting
32
+ if os.environ.get("OT_DEBUG"):
33
+ print(f"opentoken-instrument: init skipped: {err}", file=sys.stderr)
34
+
35
+
36
+ _chain_existing_sitecustomize()
37
+ _auto_init()