o11y-one 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.
@@ -0,0 +1,26 @@
1
+ # Build artifacts. Note what is NOT here: packages/*/src/o11y_one and
2
+ # gen/go/o11y_one are GENERATED but COMMITTED on purpose — see README, "The
3
+ # regen ritual". Only compiler output is ignored.
4
+ node_modules/
5
+ dist/
6
+ *.tsbuildinfo
7
+
8
+ # Python
9
+ __pycache__/
10
+ *.py[cod]
11
+ .venv/
12
+ build/
13
+ *.egg-info/
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+
17
+ # Go
18
+ /tools/ci-runner/bin/
19
+ /tools/pr-diff/bin/
20
+ /dist-bin/
21
+
22
+ # mise/local
23
+ .mise.local.toml
24
+
25
+ # OS
26
+ .DS_Store
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.5
2
+ Name: o11y-one
3
+ Version: 0.1.0
4
+ Summary: Hand-written Python client for the O11y One API: transport construction, credential injection, and the O11y One error taxonomy.
5
+ Project-URL: Homepage, https://o11y.one
6
+ Project-URL: Source, https://github.com/o11y-one/o11y-one-sdk
7
+ Author: O11y One
8
+ License-Expression: Apache-2.0
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.11
14
+ Requires-Dist: connect-python<1,>=0.9.0
15
+ Requires-Dist: httpx>=0.28
16
+ Requires-Dist: o11y-one-api-agentic
17
+ Description-Content-Type: text/markdown
18
+
19
+ # `o11y-one`
20
+
21
+ The Python client for the O11y One API. A thin, hand-written layer over the
22
+ generated [`o11y-one-api-agentic`](../gen-py-agentic) package.
23
+
24
+ ## Install
25
+
26
+ ```sh
27
+ uv add o11y-one # or: pip install o11y-one
28
+ ```
29
+
30
+ ## Use
31
+
32
+ ```python
33
+ import os
34
+
35
+ from o11y_one.sdk import O11yClient, Disposition, classify
36
+ from o11y_one.agentic.v1.evaluation_pb2 import ListEvaluationDefinitionsRequest
37
+ from o11y_one.agentic.v1.evaluation_connect import AgenticEvaluationServiceClientSync
38
+
39
+ o11y = O11yClient(
40
+ base_url="https://api.o11y.one",
41
+ credential=os.environ["O11Y_API_KEY"], # o11y_mach.<selector>.<secret>
42
+ org_id=os.environ["O11Y_ORG_ID"],
43
+ )
44
+
45
+ evals = o11y.service_sync(AgenticEvaluationServiceClientSync)
46
+ try:
47
+ defs = evals.list_evaluation_definitions(ListEvaluationDefinitionsRequest())
48
+ except Exception as err: # noqa: BLE001 - classify sorts it out
49
+ failure = classify(err)
50
+ if failure.disposition is Disposition.INSUFFICIENT_SCOPE:
51
+ raise SystemExit(f"credential is missing scope {failure.missing_scope}") from err
52
+ raise
53
+ ```
54
+
55
+ ## What is here, and what deliberately is not
56
+
57
+ Three modules and nothing else:
58
+
59
+ | module | responsibility |
60
+ |---|---|
61
+ | `o11y_one.sdk.client` | transport construction, credential and scoping header injection |
62
+ | `o11y_one.sdk.auth` | the credential wire format and local structural validation |
63
+ | `o11y_one.sdk.errors` | the failure taxonomy: reauthenticate / insufficient-scope / version-skew / retry / unclassified |
64
+
65
+ Service methods are **not** wrapped. `o11y-one-api-agentic` already ships a client class
66
+ per service; a hand-written facade over all of them would be a second API surface
67
+ to keep in sync with the proto, and it would rot the first time a field is added
68
+ upstream. Import the generated client class and hand it to
69
+ `O11yClient.service_sync()` / `O11yClient.service()`.
70
+
71
+ ## The distinction this package exists to preserve
72
+
73
+ `UNAUTHENTICATED` and `PERMISSION_DENIED` are different problems:
74
+
75
+ * **`UNAUTHENTICATED`** — the credential is absent, malformed, unknown, expired,
76
+ or revoked. Re-auth. Retrying cannot change the answer.
77
+ * **`PERMISSION_DENIED`** — the credential is fine; it lacks a scope (the server
78
+ names which one) or it was presented to a non-machine surface.
79
+
80
+ `classify()` keeps them apart, along with `UNAVAILABLE` (auth backend down —
81
+ retry with backoff) and `INVALID_ARGUMENT` (SDK/server version skew). Collapsing
82
+ these into a single "auth error" is the failure mode this module exists to
83
+ prevent.
84
+
85
+ ## Credentials
86
+
87
+ A machine credential is `o11y_mach.<selector>.<secret>`, presented in the
88
+ `x-o11y-key` header. It is:
89
+
90
+ * **returned exactly once**, by `CreateMachineCredential`. There is no read-back
91
+ RPC and no recovery path — lose it and you rotate.
92
+ * **rotated create-then-revoke**, not atomically. A principal may hold several
93
+ active credentials at once, which is what makes zero-downtime rotation work:
94
+ create the new one, deploy it, then revoke the old.
95
+ * **revoked effective on the next request**, not on a TTL boundary.
96
+ * **expiring** at +365 days by default, capped at three years.
97
+
98
+ Never send it as `authorization: Bearer` — that path carries the browser session
99
+ JWT and a machine credential presented there is rejected.
@@ -0,0 +1,81 @@
1
+ # `o11y-one`
2
+
3
+ The Python client for the O11y One API. A thin, hand-written layer over the
4
+ generated [`o11y-one-api-agentic`](../gen-py-agentic) package.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ uv add o11y-one # or: pip install o11y-one
10
+ ```
11
+
12
+ ## Use
13
+
14
+ ```python
15
+ import os
16
+
17
+ from o11y_one.sdk import O11yClient, Disposition, classify
18
+ from o11y_one.agentic.v1.evaluation_pb2 import ListEvaluationDefinitionsRequest
19
+ from o11y_one.agentic.v1.evaluation_connect import AgenticEvaluationServiceClientSync
20
+
21
+ o11y = O11yClient(
22
+ base_url="https://api.o11y.one",
23
+ credential=os.environ["O11Y_API_KEY"], # o11y_mach.<selector>.<secret>
24
+ org_id=os.environ["O11Y_ORG_ID"],
25
+ )
26
+
27
+ evals = o11y.service_sync(AgenticEvaluationServiceClientSync)
28
+ try:
29
+ defs = evals.list_evaluation_definitions(ListEvaluationDefinitionsRequest())
30
+ except Exception as err: # noqa: BLE001 - classify sorts it out
31
+ failure = classify(err)
32
+ if failure.disposition is Disposition.INSUFFICIENT_SCOPE:
33
+ raise SystemExit(f"credential is missing scope {failure.missing_scope}") from err
34
+ raise
35
+ ```
36
+
37
+ ## What is here, and what deliberately is not
38
+
39
+ Three modules and nothing else:
40
+
41
+ | module | responsibility |
42
+ |---|---|
43
+ | `o11y_one.sdk.client` | transport construction, credential and scoping header injection |
44
+ | `o11y_one.sdk.auth` | the credential wire format and local structural validation |
45
+ | `o11y_one.sdk.errors` | the failure taxonomy: reauthenticate / insufficient-scope / version-skew / retry / unclassified |
46
+
47
+ Service methods are **not** wrapped. `o11y-one-api-agentic` already ships a client class
48
+ per service; a hand-written facade over all of them would be a second API surface
49
+ to keep in sync with the proto, and it would rot the first time a field is added
50
+ upstream. Import the generated client class and hand it to
51
+ `O11yClient.service_sync()` / `O11yClient.service()`.
52
+
53
+ ## The distinction this package exists to preserve
54
+
55
+ `UNAUTHENTICATED` and `PERMISSION_DENIED` are different problems:
56
+
57
+ * **`UNAUTHENTICATED`** — the credential is absent, malformed, unknown, expired,
58
+ or revoked. Re-auth. Retrying cannot change the answer.
59
+ * **`PERMISSION_DENIED`** — the credential is fine; it lacks a scope (the server
60
+ names which one) or it was presented to a non-machine surface.
61
+
62
+ `classify()` keeps them apart, along with `UNAVAILABLE` (auth backend down —
63
+ retry with backoff) and `INVALID_ARGUMENT` (SDK/server version skew). Collapsing
64
+ these into a single "auth error" is the failure mode this module exists to
65
+ prevent.
66
+
67
+ ## Credentials
68
+
69
+ A machine credential is `o11y_mach.<selector>.<secret>`, presented in the
70
+ `x-o11y-key` header. It is:
71
+
72
+ * **returned exactly once**, by `CreateMachineCredential`. There is no read-back
73
+ RPC and no recovery path — lose it and you rotate.
74
+ * **rotated create-then-revoke**, not atomically. A principal may hold several
75
+ active credentials at once, which is what makes zero-downtime rotation work:
76
+ create the new one, deploy it, then revoke the old.
77
+ * **revoked effective on the next request**, not on a TTL boundary.
78
+ * **expiring** at +365 days by default, capped at three years.
79
+
80
+ Never send it as `authorization: Bearer` — that path carries the browser session
81
+ JWT and a machine credential presented there is rejected.
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "o11y-one"
3
+ version = "0.1.0"
4
+ description = "Hand-written Python client for the O11y One API: transport construction, credential injection, and the O11y One error taxonomy."
5
+ readme = "README.md"
6
+ license = "Apache-2.0"
7
+ requires-python = ">=3.11"
8
+ authors = [{ name = "O11y One" }]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "Programming Language :: Python :: 3",
13
+ "Typing :: Typed",
14
+ ]
15
+
16
+ dependencies = [
17
+ "o11y-one-api-agentic",
18
+ "connect-python>=0.9.0,<1",
19
+ "httpx>=0.28",
20
+ ]
21
+
22
+ [project.urls]
23
+ Homepage = "https://o11y.one"
24
+ Source = "https://github.com/o11y-one/o11y-one-sdk"
25
+
26
+ [build-system]
27
+ requires = ["hatchling>=1.32.0"]
28
+ build-backend = "hatchling.build"
29
+
30
+ [tool.hatch.build.targets.wheel]
31
+ # o11y_one is a PEP 420 namespace shared with o11y-one-api-agentic; this
32
+ # distribution owns exactly o11y_one/sdk. No src/o11y_one/__init__.py, ever —
33
+ # adding one would shadow the generated o11y_one.agentic, o11y_one.common
34
+ # packages.
35
+ packages = ["src/o11y_one"]
36
+
37
+ [tool.hatch.build.targets.sdist]
38
+ include = ["src", "README.md", "pyproject.toml"]
39
+
40
+ [tool.uv.sources]
41
+ # Inside the workspace, resolve the generated package from the sibling member
42
+ # rather than PyPI, so the SDK always builds against the generated code from
43
+ # THIS commit. Published wheels carry the plain `o11y-one-api-agentic`
44
+ # requirement. The agentic SDK depends on the per-domain subset distribution
45
+ # (agentic + common only), not the whole o11y-one-api — see
46
+ # docs/proto-subsetting.md.
47
+ o11y-one-api-agentic = { workspace = true }
48
+
49
+ [dependency-groups]
50
+ dev = ["pytest>=8.4"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
@@ -0,0 +1,82 @@
1
+ """``o11y_one.sdk`` — hand-written client layer over the generated ``o11y_one.*``
2
+ protobuf packages shipped by the ``o11y-one-api`` distribution.
3
+
4
+ Four things live here:
5
+
6
+ * transport construction (:mod:`o11y_one.sdk.client`)
7
+ * credential injection and its local validation (:mod:`o11y_one.sdk.auth`)
8
+ * the failure taxonomy an SDK exists to render (:mod:`o11y_one.sdk.errors`)
9
+ * an ergonomic facade over exactly ONE generated service,
10
+ ``AgenticEvaluationService`` (:mod:`o11y_one.sdk.agentic`)
11
+
12
+ For every other generated service, methods are still NOT wrapped or
13
+ re-exported: import the generated ``*ServiceClient`` you need from
14
+ ``o11y_one.<domain>.v1.<file>_connect`` and hand the class to
15
+ :meth:`O11yClient.service` / :meth:`O11yClient.service_sync`. The rationale for
16
+ staying thin there is unchanged — a hand-written facade over every one of the
17
+ 29 generated services would be a second API surface to keep in sync with the
18
+ proto, and it would rot the first time a field is added upstream.
19
+
20
+ ``AgenticEvaluationService`` is the deliberate exception, not a reversal of
21
+ that rule: it is the run-submission / lease-heartbeat-submit-release /
22
+ dataset-draft / annotation surface an eval-authoring or externally-executed
23
+ runtime integration calls minute to minute, its messages repeat the same
24
+ refusal-on-a-200, idempotency and capability-envelope conventions across
25
+ dozens of RPCs, and getting any one of those conventions wrong locally
26
+ degrades silently to an opaque network error rather than a loud local one. See
27
+ :mod:`o11y_one.sdk.agentic` for what the facade adds on top of the generated
28
+ client, and ``l2-notes.md`` for why this scope was drawn where it was.
29
+
30
+ Note that ``o11y_one`` itself is a PEP 420 namespace package contributed to by
31
+ two distributions: ``o11y-one-api`` (generated) and ``o11y-one`` (this one).
32
+ There is deliberately no ``o11y_one/__init__.py`` in either.
33
+ """
34
+
35
+ from __future__ import annotations
36
+
37
+ from .agentic import (
38
+ MACHINE_SCOPE_NAME_TO_ENUM,
39
+ AgenticClient,
40
+ AgenticClientSync,
41
+ CaseOutput,
42
+ IssuedMachineCredential,
43
+ )
44
+ from .auth import (
45
+ CREDENTIAL_HEADER,
46
+ CREDENTIAL_MAX_LEN,
47
+ MACHINE_CREDENTIAL_PREFIX,
48
+ ORG_ID_HEADER,
49
+ TENANT_ID_HEADER,
50
+ assert_looks_like_machine_credential,
51
+ )
52
+ from .client import CredentialInterceptor, O11yClient
53
+ from .errors import MACHINE_SCOPES, ClassifiedFailure, Disposition, classify
54
+ from .recovery import bare_enum_name, find_error_detail
55
+ from .results import Refusal
56
+ from .validation import ValidationError
57
+
58
+ __all__ = [
59
+ "CREDENTIAL_HEADER",
60
+ "CREDENTIAL_MAX_LEN",
61
+ "MACHINE_CREDENTIAL_PREFIX",
62
+ "MACHINE_SCOPES",
63
+ "MACHINE_SCOPE_NAME_TO_ENUM",
64
+ "ORG_ID_HEADER",
65
+ "TENANT_ID_HEADER",
66
+ "AgenticClient",
67
+ "AgenticClientSync",
68
+ "CaseOutput",
69
+ "ClassifiedFailure",
70
+ "CredentialInterceptor",
71
+ "Disposition",
72
+ "IssuedMachineCredential",
73
+ "O11yClient",
74
+ "Refusal",
75
+ "ValidationError",
76
+ "assert_looks_like_machine_credential",
77
+ "bare_enum_name",
78
+ "classify",
79
+ "find_error_detail",
80
+ ]
81
+
82
+ __version__ = "0.0.0"