aiogram/aiogram/utils/backoff.py
Andrew df7b16d5b3
Some checks failed
Tests / tests (macos-latest, 3.10) (push) Has been cancelled
Tests / tests (macos-latest, 3.11) (push) Has been cancelled
Tests / tests (macos-latest, 3.12) (push) Has been cancelled
Tests / tests (macos-latest, 3.13) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.10) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.11) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.12) (push) Has been cancelled
Tests / tests (ubuntu-latest, 3.13) (push) Has been cancelled
Tests / tests (windows-latest, 3.10) (push) Has been cancelled
Tests / tests (windows-latest, 3.11) (push) Has been cancelled
Tests / tests (windows-latest, 3.12) (push) Has been cancelled
Tests / tests (windows-latest, 3.13) (push) Has been cancelled
Tests / pypy-tests (macos-latest, pypy3.10) (push) Has been cancelled
Tests / pypy-tests (macos-latest, pypy3.11) (push) Has been cancelled
Tests / pypy-tests (ubuntu-latest, pypy3.10) (push) Has been cancelled
Tests / pypy-tests (ubuntu-latest, pypy3.11) (push) Has been cancelled
EOL of Py3.9 (#1726)
* Drop py3.9 and pypy3.9

Add pypy3.11 (testing) into `tests.yml`

Remove py3.9 from matrix in `tests.yml`

Refactor not auto-gen code to be compatible with py3.10+, droping ugly 3.9 annotation.

Replace some `from typing` imports to `from collections.abc`, due to deprecation

Add `from __future__ import annotations` and `if TYPE_CHECKING:` where possible

Add some `noqa` to calm down Ruff in some places, if Ruff will be used as default linting+formatting tool in future

Replace some relative imports to absolute

Sort `__all__` tuples in `__init__.py` and some other `.py` files

Sort `__slots__` tuples in classes

Split raises into `msg` and `raise` (`EM101`, `EM102`) to not duplicate error message in the traceback

Add `Self` from `typing_extenstion` where possible

Resolve typing problem in `aiogram/filters/command.py:18`

Concatenate nested `if` statements

Convert `HandlerContainer` into a dataclass in `aiogram/fsm/scene.py`

Bump tests docker-compose.yml `redis:6-alpine` -> `redis:8-alpine`

Bump tests docker-compose.yml `mongo:7.0.6` -> `mongo:8.0.14`

Bump pre-commit-config `black==24.4.2` -> `black==25.9.0`

Bump pre-commit-config `ruff==0.5.1` -> `ruff==0.13.3`

Update Makefile lint for ruff to show fixes

Add `make outdated` into Makefile

Use `pathlib` instead of `os.path`

Bump `redis[hiredis]>=5.0.1,<5.3.0` -> `redis[hiredis]>=6.2.0,<7`

Bump `cryptography>=43.0.0` -> `cryptography>=46.0.0` due to security reasons

Bump `pytz~=2023.3` -> `pytz~=2025.2`

Bump `pycryptodomex~=3.19.0` -> `pycryptodomex~=3.23.0` due to security reasons

Bump linting and formatting tools

* Add `1726.removal.rst`

* Update aiogram/utils/dataclass.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update aiogram/filters/callback_data.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update 1726.removal.rst

* Remove `outdated` from Makefile

* Add `__slots__` to `HandlerContainer`

* Remove unused imports

* Add `@dataclass` with `slots=True` to `HandlerContainer`

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-10-06 19:19:23 +03:00

85 lines
2.1 KiB
Python

import asyncio
import time
from dataclasses import dataclass
from random import normalvariate
@dataclass(frozen=True)
class BackoffConfig:
min_delay: float
max_delay: float
factor: float
jitter: float
def __post_init__(self) -> None:
if self.max_delay <= self.min_delay:
msg = "`max_delay` should be greater than `min_delay`"
raise ValueError(msg)
if self.factor <= 1:
msg = "`factor` should be greater than 1"
raise ValueError(msg)
class Backoff:
def __init__(self, config: BackoffConfig) -> None:
self.config = config
self._next_delay = config.min_delay
self._current_delay = 0.0
self._counter = 0
def __iter__(self) -> "Backoff":
return self
@property
def min_delay(self) -> float:
return self.config.min_delay
@property
def max_delay(self) -> float:
return self.config.max_delay
@property
def factor(self) -> float:
return self.config.factor
@property
def jitter(self) -> float:
return self.config.jitter
@property
def next_delay(self) -> float:
return self._next_delay
@property
def current_delay(self) -> float:
return self._current_delay
@property
def counter(self) -> int:
return self._counter
def sleep(self) -> None:
time.sleep(next(self))
async def asleep(self) -> None:
await asyncio.sleep(next(self))
def _calculate_next(self, value: float) -> float:
return normalvariate(min(value * self.factor, self.max_delay), self.jitter)
def __next__(self) -> float:
self._current_delay = self._next_delay
self._next_delay = self._calculate_next(self._next_delay)
self._counter += 1
return self._current_delay
def reset(self) -> None:
self._current_delay = 0.0
self._counter = 0
self._next_delay = self.min_delay
def __str__(self) -> str:
return (
f"Backoff(tryings={self._counter}, current_delay={self._current_delay}, "
f"next_delay={self._next_delay})"
)