phronesis-framework 0.1.0__py3-none-any.whl
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.
- phronesis/__init__.py +38 -0
- phronesis/_internal/__init__.py +1 -0
- phronesis/_internal/concurrency/__init__.py +18 -0
- phronesis/_internal/concurrency/exceptions.py +39 -0
- phronesis/_internal/concurrency/executor.py +71 -0
- phronesis/_internal/concurrency/gather.py +65 -0
- phronesis/_internal/concurrency/policies.py +70 -0
- phronesis/_internal/http/__init__.py +35 -0
- phronesis/_internal/http/client.py +429 -0
- phronesis/_internal/http/exceptions.py +61 -0
- phronesis/_internal/http/headers.py +34 -0
- phronesis/_internal/http/models.py +33 -0
- phronesis/_internal/http/timeouts.py +29 -0
- phronesis/_internal/ids/__init__.py +13 -0
- phronesis/_internal/ids/derivation.py +10 -0
- phronesis/_internal/ids/generator.py +24 -0
- phronesis/_internal/ids/id.py +37 -0
- phronesis/_internal/ids/validator.py +26 -0
- phronesis/_internal/logging/__init__.py +20 -0
- phronesis/_internal/logging/adapter.py +30 -0
- phronesis/_internal/logging/config.py +49 -0
- phronesis/_internal/logging/constants.py +12 -0
- phronesis/_internal/logging/factory.py +18 -0
- phronesis/_internal/logging/formatters.py +94 -0
- phronesis/_internal/retry/__init__.py +18 -0
- phronesis/_internal/retry/attempt.py +18 -0
- phronesis/_internal/retry/backoff.py +46 -0
- phronesis/_internal/retry/decorator.py +191 -0
- phronesis/_internal/retry/exceptions.py +27 -0
- phronesis/_internal/typing/__init__.py +45 -0
- phronesis/_internal/typing/binary.py +13 -0
- phronesis/_internal/typing/json.py +9 -0
- phronesis/_internal/typing/maybe.py +36 -0
- phronesis/_internal/typing/newtypes.py +11 -0
- phronesis/_internal/typing/protocols.py +22 -0
- phronesis/_internal/typing/result.py +27 -0
- phronesis/_internal/typing/sentinels.py +21 -0
- phronesis/_internal/typing/streaming.py +21 -0
- phronesis/agents/__init__.py +81 -0
- phronesis/agents/agent.py +292 -0
- phronesis/agents/decorator.py +200 -0
- phronesis/agents/errors.py +100 -0
- phronesis/agents/events.py +137 -0
- phronesis/agents/hooks.py +78 -0
- phronesis/agents/id.py +35 -0
- phronesis/agents/loop.py +883 -0
- phronesis/agents/registry.py +165 -0
- phronesis/agents/run.py +141 -0
- phronesis/agents/session.py +122 -0
- phronesis/agents/spec.py +88 -0
- phronesis/agents/validation.py +128 -0
- phronesis/communication/__init__.py +1 -0
- phronesis/communication/session_id.py +16 -0
- phronesis/context/__init__.py +41 -0
- phronesis/context/budget.py +17 -0
- phronesis/context/chain.py +101 -0
- phronesis/context/compacting.py +387 -0
- phronesis/context/context.py +50 -0
- phronesis/context/default.py +55 -0
- phronesis/context/dry_run.py +88 -0
- phronesis/context/errors.py +30 -0
- phronesis/context/input.py +42 -0
- phronesis/context/protocol.py +45 -0
- phronesis/core/__init__.py +36 -0
- phronesis/core/messages.py +251 -0
- phronesis/errors.py +36 -0
- phronesis/mcp/__init__.py +62 -0
- phronesis/mcp/_adapt.py +211 -0
- phronesis/mcp/client.py +165 -0
- phronesis/mcp/errors.py +58 -0
- phronesis/mcp/ids.py +50 -0
- phronesis/mcp/obs.py +52 -0
- phronesis/mcp/server.py +229 -0
- phronesis/mcp/server_spec.py +65 -0
- phronesis/mcp/transport.py +53 -0
- phronesis/memory/__init__.py +98 -0
- phronesis/memory/checkpoint.py +201 -0
- phronesis/memory/context_builder.py +194 -0
- phronesis/memory/episodic/__init__.py +22 -0
- phronesis/memory/episodic/filesystem.py +172 -0
- phronesis/memory/episodic/in_memory.py +92 -0
- phronesis/memory/episodic/protocol.py +81 -0
- phronesis/memory/errors.py +50 -0
- phronesis/memory/hooks.py +89 -0
- phronesis/memory/kv/__init__.py +21 -0
- phronesis/memory/kv/filesystem.py +247 -0
- phronesis/memory/kv/in_memory.py +175 -0
- phronesis/memory/kv/protocol.py +70 -0
- phronesis/memory/obs.py +100 -0
- phronesis/memory/scope.py +111 -0
- phronesis/memory/tools.py +296 -0
- phronesis/memory/vector/__init__.py +28 -0
- phronesis/memory/vector/_cosine.py +34 -0
- phronesis/memory/vector/embeddings.py +69 -0
- phronesis/memory/vector/filesystem.py +174 -0
- phronesis/memory/vector/in_memory.py +102 -0
- phronesis/memory/vector/protocol.py +94 -0
- phronesis/memory/working.py +165 -0
- phronesis/middleware/__init__.py +30 -0
- phronesis/middleware/chain.py +102 -0
- phronesis/middleware/errors.py +13 -0
- phronesis/middleware/protocol.py +58 -0
- phronesis/obs/__init__.py +49 -0
- phronesis/obs/_detect.py +28 -0
- phronesis/obs/_noop.py +71 -0
- phronesis/obs/attributes.py +82 -0
- phronesis/obs/config.py +257 -0
- phronesis/obs/errors.py +25 -0
- phronesis/obs/logging_filter.py +97 -0
- phronesis/obs/metrics.py +146 -0
- phronesis/obs/spans.py +165 -0
- phronesis/pipelines/__init__.py +28 -0
- phronesis/pipelines/errors.py +21 -0
- phronesis/pipelines/ids.py +63 -0
- phronesis/pipelines/pipeline.py +250 -0
- phronesis/providers/__init__.py +80 -0
- phronesis/providers/_common/__init__.py +9 -0
- phronesis/providers/anthropic/__init__.py +13 -0
- phronesis/providers/anthropic/errors.py +113 -0
- phronesis/providers/anthropic/factory.py +81 -0
- phronesis/providers/anthropic/messages.py +248 -0
- phronesis/providers/anthropic/provider.py +344 -0
- phronesis/providers/anthropic/streaming.py +305 -0
- phronesis/providers/anthropic/tools.py +48 -0
- phronesis/providers/chunks.py +85 -0
- phronesis/providers/errors.py +98 -0
- phronesis/providers/fallback.py +145 -0
- phronesis/providers/openai/__init__.py +20 -0
- phronesis/providers/openai/errors.py +101 -0
- phronesis/providers/openai/factory.py +98 -0
- phronesis/providers/openai/helpers.py +189 -0
- phronesis/providers/openai/messages.py +180 -0
- phronesis/providers/openai/provider.py +403 -0
- phronesis/providers/openai/streaming.py +275 -0
- phronesis/providers/openai/tools.py +50 -0
- phronesis/providers/protocol.py +156 -0
- phronesis/providers/retry_config.py +81 -0
- phronesis/providers/translation.py +167 -0
- phronesis/providers/types.py +186 -0
- phronesis/providers/usage.py +32 -0
- phronesis/replay/__init__.py +57 -0
- phronesis/replay/cassette.py +148 -0
- phronesis/replay/errors.py +23 -0
- phronesis/replay/recording.py +82 -0
- phronesis/replay/replay.py +127 -0
- phronesis/runtime/__init__.py +99 -0
- phronesis/runtime/context.py +153 -0
- phronesis/runtime/errors.py +77 -0
- phronesis/runtime/modes/__init__.py +5 -0
- phronesis/runtime/modes/approval.py +78 -0
- phronesis/runtime/modes/cascade.py +55 -0
- phronesis/runtime/modes/conditional.py +41 -0
- phronesis/runtime/modes/consensus.py +100 -0
- phronesis/runtime/modes/debate.py +80 -0
- phronesis/runtime/modes/fallback.py +50 -0
- phronesis/runtime/modes/handoff_chain.py +100 -0
- phronesis/runtime/modes/loop.py +80 -0
- phronesis/runtime/modes/map_reduce.py +81 -0
- phronesis/runtime/modes/parallel.py +82 -0
- phronesis/runtime/modes/plan_and_execute.py +79 -0
- phronesis/runtime/modes/race.py +90 -0
- phronesis/runtime/modes/reflexion.py +116 -0
- phronesis/runtime/modes/retry.py +83 -0
- phronesis/runtime/modes/router.py +57 -0
- phronesis/runtime/modes/sequence.py +59 -0
- phronesis/runtime/modes/supervisor.py +113 -0
- phronesis/runtime/modes/tree_search.py +151 -0
- phronesis/runtime/modes/validation.py +88 -0
- phronesis/runtime/node.py +150 -0
- phronesis/runtime/obs.py +95 -0
- phronesis/runtime/outcome.py +147 -0
- phronesis/runtime/protocol.py +36 -0
- phronesis/testing/__init__.py +19 -0
- phronesis/testing/providers.py +159 -0
- phronesis/tools/__init__.py +76 -0
- phronesis/tools/cache.py +141 -0
- phronesis/tools/decorator.py +184 -0
- phronesis/tools/discover.py +48 -0
- phronesis/tools/effects.py +46 -0
- phronesis/tools/errors.py +262 -0
- phronesis/tools/injection.py +82 -0
- phronesis/tools/lifecycle.py +47 -0
- phronesis/tools/markers.py +39 -0
- phronesis/tools/providers/__init__.py +47 -0
- phronesis/tools/providers/anthropic.py +52 -0
- phronesis/tools/providers/base.py +52 -0
- phronesis/tools/providers/openai.py +56 -0
- phronesis/tools/registry.py +148 -0
- phronesis/tools/retry.py +65 -0
- phronesis/tools/schema.py +216 -0
- phronesis/tools/single_model.py +92 -0
- phronesis/tools/spec.py +106 -0
- phronesis/tools/tool.py +317 -0
- phronesis/tools/tool_id.py +44 -0
- phronesis/tools/validation.py +160 -0
- phronesis/tools/version.py +108 -0
- phronesis_framework-0.1.0.dist-info/METADATA +79 -0
- phronesis_framework-0.1.0.dist-info/RECORD +199 -0
- phronesis_framework-0.1.0.dist-info/WHEEL +4 -0
phronesis/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Phronesis: opinionated framework for building AI agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging as _logging
|
|
6
|
+
|
|
7
|
+
from ._internal.logging import (
|
|
8
|
+
configure_logging,
|
|
9
|
+
get_logger,
|
|
10
|
+
get_logger_with_context,
|
|
11
|
+
)
|
|
12
|
+
from ._internal.logging.constants import PHRONESIS_LOGGER_PREFIX as _ROOT
|
|
13
|
+
from .context.context import Context
|
|
14
|
+
from .tools.decorator import tool
|
|
15
|
+
from .tools.discover import discover
|
|
16
|
+
from .tools.effects import ToolEffect
|
|
17
|
+
from .tools.errors import ToolError
|
|
18
|
+
from .tools.registry import tool_scope
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
|
|
22
|
+
# Defensive: prevent stdlib "no handlers" warnings when the consumer has not
|
|
23
|
+
# configured logging. Users opt in by calling `configure_logging()` or by
|
|
24
|
+
# attaching their own handler to the `phronesis` logger.
|
|
25
|
+
_logging.getLogger(_ROOT).addHandler(_logging.NullHandler())
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"Context",
|
|
29
|
+
"ToolEffect",
|
|
30
|
+
"ToolError",
|
|
31
|
+
"__version__",
|
|
32
|
+
"configure_logging",
|
|
33
|
+
"discover",
|
|
34
|
+
"get_logger",
|
|
35
|
+
"get_logger_with_context",
|
|
36
|
+
"tool",
|
|
37
|
+
"tool_scope",
|
|
38
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Internal utilities. Not part of the public Phronesis API."""
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Concurrency utilities: thread offloading and concurrent task execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .exceptions import ConcurrencyError, PartialFailureError
|
|
6
|
+
from .executor import run_sync
|
|
7
|
+
from .gather import gather_all
|
|
8
|
+
from .policies import BestEffortPolicy, FailFastPolicy, GatherPolicy
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"BestEffortPolicy",
|
|
12
|
+
"ConcurrencyError",
|
|
13
|
+
"FailFastPolicy",
|
|
14
|
+
"GatherPolicy",
|
|
15
|
+
"PartialFailureError",
|
|
16
|
+
"gather_all",
|
|
17
|
+
"run_sync",
|
|
18
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Concurrency-specific exceptions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ConcurrencyError(Exception):
|
|
9
|
+
"""Base class for concurrency-related errors raised by the framework."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PartialFailureError(ConcurrencyError):
|
|
13
|
+
"""Some tasks in a best-effort gather failed.
|
|
14
|
+
|
|
15
|
+
Successful values and exceptions are kept in the original task order:
|
|
16
|
+
if task ``i`` succeeded, ``results[i]`` is its value and
|
|
17
|
+
``exceptions[i]`` is ``None``; if it failed, ``results[i]`` is ``None``
|
|
18
|
+
and ``exceptions[i]`` is the captured exception.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
message: str,
|
|
24
|
+
*,
|
|
25
|
+
results: list[Any],
|
|
26
|
+
exceptions: list[BaseException | None],
|
|
27
|
+
) -> None:
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
|
|
30
|
+
self.results = results
|
|
31
|
+
self.exceptions = exceptions
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def failed_count(self) -> int:
|
|
35
|
+
return sum(1 for exc in self.exceptions if exc is not None)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def successful_count(self) -> int:
|
|
39
|
+
return len(self.exceptions) - self.failed_count
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Run synchronous callables from async code via a worker thread."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Callable
|
|
8
|
+
from typing import ParamSpec, TypeVar
|
|
9
|
+
|
|
10
|
+
from ..logging import get_logger
|
|
11
|
+
|
|
12
|
+
P = ParamSpec("P")
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
_LOGGER_NAME = "phronesis.concurrency"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def run_sync(
|
|
19
|
+
fn: Callable[P, T],
|
|
20
|
+
*args: P.args,
|
|
21
|
+
**kwargs: P.kwargs,
|
|
22
|
+
) -> T:
|
|
23
|
+
"""Run a synchronous callable in a worker thread.
|
|
24
|
+
|
|
25
|
+
Wraps :func:`asyncio.to_thread` with structured logging. The
|
|
26
|
+
callable runs in the default executor.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
fn: The synchronous callable to invoke off the event loop.
|
|
30
|
+
*args: Positional arguments forwarded to ``fn``.
|
|
31
|
+
**kwargs: Keyword arguments forwarded to ``fn``.
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Whatever ``fn`` returns.
|
|
35
|
+
|
|
36
|
+
Raises:
|
|
37
|
+
Exception: Any exception raised by ``fn`` propagates
|
|
38
|
+
unchanged after a warning log entry.
|
|
39
|
+
"""
|
|
40
|
+
log = get_logger(_LOGGER_NAME)
|
|
41
|
+
label = getattr(fn, "__qualname__", repr(fn))
|
|
42
|
+
|
|
43
|
+
log.debug("run_sync start", extra={"callable": label})
|
|
44
|
+
|
|
45
|
+
started = time.perf_counter()
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
result = await asyncio.to_thread(fn, *args, **kwargs)
|
|
49
|
+
|
|
50
|
+
except Exception as exc:
|
|
51
|
+
duration_ms = (time.perf_counter() - started) * 1000
|
|
52
|
+
|
|
53
|
+
log.warning(
|
|
54
|
+
"run_sync failed",
|
|
55
|
+
extra={
|
|
56
|
+
"callable": label,
|
|
57
|
+
"duration_ms": duration_ms,
|
|
58
|
+
"error": str(exc),
|
|
59
|
+
},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
raise
|
|
63
|
+
|
|
64
|
+
duration_ms = (time.perf_counter() - started) * 1000
|
|
65
|
+
|
|
66
|
+
log.debug(
|
|
67
|
+
"run_sync done",
|
|
68
|
+
extra={"callable": label, "duration_ms": duration_ms},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
return result
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Concurrent execution of awaitables with configurable error policies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
from collections.abc import Awaitable
|
|
8
|
+
from typing import TypeVar, cast
|
|
9
|
+
|
|
10
|
+
from ..logging import get_logger
|
|
11
|
+
from .policies import FailFastPolicy, GatherPolicy
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
_LOGGER_NAME = "phronesis.concurrency"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
async def gather_all(
|
|
19
|
+
*awaitables: Awaitable[T],
|
|
20
|
+
policy: GatherPolicy | None = None,
|
|
21
|
+
) -> list[T]:
|
|
22
|
+
"""Run ``awaitables`` concurrently and reconcile via ``policy``.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
*awaitables: Awaitables scheduled on the running event loop.
|
|
26
|
+
policy: Error-handling strategy. Defaults to
|
|
27
|
+
:class:`FailFastPolicy`. Use :class:`BestEffortPolicy` to
|
|
28
|
+
wait for every task and surface partial failures.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
Results in input order. For :class:`BestEffortPolicy`, failed
|
|
32
|
+
slots are replaced with ``None`` before the policy raises.
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
Exception: The first exception raised by any awaitable under
|
|
36
|
+
:class:`FailFastPolicy`.
|
|
37
|
+
PartialFailureError: When :class:`BestEffortPolicy` finds at
|
|
38
|
+
least one failed awaitable.
|
|
39
|
+
"""
|
|
40
|
+
effective_policy = policy or FailFastPolicy()
|
|
41
|
+
log = get_logger(_LOGGER_NAME)
|
|
42
|
+
|
|
43
|
+
log.debug(
|
|
44
|
+
"gather_all start",
|
|
45
|
+
extra={
|
|
46
|
+
"count": len(awaitables),
|
|
47
|
+
"policy": type(effective_policy).__name__,
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
started = time.perf_counter()
|
|
52
|
+
|
|
53
|
+
raw = await asyncio.gather(
|
|
54
|
+
*awaitables,
|
|
55
|
+
return_exceptions=effective_policy.return_exceptions,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
duration_ms = (time.perf_counter() - started) * 1000
|
|
59
|
+
|
|
60
|
+
log.debug(
|
|
61
|
+
"gather_all done",
|
|
62
|
+
extra={"count": len(awaitables), "duration_ms": duration_ms},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return cast("list[T]", effective_policy.reconcile(raw))
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Error-handling policies for concurrent task execution."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .exceptions import PartialFailureError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GatherPolicy(ABC):
|
|
13
|
+
"""Strategy for handling exceptions returned by :func:`asyncio.gather`."""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def return_exceptions(self) -> bool:
|
|
18
|
+
"""Value passed to ``asyncio.gather(return_exceptions=...)``."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def reconcile(self, results: Sequence[Any]) -> list[Any]:
|
|
22
|
+
"""Inspect raw gather output and either return successes or raise."""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FailFastPolicy(GatherPolicy):
|
|
26
|
+
"""Cancel pending tasks and propagate the first exception immediately."""
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def return_exceptions(self) -> bool:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
def reconcile(self, results: Sequence[Any]) -> list[Any]:
|
|
33
|
+
# With return_exceptions=False, asyncio.gather already raised on
|
|
34
|
+
# the first failure, so any sequence we receive is all-successful.
|
|
35
|
+
return list(results)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BestEffortPolicy(GatherPolicy):
|
|
39
|
+
"""Wait for every task; raise :class:`PartialFailureError` if any failed."""
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def return_exceptions(self) -> bool:
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
def reconcile(self, results: Sequence[Any]) -> list[Any]:
|
|
46
|
+
successes: list[Any] = []
|
|
47
|
+
failures: list[BaseException | None] = []
|
|
48
|
+
any_failed = False
|
|
49
|
+
|
|
50
|
+
for item in results:
|
|
51
|
+
if isinstance(item, BaseException):
|
|
52
|
+
successes.append(None)
|
|
53
|
+
failures.append(item)
|
|
54
|
+
any_failed = True
|
|
55
|
+
|
|
56
|
+
else:
|
|
57
|
+
successes.append(item)
|
|
58
|
+
failures.append(None)
|
|
59
|
+
|
|
60
|
+
if any_failed:
|
|
61
|
+
failed = sum(1 for exc in failures if exc is not None)
|
|
62
|
+
total = len(results)
|
|
63
|
+
|
|
64
|
+
raise PartialFailureError(
|
|
65
|
+
f"{failed} of {total} tasks failed",
|
|
66
|
+
results=successes,
|
|
67
|
+
exceptions=failures,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return successes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Async HTTP client used by every provider via composition."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .client import HttpClient, HttpStreamResponse, configure_http_client
|
|
6
|
+
from .exceptions import (
|
|
7
|
+
HttpClientError,
|
|
8
|
+
HttpConnectionError,
|
|
9
|
+
HttpError,
|
|
10
|
+
HttpResponseError,
|
|
11
|
+
HttpServerError,
|
|
12
|
+
HttpTimeoutError,
|
|
13
|
+
HttpTransportError,
|
|
14
|
+
)
|
|
15
|
+
from .headers import build_default_headers, redact_sensitive_headers
|
|
16
|
+
from .models import HttpRequest, HttpResponse
|
|
17
|
+
from .timeouts import HttpTimeouts
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"HttpClient",
|
|
21
|
+
"HttpClientError",
|
|
22
|
+
"HttpConnectionError",
|
|
23
|
+
"HttpError",
|
|
24
|
+
"HttpRequest",
|
|
25
|
+
"HttpResponse",
|
|
26
|
+
"HttpResponseError",
|
|
27
|
+
"HttpServerError",
|
|
28
|
+
"HttpStreamResponse",
|
|
29
|
+
"HttpTimeoutError",
|
|
30
|
+
"HttpTimeouts",
|
|
31
|
+
"HttpTransportError",
|
|
32
|
+
"build_default_headers",
|
|
33
|
+
"configure_http_client",
|
|
34
|
+
"redact_sensitive_headers",
|
|
35
|
+
]
|