Skip to content

Release Notes

This page is the source of truth for aio-fluid release notes. Each section below maps to a tagged release on GitHub. When a new tag is pushed, the matching section is extracted by .github/workflows/release.yml and published as the GitHub Release body.

v2.12.1

A packaging fix for the db extra.

Improvements and fixes

  • The db extra requires python-dateutil >= 2.9.0 instead of >= 3.0.0, so aio-fluid can be installed alongside packages that need dateutil 2.x, such as aiobotocore.

v2.12.0

Pagination cursors can be signed, so a client cannot change them, and Python 3.15 is supported. The sync database engine now uses psycopg 3 and some filters that used to be ignored now raise, see Breaking changes.

Breaking changes

  • The db extra installs psycopg[binary] (psycopg 3) instead of psycopg2-binary, and the sync engine uses the postgresql+psycopg driver. The minimum versions of fastapi, sqlalchemy, asyncpg and kubernetes are raised (#128).
  • With a cursor, the filters passed to Pagination.create are now applied on top of the filters carried by the cursor, and win on the same key. Before, they were ignored (pagination tutorial, #127).
  • A CRUD filter with an unknown operator, or an operator other than eq on a JSON value, raises a ValidationError instead of being silently dropped. An invalid filter value is reported against its column rather than as an invalid cursor (#128).

New features

  • The new FLUID_PAGINATION_SECRET setting signs every pagination cursor with an HMAC, and a cursor that is changed, unsigned or signed with another secret is rejected. A cursor limit outside 1 to DEFAULT_PAGINATION_MAX_LIMIT is replaced by that maximum (pagination tutorial, #127).
  • Python 3.15 is supported and tested in CI (#128).

Improvements and fixes

  • A QueueConsumer cancelled while stopping consumes the cancellation in get_message and exits its loop cleanly, with on_shutdown called as usual (workers tutorial, #128).
  • A crontab day of week range can end on 7 (Sunday), as in 1-7 (#128).
  • The create-schema database command commits the new schema, and create-ro-user with a database name keeps the original connection settings (#128).
  • Worker start errors show the worker name and state in the message, and a task run whose params cannot be serialized no longer fails at start (#128).
  • The test suite reaches full coverage (#128).

v2.11.1

A fix for CPU-bound tasks dispatched as Kubernetes Jobs.

Improvements and fixes

  • The job pod template no longer inherits the startup probe from the consumer container, alongside the liveness and readiness probes already removed, so a task job is not killed or held back by a probe meant for the long running service (K8s Jobs, #126).

v2.11.0

Task parameters are passed to the exec command as plain values, JSON is only needed for structured ones. The pydanclick dependency is gone, see Breaking changes.

Breaking changes

  • The cli extra no longer installs pydanclick, and the fluid.utils.patch_pydanclick module is removed.

Improvements and fixes

  • exec builds its options from the task params model directly: strings, emails, dates and other scalar fields are written as plain strings and validated by the model, booleans are --name/--no-name flags, a list of plain values is passed by repeating the option, and a required field can be given in --params. Invalid JSON in --params is reported as a usage error (task CLI reference, #125).

v2.10.1

The task run history endpoint gets more flexible filters: several values per filter and a new priority filter.

New features

  • The GET /tasks-history endpoint accepts several values for the task and state filters and matches runs with any of them, for example ?state=failure&state=aborted. A single value works as before (task plugin reference, #124).
  • A new priority filter returns runs with any of the given priorities (#124).

Improvements and fixes

  • The params filter responds with a 422 error when its value is not a JSON object, instead of passing it to the database query (#124).

v2.10.0

The db extra drops sqlalchemy-utils and requires a newer alembic. No public API changes, but check the minimum versions below before upgrading.

Improvements and fixes

  • sqlalchemy-utils is no longer a dependency. Creating, dropping and checking for a database now use plain SQLAlchemy against the postgres maintenance database. If your code imports sqlalchemy_utils, add it to your own dependencies (database reference, #123).
  • Dropping a database terminates its open connections with DROP DATABASE ... WITH (FORCE), which requires PostgreSQL 13 or later (#123).
  • The db extra requires alembic >= 1.20.0, which in turn requires SQLAlchemy 2 (#123).

v2.9.2

The task info served by the task manager reports every serializable field of a task.

New features

  • TaskInfo now includes short_description, timeout_seconds, k8s_config, retry, rate_limit_retry and env, so the /tasks endpoints expose the full task configuration. Retry policies are reported as RetryPolicyInfo, with exception types listed by class name. Note that env values are returned as they are, so do not put secrets there (task retry reference).

v2.9.1

A fix for database filters with long lists of values.

Improvements and fixes

  • A list passed as a filter value is sent to the database as a single array parameter (= ANY for eq, <> ALL for ne) rather than one parameter per value, so it can hold any number of values without hitting the 32767 parameter limit of a query. An empty list matches no row with eq and excludes none with ne (database tutorial, #121).

v2.9.0

Database queries can select only the columns they need, and pooled connections survive a database restart.

New features

  • CrudDB.db_select and Pagination.execute accept a columns argument to select only some columns rather than every column of the table. Large columns a query does not use are no longer read, and computed expressions can be selected alongside table columns (database tutorial, #120).

Improvements and fixes

  • Pooled database connections are tested when taken from the pool, so the connections a server dropped, for instance on a database restart or failover, are replaced rather than failing the next query. It is on by default and set by the new FLUID_DBPOOL_PRE_PING setting (settings, #120).

v2.8.3

A small addition to the task information served by the task manager API.

Improvements and fixes

  • Task information returned by the /tasks endpoint now includes cpu_bound and max_concurrency, so clients can see how each task executes without inspecting its code (#119).

v2.8.2

A fix for async task event handlers, the task database plugin included, silently stopping in a running consumer.

Improvements and fixes

  • The task database plugin no longer holds a Redis lock around its writes. The lock expired when a write took longer than five seconds, and releasing it raised LockNotOwnedError. Since CrudDB.db_upsert is atomic (v2.8.0) the lock is not needed (#118).
  • A task consumer stops when its async dispatcher stops running. An exception raised by an async handler ended the dispatcher while the consumer kept executing tasks, so every later event was silently dropped. The consumer now shuts down instead, and the application restarts (#118).

v2.8.1

A fix for CPU bound tasks run with the exec command of the task manager CLI.

Improvements and fixes

  • Running a CPU bound task with exec outside a consumer no longer spawns a further subprocess: the command calls the function the task was declared with directly, in its own process. A CPU bound task keeps that function wherever it is loaded, so the same task object works both in the consumer and in the subprocess it spawns (#117).
  • TaskManager.execute accepts in_process=True to run a CPU bound task in the current process instead of spawning one for it (#117).

v2.8.0

Upserts are atomic and can write many rows at once. CrudDB.db_upsert now runs a single INSERT ... ON CONFLICT (...) DO UPDATE statement, which requires its lookup columns to match a unique constraint: see Breaking changes below.

Breaking changes

  • CrudDB.db_upsert requires the columns of filters to match exactly a primary key or unique constraint of the table. Upserting on columns without one now fails with there is no unique or exclusion constraint matching the ON CONFLICT specification. Filter operators such as score:gt are no longer accepted and raise ValueError.
  • The filters and data passed to db_upsert must together form a valid row to insert, even when the row already exists, since Postgres checks NOT NULL constraints before it detects the conflict.
  • db_upsert without data updates the existing row to its own key values instead of selecting it, so it takes a row lock and fires UPDATE triggers.
  • Column onupdate defaults are not applied when db_upsert updates an existing row.

New features

  • CrudDB.db_upsert_many upserts a list of records on the given key columns, in batches of batch_size records (1000 by default) within a single transaction. Every record must have the same columns and no two records can share a key. See the upsert tutorial.

v2.7.0

An application can now place its tables on a database schema other than public. The database layer creates the schema for create_all, lists non-system schemas, and drop_all_schemas drops an explicit set of schemas.

New features

  • The Database metadata can hold tables on a schema other than public by passing schema to the table or a MetaData(schema=...) to CrudDB.from_env. Migration.create_all creates the declared schemas before the tables, so the tables do not fail with InvalidSchemaName.
  • Migration.schemas() returns the non-system schemas in the database.
  • Migration.drop_all_schemas(schemas=None) accepts an explicit sequence of schemas to drop; when omitted it keeps dropping only public, as before.

v2.6.0

An application that aliases the task run to bind its dependencies keeps the parameters of its tasks. Python 3.11 is no longer supported: see Breaking changes below.

Breaking changes

  • Python 3.11 is no longer supported, the minimum version is now 3.12. The alias resolution below relies on typing.TypeAliasType, which the standard library only exposes from 3.12.

Improvements and fixes

  • A task annotated with a type alias of the task run keeps its parameters model. An application binding its dependencies once, as in type AppTaskRun[P] = TaskRun[P, AppDeps], and annotating a task with AppTaskRun[Params] used to silently get EmptyParams: an alias is not a class, so it did not survive the inspection of the annotation, and the parameters passed when queueing the task were dropped. The alias is now resolved to the type it stands for before the annotation is read, both bare and subscripted.

v2.5.0

Task runs can queue other task runs and the chain is recorded on every run, CPU bound tasks behave the same way in a subprocess as on the event loop, and Kubernetes Jobs inherit the task timeout. The documentation gained a settings reference, a recipes cheat sheet and a page on pointing coding agents at the library.

  • A task run can queue another task run with TaskRun.queue, which records the queueing run in from_run_id and carries the root_run_id of the first run in the chain, so any run can be traced back to the one that started it. (#110)
  • CPU bound tasks now start the async event dispatcher in the subprocess that runs them, so lifecycle events reach the handlers and plugins there as well, and a consumer with CPU bound tasks that is not started from TaskManagerCLI raises the new CpuBoundEntryPointError on startup rather than failing when the first such task runs.
  • A Kubernetes Job created for a CPU bound task sets active_deadline_seconds from the task timeout_seconds, so the cluster terminates a Job that overruns. (#108)
  • New Settings reference page covering the environment variables that configure the task consumer, broker, database and HTTP client, including the prefix rules and the fields that keep an unprefixed name.
  • Fixed the Task Broker tutorial, which imported a name that does not exist, quoted the wrong default Redis port, and listed six outdated abstract methods instead of the sixteen a broker has to implement.
  • Admonition blocks are rendered as admonitions instead of literal text, which also fixes the note on retry delays in the Task Retry reference.
  • New recipes cheat sheet, a page on using the docs with AI agents and an AGENTS.md for contributors. (#111)
  • New tutorials on task dependencies, choosing a task manager and extending the FastAPI app. (#109, #111)
  • New comparison page placing the library next to Celery, RQ, arq and taskiq, with download numbers refreshed by a scheduled workflow, and a landing page rewritten around CPU bound work. (#104, #105, #107)

v2.4.3

Fixes two task queue issues: pydantic secret params were masked when a task run was serialized to the queue, and a params validation error on the consumer side crashed the worker.

  • Secret params now survive the round-trip through the task queue and the cpu-bound subprocess. Task runs are serialized for the queue with secret values revealed via the new params_dump helper; all other dumps (logs, endpoints) keep secrets masked. (#103)
  • A task run consumed from the queue with invalid params no longer kills the consumer worker. The broker raises the new TaskParamsError carrying the task run, and the consumer logs the error and marks the run as failed. (#103)

v2.4.2

Tasks can now be tagged at registration time.

  • The task registration methods accept an optional tags argument. When provided, the extra tags are merged into each task's own tags as it is registered — applied across register_task, register_from_module, and register_from_dict. (#102)
  • Bumped python-json-logger to >= 4.1.0 and switched JSON logging to the new pythonjsonlogger.json formatter path (the old pythonjsonlogger.jsonlogger module is deprecated). (#102)

v2.4.1

Task history can now be filtered by task tags.

  • Added a tags field to task history queries. Runs match when their task carries at least one of the given tags, resolved against the live registry. (#101)

v2.4.0

Lazy settings via pydantic-settings, JSONB params filtering for task history, and customisable route prefixes.

  • Settings are now lazy — resolved on first access instead of at import time. Env vars use a FLUID_ prefix by default; legacy unprefixed names are kept as aliases. (#100)
  • The task database plugin accepts a route_prefix parameter for customising history route URLs and replaces with_task_history_router() with a register_routes() method. (#100)
  • Task history queries support filtering by run params via a new params field (renamed from HistoryQuery to TaskHistoryQuery). (#99)
  • Database migration required: the params column is now JSONB with a GIN index. See the example migration for the schema changes.
  • Removed get_logger from fluid.utils.log. Task loggers are now obtained directly via logging.getLogger(module).

v2.3.1

v2.3.0 is broken — do not use it.

Fixes a regression in v2.3.0 where the httpx2 dependency was pinned to >=2.2.0, which fails on Python 3.14 builds missing the _zstd C extension. Pins httpx2 to >=2.0.0, <2.1.0 and switches all httpx imports to httpx2 for correct namespace resolution.

  • httpx2 is now pinned to >=2.0.0, <2.1.0 — versions 2.1.0+ require the compression.zstd stdlib module which is not available in all Python 3.14 builds. (#98)
  • All import httpx statements replaced with import httpx2 as httpx (or from httpx2 import ...) to ensure correct namespace resolution regardless of httpx2 version.
  • Added test coverage for HttpxClient and HttpxResponse.

v2.3.0

Moves development and documentation dependencies from optional-dependencies to dependency groups, switches to httpx2 for HTTP client support, and removes the inflection dependency.

  • dev and docs dependencies are now declared under [dependency-groups] instead of [project.optional-dependencies]. Installed via uv sync --all-groups.
  • The http extra now uses httpx2 instead of httpx. httpx2 provides the same httpx module so no code changes are required. (#97)
  • The inflection dependency has been removed. (#96)

v2.2.6

Adds tag filtering for task listings and fixes a race in the task database plugin.

  • Task listings can be filtered by tag: the GET /tasks endpoint and the ls command of the task CLI accept a repeatable tags option that returns only tasks carrying at least one of the given tags, and TaskInfo now reports each task's tags. (#94)
  • The task database plugin now serialises its per-run lifecycle writes with a dedicated task-run lock. CrudDB.db_upsert is not atomic — it issues an UPDATE and only INSERTs when nothing matched — so when the scheduler wrote the queued row and a consumer wrote the running row a few milliseconds later, the consumer's UPDATE could miss the not-yet-committed INSERT, fall through to its own INSERT and violate the task-runs primary key. Holding the lock around the upsert removes the race. (#95)
  • TaskRun.lock accepts an optional name to acquire a named sub-lock for the task run, and timeout now defaults to None. (#95)

v2.2.5

  • The task decorator accepts an env mapping of extra environment variables, injected into the subprocess for CPU-bound tasks and forwarded to the container for tasks dispatched as Kubernetes Jobs. (#90)

v2.2.4

Bug-fix release for the task scheduler.

  • Fix task interruption handling. (#89)
  • Fix stale concurrent tasks not being released. (#88)
  • Fix task abort behaviour. (#87)
  • Patch pydanclick to work with StrEnum. (#86)