axonflow 0.4.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.
axonflow/policies.py ADDED
@@ -0,0 +1,289 @@
1
+ """AxonFlow SDK Policy Types and Methods.
2
+
3
+ Policy CRUD types and methods for the Unified Policy Architecture v2.0.0.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from datetime import datetime
9
+ from enum import Enum
10
+ from typing import Any
11
+
12
+ from pydantic import BaseModel, ConfigDict, Field
13
+
14
+ # ============================================================================
15
+ # Policy Categories and Tiers
16
+ # ============================================================================
17
+
18
+
19
+ class PolicyCategory(str, Enum):
20
+ """Policy categories for organization and filtering."""
21
+
22
+ SECURITY_SQLI = "security-sqli"
23
+ SECURITY_ADMIN = "security-admin"
24
+ PII_GLOBAL = "pii-global"
25
+ PII_US = "pii-us"
26
+ PII_EU = "pii-eu"
27
+ PII_INDIA = "pii-india"
28
+ DYNAMIC_RISK = "dynamic-risk"
29
+ DYNAMIC_COMPLIANCE = "dynamic-compliance"
30
+ DYNAMIC_SECURITY = "dynamic-security"
31
+ DYNAMIC_COST = "dynamic-cost"
32
+ DYNAMIC_ACCESS = "dynamic-access"
33
+ CUSTOM = "custom"
34
+
35
+
36
+ class PolicyTier(str, Enum):
37
+ """Policy tiers determine where policies apply."""
38
+
39
+ SYSTEM = "system"
40
+ ORGANIZATION = "organization"
41
+ TENANT = "tenant"
42
+
43
+
44
+ class OverrideAction(str, Enum):
45
+ """Override action for policy overrides."""
46
+
47
+ BLOCK = "block"
48
+ WARN = "warn"
49
+ LOG = "log"
50
+ REDACT = "redact"
51
+
52
+
53
+ class PolicyAction(str, Enum):
54
+ """Action to take when policy matches."""
55
+
56
+ BLOCK = "block"
57
+ WARN = "warn"
58
+ LOG = "log"
59
+ REDACT = "redact"
60
+ ALLOW = "allow"
61
+
62
+
63
+ class PolicySeverity(str, Enum):
64
+ """Policy severity levels."""
65
+
66
+ CRITICAL = "critical"
67
+ HIGH = "high"
68
+ MEDIUM = "medium"
69
+ LOW = "low"
70
+
71
+
72
+ # ============================================================================
73
+ # Static Policy Types
74
+ # ============================================================================
75
+
76
+
77
+ class PolicyOverride(BaseModel):
78
+ """Policy override configuration."""
79
+
80
+ model_config = ConfigDict(populate_by_name=True)
81
+
82
+ policy_id: str = Field(..., alias="policyId")
83
+ action: OverrideAction
84
+ reason: str
85
+ created_by: str | None = Field(default=None, alias="createdBy")
86
+ created_at: datetime = Field(..., alias="createdAt")
87
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
88
+ active: bool = True
89
+
90
+
91
+ class StaticPolicy(BaseModel):
92
+ """Static policy definition."""
93
+
94
+ model_config = ConfigDict(populate_by_name=True)
95
+
96
+ id: str
97
+ name: str
98
+ description: str | None = None
99
+ category: PolicyCategory
100
+ tier: PolicyTier
101
+ pattern: str
102
+ severity: PolicySeverity = PolicySeverity.MEDIUM
103
+ enabled: bool = True
104
+ action: PolicyAction = PolicyAction.BLOCK
105
+ organization_id: str | None = Field(default=None, alias="organizationId")
106
+ tenant_id: str | None = Field(default=None, alias="tenantId")
107
+ created_at: datetime = Field(..., alias="createdAt")
108
+ updated_at: datetime = Field(..., alias="updatedAt")
109
+ version: int | None = None
110
+ has_override: bool | None = Field(default=None, alias="hasOverride")
111
+ override: PolicyOverride | None = None
112
+
113
+
114
+ class ListStaticPoliciesOptions(BaseModel):
115
+ """Options for listing static policies."""
116
+
117
+ category: PolicyCategory | None = None
118
+ tier: PolicyTier | None = None
119
+ enabled: bool | None = None
120
+ limit: int | None = Field(default=None, ge=1)
121
+ offset: int | None = Field(default=None, ge=0)
122
+ sort_by: str | None = None
123
+ sort_order: str | None = None
124
+ search: str | None = None
125
+
126
+
127
+ class CreateStaticPolicyRequest(BaseModel):
128
+ """Request to create a new static policy."""
129
+
130
+ name: str = Field(..., min_length=1)
131
+ description: str | None = None
132
+ category: PolicyCategory
133
+ tier: PolicyTier = PolicyTier.TENANT # Default to tenant tier for custom policies
134
+ pattern: str = Field(..., min_length=1)
135
+ severity: PolicySeverity = PolicySeverity.MEDIUM
136
+ enabled: bool = True
137
+ action: PolicyAction = PolicyAction.BLOCK
138
+
139
+
140
+ class UpdateStaticPolicyRequest(BaseModel):
141
+ """Request to update an existing static policy."""
142
+
143
+ name: str | None = None
144
+ description: str | None = None
145
+ category: PolicyCategory | None = None
146
+ pattern: str | None = None
147
+ severity: PolicySeverity | None = None
148
+ enabled: bool | None = None
149
+ action: PolicyAction | None = None
150
+
151
+
152
+ class CreatePolicyOverrideRequest(BaseModel):
153
+ """Request to create a policy override."""
154
+
155
+ action: OverrideAction
156
+ reason: str = Field(..., min_length=1)
157
+ expires_at: datetime | None = Field(default=None, alias="expiresAt")
158
+
159
+
160
+ # ============================================================================
161
+ # Dynamic Policy Types
162
+ # ============================================================================
163
+
164
+
165
+ class DynamicPolicyCondition(BaseModel):
166
+ """Condition for dynamic policy evaluation."""
167
+
168
+ field: str
169
+ operator: str
170
+ value: Any
171
+
172
+
173
+ class DynamicPolicyConfig(BaseModel):
174
+ """Configuration for a dynamic policy."""
175
+
176
+ type: str
177
+ rules: dict[str, Any] = Field(default_factory=dict)
178
+ conditions: list[DynamicPolicyCondition] | None = None
179
+ action: PolicyAction
180
+ parameters: dict[str, Any] | None = None
181
+
182
+
183
+ class DynamicPolicy(BaseModel):
184
+ """Dynamic policy definition."""
185
+
186
+ model_config = ConfigDict(populate_by_name=True)
187
+
188
+ id: str
189
+ name: str
190
+ description: str | None = None
191
+ category: PolicyCategory
192
+ tier: PolicyTier
193
+ enabled: bool = True
194
+ organization_id: str | None = Field(default=None, alias="organizationId")
195
+ tenant_id: str | None = Field(default=None, alias="tenantId")
196
+ config: DynamicPolicyConfig
197
+ created_at: datetime = Field(..., alias="createdAt")
198
+ updated_at: datetime = Field(..., alias="updatedAt")
199
+ version: int | None = None
200
+
201
+
202
+ class ListDynamicPoliciesOptions(BaseModel):
203
+ """Options for listing dynamic policies."""
204
+
205
+ category: PolicyCategory | None = None
206
+ tier: PolicyTier | None = None
207
+ enabled: bool | None = None
208
+ limit: int | None = Field(default=None, ge=1)
209
+ offset: int | None = Field(default=None, ge=0)
210
+ sort_by: str | None = None
211
+ sort_order: str | None = None
212
+ search: str | None = None
213
+
214
+
215
+ class CreateDynamicPolicyRequest(BaseModel):
216
+ """Request to create a dynamic policy."""
217
+
218
+ name: str = Field(..., min_length=1)
219
+ description: str | None = None
220
+ category: PolicyCategory
221
+ config: DynamicPolicyConfig
222
+ enabled: bool = True
223
+
224
+
225
+ class UpdateDynamicPolicyRequest(BaseModel):
226
+ """Request to update a dynamic policy."""
227
+
228
+ name: str | None = None
229
+ description: str | None = None
230
+ category: PolicyCategory | None = None
231
+ config: DynamicPolicyConfig | None = None
232
+ enabled: bool | None = None
233
+
234
+
235
+ # ============================================================================
236
+ # Pattern Testing Types
237
+ # ============================================================================
238
+
239
+
240
+ class TestPatternMatch(BaseModel):
241
+ """Individual pattern match result."""
242
+
243
+ model_config = ConfigDict(populate_by_name=True)
244
+
245
+ input: str
246
+ matched: bool
247
+ groups: list[str] | None = None
248
+
249
+
250
+ class TestPatternResult(BaseModel):
251
+ """Result of testing a regex pattern."""
252
+
253
+ valid: bool
254
+ error: str | None = None
255
+ pattern: str = ""
256
+ inputs: list[str] = Field(default_factory=list)
257
+ matches: list[TestPatternMatch] = Field(default_factory=list)
258
+
259
+
260
+ # ============================================================================
261
+ # Policy Version Types
262
+ # ============================================================================
263
+
264
+
265
+ class PolicyVersion(BaseModel):
266
+ """Policy version history entry."""
267
+
268
+ model_config = ConfigDict(populate_by_name=True)
269
+
270
+ version: int
271
+ changed_by: str | None = Field(default=None, alias="changedBy")
272
+ changed_at: datetime = Field(..., alias="changedAt")
273
+ change_type: str = Field(..., alias="changeType")
274
+ change_description: str | None = Field(default=None, alias="changeDescription")
275
+ previous_values: dict[str, Any] | None = Field(default=None, alias="previousValues")
276
+ new_values: dict[str, Any] | None = Field(default=None, alias="newValues")
277
+
278
+
279
+ # ============================================================================
280
+ # Effective Policies Types
281
+ # ============================================================================
282
+
283
+
284
+ class EffectivePoliciesOptions(BaseModel):
285
+ """Options for getting effective policies."""
286
+
287
+ category: PolicyCategory | None = None
288
+ include_disabled: bool = False
289
+ include_overridden: bool = False
axonflow/py.typed ADDED
File without changes
axonflow/types.py ADDED
@@ -0,0 +1,214 @@
1
+ """AxonFlow SDK Type Definitions.
2
+
3
+ All types are defined using Pydantic v2 for runtime validation
4
+ and automatic JSON serialization/deserialization.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from enum import Enum
11
+ from typing import Any
12
+
13
+ from pydantic import BaseModel, ConfigDict, Field
14
+
15
+
16
+ class Mode(str, Enum):
17
+ """SDK operation mode."""
18
+
19
+ PRODUCTION = "production"
20
+ SANDBOX = "sandbox"
21
+
22
+
23
+ class RetryConfig(BaseModel):
24
+ """Retry configuration with exponential backoff."""
25
+
26
+ model_config = ConfigDict(frozen=True)
27
+
28
+ enabled: bool = Field(default=True, description="Enable retry logic")
29
+ max_attempts: int = Field(default=3, ge=1, le=10, description="Max retry attempts")
30
+ initial_delay: float = Field(default=1.0, gt=0, description="Initial delay (seconds)")
31
+ max_delay: float = Field(default=30.0, gt=0, description="Max delay (seconds)")
32
+ exponential_base: float = Field(default=2.0, gt=1, description="Backoff multiplier")
33
+
34
+
35
+ class CacheConfig(BaseModel):
36
+ """Cache configuration."""
37
+
38
+ model_config = ConfigDict(frozen=True)
39
+
40
+ enabled: bool = Field(default=True, description="Enable caching")
41
+ ttl: float = Field(default=60.0, gt=0, description="Cache TTL (seconds)")
42
+ max_size: int = Field(default=1000, gt=0, description="Max cache entries")
43
+
44
+
45
+ class AxonFlowConfig(BaseModel):
46
+ """Configuration for AxonFlow client.
47
+
48
+ Attributes:
49
+ agent_url: AxonFlow Agent URL (required)
50
+ client_id: Client ID for authentication (required)
51
+ client_secret: Client secret for authentication (required)
52
+ license_key: Optional license key for organization-level auth
53
+ mode: Operation mode (production or sandbox)
54
+ debug: Enable debug logging
55
+ timeout: Request timeout in seconds
56
+ insecure_skip_verify: Skip TLS verification (dev only)
57
+ retry: Retry configuration
58
+ cache: Cache configuration
59
+ """
60
+
61
+ model_config = ConfigDict(frozen=True)
62
+
63
+ agent_url: str = Field(..., min_length=1, description="AxonFlow Agent URL")
64
+ client_id: str = Field(..., min_length=1, description="Client ID")
65
+ client_secret: str = Field(..., min_length=1, description="Client secret")
66
+ license_key: str | None = Field(default=None, description="License key")
67
+ mode: Mode = Field(default=Mode.PRODUCTION, description="Operation mode")
68
+ debug: bool = Field(default=False, description="Enable debug logging")
69
+ timeout: float = Field(default=60.0, gt=0, description="Request timeout (seconds)")
70
+ map_timeout: float = Field(default=120.0, gt=0, description="MAP operations timeout (seconds)")
71
+ insecure_skip_verify: bool = Field(default=False, description="Skip TLS verify")
72
+ retry: RetryConfig = Field(default_factory=RetryConfig)
73
+ cache: CacheConfig = Field(default_factory=CacheConfig)
74
+
75
+
76
+ class ClientRequest(BaseModel):
77
+ """Request to AxonFlow Agent."""
78
+
79
+ query: str = Field(..., description="Query or prompt")
80
+ user_token: str = Field(..., description="User token for auth")
81
+ client_id: str = Field(..., description="Client ID")
82
+ request_type: str = Field(..., description="Request type")
83
+ context: dict[str, Any] = Field(default_factory=dict, description="Additional context")
84
+
85
+
86
+ class PolicyEvaluationInfo(BaseModel):
87
+ """Policy evaluation metadata."""
88
+
89
+ policies_evaluated: list[str] = Field(default_factory=list)
90
+ static_checks: list[str] = Field(default_factory=list)
91
+ processing_time: str = Field(default="0ms")
92
+ tenant_id: str = Field(default="")
93
+
94
+
95
+ class ClientResponse(BaseModel):
96
+ """Response from AxonFlow Agent."""
97
+
98
+ success: bool = Field(..., description="Whether request succeeded")
99
+ data: Any | None = Field(default=None, description="Response data")
100
+ result: str | None = Field(default=None, description="Result for planning")
101
+ plan_id: str | None = Field(default=None, description="Plan ID if applicable")
102
+ metadata: dict[str, Any] = Field(default_factory=dict)
103
+ error: str | None = Field(default=None, description="Error message if failed")
104
+ blocked: bool = Field(default=False, description="Whether request was blocked")
105
+ block_reason: str | None = Field(default=None, description="Block reason")
106
+ policy_info: PolicyEvaluationInfo | None = Field(default=None)
107
+
108
+
109
+ class ConnectorMetadata(BaseModel):
110
+ """MCP connector metadata."""
111
+
112
+ id: str
113
+ name: str
114
+ type: str
115
+ version: str = ""
116
+ description: str = ""
117
+ category: str = ""
118
+ icon: str = ""
119
+ tags: list[str] = Field(default_factory=list)
120
+ capabilities: list[str] = Field(default_factory=list)
121
+ config_schema: dict[str, Any] = Field(default_factory=dict)
122
+ installed: bool = False
123
+ healthy: bool = False
124
+
125
+
126
+ class ConnectorInstallRequest(BaseModel):
127
+ """Request to install an MCP connector."""
128
+
129
+ connector_id: str
130
+ name: str
131
+ tenant_id: str
132
+ options: dict[str, Any] = Field(default_factory=dict)
133
+ credentials: dict[str, str] = Field(default_factory=dict)
134
+
135
+
136
+ class ConnectorResponse(BaseModel):
137
+ """Response from MCP connector query."""
138
+
139
+ success: bool
140
+ data: Any | None = None
141
+ error: str | None = None
142
+ meta: dict[str, Any] = Field(default_factory=dict)
143
+
144
+
145
+ class PlanStep(BaseModel):
146
+ """A step in a multi-agent plan."""
147
+
148
+ id: str
149
+ name: str
150
+ type: str
151
+ description: str = ""
152
+ depends_on: list[str] = Field(default_factory=list)
153
+ agent: str = ""
154
+ parameters: dict[str, Any] = Field(default_factory=dict)
155
+
156
+
157
+ class PlanResponse(BaseModel):
158
+ """Multi-agent plan response."""
159
+
160
+ plan_id: str
161
+ steps: list[PlanStep] = Field(default_factory=list)
162
+ domain: str = "generic"
163
+ complexity: int = 0
164
+ parallel: bool = False
165
+ metadata: dict[str, Any] = Field(default_factory=dict)
166
+
167
+
168
+ class PlanExecutionResponse(BaseModel):
169
+ """Plan execution result."""
170
+
171
+ plan_id: str
172
+ status: str # "running", "completed", "failed"
173
+ result: str | None = None
174
+ step_results: dict[str, Any] = Field(default_factory=dict)
175
+ error: str | None = None
176
+ duration: str | None = None
177
+
178
+
179
+ # Gateway Mode Types
180
+
181
+
182
+ class RateLimitInfo(BaseModel):
183
+ """Rate limiting status."""
184
+
185
+ limit: int
186
+ remaining: int
187
+ reset_at: datetime
188
+
189
+
190
+ class PolicyApprovalResult(BaseModel):
191
+ """Pre-check result from Gateway Mode."""
192
+
193
+ context_id: str = Field(..., description="Context ID for audit linking")
194
+ approved: bool = Field(..., description="Whether request is approved")
195
+ approved_data: dict[str, Any] = Field(default_factory=dict)
196
+ policies: list[str] = Field(default_factory=list)
197
+ rate_limit_info: RateLimitInfo | None = None
198
+ expires_at: datetime
199
+ block_reason: str | None = None
200
+
201
+
202
+ class TokenUsage(BaseModel):
203
+ """LLM token usage tracking."""
204
+
205
+ prompt_tokens: int = Field(ge=0)
206
+ completion_tokens: int = Field(ge=0)
207
+ total_tokens: int = Field(ge=0)
208
+
209
+
210
+ class AuditResult(BaseModel):
211
+ """Audit confirmation."""
212
+
213
+ success: bool
214
+ audit_id: str
@@ -0,0 +1,12 @@
1
+ """AxonFlow SDK Utilities."""
2
+
3
+ from axonflow.utils.cache import CacheManager
4
+ from axonflow.utils.logging import configure_logging, get_logger
5
+ from axonflow.utils.retry import RetryHandler
6
+
7
+ __all__ = [
8
+ "CacheManager",
9
+ "RetryHandler",
10
+ "configure_logging",
11
+ "get_logger",
12
+ ]
@@ -0,0 +1,102 @@
1
+ """Cache utilities for AxonFlow SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Generic, TypeVar
6
+
7
+ from cachetools import TTLCache
8
+
9
+ T = TypeVar("T")
10
+
11
+
12
+ class CacheManager(Generic[T]):
13
+ """Generic cache manager with TTL support.
14
+
15
+ Provides a simple interface for caching with automatic expiration.
16
+ """
17
+
18
+ def __init__(self, maxsize: int = 1000, ttl: float = 60.0) -> None:
19
+ """Initialize cache manager.
20
+
21
+ Args:
22
+ maxsize: Maximum number of entries
23
+ ttl: Time-to-live in seconds
24
+ """
25
+ self._cache: TTLCache[str, T] = TTLCache(maxsize=maxsize, ttl=ttl)
26
+ self._ttl = ttl
27
+ self._maxsize = maxsize
28
+
29
+ def get(self, key: str) -> T | None:
30
+ """Get a value from cache.
31
+
32
+ Args:
33
+ key: Cache key
34
+
35
+ Returns:
36
+ Cached value or None if not found
37
+ """
38
+ result: T | None = self._cache.get(key)
39
+ return result
40
+
41
+ def set(self, key: str, value: T) -> None:
42
+ """Set a value in cache.
43
+
44
+ Args:
45
+ key: Cache key
46
+ value: Value to cache
47
+ """
48
+ self._cache[key] = value
49
+
50
+ def delete(self, key: str) -> None:
51
+ """Delete a value from cache.
52
+
53
+ Args:
54
+ key: Cache key
55
+ """
56
+ self._cache.pop(key, None)
57
+
58
+ def clear(self) -> None:
59
+ """Clear all cached values."""
60
+ self._cache.clear()
61
+
62
+ def contains(self, key: str) -> bool:
63
+ """Check if key exists in cache.
64
+
65
+ Args:
66
+ key: Cache key
67
+
68
+ Returns:
69
+ True if key exists
70
+ """
71
+ return key in self._cache
72
+
73
+ @property
74
+ def size(self) -> int:
75
+ """Get current cache size."""
76
+ return len(self._cache)
77
+
78
+ @property
79
+ def ttl(self) -> float:
80
+ """Get cache TTL."""
81
+ return self._ttl
82
+
83
+ @property
84
+ def maxsize(self) -> int:
85
+ """Get maximum cache size."""
86
+ return self._maxsize
87
+
88
+ def get_or_set(self, key: str, factory: Any) -> T:
89
+ """Get from cache or create using factory.
90
+
91
+ Args:
92
+ key: Cache key
93
+ factory: Callable to create value if not cached
94
+
95
+ Returns:
96
+ Cached or newly created value
97
+ """
98
+ value = self.get(key)
99
+ if value is None:
100
+ value = factory()
101
+ self.set(key, value)
102
+ return value
@@ -0,0 +1,89 @@
1
+ """Logging utilities for AxonFlow SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import sys
7
+ from typing import Any
8
+
9
+ import structlog
10
+
11
+
12
+ def configure_logging(
13
+ level: int = logging.INFO,
14
+ json_format: bool = False,
15
+ ) -> None:
16
+ """Configure structured logging for AxonFlow SDK.
17
+
18
+ Args:
19
+ level: Logging level (default: INFO)
20
+ json_format: Use JSON format for logs (default: False)
21
+ """
22
+ # Configure structlog processors
23
+ processors: list[Any] = [
24
+ structlog.stdlib.filter_by_level,
25
+ structlog.stdlib.add_logger_name,
26
+ structlog.stdlib.add_log_level,
27
+ structlog.stdlib.PositionalArgumentsFormatter(),
28
+ structlog.processors.TimeStamper(fmt="iso"),
29
+ structlog.processors.StackInfoRenderer(),
30
+ structlog.processors.UnicodeDecoder(),
31
+ ]
32
+
33
+ if json_format:
34
+ processors.append(structlog.processors.JSONRenderer())
35
+ else:
36
+ processors.append(structlog.dev.ConsoleRenderer())
37
+
38
+ structlog.configure(
39
+ processors=processors,
40
+ wrapper_class=structlog.stdlib.BoundLogger,
41
+ context_class=dict,
42
+ logger_factory=structlog.stdlib.LoggerFactory(),
43
+ cache_logger_on_first_use=True,
44
+ )
45
+
46
+ # Configure stdlib logging
47
+ logging.basicConfig(
48
+ format="%(message)s",
49
+ stream=sys.stdout,
50
+ level=level,
51
+ )
52
+
53
+
54
+ def get_logger(name: str) -> structlog.stdlib.BoundLogger:
55
+ """Get a structured logger.
56
+
57
+ Args:
58
+ name: Logger name
59
+
60
+ Returns:
61
+ Configured structured logger
62
+ """
63
+ logger: structlog.stdlib.BoundLogger = structlog.get_logger(name)
64
+ return logger
65
+
66
+
67
+ class LogContext:
68
+ """Context manager for adding log context."""
69
+
70
+ def __init__(self, logger: structlog.stdlib.BoundLogger, **context: Any) -> None:
71
+ """Initialize log context.
72
+
73
+ Args:
74
+ logger: Logger to bind context to
75
+ **context: Context key-value pairs
76
+ """
77
+ self._logger = logger
78
+ self._context = context
79
+ self._original_context: dict[str, Any] = {}
80
+
81
+ def __enter__(self) -> structlog.stdlib.BoundLogger:
82
+ """Enter context and bind values."""
83
+ self._original_context = dict(getattr(self._logger, "_context", {}))
84
+ return self._logger.bind(**self._context)
85
+
86
+ def __exit__(self, *args: Any) -> None:
87
+ """Exit context and restore original values."""
88
+ # Restore original context
89
+ self._logger._context = self._original_context # noqa: B010, SLF001