metergraph 0.6.3__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.3/src/metergraph.egg-info → metergraph-0.6.4}/PKG-INFO +1 -1
- {metergraph-0.6.3 → metergraph-0.6.4}/pyproject.toml +1 -1
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_capture.py +94 -26
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_version.py +1 -1
- {metergraph-0.6.3 → metergraph-0.6.4/src/metergraph.egg-info}/PKG-INFO +1 -1
- {metergraph-0.6.3 → metergraph-0.6.4}/MANIFEST.in +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/README.md +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/setup.cfg +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/__init__.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_batch_first.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_config.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_context.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_failure_log.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_provider_batch.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_repo_config.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_session.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_template.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_track.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/_transport.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph/opentelemetry.py +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph.egg-info/SOURCES.txt +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph.egg-info/dependency_links.txt +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph.egg-info/requires.txt +0 -0
- {metergraph-0.6.3 → metergraph-0.6.4}/src/metergraph.egg-info/top_level.txt +0 -0
|
@@ -884,6 +884,28 @@ class _StreamState:
|
|
|
884
884
|
self.parts.append(text)
|
|
885
885
|
return value
|
|
886
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
|
+
|
|
887
909
|
def finish(
|
|
888
910
|
self, status: str | None = None, error: BaseException | None = None
|
|
889
911
|
) -> None:
|
|
@@ -933,6 +955,32 @@ class _StreamState:
|
|
|
933
955
|
stream_chunks=self.chunks,
|
|
934
956
|
)
|
|
935
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
|
+
|
|
936
984
|
|
|
937
985
|
class SyncStream:
|
|
938
986
|
def __init__(self, stream: Any, call: CallState) -> None:
|
|
@@ -951,16 +999,17 @@ class SyncStream:
|
|
|
951
999
|
self._state.iterator = iter(self._state.stream)
|
|
952
1000
|
while True:
|
|
953
1001
|
try:
|
|
954
|
-
|
|
955
|
-
if _usage_only_chunk(value, self._state.call):
|
|
956
|
-
continue
|
|
957
|
-
return value
|
|
1002
|
+
provider_chunk = next(self._state.iterator)
|
|
958
1003
|
except StopIteration:
|
|
959
|
-
self._state.
|
|
1004
|
+
self._state.safe_finish()
|
|
960
1005
|
raise
|
|
961
1006
|
except BaseException as exc:
|
|
962
|
-
self._state.
|
|
1007
|
+
self._state.safe_finish(error=exc)
|
|
963
1008
|
raise
|
|
1009
|
+
value = self._state.safe_chunk(provider_chunk)
|
|
1010
|
+
if self._state.is_usage_only(value):
|
|
1011
|
+
continue
|
|
1012
|
+
return value
|
|
964
1013
|
|
|
965
1014
|
def __enter__(self) -> "SyncStream":
|
|
966
1015
|
enter = getattr(self._state.manager, "__enter__", None)
|
|
@@ -968,15 +1017,15 @@ class SyncStream:
|
|
|
968
1017
|
try:
|
|
969
1018
|
self._state.use_entered_stream(enter())
|
|
970
1019
|
except BaseException as exc:
|
|
971
|
-
self._state.
|
|
1020
|
+
self._state.safe_finish(error=exc)
|
|
972
1021
|
raise
|
|
973
1022
|
return self
|
|
974
1023
|
|
|
975
1024
|
def __exit__(self, exc_type, exc, tb) -> Any:
|
|
976
1025
|
if exc:
|
|
977
|
-
self._state.
|
|
1026
|
+
self._state.safe_finish(error=exc)
|
|
978
1027
|
elif not self._state.call.done:
|
|
979
|
-
self._state.
|
|
1028
|
+
self._state.safe_finish(status=None if self._state.entered else "abandoned")
|
|
980
1029
|
exit_fn = getattr(self._state.manager, "__exit__", None)
|
|
981
1030
|
return exit_fn(exc_type, exc, tb) if exit_fn else None
|
|
982
1031
|
|
|
@@ -985,7 +1034,7 @@ class SyncStream:
|
|
|
985
1034
|
if close:
|
|
986
1035
|
close()
|
|
987
1036
|
if not self._state.call.done:
|
|
988
|
-
self._state.
|
|
1037
|
+
self._state.safe_finish(status="abandoned")
|
|
989
1038
|
|
|
990
1039
|
def __del__(self) -> None:
|
|
991
1040
|
try:
|
|
@@ -1012,16 +1061,17 @@ class AsyncStream:
|
|
|
1012
1061
|
self._state.iterator = self._state.stream.__aiter__()
|
|
1013
1062
|
while True:
|
|
1014
1063
|
try:
|
|
1015
|
-
|
|
1016
|
-
if _usage_only_chunk(value, self._state.call):
|
|
1017
|
-
continue
|
|
1018
|
-
return value
|
|
1064
|
+
provider_chunk = await self._state.iterator.__anext__()
|
|
1019
1065
|
except StopAsyncIteration:
|
|
1020
|
-
await self._state.
|
|
1066
|
+
await self._state.safe_finish_async()
|
|
1021
1067
|
raise
|
|
1022
1068
|
except BaseException as exc:
|
|
1023
|
-
await self._state.
|
|
1069
|
+
await self._state.safe_finish_async(error=exc)
|
|
1024
1070
|
raise
|
|
1071
|
+
value = self._state.safe_chunk(provider_chunk)
|
|
1072
|
+
if self._state.is_usage_only(value):
|
|
1073
|
+
continue
|
|
1074
|
+
return value
|
|
1025
1075
|
|
|
1026
1076
|
async def __aenter__(self) -> "AsyncStream":
|
|
1027
1077
|
enter = getattr(self._state.manager, "__aenter__", None)
|
|
@@ -1029,15 +1079,15 @@ class AsyncStream:
|
|
|
1029
1079
|
try:
|
|
1030
1080
|
self._state.use_entered_stream(await enter())
|
|
1031
1081
|
except BaseException as exc:
|
|
1032
|
-
await self._state.
|
|
1082
|
+
await self._state.safe_finish_async(error=exc)
|
|
1033
1083
|
raise
|
|
1034
1084
|
return self
|
|
1035
1085
|
|
|
1036
1086
|
async def __aexit__(self, exc_type, exc, tb) -> Any:
|
|
1037
1087
|
if exc:
|
|
1038
|
-
await self._state.
|
|
1088
|
+
await self._state.safe_finish_async(error=exc)
|
|
1039
1089
|
elif not self._state.call.done:
|
|
1040
|
-
await self._state.
|
|
1090
|
+
await self._state.safe_finish_async(
|
|
1041
1091
|
status=None if self._state.entered else "abandoned"
|
|
1042
1092
|
)
|
|
1043
1093
|
exit_fn = getattr(self._state.manager, "__aexit__", None)
|
|
@@ -1048,7 +1098,7 @@ class AsyncStream:
|
|
|
1048
1098
|
if close:
|
|
1049
1099
|
await close()
|
|
1050
1100
|
if not self._state.call.done:
|
|
1051
|
-
await self._state.
|
|
1101
|
+
await self._state.safe_finish_async(status="abandoned")
|
|
1052
1102
|
|
|
1053
1103
|
def __del__(self) -> None:
|
|
1054
1104
|
try:
|
|
@@ -1359,13 +1409,19 @@ def _patch(
|
|
|
1359
1409
|
args = (first, *args[1:])
|
|
1360
1410
|
else:
|
|
1361
1411
|
kwargs = {**kwargs, "stream_options": {"include_usage": True}}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
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)
|
|
1365
1421
|
try:
|
|
1366
1422
|
result = original(*args, **kwargs)
|
|
1367
1423
|
except BaseException as exc:
|
|
1368
|
-
call
|
|
1424
|
+
_safe_finish(call, error=exc)
|
|
1369
1425
|
raise
|
|
1370
1426
|
|
|
1371
1427
|
if inspect.isawaitable(result):
|
|
@@ -1374,7 +1430,7 @@ def _patch(
|
|
|
1374
1430
|
try:
|
|
1375
1431
|
resolved = await result
|
|
1376
1432
|
except BaseException as exc:
|
|
1377
|
-
call
|
|
1433
|
+
_safe_finish(call, error=exc)
|
|
1378
1434
|
raise
|
|
1379
1435
|
return _finish_or_stream(resolved, call, endpoint, request)
|
|
1380
1436
|
|
|
@@ -1389,6 +1445,18 @@ def _patch(
|
|
|
1389
1445
|
return True
|
|
1390
1446
|
|
|
1391
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
|
+
|
|
1392
1460
|
def _finish_or_stream(
|
|
1393
1461
|
result: Any, call: CallState, endpoint: str, request: Mapping[str, Any]
|
|
1394
1462
|
):
|
|
@@ -1403,7 +1471,7 @@ def _finish_or_stream(
|
|
|
1403
1471
|
or (hasattr(result, "__enter__") and hasattr(result, "__exit__"))
|
|
1404
1472
|
):
|
|
1405
1473
|
return SyncStream(result, call)
|
|
1406
|
-
call
|
|
1474
|
+
_safe_finish(call, result)
|
|
1407
1475
|
return result
|
|
1408
1476
|
|
|
1409
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
|