Repository navigation
Releases
5.2.0 — maintenance
Maintenance: nothing changes for a project using the package. Upgrading has nothing to do for 5.1 → 5.2.
django-redis-aiogram 3.1.1 is the last of that line. Importing it raises a FutureWarning pointing at the move to this package, and SECURITY.md lists 3.x as unsupported.
Infrastructure
5.1.0 — the webhook awaits its handlers
Added
-
A webhook server of the bot container's own:
start_tgbot --serve. Serving the view
from the project's web tier still works and needs nothing new; what it costs is that an
update is handled beside the requests people are waiting on, competing for that process's
threads and its database connections.--serveruns a server whose only routes are the
webhook's, in the container that already has the bot's loop -- on that same loop, so the
container still turns exactly one.The path is not configured twice: it is read from
WEBHOOK_URL, which is the URL
manage.py tgbot_webhook setgave Telegram, and both routes are declared -- the identity
one and the process's own -- so serving a second bot stays a row rather than a redeploy.It needs the new
webhookextra:pip install "django-aiogram[webhook]". The refusal
names every package of that group that is absent or older than the floor the extra
declares, read from the installed metadata rather than from a list in the source, so a
package added to the extra is required from the next release with nothing else edited.
Where that metadata cannot be read at all -- a source tree nobody installed -- it falls
back to the one package the server imports, rather than letting the check pass.--serveis also refused where it would bind a port
nothing posts to: a polling deployment, or a run that also passed--no-updates.
Fixed
-
The webhook awaits its handlers instead of blocking on them. Under ASGI a
synchronous view runs on the thread asgiref lends the request, and that thread
is also the thread-sensitive executor a handler is given when it reaches the
ORM -- throughafirst,agetorsync_to_async. Waiting for the update on
that thread meant the handler waited for a thread that was waiting for the
handler: neither side ever moved, Telegram timed the delivery out and
redelivered, and every retry stranded one more request thread and the database
connection it had opened. A deployment taking a handful of updates an hour ran
out ofmax_connectionsovernight, and every request after that -- the
health probe included -- was refused by the database.telegram_webhookis a coroutine now, andTelegramBot.afeed_updateis the
awaiting half offeed_update. Nothing about the update's path changes: it
still goes to the loop the bot runs on, the response still says what the
handlers did, and a shutdown still answers503so Telegram redelivers. A
project that routes the view by name needs no edit; one that calls
telegram_webhookitself now awaits it, as does a test driving it directly.feed_updatekeeps its synchronous shape for the callers that have one, and
says in its docstring what it costs an async caller.The update is handed to the loop from a plain worker thread rather than one
asgiref is borrowing. A borrowed thread carries its context into everything
scheduled from it, and an update outlives the submission: on 3.10 and 3.11 a
handler reaching the ORM asked for an executor that had already quit and died
withCurrentThreadExecutor already quit or is broken. It also means an
update no longer inherits the request's context at all, which is the right way
round -- what Telegram sent is not part of the request that carried it. -
A cancelled request is no longer answered as a shutdown. Only
close()
refuses an update in flight, and only that refusal is worth a503asking
Telegram to redeliver. A caller that went away -- the client hung up, the
server is tearing the request down -- cancels the coroutine instead, and that
cancellation now travels on rather than becoming an answer nobody is left to
read. The two look alike on the future, sinceasyncio.wrap_futurepasses a
cancellation down to the one it wraps, so the shutdown marks what it cancels.
Reachable only fromafeed_update: a thread blocked infeed_updatehas
nobody to cancel it.The set those updates are tracked in has a guard of its own now, held for an
add, adiscardor the shutdown's snapshot and never while an update is being
handed over. It wasloop_lock, which a submission holds for the whole
hand-over -- and the awaiting half forgets its update from the event loop, so
waiting for that lock there would have stalled every other request behind one
submission.A caller that leaves during the hand-over leaves nothing behind either. The
hand-over runs on a thread and cannot be stopped, so it still produces an
update nobody is waiting on; that one is forgotten rather than left in the set
close()drains, where every shutdown would have waited the whole drain for it
and then cancelled it.
5.0.0
A major, and the one thing it changes for every project is the name of the settings dict. What
else breaks depends on how far into the package a project reaches:
| what breaks | who meets it | what to do |
|---|---|---|
TELEGRAM_BOT is dead; the dict is TELEGRAM_BOT_DEFAULTS and a bot is a section under TELEGRAM_BOTS |
everybody | rename it; E050 reports the old name, and a project that kept its token there has none |
| the four tables and three new columns | everybody | manage.py migrate -- on both databases where EVENT_LOG_DATABASE names one of its own |
| every consumer must be on 5.0 before a second bot sends | a deployment adding one | deploy the bot containers first; one bot is still either order |
Event, Sent and Taken each gained a field, at the end |
a project unpacking one whole | read the fields by name; construction and indexing are unchanged |
Broker.option, call_timeout and broker_class take the settings to read from |
a project that overrides one | accept the argument and pass it on, or a bot's own settings are silently replaced by the shared ones |
Broker.take, take_nowait and reclaim take a set of queues |
a project shipping a Broker |
nothing, unless it opts into MULTIPLEXES: the argument defaults to the queue it addresses and is left off where there is one |
a DELIVERY of your own is told which bot, which queue, and which queues |
a project shipping one | take route, settings and queues; it is refused by name where it cannot serve what the container asked for |
Upgrading walks the whole
hop, including the index on the event log that is a decision rather than a formality on a large
feed.
Added
- Four tables, for the bots a project configures at run time rather than in
settings.py.
The full 5.0.0 entry -- Added, Changed, Removed, Fixed -- is in CHANGELOG.md, and the new pages are Multiple bots and Dynamic bots.
4.1.0
Eight additions, none of which change what 4.0 already does: every setting keeps its meaning and its default, the wire format is unchanged, and the bot container and the web tier may be deployed in either order. One required step — run migrate: this release adds two tables.
- A send can name a time instead of going now.
etaon every form that queues, in a table because no transport can hold a message for a week. - A failed send can be replayed from the row that recorded it.
manage.py tgbot_replayrebuilds the call from the event log, and two runs at once cannot recover one message twice — each failure is claimed against a unique constraint before its message is queued. - A send can be checked before it is made, by aiogram rather than by us.
bot.send(SendMessage(chat_id=42, text='hello')): a misspelled method is impossible and a wrong argument is a type error at the call site. This package declares none of those signatures on purpose. - A caller can find out which message its send produced.
bot.outcome()reads the ending back from the feed. - A send inside
transaction.atomic()can now wait for the commit, so a rolled-back transaction sends nothing. - Testing helpers, so a project's suite stops reading this package's wire format: an in-memory broker,
capture_sends, and apytestfixture. - A Prometheus exporter, so the metrics seam has a consumer.
- A value the log could not keep whole says so —
detailcarries markers instead of quietly shortened data, which is what makes a replay from a recorded row trustworthy.
Full entry, with what each one costs: CHANGELOG.md. Upgrading from 4.0: Upgrading.
4.0.0.post1
The documentation is a site now. The same 21 pages, in the same files, built by Material for
MkDocs and published at https://corneizer.github.io/django-aiogram/ — navigation, search, a
dark theme, versions, and a link that survives a release. The GitHub wiki is a stub per page
pointing at its replacement; a wiki page cannot redirect, so a reader gets a link to click.
.post1 is what PEP 440 has for this: the same release, published again because what surrounds it
changed. PyPI learns the new Documentation link only from a new upload, and that is what this is.
One code change rides along, stated rather than buried. eventlog/signals.py refuses a
receiver whose __qualname__ raises. Signal.send_robust evaluates that name inside its own
except block, so an unnameable receiver does not merely fail to be logged — it aborts the
dispatch loop, and every receiver after it in the list is never called. Measured, then gated.
Also in it, from turning three documented universal claims into tests: every boolean setting
is read through coerce_bool, the event log only ever writes with save(force_insert=True), and
every aiogram model is tagged with type(value).__name__. The claims were in the pages before
they were checked; now the pages and the code fail together or not at all.
Full detail: CHANGELOG.md.
Upgrading from 4.0.0 is pip install -U django-aiogram and nothing else.
4.0.0 — four transports, and the queue is a setting
The package is django-aiogram, and the queue is a setting. Redis was the transport; it is now
one of four, and which one carries a message is BROKER. Handlers stay ordinary Django app code and
bot.send() still works from anywhere in the project.
pip install 'django-aiogram[redis]' # Redis list, the default, or Redis Streams
pip install 'django-aiogram[rabbitmq,redis]' # RabbitMQ
pip install 'django-aiogram[kafka,redis]' # KafkaOne extra per transport, for the queue. redis is in the other two lines for the FSM store:
FSM_STORAGE defaults to aiogram's Redis one, so a bot keeping chat state needs that driver
whichever transport carries its messages — FSM_STORAGE: 'memory' is what drops it, and
E019 says so at startup rather than letting the dispatcher fail.
Upgrading from 3.x is a rename, an extra and a migrate, in that order — Upgrading walks it
step by step, including what to do with the old event-log table. It was rehearsed end to end on a
real consumer project before this tag: a 3.1 install with rows on the old table and messages on the
list, and a clean install into a database that had never seen the package.
Four transports
| transport | BROKER |
extra |
|---|---|---|
| Redis list (default) | django_aiogram.broker.redis_list.RedisListBroker |
[redis] |
| Redis Streams | django_aiogram.broker.redis_streams.RedisStreamsBroker |
[redis] |
| RabbitMQ | django_aiogram.broker.rabbitmq.RabbitMQBroker |
[rabbitmq] |
| Kafka | django_aiogram.broker.kafka.KafkaBroker |
[kafka] |
Your code does not change with the row: same bot.send(), same handlers, same event log, same
manage.py start_tgbot. What differs is what becomes of a message whose worker was killed
mid-send, and what recovery is — a command, a clock, or the broker's own doing. Each transport has
a page of its own, with the guarantee it gives and what a publish costs, measured.
No driver is a dependency of this package any more. pip install django-aiogram imports and
runs manage.py; the transport you name brings its own driver, and manage.py check refuses at
startup with the pip install line when the two disagree.
Renames, and what to do about them
- the distribution and import path are
django-aiogram/django_aiogram, the environment prefix
isDJANGO_AIOGRAM_, the logger isdjango_aiogram, check ids readdjango_aiogram.EXXX; send_redis→enqueue,asend_redis→aenqueue;get_redisandredis_connmoved to
django_aiogram.redis;bot.send()is untouched;- the event log has a table of its own,
django_aiogram_event. The 3.x table is left exactly where
it is —manage.py tgbot_move_eventscopies its rows in bounded, resumable chunks, and
I003says it is there to be copied; DELIVERYtakes a dotted path, so a consumer you write goes in it.DeliveryKindis gone;src/groups by what a thing is for, so several import paths moved. Two are ones a project wrote
down itself —DATABASE_ROUTERSand the webhook view inurls.py— andE048catches the first.
Nothing is aliased. An alias that outlives the release it was written for becomes a second name for
the same thing for ever, so each old name is listed against its replacement on Upgrading
instead.
Also in this release
- A short id on every event-log row, twelve characters of the correlation id in Crockford's
base32, searchable in the admin.manage.py tgbot_backfill_short_idsfills rows written before it. - The bot process resets a dead database connection, which it never did:
close_old_connections()
is wired to request signals and a bot worker has no requests. Measured in production — a Postgres
restart left every handler raisingInterfaceErrorfor ten hours while the probe said healthy. - The container probe runs on every transport, including an install with no redis-py at all.
One refusal changed wording with it:redis is unreachableis nowthe broker is unreachable. - The transport is closed at shutdown, which it never was — worst on Kafka, where a member that
disappears without leaving its group holds its partitions until the session times out. - Both blocking deadlines come from the transport being read, not from
REDIS_TIMEOUT, which
three of the four never read.W004names the setting your deployment actually has.
The full entry, with every measurement and the decisions behind them, is in
CHANGELOG.md.
v4.0.0.dev0
A development release, published to reserve the name on PyPI. Nothing here is finished and
nothing here is supported. pip install django-aiogram will not find it — a pre-release needs
--pre — which is deliberate: there is no version of 4.0 worth installing yet.
What exists so far
- The package is
django-aiogram. Import fromdjango_aiogram, set environment variables
withDJANGO_AIOGRAM_, read logs from thedjango_aiogramlogger. The event log lives in a
new table and the 3.x one is left where it is. src/is organised by what a thing is for —config/,producer/,consumer/,
wire/,eventlog/, andbroker/for the transports. Several import paths moved; the
table inCHANGELOG.mdlists every one.- The broker contract is written, with the Redis list behind it as the first
implementation. Nothing is wired to it yet.
What is not here
The four transports. Redis list is the only one, and it is still reached the way 3.x reached
it. Redis Streams, RabbitMQ and Kafka are issues #3, #5 and #6, and redis is still a hard
dependency until #2 makes every driver an extra.
If you are on 3.x
Stay there. django-redis-aiogram 3.1.0 is the release to use, and it is not going anywhere.
The 4.0 upgrade notes are being written as the work lands — see the Upgrading page.