constellation-node-sdk 1.2.0__tar.gz → 1.3.2__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.
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/PKG-INFO +1 -1
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/pyproject.toml +1 -1
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/__init__.py +12 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate_authority/dispatch.py +14 -0
- constellation_node_sdk-1.3.2/src/constellation_node_sdk/trace_context.py +278 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk.egg-info/PKG-INFO +1 -1
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk.egg-info/SOURCES.txt +1 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/LICENSE +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/README.md +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/setup.cfg +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/_packet_http.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/__init__.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/admission.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/capabilities.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/capability_client.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/client.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/config.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/errors.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/policy.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate/registration.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate_authority/__init__.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate_authority/config.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/gate_authority/errors.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/__init__.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/base.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/merge.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/packet_builder.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/retry.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/state.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/orchestrator/step_executor.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/py.typed +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/__init__.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/app.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/config.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/errors.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/execution.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/handlers.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/inbound_policy.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/lifecycle.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/observability.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/participation.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/runtime/preflight.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/security/__init__.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/security/delegation.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/security/errors.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/security/signing.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/security/validation.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/security/verification.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/__init__.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/codec.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/errors.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/hashing.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/hop_trace.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/lineage.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/models.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/packet.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/provenance.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/transport/tenant.py +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk.egg-info/dependency_links.txt +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk.egg-info/requires.txt +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk.egg-info/top_level.txt +0 -0
- {constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/tests/test_release_identity.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "constellation-node-sdk"
|
|
7
|
-
version = "1.2
|
|
7
|
+
version = "1.3.2"
|
|
8
8
|
description = "L9 Constellation Node SDK — TransportPacket runtime, Gate client, and node factory"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
{constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/__init__.py
RENAMED
|
@@ -56,6 +56,13 @@ from constellation_node_sdk.security import (
|
|
|
56
56
|
validate_transport_packet,
|
|
57
57
|
verify_transport_packet_signature,
|
|
58
58
|
)
|
|
59
|
+
from constellation_node_sdk.trace_context import (
|
|
60
|
+
TraceContext,
|
|
61
|
+
TraceContextError,
|
|
62
|
+
create_root_trace_context,
|
|
63
|
+
inject_trace_context,
|
|
64
|
+
parse_trace_context,
|
|
65
|
+
)
|
|
59
66
|
from constellation_node_sdk.transport import (
|
|
60
67
|
DelegationLink,
|
|
61
68
|
RoutingProvenance,
|
|
@@ -126,6 +133,8 @@ __all__ = [
|
|
|
126
133
|
"TransportPacket",
|
|
127
134
|
"TransportSecurity",
|
|
128
135
|
"TransportValidationError",
|
|
136
|
+
"TraceContext",
|
|
137
|
+
"TraceContextError",
|
|
129
138
|
"assert_gate_only_destination",
|
|
130
139
|
"assert_local_node_identity",
|
|
131
140
|
"assert_node_origin_packet",
|
|
@@ -137,6 +146,7 @@ __all__ = [
|
|
|
137
146
|
"compute_transport_hash",
|
|
138
147
|
"create_error_transport_packet",
|
|
139
148
|
"create_node_app",
|
|
149
|
+
"create_root_trace_context",
|
|
140
150
|
"create_transport_packet",
|
|
141
151
|
"ensure_tenant_context",
|
|
142
152
|
"execute_transport_packet",
|
|
@@ -144,7 +154,9 @@ __all__ = [
|
|
|
144
154
|
"get_gate_registration_config_from_env",
|
|
145
155
|
"get_handler",
|
|
146
156
|
"get_runtime_config",
|
|
157
|
+
"inject_trace_context",
|
|
147
158
|
"load_node_spec",
|
|
159
|
+
"parse_trace_context",
|
|
148
160
|
"recompute_transport_core",
|
|
149
161
|
"register_from_env",
|
|
150
162
|
"register_handler",
|
|
@@ -17,6 +17,7 @@ from constellation_node_sdk._packet_http import (
|
|
|
17
17
|
from constellation_node_sdk.runtime.inbound_policy import validate_execute_ingress_packet
|
|
18
18
|
from constellation_node_sdk.security.signing import sign_transport_packet
|
|
19
19
|
from constellation_node_sdk.security.validation import validate_transport_packet
|
|
20
|
+
from constellation_node_sdk.trace_context import TraceContext, inject_trace_context
|
|
20
21
|
from constellation_node_sdk.transport.errors import TransportError
|
|
21
22
|
from constellation_node_sdk.transport.packet import TransportPacket
|
|
22
23
|
|
|
@@ -144,6 +145,7 @@ class GateDispatchTransport:
|
|
|
144
145
|
packet: TransportPacket,
|
|
145
146
|
target_node: str,
|
|
146
147
|
worker_base_url: str,
|
|
148
|
+
trace_context: TraceContext | None = None,
|
|
147
149
|
) -> TransportPacket:
|
|
148
150
|
"""
|
|
149
151
|
Send a Gate-authored dispatch packet to the worker Gate resolved.
|
|
@@ -168,6 +170,10 @@ class GateDispatchTransport:
|
|
|
168
170
|
target_node: The node name Gate resolved.
|
|
169
171
|
worker_base_url: That node's base URL from Gate's registry. The
|
|
170
172
|
canonical ``/v1/execute`` endpoint is appended by the SDK.
|
|
173
|
+
trace_context: Optional validated W3C context authored by Gate.
|
|
174
|
+
It is transported only as strict ``traceparent`` and optional
|
|
175
|
+
``tracestate`` headers; baggage and arbitrary headers are not
|
|
176
|
+
accepted on this authority boundary.
|
|
171
177
|
|
|
172
178
|
Returns:
|
|
173
179
|
The validated canonical ``TransportPacket`` the worker returned.
|
|
@@ -197,6 +203,13 @@ class GateDispatchTransport:
|
|
|
197
203
|
signed_packet = self._maybe_sign(packet)
|
|
198
204
|
self._validate_outbound_transport(signed_packet)
|
|
199
205
|
timeout_seconds = self._network_timeout_seconds(signed_packet)
|
|
206
|
+
trace_headers: dict[str, str] | None = None
|
|
207
|
+
if trace_context is not None:
|
|
208
|
+
injected = inject_trace_context({}, trace_context)
|
|
209
|
+
trace_headers = {"traceparent": str(injected["traceparent"])}
|
|
210
|
+
tracestate = injected.get("tracestate")
|
|
211
|
+
if isinstance(tracestate, str):
|
|
212
|
+
trace_headers["tracestate"] = tracestate
|
|
200
213
|
|
|
201
214
|
response = await post_packet_json(
|
|
202
215
|
url=execute_url,
|
|
@@ -206,6 +219,7 @@ class GateDispatchTransport:
|
|
|
206
219
|
what=f"worker {normalized_target!r}",
|
|
207
220
|
client=self._client,
|
|
208
221
|
transport=self._transport if self._client is None else None,
|
|
222
|
+
headers=trace_headers,
|
|
209
223
|
)
|
|
210
224
|
raise_for_status(
|
|
211
225
|
response,
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Packet-neutral utilities for W3C Trace Context version 00 headers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import secrets
|
|
7
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from typing import NoReturn
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"TraceContext",
|
|
13
|
+
"TraceContextError",
|
|
14
|
+
"create_root_trace_context",
|
|
15
|
+
"inject_trace_context",
|
|
16
|
+
"parse_trace_context",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
_ERROR_MESSAGE = "invalid W3C trace context"
|
|
20
|
+
_TRACEPARENT_RE = re.compile(
|
|
21
|
+
r"00-[0-9a-f]{32}-[0-9a-f]{16}-[0-9a-f]{2}\Z",
|
|
22
|
+
re.ASCII,
|
|
23
|
+
)
|
|
24
|
+
_TRACESTATE_KEY_RE = re.compile(
|
|
25
|
+
r"(?:[a-z][a-z0-9_*/-]{0,255}|[a-z0-9][a-z0-9_*/-]{0,240}@[a-z][a-z0-9_*/-]{0,13})\Z",
|
|
26
|
+
re.ASCII,
|
|
27
|
+
)
|
|
28
|
+
_CONTEXT_HEADER_NAMES = frozenset({"traceparent", "tracestate", "baggage"})
|
|
29
|
+
_MAX_SCANNED_HEADER_FIELDS = 64
|
|
30
|
+
_MAX_TRACEPARENT_VALUES = 1
|
|
31
|
+
_MAX_TRACESTATE_FIELD_VALUES = 32
|
|
32
|
+
_MAX_TRACESTATE_LENGTH = 512
|
|
33
|
+
_MAX_TRACESTATE_MEMBERS = 32
|
|
34
|
+
_MAX_TRACESTATE_VALUE_LENGTH = 256
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TraceContextError(ValueError):
|
|
38
|
+
"""Raised when W3C trace-context input is malformed or unsafe."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _raise_invalid() -> NoReturn:
|
|
42
|
+
raise TraceContextError(_ERROR_MESSAGE) from None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_root_trace_context() -> TraceContext:
|
|
46
|
+
"""Create a fresh valid, unsampled W3C v00 root context.
|
|
47
|
+
|
|
48
|
+
This establishes correlation only. It carries no caller identity, routing
|
|
49
|
+
authority, packet state, or sampling policy beyond the conservative
|
|
50
|
+
unsampled default. Deployment-specific sampling remains outside this
|
|
51
|
+
packet-neutral utility.
|
|
52
|
+
"""
|
|
53
|
+
while True:
|
|
54
|
+
trace_id = secrets.token_hex(16)
|
|
55
|
+
parent_id = secrets.token_hex(8)
|
|
56
|
+
if trace_id != "0" * 32 and parent_id != "0" * 16:
|
|
57
|
+
return TraceContext(f"00-{trace_id}-{parent_id}-00")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True, slots=True, repr=False)
|
|
61
|
+
class TraceContext:
|
|
62
|
+
"""Validated W3C version 00 trace headers without packet coupling."""
|
|
63
|
+
|
|
64
|
+
traceparent: str
|
|
65
|
+
tracestate: str | None = None
|
|
66
|
+
|
|
67
|
+
def __post_init__(self) -> None:
|
|
68
|
+
try:
|
|
69
|
+
traceparent = _validate_traceparent(self.traceparent)
|
|
70
|
+
tracestate = (
|
|
71
|
+
None if self.tracestate is None else _normalize_tracestate((self.tracestate,))
|
|
72
|
+
)
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
else:
|
|
76
|
+
object.__setattr__(self, "traceparent", traceparent)
|
|
77
|
+
object.__setattr__(self, "tracestate", tracestate)
|
|
78
|
+
return
|
|
79
|
+
_raise_invalid()
|
|
80
|
+
|
|
81
|
+
def __repr__(self) -> str:
|
|
82
|
+
tracestate = "None" if self.tracestate is None else "<redacted>"
|
|
83
|
+
return f"TraceContext(traceparent=<redacted>, tracestate={tracestate})"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def parse_trace_context(
|
|
87
|
+
headers: Mapping[str, str | Sequence[str]],
|
|
88
|
+
) -> TraceContext | None:
|
|
89
|
+
"""Parse strict W3C v00 context from case-insensitive HTTP-style headers.
|
|
90
|
+
|
|
91
|
+
Baggage is intentionally ignored and never becomes part of ``TraceContext``.
|
|
92
|
+
A malformed ``tracestate`` is discarded when its ``traceparent`` is valid, as
|
|
93
|
+
required for receiver interoperability.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
if not isinstance(headers, Mapping):
|
|
97
|
+
_raise_invalid()
|
|
98
|
+
|
|
99
|
+
traceparent_values: tuple[str, ...] = ()
|
|
100
|
+
raw_tracestate_fields: list[object] = []
|
|
101
|
+
traceparent_fields = 0
|
|
102
|
+
tracestate_fields = 0
|
|
103
|
+
|
|
104
|
+
for header_name, header_value in _bounded_mapping_items(headers):
|
|
105
|
+
if not isinstance(header_name, str):
|
|
106
|
+
_raise_invalid()
|
|
107
|
+
normalized_name = header_name.lower()
|
|
108
|
+
if normalized_name == "traceparent":
|
|
109
|
+
traceparent_fields += 1
|
|
110
|
+
if traceparent_fields > 1:
|
|
111
|
+
_raise_invalid()
|
|
112
|
+
traceparent_values = _header_values(
|
|
113
|
+
header_value,
|
|
114
|
+
maximum=_MAX_TRACEPARENT_VALUES,
|
|
115
|
+
)
|
|
116
|
+
elif normalized_name == "tracestate":
|
|
117
|
+
tracestate_fields += 1
|
|
118
|
+
raw_tracestate_fields.append(header_value)
|
|
119
|
+
|
|
120
|
+
if traceparent_fields == 0:
|
|
121
|
+
if tracestate_fields:
|
|
122
|
+
_raise_invalid()
|
|
123
|
+
return None
|
|
124
|
+
if len(traceparent_values) != _MAX_TRACEPARENT_VALUES:
|
|
125
|
+
_raise_invalid()
|
|
126
|
+
|
|
127
|
+
traceparent = _validate_traceparent(traceparent_values[0])
|
|
128
|
+
tracestate: str | None = None
|
|
129
|
+
if tracestate_fields:
|
|
130
|
+
try:
|
|
131
|
+
tracestate_values = _tracestate_header_values(raw_tracestate_fields)
|
|
132
|
+
tracestate = _normalize_tracestate(
|
|
133
|
+
tracestate_values,
|
|
134
|
+
allow_empty_members=True,
|
|
135
|
+
)
|
|
136
|
+
except Exception:
|
|
137
|
+
tracestate = None
|
|
138
|
+
|
|
139
|
+
return TraceContext(traceparent=traceparent, tracestate=tracestate)
|
|
140
|
+
except Exception:
|
|
141
|
+
pass
|
|
142
|
+
_raise_invalid()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def inject_trace_context(headers: Mapping[str, object], context: TraceContext) -> dict[str, object]:
|
|
146
|
+
"""Copy headers, replace trace context, and deliberately drop baggage."""
|
|
147
|
+
try:
|
|
148
|
+
if not isinstance(headers, Mapping) or not isinstance(context, TraceContext):
|
|
149
|
+
_raise_invalid()
|
|
150
|
+
validated_context = TraceContext(
|
|
151
|
+
traceparent=context.traceparent,
|
|
152
|
+
tracestate=context.tracestate,
|
|
153
|
+
)
|
|
154
|
+
injected: dict[str, object] = {}
|
|
155
|
+
for header_name, header_value in _bounded_mapping_items(headers):
|
|
156
|
+
if not isinstance(header_name, str):
|
|
157
|
+
_raise_invalid()
|
|
158
|
+
if header_name.lower() not in _CONTEXT_HEADER_NAMES:
|
|
159
|
+
injected[header_name] = header_value
|
|
160
|
+
injected["traceparent"] = validated_context.traceparent
|
|
161
|
+
if validated_context.tracestate is not None:
|
|
162
|
+
injected["tracestate"] = validated_context.tracestate
|
|
163
|
+
return injected
|
|
164
|
+
except Exception:
|
|
165
|
+
pass
|
|
166
|
+
_raise_invalid()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _bounded_mapping_items[HeaderName, HeaderValue](
|
|
170
|
+
headers: Mapping[HeaderName, HeaderValue],
|
|
171
|
+
) -> Iterator[tuple[HeaderName, HeaderValue]]:
|
|
172
|
+
"""Yield at most a fixed number of mapping fields without using ``items()``."""
|
|
173
|
+
header_names = iter(headers)
|
|
174
|
+
for _ in range(_MAX_SCANNED_HEADER_FIELDS):
|
|
175
|
+
try:
|
|
176
|
+
header_name = next(header_names)
|
|
177
|
+
except StopIteration:
|
|
178
|
+
return
|
|
179
|
+
yield header_name, headers[header_name]
|
|
180
|
+
_raise_invalid()
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _header_values(value: object, *, maximum: int) -> tuple[str, ...]:
|
|
184
|
+
"""Read a bounded HTTP header value sequence using indexed access only."""
|
|
185
|
+
if isinstance(value, str):
|
|
186
|
+
return (value,)
|
|
187
|
+
if isinstance(value, (bytes, bytearray)) or not isinstance(value, Sequence):
|
|
188
|
+
_raise_invalid()
|
|
189
|
+
|
|
190
|
+
value_count = len(value)
|
|
191
|
+
if value_count > maximum:
|
|
192
|
+
_raise_invalid()
|
|
193
|
+
|
|
194
|
+
values: list[str] = []
|
|
195
|
+
for index in range(value_count):
|
|
196
|
+
item = value[index]
|
|
197
|
+
if not isinstance(item, str):
|
|
198
|
+
_raise_invalid()
|
|
199
|
+
values.append(item)
|
|
200
|
+
return tuple(values)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _tracestate_header_values(raw_values: Sequence[object]) -> tuple[str, ...]:
|
|
204
|
+
"""Collect bounded tracestate fields without splitting an oversized value."""
|
|
205
|
+
values: list[str] = []
|
|
206
|
+
for raw_value in raw_values:
|
|
207
|
+
remaining = _MAX_TRACESTATE_FIELD_VALUES - len(values)
|
|
208
|
+
if remaining <= 0:
|
|
209
|
+
_raise_invalid()
|
|
210
|
+
for value in _header_values(raw_value, maximum=remaining):
|
|
211
|
+
if len(value) > _MAX_TRACESTATE_LENGTH:
|
|
212
|
+
_raise_invalid()
|
|
213
|
+
values.append(value)
|
|
214
|
+
return tuple(values)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _validate_traceparent(value: object) -> str:
|
|
218
|
+
if not isinstance(value, str) or not _TRACEPARENT_RE.fullmatch(value):
|
|
219
|
+
_raise_invalid()
|
|
220
|
+
trace_id = value[3:35]
|
|
221
|
+
parent_id = value[36:52]
|
|
222
|
+
if trace_id == "0" * 32 or parent_id == "0" * 16:
|
|
223
|
+
_raise_invalid()
|
|
224
|
+
return value
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _normalize_tracestate(
|
|
228
|
+
values: Sequence[str],
|
|
229
|
+
*,
|
|
230
|
+
allow_empty_members: bool = False,
|
|
231
|
+
) -> str | None:
|
|
232
|
+
if any(not isinstance(value, str) for value in values):
|
|
233
|
+
_raise_invalid()
|
|
234
|
+
|
|
235
|
+
raw_length = max(len(values) - 1, 0)
|
|
236
|
+
for value in values:
|
|
237
|
+
if len(value) > _MAX_TRACESTATE_LENGTH:
|
|
238
|
+
_raise_invalid()
|
|
239
|
+
raw_length += len(value)
|
|
240
|
+
if raw_length > _MAX_TRACESTATE_LENGTH:
|
|
241
|
+
_raise_invalid()
|
|
242
|
+
|
|
243
|
+
members: list[str] = []
|
|
244
|
+
seen_keys: set[str] = set()
|
|
245
|
+
for raw_value in values:
|
|
246
|
+
for raw_member in raw_value.split(","):
|
|
247
|
+
member = raw_member.strip(" \t")
|
|
248
|
+
if not member:
|
|
249
|
+
if allow_empty_members:
|
|
250
|
+
continue
|
|
251
|
+
_raise_invalid()
|
|
252
|
+
if member.count("=") != 1:
|
|
253
|
+
_raise_invalid()
|
|
254
|
+
|
|
255
|
+
key, value = member.split("=", 1)
|
|
256
|
+
if not key or not value or key[-1] in " \t" or value[0] in " \t":
|
|
257
|
+
_raise_invalid()
|
|
258
|
+
if not _TRACESTATE_KEY_RE.fullmatch(key) or key in seen_keys:
|
|
259
|
+
_raise_invalid()
|
|
260
|
+
if len(value) > _MAX_TRACESTATE_VALUE_LENGTH:
|
|
261
|
+
_raise_invalid()
|
|
262
|
+
if any(character in ",=" or not 0x20 <= ord(character) <= 0x7E for character in value):
|
|
263
|
+
_raise_invalid()
|
|
264
|
+
|
|
265
|
+
seen_keys.add(key)
|
|
266
|
+
members.append(f"{key}={value}")
|
|
267
|
+
if len(members) > _MAX_TRACESTATE_MEMBERS:
|
|
268
|
+
_raise_invalid()
|
|
269
|
+
|
|
270
|
+
if not members:
|
|
271
|
+
if allow_empty_members:
|
|
272
|
+
return None
|
|
273
|
+
_raise_invalid()
|
|
274
|
+
|
|
275
|
+
normalized = ",".join(members)
|
|
276
|
+
if len(normalized) > _MAX_TRACESTATE_LENGTH:
|
|
277
|
+
_raise_invalid()
|
|
278
|
+
return normalized
|
|
@@ -4,6 +4,7 @@ pyproject.toml
|
|
|
4
4
|
src/constellation_node_sdk/__init__.py
|
|
5
5
|
src/constellation_node_sdk/_packet_http.py
|
|
6
6
|
src/constellation_node_sdk/py.typed
|
|
7
|
+
src/constellation_node_sdk/trace_context.py
|
|
7
8
|
src/constellation_node_sdk.egg-info/PKG-INFO
|
|
8
9
|
src/constellation_node_sdk.egg-info/SOURCES.txt
|
|
9
10
|
src/constellation_node_sdk.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/src/constellation_node_sdk/py.typed
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{constellation_node_sdk-1.2.0 → constellation_node_sdk-1.3.2}/tests/test_release_identity.py
RENAMED
|
File without changes
|