resilient-circuit 0.4.1__py3-none-any.whl → 0.4.2__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.
@@ -1,7 +1,8 @@
1
1
  import abc
2
2
  import enum
3
3
  import logging
4
- from datetime import datetime, timedelta
4
+ import time
5
+ from datetime import timedelta
5
6
  from fractions import Fraction
6
7
  from functools import wraps
7
8
  from typing import Callable, Optional, TypeVar
@@ -11,7 +12,10 @@ from typing_extensions import ParamSpec
11
12
  from resilient_circuit.buffer import BinaryCircularBuffer
12
13
  from resilient_circuit.exceptions import ProtectedCallError
13
14
  from resilient_circuit.policy import ProtectionPolicy
14
- from resilient_circuit.storage import CircuitBreakerStorage, InMemoryStorage, create_storage
15
+ from resilient_circuit.storage import (
16
+ CircuitBreakerStorage,
17
+ create_storage,
18
+ )
15
19
 
16
20
  R = TypeVar("R")
17
21
  P = ParamSpec("P")
@@ -56,7 +60,7 @@ class CircuitProtectorPolicy(ProtectionPolicy):
56
60
  self._load_state()
57
61
 
58
62
  def _load_state(self) -> None:
59
- """Load circuit breaker state from storage."""
63
+ """Load circuit breaker state from storage including execution log buffer."""
60
64
  try:
61
65
  state_data = self.storage.get_state(self.resource_key)
62
66
  if state_data:
@@ -64,6 +68,7 @@ class CircuitProtectorPolicy(ProtectionPolicy):
64
68
  state = CircuitStatus(state_data["state"])
65
69
  failure_count = int(state_data.get("failure_count", 0))
66
70
  open_until = float(state_data.get("open_until", 0))
71
+ execution_log_data = state_data.get("execution_log")
67
72
 
68
73
  # Initialize status based on stored state
69
74
  status: CircuitStatusBase
@@ -77,6 +82,13 @@ class CircuitProtectorPolicy(ProtectionPolicy):
77
82
  else: # HALF_OPEN
78
83
  status = StatusHalfOpen(policy=self, failure_count=failure_count)
79
84
 
85
+ # Restore execution_log buffer if available
86
+ if execution_log_data and isinstance(execution_log_data, list):
87
+ if hasattr(status, 'execution_log') and hasattr(status.execution_log, '_items'):
88
+ # Restore buffer maintaining size limit
89
+ status.execution_log._items = execution_log_data[-status.execution_log.size:]
90
+ logger.debug(f"Restored buffer for {self.resource_key}: {len(status.execution_log._items)} entries")
91
+
80
92
  self._status = status
81
93
  logger.debug(f"Loaded circuit breaker state for {self.resource_key}: {state.value}")
82
94
  else:
@@ -89,7 +101,7 @@ class CircuitProtectorPolicy(ProtectionPolicy):
89
101
  self._status = StatusClosed(policy=self)
90
102
 
91
103
  def _save_state(self) -> None:
92
- """Save circuit breaker state to storage."""
104
+ """Save circuit breaker state to storage including execution log buffer."""
93
105
  try:
94
106
  state_value: str = self._status.status_type.value
95
107
  failure_count_val: int = int(getattr(self._status, 'failure_count', 0))
@@ -99,13 +111,19 @@ class CircuitProtectorPolicy(ProtectionPolicy):
99
111
  if hasattr(self._status, 'open_until_timestamp') else 0
100
112
  )
101
113
 
114
+ # Extract execution_log buffer if available
115
+ execution_log_data = None
116
+ if hasattr(self._status, 'execution_log') and hasattr(self._status.execution_log, '_items'):
117
+ execution_log_data = list(self._status.execution_log._items)
118
+
102
119
  self.storage.set_state(
103
120
  self.resource_key,
104
121
  state_value,
105
122
  failure_count_val,
106
- open_until_val
123
+ open_until_val,
124
+ execution_log=execution_log_data
107
125
  )
108
- logger.debug(f"Saved circuit breaker state for {self.resource_key}: {state_value}")
126
+ logger.debug(f"Saved circuit breaker state for {self.resource_key}: {state_value}, buffer_size={len(execution_log_data) if execution_log_data else 0}")
109
127
  except Exception as e:
110
128
  logger.error(f"Failed to save state for {self.resource_key}: {e}")
111
129
 
@@ -126,10 +144,8 @@ class CircuitProtectorPolicy(ProtectionPolicy):
126
144
  new_status_obj = StatusClosed(policy=self, failure_count=0)
127
145
  elif new_status is CircuitStatus.OPEN:
128
146
  # When transitioning to OPEN, keep the failure count from current status
129
- current_failure_count = getattr(self._status, 'failure_count', 0)
130
147
  # Calculate the open_until timestamp based on current time and cooldown
131
- from datetime import datetime
132
- open_until = (datetime.now() + self.cooldown).timestamp()
148
+ open_until = time.time() + self.cooldown.total_seconds()
133
149
  new_status_obj = StatusOpen(policy=self, previous_status=self._status, open_until=open_until)
134
150
  else: # HALF_OPEN
135
151
  # When transitioning to HALF_OPEN, reset failure count
@@ -147,17 +163,28 @@ class CircuitProtectorPolicy(ProtectionPolicy):
147
163
  def __call__(self, func: Callable[P, R]) -> Callable[P, R]:
148
164
  @wraps(func)
149
165
  def decorated(*args: P.args, **kwargs: P.kwargs) -> R:
166
+ import logging
167
+ logger = logging.getLogger(__name__)
168
+
150
169
  self._status.validate_execution()
151
170
  try:
152
171
  result = func(*args, **kwargs)
153
172
  except Exception as e:
154
- if self.should_consider_failure(e):
173
+ should_fail = self.should_consider_failure(e)
174
+ logger.warning(
175
+ f"🚨 CB {self.resource_key}: Exception {type(e).__name__}: {e}, "
176
+ f"should_consider_failure={should_fail}"
177
+ )
178
+ if should_fail:
179
+ logger.warning(f"❌ CB {self.resource_key}: Calling mark_failure()")
155
180
  self._status.mark_failure()
156
181
  else:
182
+ logger.warning(f"✅ CB {self.resource_key}: Exception ignored, calling mark_success()")
157
183
  self._status.mark_success()
158
184
  self._save_state() # Persist state after exception
159
185
  raise
160
186
  else:
187
+ logger.warning(f"✅ CB {self.resource_key}: Success, calling mark_success()")
161
188
  self._status.mark_success()
162
189
  self._save_state() # Persist state after success
163
190
  return result
@@ -205,13 +232,31 @@ class StatusClosed(CircuitStatusBase):
205
232
  pass
206
233
 
207
234
  def mark_failure(self) -> None:
235
+ import logging
236
+ logger = logging.getLogger(__name__)
237
+
208
238
  self.failure_count += 1 # Increment failure count
209
239
  self.execution_log.add(False)
240
+
241
+ logger.warning(
242
+ f"📊 CB {self.policy.resource_key} mark_failure: "
243
+ f"buffer={list(self.execution_log._items)}, "
244
+ f"is_full={self.execution_log.is_full}, "
245
+ f"size={self.execution_log.size}, "
246
+ f"failure_rate={self.execution_log.failure_rate}, "
247
+ f"threshold={self.policy.failure_limit}"
248
+ )
249
+
210
250
  if (
211
251
  self.execution_log.is_full
212
252
  and self.execution_log.failure_rate >= self.policy.failure_limit
213
253
  ):
214
- self.policy.status = CircuitStatus.OPEN
254
+ logger.warning(f"🔴 CB {self.policy.resource_key}: Opening circuit!")
255
+ try:
256
+ self.policy.status = CircuitStatus.OPEN
257
+ logger.warning(f"✅ CB {self.policy.resource_key}: Successfully set status to OPEN")
258
+ except Exception as e:
259
+ logger.error(f"❌ CB {self.policy.resource_key}: Failed to set status to OPEN: {type(e).__name__}: {e}", exc_info=True)
215
260
 
216
261
  def mark_success(self) -> None:
217
262
  self.failure_count = 0 # Reset failure count on success
@@ -232,17 +277,15 @@ class StatusOpen(CircuitStatusBase):
232
277
 
233
278
  # Store the timestamp when the OPEN state should end (cooldown period)
234
279
  # If open_until is 0, circuit should be blocked for the full cooldown period
235
- from datetime import datetime
236
280
  if open_until and open_until > 0:
237
281
  self.open_until_timestamp = open_until # This is when cooldown ends
238
282
  else:
239
283
  # Calculate when cooldown should end based on current time and policy cooldown
240
- self.open_until_timestamp = (datetime.now() + policy.cooldown).timestamp()
284
+ self.open_until_timestamp = time.time() + policy.cooldown.total_seconds()
241
285
 
242
286
  def validate_execution(self) -> None:
243
- from datetime import datetime
244
287
  # Check if cooldown period has expired
245
- if datetime.now().timestamp() >= self.open_until_timestamp:
288
+ if time.time() >= self.open_until_timestamp:
246
289
  # Cooldown expired, transition to HALF_OPEN to allow test requests
247
290
  self.policy.status = CircuitStatus.HALF_OPEN
248
291
  return # Allow execution in HALF_OPEN state
resilient_circuit/cli.py CHANGED
@@ -5,7 +5,6 @@ CLI module for Highway Circuit Breaker
5
5
  import argparse
6
6
  import os
7
7
  import sys
8
- from typing import Optional
9
8
 
10
9
  try:
11
10
  import psycopg
@@ -66,7 +65,7 @@ def create_postgres_table(config: dict) -> bool:
66
65
 
67
66
  table_exists = cur.fetchone()[0]
68
67
  if table_exists:
69
- print(f"ℹ️ Table 'rc_circuit_breakers' already exists, checking for updates...")
68
+ print("ℹ️ Table 'rc_circuit_breakers' already exists, checking for updates...")
70
69
 
71
70
  # Create the circuit breaker table
72
71
  cur.execute("""
@@ -180,7 +179,7 @@ def run_pg_setup(args: argparse.Namespace) -> int:
180
179
  # Get config from environment
181
180
  config = get_db_config_from_env()
182
181
 
183
- print(f"🔧 Using database configuration from environment:")
182
+ print("🔧 Using database configuration from environment:")
184
183
  print(f" Host: {config['host']}")
185
184
  print(f" Port: {config['port']}")
186
185
  print(f" Database: {config['dbname']}")
@@ -258,4 +257,4 @@ def main():
258
257
  return 1
259
258
 
260
259
  if __name__ == '__main__':
261
- sys.exit(main())
260
+ sys.exit(main())
@@ -5,7 +5,7 @@ from typing import Callable, Optional, TypeVar
5
5
  from typing_extensions import ParamSpec
6
6
 
7
7
  from resilient_circuit.backoff import ExponentialDelay
8
- from resilient_circuit.exceptions import ProtectedCallError, RetryLimitReached
8
+ from resilient_circuit.exceptions import RetryLimitReached
9
9
  from resilient_circuit.policy import ProtectionPolicy
10
10
 
11
11
  R = TypeVar("R")
@@ -1,8 +1,8 @@
1
+ import logging
1
2
  import os
2
3
  import time
3
4
  from abc import ABC, abstractmethod
4
- from typing import Optional, Dict, Any
5
- import logging
5
+ from typing import Any, Dict, Optional
6
6
 
7
7
  try:
8
8
  from dotenv import load_dotenv
@@ -23,20 +23,24 @@ logger = logging.getLogger(__name__)
23
23
 
24
24
  class CircuitBreakerStorage(ABC):
25
25
  """Abstract base class for circuit breaker storage backends."""
26
-
26
+
27
27
  @abstractmethod
28
28
  def get_state(self, resource_key: str) -> Optional[Dict[str, Any]]:
29
29
  """Get the state for a given resource key.
30
-
30
+
31
31
  Returns:
32
- Dictionary with keys: state, failure_count, open_until
32
+ Dictionary with keys: state, failure_count, open_until, execution_log (optional)
33
33
  or None if no state found
34
34
  """
35
35
  pass
36
-
36
+
37
37
  @abstractmethod
38
- def set_state(self, resource_key: str, state: str, failure_count: int, open_until: float) -> None:
39
- """Set the state for a given resource key."""
38
+ def set_state(self, resource_key: str, state: str, failure_count: int, open_until: float, execution_log: Optional[list] = None) -> None:
39
+ """Set the state for a given resource key.
40
+
41
+ Args:
42
+ execution_log: Optional list of boolean success/failure results
43
+ """
40
44
  pass
41
45
 
42
46
 
@@ -49,12 +53,15 @@ class InMemoryStorage(CircuitBreakerStorage):
49
53
  def get_state(self, resource_key: str) -> Optional[Dict[str, Any]]:
50
54
  return self._states.get(resource_key)
51
55
 
52
- def set_state(self, resource_key: str, state: str, failure_count: int, open_until: float) -> None:
53
- self._states[resource_key] = {
56
+ def set_state(self, resource_key: str, state: str, failure_count: int, open_until: float, execution_log: Optional[list] = None) -> None:
57
+ state_dict = {
54
58
  "state": state,
55
59
  "failure_count": failure_count,
56
60
  "open_until": open_until
57
61
  }
62
+ if execution_log is not None:
63
+ state_dict["execution_log"] = execution_log
64
+ self._states[resource_key] = state_dict
58
65
 
59
66
 
60
67
  class PostgresStorage(CircuitBreakerStorage):
@@ -67,11 +74,11 @@ class PostgresStorage(CircuitBreakerStorage):
67
74
  self.connection_string = connection_string
68
75
  self.namespace = namespace
69
76
  self._ensure_table_exists()
70
-
77
+
71
78
  def _get_connection(self) -> Connection:
72
79
  """Get a database connection."""
73
80
  return psycopg.connect(self.connection_string)
74
-
81
+
75
82
  def _ensure_table_exists(self) -> None:
76
83
  """Ensure the circuit breaker table exists with namespace support."""
77
84
  try:
@@ -154,7 +161,7 @@ class PostgresStorage(CircuitBreakerStorage):
154
161
  except Exception as e:
155
162
  logger.error(f"Failed to ensure table exists: {e}")
156
163
  raise
157
-
164
+
158
165
  def get_state(self, resource_key: str) -> Optional[Dict[str, Any]]:
159
166
  """Get the state for a given resource key within this namespace.
160
167
 
@@ -166,7 +173,7 @@ class PostgresStorage(CircuitBreakerStorage):
166
173
  with self._get_connection() as conn:
167
174
  with conn.cursor() as cur:
168
175
  cur.execute(
169
- "SELECT state, failure_count, open_until "
176
+ "SELECT state, failure_count, open_until, execution_log "
170
177
  "FROM rc_circuit_breakers "
171
178
  "WHERE resource_key = %s AND namespace = %s "
172
179
  "FOR UPDATE",
@@ -174,19 +181,32 @@ class PostgresStorage(CircuitBreakerStorage):
174
181
  )
175
182
  row = cur.fetchone()
176
183
  if row:
177
- return {
184
+ result = {
178
185
  "state": row[0],
179
186
  "failure_count": row[1],
180
187
  "open_until": row[2].timestamp() if row[2] else 0
181
188
  }
189
+ # Add execution_log if present
190
+ if row[3] is not None:
191
+ result["execution_log"] = row[3]
192
+ return result
182
193
  return None
183
194
  except Exception as e:
184
195
  logger.error(f"Failed to get state for {resource_key} (namespace={self.namespace}): {e}")
185
196
  raise
186
-
187
- def set_state(self, resource_key: str, state: str, failure_count: int, open_until: float) -> None:
188
- """Set the state for a given resource key within this namespace."""
197
+
198
+ def set_state(self, resource_key: str, state: str, failure_count: int, open_until: float, execution_log: Optional[list] = None) -> None:
199
+ """Set the state for a given resource key within this namespace.
200
+
201
+ Args:
202
+ resource_key: Unique circuit breaker identifier
203
+ state: Circuit state (CLOSED, OPEN, HALF_OPEN)
204
+ failure_count: Number of consecutive failures
205
+ open_until: Timestamp when circuit can transition from OPEN
206
+ execution_log: Optional list of boolean success/failure results for the circular buffer
207
+ """
189
208
  try:
209
+ import json
190
210
  with self._get_connection() as conn:
191
211
  with conn.cursor() as cur:
192
212
  # Convert timestamp to PostgreSQL timestamp
@@ -194,19 +214,40 @@ class PostgresStorage(CircuitBreakerStorage):
194
214
  if open_until > 0:
195
215
  open_until_ts = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(open_until))
196
216
 
197
- cur.execute(
198
- """
199
- INSERT INTO rc_circuit_breakers
200
- (resource_key, namespace, state, failure_count, open_until)
201
- VALUES (%s, %s, %s, %s, %s)
202
- ON CONFLICT (resource_key, namespace) DO UPDATE SET
203
- state = EXCLUDED.state,
204
- failure_count = EXCLUDED.failure_count,
205
- open_until = EXCLUDED.open_until,
206
- updated_at = CURRENT_TIMESTAMP
207
- """,
208
- (resource_key, self.namespace, state, failure_count, open_until_ts)
209
- )
217
+ # Serialize execution_log to JSON if provided
218
+ execution_log_json = json.dumps(execution_log) if execution_log is not None else None
219
+
220
+ if execution_log is not None:
221
+ # Update including execution_log
222
+ cur.execute(
223
+ """
224
+ INSERT INTO rc_circuit_breakers
225
+ (resource_key, namespace, state, failure_count, open_until, execution_log)
226
+ VALUES (%s, %s, %s, %s, %s, %s::jsonb)
227
+ ON CONFLICT (resource_key, namespace) DO UPDATE SET
228
+ state = EXCLUDED.state,
229
+ failure_count = EXCLUDED.failure_count,
230
+ open_until = EXCLUDED.open_until,
231
+ execution_log = EXCLUDED.execution_log,
232
+ updated_at = CURRENT_TIMESTAMP
233
+ """,
234
+ (resource_key, self.namespace, state, failure_count, open_until_ts, execution_log_json)
235
+ )
236
+ else:
237
+ # Update without execution_log (preserve existing value)
238
+ cur.execute(
239
+ """
240
+ INSERT INTO rc_circuit_breakers
241
+ (resource_key, namespace, state, failure_count, open_until)
242
+ VALUES (%s, %s, %s, %s, %s)
243
+ ON CONFLICT (resource_key, namespace) DO UPDATE SET
244
+ state = EXCLUDED.state,
245
+ failure_count = EXCLUDED.failure_count,
246
+ open_until = EXCLUDED.open_until,
247
+ updated_at = CURRENT_TIMESTAMP
248
+ """,
249
+ (resource_key, self.namespace, state, failure_count, open_until_ts)
250
+ )
210
251
  conn.commit()
211
252
  except Exception as e:
212
253
  logger.error(f"Failed to set state for {resource_key} (namespace={self.namespace}): {e}")
@@ -249,4 +290,4 @@ def create_storage(namespace: Optional[str] = None) -> CircuitBreakerStorage:
249
290
  else:
250
291
  # Default to in-memory storage
251
292
  logger.info(f"Using in-memory storage for circuit breaker (no PostgreSQL config found), namespace={namespace}")
252
- return InMemoryStorage()
293
+ return InMemoryStorage()
@@ -1,15 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: resilient-circuit
3
- Version: 0.4.1
3
+ Version: 0.4.2
4
4
  Summary: A resilient circuit breaker and retry library with PostgreSQL support for distributed systems
5
5
  Author-email: Farshid Ashouri <farsheed.ashouri@gmail.com>
6
- License: Apache Software License 2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://github.com/rodmena-limited/resilient-circuit
8
8
  Project-URL: Repository, https://github.com/rodmena-limited/resilient-circuit
9
9
  Project-URL: Documentation, https://resilient-circuit.readthedocs.io
10
10
  Classifier: Development Status :: 4 - Beta
11
11
  Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: Apache Software License
13
12
  Classifier: Programming Language :: Python :: 3
14
13
  Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
@@ -1,17 +1,17 @@
1
1
  resilient_circuit/__init__.py,sha256=Z3WOzwCT5fP0VptBNu-bkrbKGGljaDB9QL9FwkcBpQI,552
2
2
  resilient_circuit/backoff.py,sha256=aLSFYqU_CROrkDaepZaTF_vUy05fBvRhUruXe3Wcmlk,1148
3
3
  resilient_circuit/buffer.py,sha256=UIOxPgo07TmveTbaSSJXHzDZwu_aYRVkgIWbGuMhH-8,1385
4
- resilient_circuit/circuit_breaker.py,sha256=RwQu490pBulAk5Fl4Kn3URsfrEHqwXqVGa3WxSZuwHc,12190
5
- resilient_circuit/cli.py,sha256=fQpoajQWO-KOpxjVcRsw_oPtXXPw8mfid4pqFwHmRkw,9563
4
+ resilient_circuit/circuit_breaker.py,sha256=yawdm0bJz11kqeIs5UIWlCJehQeF1t9X2E1Pvo1HkDY,14472
5
+ resilient_circuit/cli.py,sha256=_XdXdPaCewVL0Tg6MP-zRLWx7qpbJ54ysMnUht6nEBw,9534
6
6
  resilient_circuit/exceptions.py,sha256=f1A9hK1y9kxOFtzldIMCOy0Ui9cKnLuCkUzPUU7zyR4,162
7
7
  resilient_circuit/failsafe.py,sha256=B6piP5gWAn88maJnk_Gp3nSRp0FCCtZ4Lnd7Ekrgo-M,1068
8
8
  resilient_circuit/policy.py,sha256=MJeETZKw_oMrRkgfq160nrFrSm6tv1N2pgkGxsaQmRs,289
9
9
  resilient_circuit/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- resilient_circuit/retry.py,sha256=I0QWkhXlEj3Pn6yMbTgT70XPM99Rr3wEoIWL6ThaLCY,1476
11
- resilient_circuit/storage.py,sha256=PI31JdWT11H7Sxc8YrwSth351bWPT_ZfdzTGuhZ78E8,10874
12
- resilient_circuit-0.4.1.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
13
- resilient_circuit-0.4.1.dist-info/METADATA,sha256=PmpXbOfalK3TjPz6LEDbapvtRgealXZhtRskcl8AKec,14761
14
- resilient_circuit-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
- resilient_circuit-0.4.1.dist-info/entry_points.txt,sha256=JbXU75gDeU3PsVykHG7Bq1lYJG7hr5KG-3ny-HGCcZI,65
16
- resilient_circuit-0.4.1.dist-info/top_level.txt,sha256=SFJWwSDlbsmTWV8F67fdmfAK_dtTT9c8L-kfdnior4s,18
17
- resilient_circuit-0.4.1.dist-info/RECORD,,
10
+ resilient_circuit/retry.py,sha256=DYeqVLHfMszM7UiV89rxJ7QA_eRofJwz7vmOeSTtJwM,1456
11
+ resilient_circuit/storage.py,sha256=1eyhDnP5jReZIpzsIvVrn7HBet_lJFCVaJoe6CTFh1M,13150
12
+ resilient_circuit-0.4.2.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
13
+ resilient_circuit-0.4.2.dist-info/METADATA,sha256=uHeP4j7TX_WcxMh4yDYbJEipWN2ENqD0jtMqJieRpD8,14692
14
+ resilient_circuit-0.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
15
+ resilient_circuit-0.4.2.dist-info/entry_points.txt,sha256=JbXU75gDeU3PsVykHG7Bq1lYJG7hr5KG-3ny-HGCcZI,65
16
+ resilient_circuit-0.4.2.dist-info/top_level.txt,sha256=SFJWwSDlbsmTWV8F67fdmfAK_dtTT9c8L-kfdnior4s,18
17
+ resilient_circuit-0.4.2.dist-info/RECORD,,