Skip to content

Releases

5.2.0 — maintenance

Choose a tag to compare

@CorneiZeR CorneiZeR released this 17 Oct 22:39
97bbedb

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

  • upload-artifact / download-artifact 7.0.2 / 8.0.2 in the release workflow (#169), release pins refreshed (#171)
  • ruff pinned to 0.17.0, datetime imported as dt (#170): a new ruff version now arrives as its own pull request instead of a red master

5.1.0 — the webhook awaits its handlers

Choose a tag to compare

@CorneiZeR CorneiZeR released this 14 Sep 16:30
e27d070

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. --serve runs 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 set gave 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 webhook extra: 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. --serve is 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 -- through afirst, aget or sync_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 of max_connections overnight, and every request after that -- the
    health probe included -- was refused by the database.

    telegram_webhook is a coroutine now, and TelegramBot.afeed_update is the
    awaiting half of feed_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 answers 503 so Telegram redelivers. A
    project that routes the view by name needs no edit; one that calls
    telegram_webhook itself now awaits it, as does a test driving it directly.

    feed_update keeps 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
    with CurrentThreadExecutor 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 a 503 asking
    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, since asyncio.wrap_future passes a
    cancellation down to the one it wraps, so the shutdown marks what it cancels.
    Reachable only from afeed_update: a thread blocked in feed_update has
    nobody to cancel it.

    The set those updates are tracked in has a guard of its own now, held for an
    add, a discard or the shutdown's snapshot and never while an update is being
    handed over. It was loop_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

Choose a tag to compare

@CorneiZeR CorneiZeR released this 13 Sep 21:54
6ebb398

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

Choose a tag to compare

@CorneiZeR CorneiZeR released this 05 Sep 22:27
faf31c2

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. eta on 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_replay rebuilds 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 a pytest fixture.
  • A Prometheus exporter, so the metrics seam has a consumer.
  • A value the log could not keep whole says so — detail carries 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

Choose a tag to compare

@CorneiZeR CorneiZeR released this 01 Sep 14:18
8a544e1

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

Choose a tag to compare

@CorneiZeR CorneiZeR released this 31 Aug 22:02
ce26dca

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]'           # Kafka

One 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
    is DJANGO_AIOGRAM_, the logger is django_aiogram, check ids read django_aiogram.EXXX;
  • send_redis → enqueue, asend_redis → aenqueue; get_redis and redis_conn moved 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_events copies its rows in bounded, resumable chunks, and
    I003 says it is there to be copied;
  • DELIVERY takes a dotted path, so a consumer you write goes in it. DeliveryKind is gone;
  • src/ groups by what a thing is for, so several import paths moved. Two are ones a project wrote
    down itself — DATABASE_ROUTERS and the webhook view in urls.py — and E048 catches 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_ids fills 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 raising InterfaceError for 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 unreachable is now the 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. W004 names 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

v4.0.0.dev0 Pre-release
Pre-release

Choose a tag to compare

@CorneiZeR CorneiZeR released this 22 Aug 13:29
ca1de47

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 from django_aiogram, set environment variables
    with DJANGO_AIOGRAM_, read logs from the django_aiogram logger. 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/, and broker/ for the transports. Several import paths moved; the
    table in CHANGELOG.md lists 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.