metergraph 0.6.2__tar.gz → 0.6.4__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.
- {metergraph-0.6.2/src/metergraph.egg-info → metergraph-0.6.4}/PKG-INFO +1 -1
- {metergraph-0.6.2 → metergraph-0.6.4}/pyproject.toml +1 -1
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_capture.py +124 -32
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_version.py +1 -1
- {metergraph-0.6.2 → metergraph-0.6.4/src/metergraph.egg-info}/PKG-INFO +1 -1
- {metergraph-0.6.2 → metergraph-0.6.4}/MANIFEST.in +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/README.md +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/setup.cfg +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/__init__.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_batch_first.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_config.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_context.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_failure_log.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_provider_batch.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_repo_config.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_session.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_template.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_track.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/_transport.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph/opentelemetry.py +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph.egg-info/SOURCES.txt +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph.egg-info/dependency_links.txt +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph.egg-info/requires.txt +0 -0
- {metergraph-0.6.2 → metergraph-0.6.4}/src/metergraph.egg-info/top_level.txt +0 -0
|
@@ -858,14 +858,22 @@ class CallState:
|
|
|
858
858
|
|
|
859
859
|
class _StreamState:
|
|
860
860
|
def __init__(self, stream: Any, call: CallState) -> None:
|
|
861
|
+
self.manager = stream
|
|
861
862
|
self.stream = stream
|
|
862
863
|
self.call = call
|
|
863
864
|
self.iterator = None
|
|
865
|
+
self.entered = False
|
|
864
866
|
self.last = None
|
|
865
867
|
self.parts: list[str] = []
|
|
866
868
|
self.chunks: list[Any] = []
|
|
867
869
|
self.ttft_ms: int | None = None
|
|
868
870
|
|
|
871
|
+
def use_entered_stream(self, stream: Any) -> None:
|
|
872
|
+
if stream is not None:
|
|
873
|
+
self.stream = stream
|
|
874
|
+
self.iterator = None
|
|
875
|
+
self.entered = True
|
|
876
|
+
|
|
869
877
|
def chunk(self, value: Any) -> Any:
|
|
870
878
|
self.last = value
|
|
871
879
|
self.chunks.append(value)
|
|
@@ -876,6 +884,28 @@ class _StreamState:
|
|
|
876
884
|
self.parts.append(text)
|
|
877
885
|
return value
|
|
878
886
|
|
|
887
|
+
def safe_chunk(self, value: Any) -> Any:
|
|
888
|
+
"""Record a provider chunk, but return the raw provider value if
|
|
889
|
+
MeterGraph's own normalization/bookkeeping fails. The caller passes the
|
|
890
|
+
chunk it already obtained from the provider, so a telemetry error here
|
|
891
|
+
never drops or corrupts iteration. Only MeterGraph's ``Exception`` is
|
|
892
|
+
swallowed; a cancellation propagating through ``chunk`` is not."""
|
|
893
|
+
try:
|
|
894
|
+
return self.chunk(value)
|
|
895
|
+
except Exception:
|
|
896
|
+
return value
|
|
897
|
+
|
|
898
|
+
def is_usage_only(self, value: Any) -> bool:
|
|
899
|
+
"""Classify a usage-only chunk, failing open. A genuine usage-only chunk
|
|
900
|
+
is suppressed only when classification succeeds; if MeterGraph's own
|
|
901
|
+
classification raises on a pathological/proxied chunk, treat it as an
|
|
902
|
+
ordinary provider chunk so it is yielded, not dropped. Only MeterGraph's
|
|
903
|
+
``Exception`` is swallowed here."""
|
|
904
|
+
try:
|
|
905
|
+
return _usage_only_chunk(value, self.call)
|
|
906
|
+
except Exception:
|
|
907
|
+
return False
|
|
908
|
+
|
|
879
909
|
def finish(
|
|
880
910
|
self, status: str | None = None, error: BaseException | None = None
|
|
881
911
|
) -> None:
|
|
@@ -925,6 +955,32 @@ class _StreamState:
|
|
|
925
955
|
stream_chunks=self.chunks,
|
|
926
956
|
)
|
|
927
957
|
|
|
958
|
+
def safe_finish(
|
|
959
|
+
self, status: str | None = None, error: BaseException | None = None
|
|
960
|
+
) -> None:
|
|
961
|
+
"""Shared fail-open finalization for every sync lifecycle path (entry
|
|
962
|
+
error, normal exhaustion, provider iteration error, context exit, and
|
|
963
|
+
close). MeterGraph's own telemetry ``Exception`` is swallowed so a
|
|
964
|
+
failed finish can never replace normal completion, the provider
|
|
965
|
+
iteration exception, or a close; the trace may be lost, but application
|
|
966
|
+
behavior is unchanged. Provider/application exceptions never reach
|
|
967
|
+
here."""
|
|
968
|
+
try:
|
|
969
|
+
self.finish(status=status, error=error)
|
|
970
|
+
except Exception:
|
|
971
|
+
pass
|
|
972
|
+
|
|
973
|
+
async def safe_finish_async(
|
|
974
|
+
self, status: str | None = None, error: BaseException | None = None
|
|
975
|
+
) -> None:
|
|
976
|
+
"""Async counterpart to :meth:`safe_finish`, shared by every async
|
|
977
|
+
lifecycle path (entry error, normal exhaustion, provider iteration
|
|
978
|
+
error, context exit, and aclose)."""
|
|
979
|
+
try:
|
|
980
|
+
await self.finish_async(status=status, error=error)
|
|
981
|
+
except Exception:
|
|
982
|
+
pass
|
|
983
|
+
|
|
928
984
|
|
|
929
985
|
class SyncStream:
|
|
930
986
|
def __init__(self, stream: Any, call: CallState) -> None:
|
|
@@ -943,29 +999,34 @@ class SyncStream:
|
|
|
943
999
|
self._state.iterator = iter(self._state.stream)
|
|
944
1000
|
while True:
|
|
945
1001
|
try:
|
|
946
|
-
|
|
947
|
-
if _usage_only_chunk(value, self._state.call):
|
|
948
|
-
continue
|
|
949
|
-
return value
|
|
1002
|
+
provider_chunk = next(self._state.iterator)
|
|
950
1003
|
except StopIteration:
|
|
951
|
-
self._state.
|
|
1004
|
+
self._state.safe_finish()
|
|
952
1005
|
raise
|
|
953
1006
|
except BaseException as exc:
|
|
954
|
-
self._state.
|
|
1007
|
+
self._state.safe_finish(error=exc)
|
|
955
1008
|
raise
|
|
1009
|
+
value = self._state.safe_chunk(provider_chunk)
|
|
1010
|
+
if self._state.is_usage_only(value):
|
|
1011
|
+
continue
|
|
1012
|
+
return value
|
|
956
1013
|
|
|
957
1014
|
def __enter__(self) -> "SyncStream":
|
|
958
|
-
enter = getattr(self._state.
|
|
1015
|
+
enter = getattr(self._state.manager, "__enter__", None)
|
|
959
1016
|
if enter:
|
|
960
|
-
|
|
1017
|
+
try:
|
|
1018
|
+
self._state.use_entered_stream(enter())
|
|
1019
|
+
except BaseException as exc:
|
|
1020
|
+
self._state.safe_finish(error=exc)
|
|
1021
|
+
raise
|
|
961
1022
|
return self
|
|
962
1023
|
|
|
963
1024
|
def __exit__(self, exc_type, exc, tb) -> Any:
|
|
964
1025
|
if exc:
|
|
965
|
-
self._state.
|
|
1026
|
+
self._state.safe_finish(error=exc)
|
|
966
1027
|
elif not self._state.call.done:
|
|
967
|
-
self._state.
|
|
968
|
-
exit_fn = getattr(self._state.
|
|
1028
|
+
self._state.safe_finish(status=None if self._state.entered else "abandoned")
|
|
1029
|
+
exit_fn = getattr(self._state.manager, "__exit__", None)
|
|
969
1030
|
return exit_fn(exc_type, exc, tb) if exit_fn else None
|
|
970
1031
|
|
|
971
1032
|
def close(self) -> None:
|
|
@@ -973,7 +1034,7 @@ class SyncStream:
|
|
|
973
1034
|
if close:
|
|
974
1035
|
close()
|
|
975
1036
|
if not self._state.call.done:
|
|
976
|
-
self._state.
|
|
1037
|
+
self._state.safe_finish(status="abandoned")
|
|
977
1038
|
|
|
978
1039
|
def __del__(self) -> None:
|
|
979
1040
|
try:
|
|
@@ -1000,29 +1061,36 @@ class AsyncStream:
|
|
|
1000
1061
|
self._state.iterator = self._state.stream.__aiter__()
|
|
1001
1062
|
while True:
|
|
1002
1063
|
try:
|
|
1003
|
-
|
|
1004
|
-
if _usage_only_chunk(value, self._state.call):
|
|
1005
|
-
continue
|
|
1006
|
-
return value
|
|
1064
|
+
provider_chunk = await self._state.iterator.__anext__()
|
|
1007
1065
|
except StopAsyncIteration:
|
|
1008
|
-
await self._state.
|
|
1066
|
+
await self._state.safe_finish_async()
|
|
1009
1067
|
raise
|
|
1010
1068
|
except BaseException as exc:
|
|
1011
|
-
await self._state.
|
|
1069
|
+
await self._state.safe_finish_async(error=exc)
|
|
1012
1070
|
raise
|
|
1071
|
+
value = self._state.safe_chunk(provider_chunk)
|
|
1072
|
+
if self._state.is_usage_only(value):
|
|
1073
|
+
continue
|
|
1074
|
+
return value
|
|
1013
1075
|
|
|
1014
1076
|
async def __aenter__(self) -> "AsyncStream":
|
|
1015
|
-
enter = getattr(self._state.
|
|
1077
|
+
enter = getattr(self._state.manager, "__aenter__", None)
|
|
1016
1078
|
if enter:
|
|
1017
|
-
|
|
1079
|
+
try:
|
|
1080
|
+
self._state.use_entered_stream(await enter())
|
|
1081
|
+
except BaseException as exc:
|
|
1082
|
+
await self._state.safe_finish_async(error=exc)
|
|
1083
|
+
raise
|
|
1018
1084
|
return self
|
|
1019
1085
|
|
|
1020
1086
|
async def __aexit__(self, exc_type, exc, tb) -> Any:
|
|
1021
1087
|
if exc:
|
|
1022
|
-
await self._state.
|
|
1088
|
+
await self._state.safe_finish_async(error=exc)
|
|
1023
1089
|
elif not self._state.call.done:
|
|
1024
|
-
await self._state.
|
|
1025
|
-
|
|
1090
|
+
await self._state.safe_finish_async(
|
|
1091
|
+
status=None if self._state.entered else "abandoned"
|
|
1092
|
+
)
|
|
1093
|
+
exit_fn = getattr(self._state.manager, "__aexit__", None)
|
|
1026
1094
|
return await exit_fn(exc_type, exc, tb) if exit_fn else None
|
|
1027
1095
|
|
|
1028
1096
|
async def aclose(self) -> None:
|
|
@@ -1030,7 +1098,7 @@ class AsyncStream:
|
|
|
1030
1098
|
if close:
|
|
1031
1099
|
await close()
|
|
1032
1100
|
if not self._state.call.done:
|
|
1033
|
-
await self._state.
|
|
1101
|
+
await self._state.safe_finish_async(status="abandoned")
|
|
1034
1102
|
|
|
1035
1103
|
def __del__(self) -> None:
|
|
1036
1104
|
try:
|
|
@@ -1341,13 +1409,19 @@ def _patch(
|
|
|
1341
1409
|
args = (first, *args[1:])
|
|
1342
1410
|
else:
|
|
1343
1411
|
kwargs = {**kwargs, "stream_options": {"include_usage": True}}
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1412
|
+
try:
|
|
1413
|
+
request = _request(args, kwargs)
|
|
1414
|
+
capture_provider = _gateway_provider(request) if gateway else provider
|
|
1415
|
+
call = runtime.call_state(capture_provider, endpoint, request)
|
|
1416
|
+
except Exception:
|
|
1417
|
+
# Capture-state creation is telemetry-only. If it fails, invoke the
|
|
1418
|
+
# provider exactly once and return/await its native result with no
|
|
1419
|
+
# MeterGraph wrapping — the caller observes an uninstrumented call.
|
|
1420
|
+
return original(*args, **kwargs)
|
|
1347
1421
|
try:
|
|
1348
1422
|
result = original(*args, **kwargs)
|
|
1349
1423
|
except BaseException as exc:
|
|
1350
|
-
call
|
|
1424
|
+
_safe_finish(call, error=exc)
|
|
1351
1425
|
raise
|
|
1352
1426
|
|
|
1353
1427
|
if inspect.isawaitable(result):
|
|
@@ -1356,7 +1430,7 @@ def _patch(
|
|
|
1356
1430
|
try:
|
|
1357
1431
|
resolved = await result
|
|
1358
1432
|
except BaseException as exc:
|
|
1359
|
-
call
|
|
1433
|
+
_safe_finish(call, error=exc)
|
|
1360
1434
|
raise
|
|
1361
1435
|
return _finish_or_stream(resolved, call, endpoint, request)
|
|
1362
1436
|
|
|
@@ -1371,15 +1445,33 @@ def _patch(
|
|
|
1371
1445
|
return True
|
|
1372
1446
|
|
|
1373
1447
|
|
|
1448
|
+
def _safe_finish(call: CallState, response: Any = None, **kwargs: Any) -> None:
|
|
1449
|
+
"""Fail-open finalization for a non-streaming call. MeterGraph's own
|
|
1450
|
+
telemetry ``Exception`` is swallowed so a failed ``CallState.finish`` can
|
|
1451
|
+
never replace the provider result or the provider exception the caller must
|
|
1452
|
+
observe. Only telemetry runs here — the provider result/exception is passed
|
|
1453
|
+
in, never produced — so a cancellation is never wrongly swallowed."""
|
|
1454
|
+
try:
|
|
1455
|
+
call.finish(response, **kwargs)
|
|
1456
|
+
except Exception:
|
|
1457
|
+
pass
|
|
1458
|
+
|
|
1459
|
+
|
|
1374
1460
|
def _finish_or_stream(
|
|
1375
1461
|
result: Any, call: CallState, endpoint: str, request: Mapping[str, Any]
|
|
1376
1462
|
):
|
|
1377
1463
|
is_stream = endpoint.endswith(".stream") or bool(request.get("stream"))
|
|
1378
|
-
if is_stream and
|
|
1464
|
+
if is_stream and (
|
|
1465
|
+
hasattr(result, "__aiter__")
|
|
1466
|
+
or (hasattr(result, "__aenter__") and hasattr(result, "__aexit__"))
|
|
1467
|
+
):
|
|
1379
1468
|
return AsyncStream(result, call)
|
|
1380
|
-
if is_stream and
|
|
1469
|
+
if is_stream and (
|
|
1470
|
+
hasattr(result, "__iter__")
|
|
1471
|
+
or (hasattr(result, "__enter__") and hasattr(result, "__exit__"))
|
|
1472
|
+
):
|
|
1381
1473
|
return SyncStream(result, call)
|
|
1382
|
-
call
|
|
1474
|
+
_safe_finish(call, result)
|
|
1383
1475
|
return result
|
|
1384
1476
|
|
|
1385
1477
|
|
|
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
|