How to Implement a Temporary Block in Python
Build a reusable PenaltyBox class that temporarily blocks access after a failure and reports remaining lockout time.
Python code
36 linesclass PenaltyBox:
def __init__(self, block_seconds: int = 30):
self.block_seconds = block_seconds
self._blocked_until = 0.0
self._attempts = 0
def try_access(self, current_time: float) -> bool:
if self._blocked_until and current_time < self._blocked_until:
return False
return True
def record_failure(self, current_time: float) -> None:
self._attempts += 1
self._blocked_until = current_time + self.block_seconds
def remaining_seconds(self, current_time: float) -> float:
if self._blocked_until and current_time < self._blocked_until:
return round(self._blocked_until - current_time, 2)
return 0.0
if __name__ == "__main__":
mock_time = 1000.0
box = PenaltyBox(block_seconds=10)
box.record_failure(mock_time)
print(f"Immediately after failure — allowed: {box.try_access(mock_time)}")
print(f"Remaining: {box.remaining_seconds(mock_time)}s")
mock_time += 5
print(f"5s later — allowed: {box.try_access(mock_time)}")
print(f"Remaining: {box.remaining_seconds(mock_time)}s")
mock_time += 6
print(f"11s later — allowed: {box.try_access(mock_time)}")
print(f"Remaining: {box.remaining_seconds(mock_time)}s")
Output
Immediately after failure — allowed: False
Remaining: 10.0s
5s later — allowed: False
Remaining: 5.0s
11s later — allowed: True
Remaining: 0.0s
How it works
The PenaltyBox tracks a _blocked_until timestamp (in seconds) and increments _attempts on each failure. try_access compares the current time against _blocked_until to decide if access is allowed — returning False while blocked and True once the lockout expires. remaining_seconds computes the difference between the lockout deadline and now, rounded to two decimals, or zero when no block is active. The class accepts an injected block_seconds duration and uses a monotonic-style current_time parameter, so tests and real code can both feed the same clock source. Because the state is just two floats, the box is trivially serializable and easy to reason about under concurrency.
Common mistakes
- Using `time.time()` directly inside the class, which makes unit tests flaky without mocking
- Forgetting to reset `_blocked_until` to 0 after the lockout expires, causing stale checks
- Relying on wall-clock time instead of an injectable current_time for deterministic behavior
Variations
- Add a `reset()` method to clear `_attempts` and `_blocked_until` manually
- Use a `functools.lru_cache` or Redis-backed store for distributed locking
Real-world use cases
- Lock a user out of a login form for 30 seconds after five failed password attempts.
- Rate-limit webhook retries by blocking a client for a fixed window after repeated 429 responses.
- Pause a background worker after consecutive API auth failures, then automatically resume.
Sponsored
More from Reliability & rate limiting
- At Least Once with Idempotent Consumer in Python medium
- Build a Rate Limiter Decorator in Python easy
- Build a queue-based admission control system in Python easy
- Chaos Inject Random Failures in Python easy
- Circuit breaker failure threshold count in Python medium
- Exactly Once Processing Dedupe Mock in Python easy
Keep learning
Related tutorials and quizzes for this topic.