witdem-sdk 0.2.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.
- witdem_sdk-0.2.0/.gitignore +27 -0
- witdem_sdk-0.2.0/PKG-INFO +44 -0
- witdem_sdk-0.2.0/pyproject.toml +84 -0
- witdem_sdk-0.2.0/src/witdem_sdk/__init__.py +185 -0
- witdem_sdk-0.2.0/src/witdem_sdk/__main__.py +3 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_config.py +38 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_contract.py +1161 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_correlation.py +64 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_errors.py +15 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_payload.py +80 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_protocol.py +7 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_telemetry.py +879 -0
- witdem_sdk-0.2.0/src/witdem_sdk/_transport.py +164 -0
- witdem_sdk-0.2.0/src/witdem_sdk/cli.py +101 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/__init__.py +3 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/_common.py +73 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/anthropic.py +163 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/claude_agent.py +135 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/generic.py +145 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/haystack.py +557 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/langchain.py +414 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/langgraph.py +221 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/litellm.py +396 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/openai_agents.py +281 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/openrouter.py +368 -0
- witdem_sdk-0.2.0/src/witdem_sdk/integrations/smolagents.py +145 -0
- witdem_sdk-0.2.0/src/witdem_sdk/py.typed +0 -0
- witdem_sdk-0.2.0/tests/conftest.py +69 -0
- witdem_sdk-0.2.0/tests/test_contract_tutorial.py +42 -0
- witdem_sdk-0.2.0/tests/test_contracts.py +696 -0
- witdem_sdk-0.2.0/tests/test_correlation.py +95 -0
- witdem_sdk-0.2.0/tests/test_gateway_integrations.py +241 -0
- witdem_sdk-0.2.0/tests/test_haystack_observation.py +194 -0
- witdem_sdk-0.2.0/tests/test_high_level_integrations.py +412 -0
- witdem_sdk-0.2.0/tests/test_langgraph_instrumentation.py +161 -0
- witdem_sdk-0.2.0/tests/test_optional_integrations.py +301 -0
- witdem_sdk-0.2.0/tests/test_public_api.py +253 -0
- witdem_sdk-0.2.0/tests/test_telemetry.py +282 -0
- witdem_sdk-0.2.0/tests/test_transport.py +181 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.venv/
|
|
4
|
+
.mypy_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
*.duckdb
|
|
8
|
+
/data/backups/
|
|
9
|
+
.env
|
|
10
|
+
.DS_Store
|
|
11
|
+
!data/synthetic-ui-v2/
|
|
12
|
+
!data/synthetic-ui-v2/**
|
|
13
|
+
!data/synthetic-ui-v2/analytics.duckdb
|
|
14
|
+
data/live/
|
|
15
|
+
.claude/
|
|
16
|
+
data/**/.DS_Store
|
|
17
|
+
|
|
18
|
+
# Dashboard contributor build artifacts
|
|
19
|
+
web/node_modules/
|
|
20
|
+
web/*.tsbuildinfo
|
|
21
|
+
web/vite.config.js
|
|
22
|
+
web/vite.config.d.ts
|
|
23
|
+
web/tailwind.config.js
|
|
24
|
+
web/tailwind.config.d.ts
|
|
25
|
+
|
|
26
|
+
# Locally generated live-matrix reports
|
|
27
|
+
examples/product-factory/reports/
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: witdem-sdk
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Public client SDK for emitting Witdem business-semantic records correlated to the caller's active OpenTelemetry execution.
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: httpx<1,>=0.27
|
|
7
|
+
Requires-Dist: opentelemetry-api<2,>=1.27
|
|
8
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<2,>=1.27
|
|
9
|
+
Requires-Dist: opentelemetry-sdk<2,>=1.27
|
|
10
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
11
|
+
Requires-Dist: pyyaml<7,>=6.0
|
|
12
|
+
Requires-Dist: typing-extensions<5,>=4.12
|
|
13
|
+
Provides-Extra: all
|
|
14
|
+
Requires-Dist: anthropic<1,>=0.50; extra == 'all'
|
|
15
|
+
Requires-Dist: haystack-ai<4,>=3.0; extra == 'all'
|
|
16
|
+
Requires-Dist: langchain-core<2,>=0.3; extra == 'all'
|
|
17
|
+
Requires-Dist: langgraph<2,>=0.2; extra == 'all'
|
|
18
|
+
Requires-Dist: litellm<2,>=1.86; extra == 'all'
|
|
19
|
+
Requires-Dist: openai-agents<1,>=0.0.10; extra == 'all'
|
|
20
|
+
Requires-Dist: openai<4,>=1.68; extra == 'all'
|
|
21
|
+
Requires-Dist: openinference-instrumentation-smolagents<1,>=0.1.20; extra == 'all'
|
|
22
|
+
Requires-Dist: opentelemetry-haystack<2,>=1; extra == 'all'
|
|
23
|
+
Requires-Dist: smolagents<2,>=1.18; extra == 'all'
|
|
24
|
+
Provides-Extra: anthropic
|
|
25
|
+
Requires-Dist: anthropic<1,>=0.50; extra == 'anthropic'
|
|
26
|
+
Provides-Extra: haystack
|
|
27
|
+
Requires-Dist: haystack-ai<4,>=3.0; extra == 'haystack'
|
|
28
|
+
Requires-Dist: opentelemetry-haystack<2,>=1; extra == 'haystack'
|
|
29
|
+
Provides-Extra: langchain
|
|
30
|
+
Requires-Dist: langchain-core<2,>=0.3; extra == 'langchain'
|
|
31
|
+
Provides-Extra: langgraph
|
|
32
|
+
Requires-Dist: langgraph<2,>=0.2; extra == 'langgraph'
|
|
33
|
+
Provides-Extra: litellm
|
|
34
|
+
Requires-Dist: litellm<2,>=1.86; extra == 'litellm'
|
|
35
|
+
Provides-Extra: openai
|
|
36
|
+
Requires-Dist: openai-agents<1,>=0.0.10; extra == 'openai'
|
|
37
|
+
Provides-Extra: openrouter
|
|
38
|
+
Requires-Dist: openai<4,>=1.68; extra == 'openrouter'
|
|
39
|
+
Provides-Extra: smolagents
|
|
40
|
+
Requires-Dist: openinference-instrumentation-smolagents<1,>=0.1.20; extra == 'smolagents'
|
|
41
|
+
Requires-Dist: smolagents<2,>=1.18; extra == 'smolagents'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
Witdem SDK provides optional application semantics and framework integrations for Witdem Analytics.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "witdem-sdk"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Public client SDK for emitting Witdem business-semantic records correlated to the caller's active OpenTelemetry execution."
|
|
9
|
+
readme = { text = "Witdem SDK provides optional application semantics and framework integrations for Witdem Analytics.", content-type = "text/markdown" }
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"opentelemetry-api>=1.27,<2",
|
|
13
|
+
"opentelemetry-exporter-otlp-proto-http>=1.27,<2",
|
|
14
|
+
"opentelemetry-sdk>=1.27,<2",
|
|
15
|
+
"httpx>=0.27,<1",
|
|
16
|
+
"pydantic>=2.10,<3",
|
|
17
|
+
"pyyaml>=6.0,<7",
|
|
18
|
+
"typing-extensions>=4.12,<5",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
witdem = "witdem_sdk.cli:main"
|
|
23
|
+
witdem-sdk = "witdem_sdk.cli:main"
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
anthropic = ["anthropic>=0.50,<1"]
|
|
27
|
+
openai = ["openai-agents>=0.0.10,<1"]
|
|
28
|
+
langchain = ["langchain-core>=0.3,<2"]
|
|
29
|
+
langgraph = ["langgraph>=0.2,<2"]
|
|
30
|
+
haystack = ["haystack-ai>=3.0,<4", "opentelemetry-haystack>=1,<2"]
|
|
31
|
+
litellm = ["litellm>=1.86,<2"]
|
|
32
|
+
openrouter = ["openai>=1.68,<4"]
|
|
33
|
+
smolagents = [
|
|
34
|
+
"smolagents>=1.18,<2",
|
|
35
|
+
"openinference-instrumentation-smolagents>=0.1.20,<1",
|
|
36
|
+
]
|
|
37
|
+
all = [
|
|
38
|
+
"anthropic>=0.50,<1",
|
|
39
|
+
"openai-agents>=0.0.10,<1",
|
|
40
|
+
"langchain-core>=0.3,<2",
|
|
41
|
+
"langgraph>=0.2,<2",
|
|
42
|
+
"haystack-ai>=3.0,<4",
|
|
43
|
+
"opentelemetry-haystack>=1,<2",
|
|
44
|
+
"litellm>=1.86,<2",
|
|
45
|
+
"openai>=1.68,<4",
|
|
46
|
+
"smolagents>=1.18,<2",
|
|
47
|
+
"openinference-instrumentation-smolagents>=0.1.20,<1",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[dependency-groups]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=8.3,<9",
|
|
53
|
+
"pytest-cov>=6,<7",
|
|
54
|
+
"ruff>=0.9,<1",
|
|
55
|
+
"mypy>=1.15,<2",
|
|
56
|
+
"types-PyYAML>=6.0,<7",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.hatch.build.targets.wheel]
|
|
60
|
+
packages = ["src/witdem_sdk"]
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.sdist]
|
|
63
|
+
include = ["src/witdem_sdk", "tests", "pyproject.toml"]
|
|
64
|
+
|
|
65
|
+
[tool.pytest.ini_options]
|
|
66
|
+
addopts = "--strict-markers"
|
|
67
|
+
testpaths = ["tests"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff]
|
|
70
|
+
line-length = 120
|
|
71
|
+
target-version = "py310"
|
|
72
|
+
|
|
73
|
+
[tool.ruff.lint]
|
|
74
|
+
select = ["E", "F", "I", "B", "UP", "SIM"]
|
|
75
|
+
|
|
76
|
+
[tool.mypy]
|
|
77
|
+
python_version = "3.10"
|
|
78
|
+
strict = true
|
|
79
|
+
packages = ["witdem_sdk"]
|
|
80
|
+
follow_imports_for_stubs = true
|
|
81
|
+
|
|
82
|
+
[[tool.mypy.overrides]]
|
|
83
|
+
module = ["numpy", "numpy.*"]
|
|
84
|
+
follow_imports = "skip"
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""witdem_sdk -- the public Witdem client SDK.
|
|
2
|
+
|
|
3
|
+
``configure(...)`` provides one setup for OpenTelemetry traces and Witdem's
|
|
4
|
+
business-semantic records. ``WITDEM_ENDPOINT`` is the common base URL for the
|
|
5
|
+
trace and SDK ingest routes and defaults to ``http://localhost:4318``.
|
|
6
|
+
|
|
7
|
+
The five typed semantic functions (events, decisions, evaluations, outcomes,
|
|
8
|
+
metrics) remain available independently for applications that already own
|
|
9
|
+
their OpenTelemetry setup.
|
|
10
|
+
|
|
11
|
+
Correlation is automatic: each call reads the active OTel span
|
|
12
|
+
(``trace_id``/``span_id``) and ``"witdem.execution_id"`` baggage
|
|
13
|
+
(``execution_id``) from the current OpenTelemetry context -- callers never
|
|
14
|
+
copy ids by hand. If no execution id can be resolved (no baggage and no
|
|
15
|
+
explicit ``execution_id=`` keyword argument), :class:`WitdemSDKError` is raised
|
|
16
|
+
instead of sending an uncorrelated record.
|
|
17
|
+
|
|
18
|
+
Sending is fire-and-forget on a small bounded background thread pool: a
|
|
19
|
+
network error, or Witdem being unreachable, is logged and dropped (after one
|
|
20
|
+
quick retry) and never raised into caller code -- the one hard reliability
|
|
21
|
+
requirement for this package.
|
|
22
|
+
|
|
23
|
+
The unified client exposes execution, model, tool, and generic operation
|
|
24
|
+
contexts. It records standard GenAI attributes and flushes traces and semantic
|
|
25
|
+
records at shutdown; provider pricing remains a server-side concern.
|
|
26
|
+
|
|
27
|
+
See ``docs/sdk.md`` §5 in the Witdem AI repository for the wire
|
|
28
|
+
contract this package implements.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from collections.abc import Mapping
|
|
34
|
+
from typing import Any
|
|
35
|
+
|
|
36
|
+
from witdem_sdk._contract import ContractResult, WitdemProjectConfig, load_project_config
|
|
37
|
+
from witdem_sdk._correlation import resolve_correlation
|
|
38
|
+
from witdem_sdk._errors import WitdemSDKError
|
|
39
|
+
from witdem_sdk._payload import Kind as _Kind
|
|
40
|
+
from witdem_sdk._payload import build_payload
|
|
41
|
+
from witdem_sdk._protocol import SEMANTIC_RECORD_PROTOCOL_VERSION
|
|
42
|
+
from witdem_sdk._telemetry import Operation, Witdem, configure
|
|
43
|
+
from witdem_sdk._transport import DeliveryStatus, flush, submit_record
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"WitdemSDKError",
|
|
47
|
+
"Witdem",
|
|
48
|
+
"Operation",
|
|
49
|
+
"configure",
|
|
50
|
+
"decision",
|
|
51
|
+
"event",
|
|
52
|
+
"evaluation",
|
|
53
|
+
"metric",
|
|
54
|
+
"outcome",
|
|
55
|
+
"flush",
|
|
56
|
+
"DeliveryStatus",
|
|
57
|
+
"SEMANTIC_RECORD_PROTOCOL_VERSION",
|
|
58
|
+
"ContractResult",
|
|
59
|
+
"WitdemProjectConfig",
|
|
60
|
+
"load_project_config",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
__version__ = "0.2.0"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _emit(
|
|
67
|
+
kind: _Kind,
|
|
68
|
+
name: str,
|
|
69
|
+
value: Any,
|
|
70
|
+
attributes: dict[str, Any],
|
|
71
|
+
*,
|
|
72
|
+
execution_id: str | None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Shared plumbing: resolve correlation, build the wire payload, send it."""
|
|
75
|
+
|
|
76
|
+
resolved_execution_id, trace_id, span_id = resolve_correlation(execution_id)
|
|
77
|
+
payload = build_payload(
|
|
78
|
+
kind=kind,
|
|
79
|
+
name=name,
|
|
80
|
+
value=value,
|
|
81
|
+
execution_id=resolved_execution_id,
|
|
82
|
+
trace_id=trace_id,
|
|
83
|
+
span_id=span_id,
|
|
84
|
+
attributes=attributes,
|
|
85
|
+
)
|
|
86
|
+
submit_record(payload)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def event(
|
|
90
|
+
name: str,
|
|
91
|
+
payload: dict[str, Any] | None = None,
|
|
92
|
+
*,
|
|
93
|
+
attributes: Mapping[str, Any] | None = None,
|
|
94
|
+
execution_id: str | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Record a domain event that happened during the active execution.
|
|
97
|
+
|
|
98
|
+
``payload`` is an arbitrary JSON-serializable dict describing what
|
|
99
|
+
happened; it is sent as the wire record's ``attributes``. Raises
|
|
100
|
+
:class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see module
|
|
101
|
+
docs). Never raises when Witdem is unavailable on the network.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
_emit(
|
|
105
|
+
"event",
|
|
106
|
+
name,
|
|
107
|
+
None,
|
|
108
|
+
{**dict(payload or {}), **dict(attributes or {})},
|
|
109
|
+
execution_id=execution_id,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def decision(
|
|
114
|
+
name: str,
|
|
115
|
+
value: Any,
|
|
116
|
+
*,
|
|
117
|
+
attributes: Mapping[str, Any] | None = None,
|
|
118
|
+
execution_id: str | None = None,
|
|
119
|
+
) -> None:
|
|
120
|
+
"""Record a business decision (e.g. a chosen route/branch) and its value.
|
|
121
|
+
|
|
122
|
+
Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
|
|
123
|
+
module docs). Never raises when Witdem is unavailable on the network.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
_emit("decision", name, value, dict(attributes or {}), execution_id=execution_id)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def evaluation(
|
|
130
|
+
name: str,
|
|
131
|
+
*,
|
|
132
|
+
score: float | None = None,
|
|
133
|
+
label: str | None = None,
|
|
134
|
+
value: Any = None,
|
|
135
|
+
attributes: Mapping[str, Any] | None = None,
|
|
136
|
+
execution_id: str | None = None,
|
|
137
|
+
) -> None:
|
|
138
|
+
"""Record a structured assessment: a ``score``, a ``label``, or both.
|
|
139
|
+
|
|
140
|
+
Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
|
|
141
|
+
module docs). Never raises when Witdem is unavailable on the network.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
resolved_attributes = dict(attributes or {})
|
|
145
|
+
if score is not None:
|
|
146
|
+
resolved_attributes["score"] = score
|
|
147
|
+
if label is not None:
|
|
148
|
+
resolved_attributes["label"] = label
|
|
149
|
+
_emit("evaluation", name, value, resolved_attributes, execution_id=execution_id)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def outcome(
|
|
153
|
+
name: str,
|
|
154
|
+
*,
|
|
155
|
+
status: str | None = None,
|
|
156
|
+
value: Any = None,
|
|
157
|
+
attributes: Mapping[str, Any] | None = None,
|
|
158
|
+
execution_id: str | None = None,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""Record an externally meaningful result of the active execution.
|
|
161
|
+
|
|
162
|
+
Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
|
|
163
|
+
module docs). Never raises when Witdem is unavailable on the network.
|
|
164
|
+
"""
|
|
165
|
+
|
|
166
|
+
resolved_attributes = dict(attributes or {})
|
|
167
|
+
if status is not None:
|
|
168
|
+
resolved_attributes["status"] = status
|
|
169
|
+
_emit("outcome", name, value, resolved_attributes, execution_id=execution_id)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def metric(
|
|
173
|
+
name: str,
|
|
174
|
+
value: Any,
|
|
175
|
+
*,
|
|
176
|
+
attributes: Mapping[str, Any] | None = None,
|
|
177
|
+
execution_id: str | None = None,
|
|
178
|
+
) -> None:
|
|
179
|
+
"""Record a numeric (or otherwise quantitative) measurement.
|
|
180
|
+
|
|
181
|
+
Raises :class:`WitdemSDKError` if ``execution_id`` cannot be resolved (see
|
|
182
|
+
module docs). Never raises when Witdem is unavailable on the network.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
_emit("metric", name, value, dict(attributes or {}), execution_id=execution_id)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Environment-based configuration for :mod:`witdem_sdk`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
_ENDPOINT_ENV_VAR = "WITDEM_ENDPOINT"
|
|
8
|
+
_DEFAULT_ENDPOINT = "http://localhost:4318"
|
|
9
|
+
_endpoint_override: str | None = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def configure_records_endpoint(endpoint: str | None) -> str | None:
|
|
13
|
+
"""Set the process-local endpoint override and return its previous value."""
|
|
14
|
+
|
|
15
|
+
global _endpoint_override
|
|
16
|
+
previous = _endpoint_override
|
|
17
|
+
_endpoint_override = endpoint.rstrip("/") if endpoint else None
|
|
18
|
+
return previous
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def records_endpoint() -> str:
|
|
22
|
+
"""Base URL of the Witdem service.
|
|
23
|
+
|
|
24
|
+
Records are POSTed to ``f"{records_endpoint()}/sdk/v1/records"``. Read
|
|
25
|
+
fresh on every call (not cached at import time) so tests can repoint it
|
|
26
|
+
per-test via ``monkeypatch.setenv`` without reloading this module.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if _endpoint_override is not None:
|
|
30
|
+
return _endpoint_override
|
|
31
|
+
value = os.environ.get(_ENDPOINT_ENV_VAR)
|
|
32
|
+
if value:
|
|
33
|
+
return value.rstrip("/")
|
|
34
|
+
return _DEFAULT_ENDPOINT
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def api_key(explicit: str | None = None) -> str | None:
|
|
38
|
+
return explicit or os.getenv("WITDEM_API_KEY")
|