Skip to content

Persistence

Persistence layers for the Circuit Breaker utility.

MODULE DESCRIPTION
base

Abstract persistence layer for the Circuit Breaker utility.

dynamodb

DynamoDB persistence backend for the Circuit Breaker utility.

record

Internal record type for circuit state held in a persistence store.

CLASS DESCRIPTION
CircuitBreakerDynamoDBPersistence

Store circuit state in an Amazon DynamoDB table, one item per circuit.

CircuitBreakerPersistenceLayer

Abstract base class for circuit breaker persistence layers.

CircuitStateRecord

The persisted state of a single circuit.

CircuitBreakerDynamoDBPersistence

CircuitBreakerDynamoDBPersistence(table_name: str, key_attr: str = 'id', state_attr: str = 'state', failure_count_attr: str = 'failure_count', opened_at_attr: str = 'opened_at', half_open_owner_attr: str = 'half_open_owner', probe_lease_expiry_attr: str = 'probe_lease_expiry', expiry_attr: str = 'expiration', boto_config: Config | None = None, boto3_session: Session | None = None, boto3_client: DynamoDBClient | None = None)

Bases: CircuitBreakerPersistenceLayer

Store circuit state in an Amazon DynamoDB table, one item per circuit.

The class name is prefixed with CircuitBreaker so a function using both the Idempotency and Circuit Breaker utilities can import both persistence layers without an alias.

PARAMETER DESCRIPTION
table_name

Name of the DynamoDB table that stores circuit state.

TYPE: str

key_attr

Partition key attribute holding the circuit name. Defaults to "id".

TYPE: str DEFAULT: 'id'

state_attr

Attribute holding the circuit state. Defaults to "state".

TYPE: str DEFAULT: 'state'

failure_count_attr

Attribute holding the consecutive failure count. Defaults to "failure_count".

TYPE: str DEFAULT: 'failure_count'

opened_at_attr

Attribute holding the open timestamp. Defaults to "opened_at".

TYPE: str DEFAULT: 'opened_at'

half_open_owner_attr

Attribute holding the half-open probe lock owner. Defaults to "half_open_owner".

TYPE: str DEFAULT: 'half_open_owner'

probe_lease_expiry_attr

Attribute holding the probe lease expiry timestamp. Defaults to "probe_lease_expiry".

TYPE: str DEFAULT: 'probe_lease_expiry'

expiry_attr

TTL attribute. Defaults to "expiration".

TYPE: str DEFAULT: 'expiration'

boto_config

Botocore configuration used when creating the client.

TYPE: Config DEFAULT: None

boto3_session

Session used to create the client.

TYPE: Session DEFAULT: None

boto3_client

Pre-built client; boto3_session and boto_config are ignored if given.

TYPE: DynamoDBClient DEFAULT: None

Example

Create a DynamoDB-backed circuit breaker store

1
2
3
4
5
from aws_lambda_powertools.utilities.circuit_breaker_alpha.persistence import (
    CircuitBreakerDynamoDBPersistence,
)

persistence = CircuitBreakerDynamoDBPersistence(table_name="CircuitBreakerState")
Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/dynamodb.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def __init__(
    self,
    table_name: str,
    key_attr: str = "id",
    state_attr: str = "state",
    failure_count_attr: str = "failure_count",
    opened_at_attr: str = "opened_at",
    half_open_owner_attr: str = "half_open_owner",
    probe_lease_expiry_attr: str = "probe_lease_expiry",
    expiry_attr: str = "expiration",
    boto_config: Config | None = None,
    boto3_session: boto3.session.Session | None = None,
    boto3_client: DynamoDBClient | None = None,
):
    if boto3_client is None:
        boto3_session = boto3_session or boto3.session.Session()
        boto3_client = boto3_session.client("dynamodb", config=boto_config)
    self.client = boto3_client

    user_agent.register_feature_to_client(client=self.client, feature="circuit_breaker")

    self.table_name = table_name
    self.key_attr = key_attr
    self.state_attr = state_attr
    self.failure_count_attr = failure_count_attr
    self.opened_at_attr = opened_at_attr
    self.half_open_owner_attr = half_open_owner_attr
    self.probe_lease_expiry_attr = probe_lease_expiry_attr
    self.expiry_attr = expiry_attr

    self._deserializer = TypeDeserializer()

    super().__init__()

CircuitBreakerPersistenceLayer

CircuitBreakerPersistenceLayer()

Bases: ABC

Abstract base class for circuit breaker persistence layers.

Owns the per-environment read cache and the fail-open behavior. Subclasses implement :meth:_get_record, :meth:_put_record, and :meth:_update_record for a specific store.

A persistence layer is keyed by circuit name, not by a payload hash, which is the main reason it does not reuse the Idempotency persistence layer.

METHOD DESCRIPTION
configure

Bind the layer to a circuit and its configuration.

get_state

Return the current circuit state, reading the store only on a cache miss.

save_closed

Persist a transition back to CLOSED and reset counters.

save_open

Persist a CLOSED to OPEN transition.

save_reopen

Persist a HALF_OPEN to OPEN transition after a failed probe.

try_acquire_half_open

Atomically elect a single environment to run the half-open probe.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
51
52
53
54
55
56
57
58
59
def __init__(self) -> None:
    """Initialize defaults; real configuration happens in :meth:`configure`."""
    self.circuit_name: str = ""
    self.local_cache_max_age: int = 5
    self.recovery_timeout: int = 30
    # Maps circuit name -> the unix timestamp the locally cached record goes stale.
    # Kept separate from the record's durable ``expiry_timestamp`` (the store TTL) so
    # the short in-memory freshness window is never mistaken for the long store TTL.
    self._cache: LRUDict = LRUDict(max_items=LOCAL_CACHE_MAX_ITEMS)

configure

configure(config: CircuitBreakerConfig, circuit_name: str) -> None

Bind the layer to a circuit and its configuration.

Called once per invocation by the handler; the assignments are cheap and the same persistence instance is reused across invocations within an environment.

PARAMETER DESCRIPTION
config

Configuration providing the local cache TTL and recovery timeout.

TYPE: CircuitBreakerConfig

circuit_name

The circuit this layer instance serves.

TYPE: str

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def configure(self, config: CircuitBreakerConfig, circuit_name: str) -> None:
    """
    Bind the layer to a circuit and its configuration.

    Called once per invocation by the handler; the assignments are cheap and the
    same persistence instance is reused across invocations within an environment.

    Parameters
    ----------
    config : CircuitBreakerConfig
        Configuration providing the local cache TTL and recovery timeout.
    circuit_name : str
        The circuit this layer instance serves.
    """
    self.circuit_name = circuit_name
    self.local_cache_max_age = config.local_cache_max_age
    self.recovery_timeout = config.recovery_timeout

get_state

get_state(name: str) -> CircuitStateRecord

Return the current circuit state, reading the store only on a cache miss.

A cache miss (cold start or expired local entry) forces a read-through before the caller routes the request, so a freshly started environment never assumes a circuit is closed without checking.

Fail-open: if the store read itself raises, the circuit is treated as CLOSED. A circuit breaker must never become the outage it is meant to prevent.

PARAMETER DESCRIPTION
name

Circuit name.

TYPE: str

RETURNS DESCRIPTION
CircuitStateRecord

The current record, a synthesized closed record if none exists yet, or a synthesized closed record if the store could not be reached.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def get_state(self, name: str) -> CircuitStateRecord:
    """
    Return the current circuit state, reading the store only on a cache miss.

    A cache miss (cold start or expired local entry) forces a read-through before
    the caller routes the request, so a freshly started environment never assumes a
    circuit is closed without checking.

    Fail-open: if the store read itself raises, the circuit is treated as
    ``CLOSED``. A circuit breaker must never become the outage it is meant to
    prevent.

    Parameters
    ----------
    name : str
        Circuit name.

    Returns
    -------
    CircuitStateRecord
        The current record, a synthesized closed record if none exists yet, or a
        synthesized closed record if the store could not be reached.
    """
    cached = self._retrieve_from_cache(name)
    if cached is not None:
        return cached

    try:
        record = self._get_record(name)
    except Exception:
        # Fail open without caching, so the next invocation retries the store rather
        # than serving a synthesized CLOSED for the whole local cache window.
        logger.warning(
            "Failed to read circuit state for '%s'; failing open (treating as CLOSED).",
            name,
            exc_info=True,
        )
        return CircuitStateRecord(name=name, state=CircuitState.CLOSED)

    # A missing record is the expected cold-start case, not a failure: treat it as a
    # closed circuit and cache it like any other read.
    if record is None:
        record = CircuitStateRecord(name=name, state=CircuitState.CLOSED)

    self._save_to_cache(record)
    return record

save_closed

save_closed(name: str) -> None

Persist a transition back to CLOSED and reset counters.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
225
226
227
228
229
230
231
232
233
234
def save_closed(self, name: str) -> None:
    """Persist a transition back to CLOSED and reset counters."""
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.CLOSED,
        failure_count=0,
        expiry_timestamp=self._durable_ttl(),
    )
    self._update_record(record)
    self._save_to_cache(record)

save_open

save_open(name: str, failure_count: int, opened_at: int) -> None

Persist a CLOSED to OPEN transition.

PARAMETER DESCRIPTION
name

Circuit name.

TYPE: str

failure_count

Consecutive failures that tripped the circuit.

TYPE: int

opened_at

Unix timestamp the circuit opened; anchors the recovery timeout.

TYPE: int

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def save_open(self, name: str, failure_count: int, opened_at: int) -> None:
    """
    Persist a CLOSED to OPEN transition.

    Parameters
    ----------
    name : str
        Circuit name.
    failure_count : int
        Consecutive failures that tripped the circuit.
    opened_at : int
        Unix timestamp the circuit opened; anchors the recovery timeout.
    """
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.OPEN,
        failure_count=failure_count,
        opened_at=opened_at,
        expiry_timestamp=self._durable_ttl(),
    )
    self._put_record(record)
    self._save_to_cache(record)

save_reopen

save_reopen(name: str, opened_at: int) -> None

Persist a HALF_OPEN to OPEN transition after a failed probe.

If this write fails the stored row stays in HALF_OPEN, but it does not strand the circuit: the probe lease expiry still applies, so once it lapses another environment wins the next election and drives the transition.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def save_reopen(self, name: str, opened_at: int) -> None:
    """
    Persist a HALF_OPEN to OPEN transition after a failed probe.

    If this write fails the stored row stays in HALF_OPEN, but it does not strand the
    circuit: the probe lease expiry still applies, so once it lapses another
    environment wins the next election and drives the transition.
    """
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.OPEN,
        opened_at=opened_at,
        expiry_timestamp=self._durable_ttl(),
    )
    self._update_record(record)
    self._save_to_cache(record)

try_acquire_half_open

try_acquire_half_open(name: str, owner: str, opened_at: int) -> bool

Atomically elect a single environment to run the half-open probe.

The conditional write succeeds only when the circuit is OPEN with no existing lock owner AND the opened_at matches what the caller observed (guards against stale eventually-consistent reads). A lease expiry is stamped so that if the winning environment is recycled before completing the probe, others can take over once the lease lapses.

PARAMETER DESCRIPTION
name

Circuit name.

TYPE: str

owner

Identifier of the environment attempting the probe.

TYPE: str

opened_at

The opened_at the caller observed, kept stable across the transition.

TYPE: int

RETURNS DESCRIPTION
bool

True if this environment won the probe lock, False if another environment already holds it.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def try_acquire_half_open(self, name: str, owner: str, opened_at: int) -> bool:
    """
    Atomically elect a single environment to run the half-open probe.

    The conditional write succeeds only when the circuit is OPEN with no existing
    lock owner AND the ``opened_at`` matches what the caller observed (guards against
    stale eventually-consistent reads). A lease expiry is stamped so that if the
    winning environment is recycled before completing the probe, others can take over
    once the lease lapses.

    Parameters
    ----------
    name : str
        Circuit name.
    owner : str
        Identifier of the environment attempting the probe.
    opened_at : int
        The ``opened_at`` the caller observed, kept stable across the transition.

    Returns
    -------
    bool
        ``True`` if this environment won the probe lock, ``False`` if another
        environment already holds it.
    """
    # Lease = recovery_timeout gives the probe a full cycle to complete.
    probe_lease_expiry = int(datetime.datetime.now().timestamp()) + self.recovery_timeout
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.HALF_OPEN,
        opened_at=opened_at,
        half_open_owner=owner,
        probe_lease_expiry=probe_lease_expiry,
        expiry_timestamp=self._durable_ttl(),
    )
    try:
        self._put_record(record, condition="half_open", expected_opened_at=opened_at)
    except CircuitBreakerExistingLockError:
        return False
    self._save_to_cache(record)
    return True

CircuitStateRecord dataclass

CircuitStateRecord(name: str, state: CircuitState, failure_count: int = 0, opened_at: int | None = None, half_open_owner: str | None = None, probe_lease_expiry: int | None = None, expiry_timestamp: int | None = None)

The persisted state of a single circuit.

One record exists per circuit name. This is the utility's internal representation; user code never sees it directly, only the CircuitInfo produced by :meth:to_circuit_info.

PARAMETER DESCRIPTION
name

Circuit name, used as the partition key in the store.

TYPE: str

state

Current circuit state.

TYPE: CircuitState

failure_count

Consecutive failures recorded by the environment that last wrote the record.

TYPE: int DEFAULT: 0

opened_at

Unix timestamp (seconds) the circuit opened. Anchors the recovery timeout; None while closed.

TYPE: int | None DEFAULT: None

half_open_owner

Identifier of the execution environment that won the half-open probe lock, if any.

TYPE: str | None DEFAULT: None

expiry_timestamp

Unix timestamp (seconds) for the store's TTL attribute.

TYPE: int | None DEFAULT: None

METHOD DESCRIPTION
to_circuit_info

Project this record to the public CircuitInfo handed to user code.

to_circuit_info

to_circuit_info() -> CircuitInfo

Project this record to the public CircuitInfo handed to user code.

Strips internal fields (half_open_owner, expiry_timestamp) so no persistence detail leaks across the public boundary.

RETURNS DESCRIPTION
CircuitInfo

Public snapshot of the circuit.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/record.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def to_circuit_info(self) -> CircuitInfo:
    """
    Project this record to the public ``CircuitInfo`` handed to user code.

    Strips internal fields (``half_open_owner``, ``expiry_timestamp``) so no
    persistence detail leaks across the public boundary.

    Returns
    -------
    CircuitInfo
        Public snapshot of the circuit.
    """
    return CircuitInfo(
        name=self.name,
        state=self.state,
        failure_count=self.failure_count,
        opened_at=self.opened_at,
    )

base

Abstract persistence layer for the Circuit Breaker utility.

Concrete backends (DynamoDB, cache) subclass :class:CircuitBreakerPersistenceLayer and implement the small set of store primitives. The base class owns the local read-through cache and the fail-open policy so every backend behaves identically.

CLASS DESCRIPTION
CircuitBreakerExistingLockError

Internal signal that a conditional half-open probe write lost the race.

CircuitBreakerPersistenceLayer

Abstract base class for circuit breaker persistence layers.

CircuitBreakerExistingLockError

Bases: Exception

Internal signal that a conditional half-open probe write lost the race.

CircuitBreakerPersistenceLayer

CircuitBreakerPersistenceLayer()

Bases: ABC

Abstract base class for circuit breaker persistence layers.

Owns the per-environment read cache and the fail-open behavior. Subclasses implement :meth:_get_record, :meth:_put_record, and :meth:_update_record for a specific store.

A persistence layer is keyed by circuit name, not by a payload hash, which is the main reason it does not reuse the Idempotency persistence layer.

METHOD DESCRIPTION
configure

Bind the layer to a circuit and its configuration.

get_state

Return the current circuit state, reading the store only on a cache miss.

save_closed

Persist a transition back to CLOSED and reset counters.

save_open

Persist a CLOSED to OPEN transition.

save_reopen

Persist a HALF_OPEN to OPEN transition after a failed probe.

try_acquire_half_open

Atomically elect a single environment to run the half-open probe.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
51
52
53
54
55
56
57
58
59
def __init__(self) -> None:
    """Initialize defaults; real configuration happens in :meth:`configure`."""
    self.circuit_name: str = ""
    self.local_cache_max_age: int = 5
    self.recovery_timeout: int = 30
    # Maps circuit name -> the unix timestamp the locally cached record goes stale.
    # Kept separate from the record's durable ``expiry_timestamp`` (the store TTL) so
    # the short in-memory freshness window is never mistaken for the long store TTL.
    self._cache: LRUDict = LRUDict(max_items=LOCAL_CACHE_MAX_ITEMS)

configure

configure(config: CircuitBreakerConfig, circuit_name: str) -> None

Bind the layer to a circuit and its configuration.

Called once per invocation by the handler; the assignments are cheap and the same persistence instance is reused across invocations within an environment.

PARAMETER DESCRIPTION
config

Configuration providing the local cache TTL and recovery timeout.

TYPE: CircuitBreakerConfig

circuit_name

The circuit this layer instance serves.

TYPE: str

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
def configure(self, config: CircuitBreakerConfig, circuit_name: str) -> None:
    """
    Bind the layer to a circuit and its configuration.

    Called once per invocation by the handler; the assignments are cheap and the
    same persistence instance is reused across invocations within an environment.

    Parameters
    ----------
    config : CircuitBreakerConfig
        Configuration providing the local cache TTL and recovery timeout.
    circuit_name : str
        The circuit this layer instance serves.
    """
    self.circuit_name = circuit_name
    self.local_cache_max_age = config.local_cache_max_age
    self.recovery_timeout = config.recovery_timeout

get_state

get_state(name: str) -> CircuitStateRecord

Return the current circuit state, reading the store only on a cache miss.

A cache miss (cold start or expired local entry) forces a read-through before the caller routes the request, so a freshly started environment never assumes a circuit is closed without checking.

Fail-open: if the store read itself raises, the circuit is treated as CLOSED. A circuit breaker must never become the outage it is meant to prevent.

PARAMETER DESCRIPTION
name

Circuit name.

TYPE: str

RETURNS DESCRIPTION
CircuitStateRecord

The current record, a synthesized closed record if none exists yet, or a synthesized closed record if the store could not be reached.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
def get_state(self, name: str) -> CircuitStateRecord:
    """
    Return the current circuit state, reading the store only on a cache miss.

    A cache miss (cold start or expired local entry) forces a read-through before
    the caller routes the request, so a freshly started environment never assumes a
    circuit is closed without checking.

    Fail-open: if the store read itself raises, the circuit is treated as
    ``CLOSED``. A circuit breaker must never become the outage it is meant to
    prevent.

    Parameters
    ----------
    name : str
        Circuit name.

    Returns
    -------
    CircuitStateRecord
        The current record, a synthesized closed record if none exists yet, or a
        synthesized closed record if the store could not be reached.
    """
    cached = self._retrieve_from_cache(name)
    if cached is not None:
        return cached

    try:
        record = self._get_record(name)
    except Exception:
        # Fail open without caching, so the next invocation retries the store rather
        # than serving a synthesized CLOSED for the whole local cache window.
        logger.warning(
            "Failed to read circuit state for '%s'; failing open (treating as CLOSED).",
            name,
            exc_info=True,
        )
        return CircuitStateRecord(name=name, state=CircuitState.CLOSED)

    # A missing record is the expected cold-start case, not a failure: treat it as a
    # closed circuit and cache it like any other read.
    if record is None:
        record = CircuitStateRecord(name=name, state=CircuitState.CLOSED)

    self._save_to_cache(record)
    return record

save_closed

save_closed(name: str) -> None

Persist a transition back to CLOSED and reset counters.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
225
226
227
228
229
230
231
232
233
234
def save_closed(self, name: str) -> None:
    """Persist a transition back to CLOSED and reset counters."""
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.CLOSED,
        failure_count=0,
        expiry_timestamp=self._durable_ttl(),
    )
    self._update_record(record)
    self._save_to_cache(record)

save_open

save_open(name: str, failure_count: int, opened_at: int) -> None

Persist a CLOSED to OPEN transition.

PARAMETER DESCRIPTION
name

Circuit name.

TYPE: str

failure_count

Consecutive failures that tripped the circuit.

TYPE: int

opened_at

Unix timestamp the circuit opened; anchors the recovery timeout.

TYPE: int

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
def save_open(self, name: str, failure_count: int, opened_at: int) -> None:
    """
    Persist a CLOSED to OPEN transition.

    Parameters
    ----------
    name : str
        Circuit name.
    failure_count : int
        Consecutive failures that tripped the circuit.
    opened_at : int
        Unix timestamp the circuit opened; anchors the recovery timeout.
    """
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.OPEN,
        failure_count=failure_count,
        opened_at=opened_at,
        expiry_timestamp=self._durable_ttl(),
    )
    self._put_record(record)
    self._save_to_cache(record)

save_reopen

save_reopen(name: str, opened_at: int) -> None

Persist a HALF_OPEN to OPEN transition after a failed probe.

If this write fails the stored row stays in HALF_OPEN, but it does not strand the circuit: the probe lease expiry still applies, so once it lapses another environment wins the next election and drives the transition.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def save_reopen(self, name: str, opened_at: int) -> None:
    """
    Persist a HALF_OPEN to OPEN transition after a failed probe.

    If this write fails the stored row stays in HALF_OPEN, but it does not strand the
    circuit: the probe lease expiry still applies, so once it lapses another
    environment wins the next election and drives the transition.
    """
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.OPEN,
        opened_at=opened_at,
        expiry_timestamp=self._durable_ttl(),
    )
    self._update_record(record)
    self._save_to_cache(record)

try_acquire_half_open

try_acquire_half_open(name: str, owner: str, opened_at: int) -> bool

Atomically elect a single environment to run the half-open probe.

The conditional write succeeds only when the circuit is OPEN with no existing lock owner AND the opened_at matches what the caller observed (guards against stale eventually-consistent reads). A lease expiry is stamped so that if the winning environment is recycled before completing the probe, others can take over once the lease lapses.

PARAMETER DESCRIPTION
name

Circuit name.

TYPE: str

owner

Identifier of the environment attempting the probe.

TYPE: str

opened_at

The opened_at the caller observed, kept stable across the transition.

TYPE: int

RETURNS DESCRIPTION
bool

True if this environment won the probe lock, False if another environment already holds it.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/base.py
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def try_acquire_half_open(self, name: str, owner: str, opened_at: int) -> bool:
    """
    Atomically elect a single environment to run the half-open probe.

    The conditional write succeeds only when the circuit is OPEN with no existing
    lock owner AND the ``opened_at`` matches what the caller observed (guards against
    stale eventually-consistent reads). A lease expiry is stamped so that if the
    winning environment is recycled before completing the probe, others can take over
    once the lease lapses.

    Parameters
    ----------
    name : str
        Circuit name.
    owner : str
        Identifier of the environment attempting the probe.
    opened_at : int
        The ``opened_at`` the caller observed, kept stable across the transition.

    Returns
    -------
    bool
        ``True`` if this environment won the probe lock, ``False`` if another
        environment already holds it.
    """
    # Lease = recovery_timeout gives the probe a full cycle to complete.
    probe_lease_expiry = int(datetime.datetime.now().timestamp()) + self.recovery_timeout
    record = CircuitStateRecord(
        name=name,
        state=CircuitState.HALF_OPEN,
        opened_at=opened_at,
        half_open_owner=owner,
        probe_lease_expiry=probe_lease_expiry,
        expiry_timestamp=self._durable_ttl(),
    )
    try:
        self._put_record(record, condition="half_open", expected_opened_at=opened_at)
    except CircuitBreakerExistingLockError:
        return False
    self._save_to_cache(record)
    return True

dynamodb

DynamoDB persistence backend for the Circuit Breaker utility.

CLASS DESCRIPTION
CircuitBreakerDynamoDBPersistence

Store circuit state in an Amazon DynamoDB table, one item per circuit.

CircuitBreakerDynamoDBPersistence

CircuitBreakerDynamoDBPersistence(table_name: str, key_attr: str = 'id', state_attr: str = 'state', failure_count_attr: str = 'failure_count', opened_at_attr: str = 'opened_at', half_open_owner_attr: str = 'half_open_owner', probe_lease_expiry_attr: str = 'probe_lease_expiry', expiry_attr: str = 'expiration', boto_config: Config | None = None, boto3_session: Session | None = None, boto3_client: DynamoDBClient | None = None)

Bases: CircuitBreakerPersistenceLayer

Store circuit state in an Amazon DynamoDB table, one item per circuit.

The class name is prefixed with CircuitBreaker so a function using both the Idempotency and Circuit Breaker utilities can import both persistence layers without an alias.

PARAMETER DESCRIPTION
table_name

Name of the DynamoDB table that stores circuit state.

TYPE: str

key_attr

Partition key attribute holding the circuit name. Defaults to "id".

TYPE: str DEFAULT: 'id'

state_attr

Attribute holding the circuit state. Defaults to "state".

TYPE: str DEFAULT: 'state'

failure_count_attr

Attribute holding the consecutive failure count. Defaults to "failure_count".

TYPE: str DEFAULT: 'failure_count'

opened_at_attr

Attribute holding the open timestamp. Defaults to "opened_at".

TYPE: str DEFAULT: 'opened_at'

half_open_owner_attr

Attribute holding the half-open probe lock owner. Defaults to "half_open_owner".

TYPE: str DEFAULT: 'half_open_owner'

probe_lease_expiry_attr

Attribute holding the probe lease expiry timestamp. Defaults to "probe_lease_expiry".

TYPE: str DEFAULT: 'probe_lease_expiry'

expiry_attr

TTL attribute. Defaults to "expiration".

TYPE: str DEFAULT: 'expiration'

boto_config

Botocore configuration used when creating the client.

TYPE: Config DEFAULT: None

boto3_session

Session used to create the client.

TYPE: Session DEFAULT: None

boto3_client

Pre-built client; boto3_session and boto_config are ignored if given.

TYPE: DynamoDBClient DEFAULT: None

Example

Create a DynamoDB-backed circuit breaker store

1
2
3
4
5
from aws_lambda_powertools.utilities.circuit_breaker_alpha.persistence import (
    CircuitBreakerDynamoDBPersistence,
)

persistence = CircuitBreakerDynamoDBPersistence(table_name="CircuitBreakerState")
Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/dynamodb.py
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
def __init__(
    self,
    table_name: str,
    key_attr: str = "id",
    state_attr: str = "state",
    failure_count_attr: str = "failure_count",
    opened_at_attr: str = "opened_at",
    half_open_owner_attr: str = "half_open_owner",
    probe_lease_expiry_attr: str = "probe_lease_expiry",
    expiry_attr: str = "expiration",
    boto_config: Config | None = None,
    boto3_session: boto3.session.Session | None = None,
    boto3_client: DynamoDBClient | None = None,
):
    if boto3_client is None:
        boto3_session = boto3_session or boto3.session.Session()
        boto3_client = boto3_session.client("dynamodb", config=boto_config)
    self.client = boto3_client

    user_agent.register_feature_to_client(client=self.client, feature="circuit_breaker")

    self.table_name = table_name
    self.key_attr = key_attr
    self.state_attr = state_attr
    self.failure_count_attr = failure_count_attr
    self.opened_at_attr = opened_at_attr
    self.half_open_owner_attr = half_open_owner_attr
    self.probe_lease_expiry_attr = probe_lease_expiry_attr
    self.expiry_attr = expiry_attr

    self._deserializer = TypeDeserializer()

    super().__init__()

record

Internal record type for circuit state held in a persistence store.

CLASS DESCRIPTION
CircuitStateRecord

The persisted state of a single circuit.

CircuitStateRecord dataclass

CircuitStateRecord(name: str, state: CircuitState, failure_count: int = 0, opened_at: int | None = None, half_open_owner: str | None = None, probe_lease_expiry: int | None = None, expiry_timestamp: int | None = None)

The persisted state of a single circuit.

One record exists per circuit name. This is the utility's internal representation; user code never sees it directly, only the CircuitInfo produced by :meth:to_circuit_info.

PARAMETER DESCRIPTION
name

Circuit name, used as the partition key in the store.

TYPE: str

state

Current circuit state.

TYPE: CircuitState

failure_count

Consecutive failures recorded by the environment that last wrote the record.

TYPE: int DEFAULT: 0

opened_at

Unix timestamp (seconds) the circuit opened. Anchors the recovery timeout; None while closed.

TYPE: int | None DEFAULT: None

half_open_owner

Identifier of the execution environment that won the half-open probe lock, if any.

TYPE: str | None DEFAULT: None

expiry_timestamp

Unix timestamp (seconds) for the store's TTL attribute.

TYPE: int | None DEFAULT: None

METHOD DESCRIPTION
to_circuit_info

Project this record to the public CircuitInfo handed to user code.

to_circuit_info

to_circuit_info() -> CircuitInfo

Project this record to the public CircuitInfo handed to user code.

Strips internal fields (half_open_owner, expiry_timestamp) so no persistence detail leaks across the public boundary.

RETURNS DESCRIPTION
CircuitInfo

Public snapshot of the circuit.

Source code in aws_lambda_powertools/utilities/circuit_breaker_alpha/persistence/record.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
def to_circuit_info(self) -> CircuitInfo:
    """
    Project this record to the public ``CircuitInfo`` handed to user code.

    Strips internal fields (``half_open_owner``, ``expiry_timestamp``) so no
    persistence detail leaks across the public boundary.

    Returns
    -------
    CircuitInfo
        Public snapshot of the circuit.
    """
    return CircuitInfo(
        name=self.name,
        state=self.state,
        failure_count=self.failure_count,
        opened_at=self.opened_at,
    )