agentle 0.9.4__py3-none-any.whl → 0.9.28__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.
- agentle/agents/agent.py +175 -10
- agentle/agents/agent_run_output.py +8 -1
- agentle/agents/apis/__init__.py +79 -6
- agentle/agents/apis/api.py +342 -73
- agentle/agents/apis/api_key_authentication.py +43 -0
- agentle/agents/apis/api_key_location.py +11 -0
- agentle/agents/apis/api_metrics.py +16 -0
- agentle/agents/apis/auth_type.py +17 -0
- agentle/agents/apis/authentication.py +32 -0
- agentle/agents/apis/authentication_base.py +42 -0
- agentle/agents/apis/authentication_config.py +117 -0
- agentle/agents/apis/basic_authentication.py +34 -0
- agentle/agents/apis/bearer_authentication.py +52 -0
- agentle/agents/apis/cache_strategy.py +12 -0
- agentle/agents/apis/circuit_breaker.py +69 -0
- agentle/agents/apis/circuit_breaker_error.py +7 -0
- agentle/agents/apis/circuit_breaker_state.py +11 -0
- agentle/agents/apis/endpoint.py +413 -254
- agentle/agents/apis/file_upload.py +23 -0
- agentle/agents/apis/hmac_authentication.py +56 -0
- agentle/agents/apis/no_authentication.py +27 -0
- agentle/agents/apis/oauth2_authentication.py +111 -0
- agentle/agents/apis/oauth2_grant_type.py +12 -0
- agentle/agents/apis/object_schema.py +86 -1
- agentle/agents/apis/params/__init__.py +10 -1
- agentle/agents/apis/params/boolean_param.py +44 -0
- agentle/agents/apis/params/number_param.py +56 -0
- agentle/agents/apis/rate_limit_error.py +7 -0
- agentle/agents/apis/rate_limiter.py +57 -0
- agentle/agents/apis/request_config.py +126 -4
- agentle/agents/apis/request_hook.py +16 -0
- agentle/agents/apis/response_cache.py +49 -0
- agentle/agents/apis/retry_strategy.py +12 -0
- agentle/agents/whatsapp/human_delay_calculator.py +462 -0
- agentle/agents/whatsapp/models/audio_message.py +6 -4
- agentle/agents/whatsapp/models/key.py +2 -2
- agentle/agents/whatsapp/models/whatsapp_bot_config.py +375 -21
- agentle/agents/whatsapp/models/whatsapp_response_base.py +31 -0
- agentle/agents/whatsapp/models/whatsapp_webhook_payload.py +5 -1
- agentle/agents/whatsapp/providers/base/whatsapp_provider.py +51 -0
- agentle/agents/whatsapp/providers/evolution/evolution_api_provider.py +237 -10
- agentle/agents/whatsapp/providers/meta/meta_whatsapp_provider.py +126 -0
- agentle/agents/whatsapp/v2/batch_processor_manager.py +4 -0
- agentle/agents/whatsapp/v2/bot_config.py +188 -0
- agentle/agents/whatsapp/v2/message_limit.py +9 -0
- agentle/agents/whatsapp/v2/payload.py +0 -0
- agentle/agents/whatsapp/v2/whatsapp_bot.py +13 -0
- agentle/agents/whatsapp/v2/whatsapp_cloud_api_provider.py +0 -0
- agentle/agents/whatsapp/v2/whatsapp_provider.py +0 -0
- agentle/agents/whatsapp/whatsapp_bot.py +827 -45
- agentle/generations/providers/google/adapters/generate_generate_content_response_to_generation_adapter.py +13 -10
- agentle/generations/providers/google/google_generation_provider.py +35 -5
- agentle/generations/providers/openrouter/_adapters/openrouter_message_to_generated_assistant_message_adapter.py +35 -1
- agentle/mcp/servers/stdio_mcp_server.py +23 -4
- agentle/parsing/parsers/docx.py +8 -0
- agentle/parsing/parsers/file_parser.py +4 -0
- agentle/parsing/parsers/pdf.py +7 -1
- agentle/storage/__init__.py +11 -0
- agentle/storage/file_storage_manager.py +44 -0
- agentle/storage/local_file_storage_manager.py +122 -0
- agentle/storage/s3_file_storage_manager.py +124 -0
- agentle/tts/audio_format.py +6 -0
- agentle/tts/elevenlabs_tts_provider.py +108 -0
- agentle/tts/output_format_type.py +26 -0
- agentle/tts/speech_config.py +14 -0
- agentle/tts/speech_result.py +15 -0
- agentle/tts/tts_provider.py +16 -0
- agentle/tts/voice_settings.py +30 -0
- agentle/utils/parse_streaming_json.py +39 -13
- agentle/voice_cloning/__init__.py +0 -0
- agentle/voice_cloning/voice_cloner.py +0 -0
- agentle/web/extractor.py +282 -148
- {agentle-0.9.4.dist-info → agentle-0.9.28.dist-info}/METADATA +1 -1
- {agentle-0.9.4.dist-info → agentle-0.9.28.dist-info}/RECORD +78 -39
- agentle/tts/real_time/definitions/audio_data.py +0 -20
- agentle/tts/real_time/definitions/speech_config.py +0 -27
- agentle/tts/real_time/definitions/speech_result.py +0 -14
- agentle/tts/real_time/definitions/tts_stream_chunk.py +0 -15
- agentle/tts/real_time/definitions/voice_gender.py +0 -9
- agentle/tts/real_time/definitions/voice_info.py +0 -18
- agentle/tts/real_time/real_time_speech_to_text_provider.py +0 -66
- /agentle/{tts/real_time → agents/whatsapp/v2}/__init__.py +0 -0
- /agentle/{tts/real_time/definitions/__init__.py → agents/whatsapp/v2/in_memory_batch_processor_manager.py} +0 -0
- {agentle-0.9.4.dist-info → agentle-0.9.28.dist-info}/WHEEL +0 -0
- {agentle-0.9.4.dist-info → agentle-0.9.28.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""API metrics tracking."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from rsb.models.base_model import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class APIMetrics(BaseModel):
|
|
9
|
+
"""Metrics for API usage."""
|
|
10
|
+
|
|
11
|
+
total_requests: int = 0
|
|
12
|
+
successful_requests: int = 0
|
|
13
|
+
failed_requests: int = 0
|
|
14
|
+
total_latency_ms: float = 0.0
|
|
15
|
+
average_latency_ms: float = 0.0
|
|
16
|
+
requests_by_endpoint: dict[str, int] = {}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Authentication types."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class AuthType(StrEnum):
|
|
7
|
+
"""Types of authentication supported."""
|
|
8
|
+
|
|
9
|
+
NONE = "none"
|
|
10
|
+
BEARER = "bearer"
|
|
11
|
+
BASIC = "basic"
|
|
12
|
+
API_KEY = "api_key"
|
|
13
|
+
OAUTH2 = "oauth2"
|
|
14
|
+
JWT = "jwt"
|
|
15
|
+
CUSTOM = "custom"
|
|
16
|
+
AWS_SIGNATURE = "aws_signature"
|
|
17
|
+
HMAC = "hmac"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Authentication support for API endpoints.
|
|
3
|
+
|
|
4
|
+
Provides various authentication methods including Bearer, Basic, OAuth2, API Key, and custom schemes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Import and re-export for backward compatibility
|
|
8
|
+
from agentle.agents.apis.api_key_location import ApiKeyLocation
|
|
9
|
+
from agentle.agents.apis.api_key_authentication import ApiKeyAuthentication
|
|
10
|
+
from agentle.agents.apis.auth_type import AuthType
|
|
11
|
+
from agentle.agents.apis.authentication_base import AuthenticationBase
|
|
12
|
+
from agentle.agents.apis.authentication_config import AuthenticationConfig
|
|
13
|
+
from agentle.agents.apis.basic_authentication import BasicAuthentication
|
|
14
|
+
from agentle.agents.apis.bearer_authentication import BearerAuthentication
|
|
15
|
+
from agentle.agents.apis.hmac_authentication import HMACAuthentication
|
|
16
|
+
from agentle.agents.apis.no_authentication import NoAuthentication
|
|
17
|
+
from agentle.agents.apis.oauth2_authentication import OAuth2Authentication
|
|
18
|
+
from agentle.agents.apis.oauth2_grant_type import OAuth2GrantType
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AuthType",
|
|
22
|
+
"ApiKeyLocation",
|
|
23
|
+
"OAuth2GrantType",
|
|
24
|
+
"AuthenticationBase",
|
|
25
|
+
"NoAuthentication",
|
|
26
|
+
"BearerAuthentication",
|
|
27
|
+
"BasicAuthentication",
|
|
28
|
+
"ApiKeyAuthentication",
|
|
29
|
+
"OAuth2Authentication",
|
|
30
|
+
"HMACAuthentication",
|
|
31
|
+
"AuthenticationConfig",
|
|
32
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Base authentication handler."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from collections.abc import MutableMapping
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AuthenticationBase(ABC):
|
|
13
|
+
"""Base class for authentication handlers."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
async def apply_auth(
|
|
17
|
+
self,
|
|
18
|
+
session: aiohttp.ClientSession,
|
|
19
|
+
url: str,
|
|
20
|
+
headers: MutableMapping[str, str],
|
|
21
|
+
params: MutableMapping[str, Any],
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Apply authentication to the request.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
session: aiohttp session
|
|
28
|
+
url: Request URL
|
|
29
|
+
headers: Request headers (will be modified)
|
|
30
|
+
params: Request parameters (will be modified)
|
|
31
|
+
"""
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
async def refresh_if_needed(self) -> bool:
|
|
36
|
+
"""
|
|
37
|
+
Refresh authentication if needed (e.g., expired tokens).
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
True if refresh was performed, False otherwise
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Authentication configuration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import MutableMapping
|
|
6
|
+
|
|
7
|
+
from rsb.models.base_model import BaseModel
|
|
8
|
+
from rsb.models.field import Field
|
|
9
|
+
|
|
10
|
+
from agentle.agents.apis.api_key_authentication import ApiKeyAuthentication
|
|
11
|
+
from agentle.agents.apis.api_key_location import ApiKeyLocation
|
|
12
|
+
from agentle.agents.apis.auth_type import AuthType
|
|
13
|
+
from agentle.agents.apis.authentication_base import AuthenticationBase
|
|
14
|
+
from agentle.agents.apis.basic_authentication import BasicAuthentication
|
|
15
|
+
from agentle.agents.apis.bearer_authentication import BearerAuthentication
|
|
16
|
+
from agentle.agents.apis.hmac_authentication import HMACAuthentication
|
|
17
|
+
from agentle.agents.apis.no_authentication import NoAuthentication
|
|
18
|
+
from agentle.agents.apis.oauth2_authentication import OAuth2Authentication
|
|
19
|
+
from agentle.agents.apis.oauth2_grant_type import OAuth2GrantType
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AuthenticationConfig(BaseModel):
|
|
23
|
+
"""Configuration for API authentication."""
|
|
24
|
+
|
|
25
|
+
type: AuthType = Field(default=AuthType.NONE)
|
|
26
|
+
|
|
27
|
+
# Bearer token
|
|
28
|
+
bearer_token: str | None = Field(default=None)
|
|
29
|
+
|
|
30
|
+
# Basic auth
|
|
31
|
+
username: str | None = Field(default=None)
|
|
32
|
+
password: str | None = Field(default=None)
|
|
33
|
+
|
|
34
|
+
# API Key
|
|
35
|
+
api_key: str | None = Field(default=None)
|
|
36
|
+
api_key_location: ApiKeyLocation = Field(default=ApiKeyLocation.HEADER)
|
|
37
|
+
api_key_name: str = Field(default="X-API-Key")
|
|
38
|
+
|
|
39
|
+
# OAuth2
|
|
40
|
+
oauth2_token_url: str | None = Field(default=None)
|
|
41
|
+
oauth2_client_id: str | None = Field(default=None)
|
|
42
|
+
oauth2_client_secret: str | None = Field(default=None)
|
|
43
|
+
oauth2_grant_type: OAuth2GrantType = Field(
|
|
44
|
+
default=OAuth2GrantType.CLIENT_CREDENTIALS
|
|
45
|
+
)
|
|
46
|
+
oauth2_scope: str | None = Field(
|
|
47
|
+
default=None,
|
|
48
|
+
description="Single scope string (deprecated, use oauth2_scopes for multiple)",
|
|
49
|
+
)
|
|
50
|
+
oauth2_scopes: list[str] | None = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
description="List of OAuth2 scopes to request (e.g., ['read', 'write', 'admin'])",
|
|
53
|
+
)
|
|
54
|
+
oauth2_refresh_token: str | None = Field(default=None)
|
|
55
|
+
|
|
56
|
+
# HMAC
|
|
57
|
+
hmac_secret_key: str | None = Field(default=None)
|
|
58
|
+
hmac_algorithm: str = Field(default="sha256")
|
|
59
|
+
hmac_header_name: str = Field(default="X-Signature")
|
|
60
|
+
|
|
61
|
+
# Custom
|
|
62
|
+
custom_headers: MutableMapping[str, str] = Field(default_factory=dict)
|
|
63
|
+
|
|
64
|
+
def create_handler(self) -> AuthenticationBase:
|
|
65
|
+
"""Create authentication handler from config."""
|
|
66
|
+
if self.type == AuthType.NONE:
|
|
67
|
+
return NoAuthentication()
|
|
68
|
+
|
|
69
|
+
elif self.type == AuthType.BEARER:
|
|
70
|
+
if not self.bearer_token:
|
|
71
|
+
raise ValueError("Bearer token required for Bearer authentication")
|
|
72
|
+
return BearerAuthentication(self.bearer_token)
|
|
73
|
+
|
|
74
|
+
elif self.type == AuthType.BASIC:
|
|
75
|
+
if not self.username or not self.password:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"Username and password required for Basic authentication"
|
|
78
|
+
)
|
|
79
|
+
return BasicAuthentication(self.username, self.password)
|
|
80
|
+
|
|
81
|
+
elif self.type == AuthType.API_KEY:
|
|
82
|
+
if not self.api_key:
|
|
83
|
+
raise ValueError("API key required for API Key authentication")
|
|
84
|
+
return ApiKeyAuthentication(
|
|
85
|
+
self.api_key, self.api_key_location, self.api_key_name
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
elif self.type == AuthType.OAUTH2:
|
|
89
|
+
if not all(
|
|
90
|
+
[
|
|
91
|
+
self.oauth2_token_url,
|
|
92
|
+
self.oauth2_client_id,
|
|
93
|
+
self.oauth2_client_secret,
|
|
94
|
+
]
|
|
95
|
+
):
|
|
96
|
+
raise ValueError(
|
|
97
|
+
"OAuth2 credentials required for OAuth2 authentication"
|
|
98
|
+
)
|
|
99
|
+
return OAuth2Authentication(
|
|
100
|
+
self.oauth2_token_url, # type: ignore
|
|
101
|
+
self.oauth2_client_id, # type: ignore
|
|
102
|
+
self.oauth2_client_secret, # type: ignore
|
|
103
|
+
self.oauth2_grant_type,
|
|
104
|
+
self.oauth2_scope,
|
|
105
|
+
self.oauth2_refresh_token,
|
|
106
|
+
self.oauth2_scopes,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
elif self.type == AuthType.HMAC:
|
|
110
|
+
if not self.hmac_secret_key:
|
|
111
|
+
raise ValueError("HMAC secret key required for HMAC authentication")
|
|
112
|
+
return HMACAuthentication(
|
|
113
|
+
self.hmac_secret_key, self.hmac_algorithm, self.hmac_header_name
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
else:
|
|
117
|
+
return NoAuthentication()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Basic authentication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
from collections.abc import MutableMapping
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
from agentle.agents.apis.authentication_base import AuthenticationBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BasicAuthentication(AuthenticationBase):
|
|
14
|
+
"""HTTP Basic authentication."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, username: str, password: str):
|
|
17
|
+
self.username = username
|
|
18
|
+
self.password = password
|
|
19
|
+
|
|
20
|
+
async def apply_auth(
|
|
21
|
+
self,
|
|
22
|
+
session: aiohttp.ClientSession,
|
|
23
|
+
url: str,
|
|
24
|
+
headers: MutableMapping[str, str],
|
|
25
|
+
params: MutableMapping[str, Any],
|
|
26
|
+
) -> None:
|
|
27
|
+
"""Add Basic auth to Authorization header."""
|
|
28
|
+
credentials = f"{self.username}:{self.password}"
|
|
29
|
+
encoded = base64.b64encode(credentials.encode()).decode()
|
|
30
|
+
headers["Authorization"] = f"Basic {encoded}"
|
|
31
|
+
|
|
32
|
+
async def refresh_if_needed(self) -> bool:
|
|
33
|
+
"""No refresh needed for Basic auth."""
|
|
34
|
+
return False
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Bearer token authentication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import MutableMapping
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
from agentle.agents.apis.authentication_base import AuthenticationBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BearerAuthentication(AuthenticationBase):
|
|
14
|
+
"""Bearer token authentication."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, token: str, auto_refresh: bool = False):
|
|
17
|
+
self.token = token
|
|
18
|
+
self.auto_refresh = auto_refresh
|
|
19
|
+
self._token_expiry: datetime | None = None
|
|
20
|
+
|
|
21
|
+
async def apply_auth(
|
|
22
|
+
self,
|
|
23
|
+
session: aiohttp.ClientSession,
|
|
24
|
+
url: str,
|
|
25
|
+
headers: MutableMapping[str, str],
|
|
26
|
+
params: MutableMapping[str, Any],
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Add Bearer token to Authorization header."""
|
|
29
|
+
headers["Authorization"] = f"Bearer {self.token}"
|
|
30
|
+
|
|
31
|
+
async def refresh_if_needed(self) -> bool:
|
|
32
|
+
"""Check if token needs refresh."""
|
|
33
|
+
if not self.auto_refresh:
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
if self._token_expiry and datetime.now() >= self._token_expiry:
|
|
37
|
+
# Token expired - subclass should implement refresh logic
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
def set_token(self, token: str, expires_in: int | None = None) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Update the token.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
token: New token
|
|
48
|
+
expires_in: Token expiry in seconds
|
|
49
|
+
"""
|
|
50
|
+
self.token = token
|
|
51
|
+
if expires_in:
|
|
52
|
+
self._token_expiry = datetime.now() + timedelta(seconds=expires_in)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Circuit breaker for resilient API calls.
|
|
2
|
+
|
|
3
|
+
This module adapts the resilience module's circuit breaker implementations
|
|
4
|
+
for use in the APIs module, maintaining backward compatibility.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from agentle.agents.apis.circuit_breaker_error import CircuitBreakerError
|
|
13
|
+
from agentle.agents.apis.request_config import RequestConfig
|
|
14
|
+
from agentle.resilience.circuit_breaker.in_memory_circuit_breaker import (
|
|
15
|
+
InMemoryCircuitBreaker,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CircuitBreaker:
|
|
20
|
+
"""
|
|
21
|
+
Circuit breaker implementation for resilient API calls.
|
|
22
|
+
|
|
23
|
+
This wraps the resilience module's InMemoryCircuitBreaker to provide
|
|
24
|
+
a simpler call-based API for endpoint usage.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: RequestConfig):
|
|
28
|
+
self.config = config
|
|
29
|
+
self._circuit_id = "default" # Single circuit per endpoint
|
|
30
|
+
# Initialize the underlying circuit breaker from resilience module
|
|
31
|
+
self._impl = InMemoryCircuitBreaker(
|
|
32
|
+
failure_threshold=config.circuit_breaker_failure_threshold,
|
|
33
|
+
recovery_timeout=config.circuit_breaker_recovery_timeout,
|
|
34
|
+
half_open_success_threshold=config.circuit_breaker_success_threshold,
|
|
35
|
+
enable_metrics=config.enable_metrics,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
async def call(self, func: Callable[[], Any]) -> Any:
|
|
39
|
+
"""
|
|
40
|
+
Execute function with circuit breaker protection.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
func: Async function to execute
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Result of func call
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
CircuitBreakerError: If circuit is open
|
|
50
|
+
"""
|
|
51
|
+
# Check if circuit is open
|
|
52
|
+
if await self._impl.is_open(self._circuit_id):
|
|
53
|
+
# Get circuit state for more details
|
|
54
|
+
state = await self._impl.get_circuit_state(self._circuit_id)
|
|
55
|
+
next_retry_seconds = state.get("next_recovery_attempt_in_seconds", 0)
|
|
56
|
+
|
|
57
|
+
if next_retry_seconds > 0:
|
|
58
|
+
raise CircuitBreakerError(
|
|
59
|
+
f"Circuit breaker is OPEN. Retry after {next_retry_seconds:.1f}s"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# Execute the function
|
|
63
|
+
try:
|
|
64
|
+
result = await func()
|
|
65
|
+
await self._impl.record_success(self._circuit_id)
|
|
66
|
+
return result
|
|
67
|
+
except Exception:
|
|
68
|
+
await self._impl.record_failure(self._circuit_id)
|
|
69
|
+
raise
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Circuit breaker states."""
|
|
2
|
+
|
|
3
|
+
from enum import StrEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CircuitBreakerState(StrEnum):
|
|
7
|
+
"""Circuit breaker states."""
|
|
8
|
+
|
|
9
|
+
CLOSED = "closed" # Normal operation
|
|
10
|
+
OPEN = "open" # Failing, reject requests
|
|
11
|
+
HALF_OPEN = "half_open" # Testing if service recovered
|