Skip to content

Release notes

v0.5.0 - Correctness & Stability (Phase 1/6) - 2026-07-26

Phase 1 of the roadmap to v1.0.0: three concurrency bug fixes.

Behavior changes

  • run_task_immediately() now raises TaskNotActiveError when the task is not active. Previously a running task could be dispatched a second time concurrently (breaking the no-overlap guarantee) and a paused task was silently un-paused. Resume paused tasks explicitly with resume_task().

What's new

  • shutdown(timeout=...) / stop(timeout=...) — optional bound on how long shutdown waits for the scheduler thread and in-flight jobs. Jobs that do not exit within the deadline are abandoned on their worker threads with a warning, so a hung handler can no longer block application shutdown forever. Default (timeout=None) keeps the previous wait-forever behavior.
  • New exception TaskNotActiveError (exported from quiv).

Fixes

  • remove_task() racing the dispatch loop could raise a KeyError that stalled the scheduler for 5 seconds; dispatch now skips removed tasks gracefully, and the shared handler/callback/stop-event registries are protected by a lock.
  • shutdown() now signals cancellation only to running jobs instead of scanning the entire retained job history.

Housekeeping

  • Roadmap phases renumbered: v0.3.x/v0.4.x were already consumed by earlier feature releases, so Phase 1 ships as v0.5.0 and later phases shift down accordingly (Phase 6 stays v1.0.0).
  • 7 new regression tests; docs, release notes, and AI-tooling artifacts updated; version bumped to 0.5.0.
  • Docs cleanup: all Markdown files unwrapped to one paragraph per continuous line (zensical can break rendering when a sentence/paragraph is split across source lines). No content changes.

Changes

v0.4.1 - AI Tooling & Async teardown fix - 2026-07-20

What's new

  • AI tooling — quiv now teaches AI coding assistants (Claude Code, Cursor, Copilot, …) how to use it correctly, in #51:

    • Packaged agent guide — every install ships a condensed agent-facing reference at quiv/AGENTS.md inside the package (lands in site-packages next to the code): API surface, handler injection rules, cancellation semantics, FastAPI wiring, and common pitfalls. AI tools exploring installed dependencies pick it up with zero setup.
    • llms.txt — the docs site publishes llms.txt (index linking every docs page as raw Markdown) and llms-full.txt (the full docs in one file) following the llms.txt convention.
    • Claude Code plugin — the quiv repository doubles as a plugin marketplace shipping a quiv skill that Claude loads automatically whenever a conversation touches quiv. Install once with /plugin marketplace add nandyalu/quiv then /plugin install quiv@quiv; /plugin update quiv picks up the latest guidance.

    See the new AI Tools docs page for all three entry points.

Fixes

  • Per-invocation async event loops now finalize outstanding async generators and shut down the loop's default executor before closing. Previously, if a handler raised while an async generator was still active, Python could emit Task was destroyed but it is pending and async_generator_athrow was never awaited warnings during teardown. The original handler exception remains visible. by @d4rk22 in #50

Other changes

  • ci: skip coverage PR comment for fork PRs
  • upd: roadmap for v1.0.0
  • build(deps-dev): bump zensical from 0.0.43 to 0.0.45 by @dependabot[bot] in #42
  • build(deps): bump actions/checkout from 6 to 7 by @dependabot[bot] in #44
  • build(deps-dev): bump pytest from 9.0.3 to 9.1.1 by @dependabot[bot] in #45
  • build(deps): bump sqlmodel from 0.0.38 to 0.0.39 by @dependabot[bot] in #46
  • build(deps-dev): bump zensical from 0.0.45 to 0.0.50 by @dependabot[bot] in #47
  • build(deps): bump tzdata from 2026.2 to 2026.3 by @dependabot[bot] in #48
  • build(deps-dev): bump mypy from 2.1.0 to 2.2.0 by @dependabot[bot] in #49
  • fix: finalize async resources before closing job loops by @d4rk22 in #50

New Contributors

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.4.0...v0.4.1

Changes

v0.4.0 - run_on_main helper method - 2026-05-31

What's new

  • quiv.run_on_main(func, *args, **kwargs) — fire-and-forget helper that dispatches a callable onto the active Quiv instance's main event loop. Importable at module level (from quiv import run_on_main) and callable from anywhere in a task handler's call stack - no _progress_hook parameter to thread through intermediate functions.

    • Auto-detects whether the caller is already on the main loop's thread: sync targets run inline on-loop and async targets are scheduled via main_loop.create_task. From a worker thread, sync targets dispatch via call_soon_threadsafe and async targets via run_coroutine_threadsafe.
    • The same helper works from a FastAPI route handler on the main loop and from inside a Quiv task — one utility shared between request and task code (e.g., a WebSocket broadcast whose connected-clients state lives on uvicorn's loop).
    • Exceptions raised by the target are logged on the active Quiv's logger and swallowed, mirroring _progress_hook and event-listener semantics.

    See Running on the main event loop for the full walkthrough.

Other changes

  • Quiv.start() now registers the instance as the process-level "active" Quiv (cleared by shutdown()) so run_on_main can resolve a target loop when called outside a task context. Multiple concurrent instances log a warning naming the most recently started instance as the winner for out-of-task callers.
  • Inside _run_job, a ContextVar is set to the running Quiv instance for the duration of each handler invocation; this propagates into nested sync calls, the per-job async event loop, and asyncio.create_task spawned inside an async handler.

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.5...v0.4.0

Changes

v0.3.5 - Cleanup noise & update packages - 2026-05-27

What's new

  • Removed a debug logging statement from the set_timezone_to_utc method to reduce log verbosity by @nandyalu in #38

Other changes

  • Updated various python libraries to latest versions.
  • build(deps): bump actions/upload-pages-artifact from 4 to 5 by @dependabot[bot] in #24
  • build(deps-dev): bump zensical from 0.0.32 to 0.0.33 by @dependabot[bot] in #25
  • build(deps-dev): bump mypy from 1.20.0 to 1.20.1 by @dependabot[bot] in #26
  • build(deps-dev): bump zensical from 0.0.33 to 0.0.36 by @dependabot[bot] in #29
  • build(deps): bump tzdata from 2026.1 to 2026.2 by @dependabot[bot] in #28
  • build(deps-dev): bump mypy from 1.20.1 to 1.20.2 by @dependabot[bot] in #27
  • build(deps): bump orgoro/coverage from 3.2 to 3.3 by @dependabot[bot] in #30
  • build(deps-dev): bump zensical from 0.0.36 to 0.0.39 by @dependabot[bot] in #31
  • build(deps-dev): bump zensical from 0.0.39 to 0.0.41 by @dependabot[bot] in #32
  • build(deps-dev): bump mypy from 1.20.2 to 2.0.0 by @dependabot[bot] in #33
  • build(deps-dev): bump zensical from 0.0.41 to 0.0.42 by @dependabot[bot] in #34
  • build(deps-dev): bump mypy from 2.0.0 to 2.1.0 by @dependabot[bot] in #35
  • build(deps): bump pymdown-extensions from 10.21.2 to 10.21.3 in the uv group across 1 directory by @dependabot[bot] in #36
  • build(deps-dev): bump zensical from 0.0.42 to 0.0.43 by @dependabot[bot] in #37

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.4...v0.3.5

Changes

v0.3.4 - Job and Task attrs datetime aware - 2026-04-10

Bug fixes

  • Job datetime normalization: Job datetimes (started_at, ended_at) loaded from SQLite are now correctly normalized to UTC-aware. Previously, the @model_validator on Job was dead code because SQLAlchemy bypasses Pydantic validators when hydrating table classes.

Improvements

  • Generic datetime normalization via @reconstructor: Added a @reconstructor method on QuivModelBase that automatically normalizes all datetime fields to UTC-aware on every DB load. This applies to both TaskDB and Job models (and any future models), replacing the per-model dead-code validators.
  • Removed dead @model_validator from TaskDB and Job models.
  • delete_task() now has test coverage for TaskNotFoundError on missing task.
  • updated python packages to latest versions.

Documentation

  • New Testing page documenting all 108 tests with a breakdown of edge cases covered across 18 categories.

CI

  • docs-release.yml now waits for the release to be visible in the GitHub API before generating the changelog, fixing a race condition where the current release was missing from the generated docs.

Changes

v0.3.3 - fixed inteval option for tasks - 2026-04-09

Breaking changes

  • Default interval scheduling changed to fixed intervals: fixed_interval defaults to True, meaning next run is now scheduled from the job start time rather than completion time. Set fixed_interval=False to restore the previous wait-between-runs behavior.

What's new

  • fixed_interval per-task scheduling mode: add_task() accepts a new fixed_interval parameter:

    • True (default) — next run at fixed intervals from job start time. If a run exceeds the interval, missed intervals are skipped.
    • False — next run interval seconds after job completion (old behavior).

Other changes

  • finalize_task_after_job() accepts job_started_at for fixed-interval scheduling.

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.2...v0.3.3

Changes

v0.3.2 - Removed unique task name constraint - 2026-04-09

Breaking changes

  • Task operations now use task_id instead of task_name: All public methods that previously accepted a task_name string now accept the task_id (UUID string) returned by add_task(). This removes the uniqueness constraint on task names — multiple tasks can now share the same task_name.

    Affected methods: - remove_task(task_id) — previously remove_task(task_name) - pause_task(task_id) — previously pause_task(task_name) - resume_task(task_id) — previously resume_task(task_name) - run_task_immediately(task_id) — previously run_task_immediately(task_name) - get_task(task_id) — previously get_task(task_name) (by-name lookup)

    Removed methods: - get_task_by_id() — merged into get_task(task_id)

    Migration: Store the return value of add_task() and pass it to all task operations:

    # Before
    scheduler.add_task("my-task", handler, interval=60)
    scheduler.pause_task("my-task")
    
    # After
    task_id = scheduler.add_task("my-task", handler, interval=60)
    scheduler.pause_task(task_id)
    
  • Event listeners receive typed model objects instead of dicts

    Event listener callbacks now receive Task and Job model objects directly instead of untyped dict[str, Any].

    • TASK_* events: callback(event: Event, task: Task)
    • JOB_* events: callback(event: Event, task: Task, job: Job)

    Migration:

    # Before
    def on_completed(event, data):
        print(data["task_name"], data["duration"])
    
    # After
    from quiv.models import Task, Job
    
    def on_completed(event: Event, task: Task, job: Job):
        print(task.task_name, job.duration_seconds)
    

What's new

  • Duplicate task names allowed

    add_task() no longer raises ConfigurationError on duplicate task_name. Each call returns a unique task_id (UUID), so multiple tasks can share a display name. This is especially useful for one-shot tasks that may be scheduled repeatedly with the same name.

  • duration_seconds and error_message on the Job model

    The Job model now includes two new fields:

    • duration_seconds: float | None — job runtime in seconds, set when the job finishes
    • error_message: str | None — error description, set when a job fails

    Both fields are persisted in the database and available via get_job() and get_all_jobs(), making it easy to inspect job history without parsing logs.

  • Typed event listener callbacks

    Event listeners now receive real Task and Job model objects with full IDE autocomplete and type checking. JOB_* events include the parent Task alongside the Job, so listeners have full context without extra lookups.

Documentation

  • Updated all docs to reflect task_id-based API across getting-started, API reference, architecture, event listeners, bigger applications, progress callbacks, and exceptions pages.
  • Updated all code examples to use task_id for runtime operations.
  • Added admonitions and footnotes throughout docs for better readability.
  • Added _job_id tracing section to the "Why quiv?" page, describing how Trailarr uses injected job IDs as trace context for log correlation.
  • Rewrote event listeners documentation with typed callback signatures, updated examples, and new FastAPI WebSocket example using model objects.

Other changes

  • Internal handler and progress callback registries are now keyed by task_id instead of task_name.
  • prepare_invocation() in the execution layer uses task_id for progress callback dispatch.
  • Removed get_task_by_name(), get_task_id_by_name() from the persistence layer.
  • Renamed get_task_by_id() to get_task() in the persistence layer.
  • delete_task() and queue_task_for_immediate_run() in the persistence layer now accept task_id instead of task_name.
  • finalize_job() now accepts optional duration_seconds and error_message parameters.
  • Removed unused timezone import from persistence module.

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.1...v0.3.2

Changes

v0.3.1 - Better task pickling - 2026-04-07

Breaking Changes

  • The public methods get_task(), get_task_by_id(), and get_all_tasks() return Task objects with unpickled args (tuple) and kwargs (dict) — ready for JSON serialization in FastAPI endpoints.
  • Internal model renamed: The SQLModel database model is now TaskDB (internal only, not exported). External model Task is still the same - but is now only used for Public API responses and has correct types.

What's New

  • Async callbacks without event loop: Async progress callbacks and event listeners now run in a temporary event loop when no main loop is available, instead of being skipped with a warning.
  • Eager main loop resolution: The main event loop is now resolved at start() time (in addition to lazy resolution on first callback), improving reliability in FastAPI apps.

Documentation

  • Added return types to all method signatures in API docs (e.g., get_task(task_name: str) -> Task.
  • Updated architecture, progress-callbacks, and event-listeners docs to reflect new async callback behavior

Changes by @nandyalu in #20

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.0...v0.3.1

Changes

v0.3.0 - Better pickling and Event listeners - 2026-04-07

What's Changed

  • Changed argument serialization for task persistence from JSON to pickle, allowing most Python objects (except lambdas and inner functions) to be scheduled as arguments by @nandyalu in #19
  • Added support for global event listeners via add_listener(event, callback) and remove_listener(event, callback), including both sync and async callbacks, with robust dispatch and error handling. Events include all major task and job lifecycle transitions. by @nandyalu in #19
  • Introduced startup() as an alias for start(), and stop() as an alias for shutdown(), making start/stop pairs more natural in user code by @nandyalu in #19
  • Job.id now uses UUID and can be injected into task function (as _job_id) if function accepts it - can be used for task tracing / logging by @nandyalu in #19
  • Updated all relevant documentation by @nandyalu in #19

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.4...v0.3.0

Changes

v0.2.4 - Preserve task args order - 2026-04-07

What's Changed

  • add_task args as tuple to preserve order by @nandyalu in #18
  • logger accepts logging.Logger as well as logging.LoggerAdapter by @nandyalu in #18

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.3...v0.2.4

Changes

v0.2.3 - Exception logging improvements - 2026-04-06

What's Changed

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.2...v0.2.3

Changes

v0.2.2 - SQLModel Registry Fix - 2026-03-13

What's Changed

  • fix: quiv registry to not include user models. Updated the private registry usage for SQLModel models of Quiv to the method from https://github.com/fastapi/sqlmodel/discussions/1539#discussioncomment-14229572 by @nandyalu in #7

  • Added a test to ensure user's SQLModel with table=True does not get created in Quiv database by @nandyalu in #7

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.0...v0.2.2

Changes

v0.2.1 - Fix Release Build - 2026-03-11

What's Changed

  • Fix release build and auto update release-notes in docs by @nandyalu in #6

Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.0...v0.2.1

Changes

v0.2.0-Bug Fixes and minor updates - 2026-03-09

Breaking Changes

  • timezone_name renamed to timezone — Both Quiv() and QuivConfig() now use timezone for the display timezone parameter. Update any timezone_name= keyword arguments.
  • register_handler / register_progress_callback are now private — Renamed to _register_handler / _register_progress_callback. Use add_task() instead, which handles registration internally.
  • add_task() raises on duplicate task names — Call remove_task() first if you need to replace a task.

New Features

  • remove_task(task_name) — Remove a task and its handler/callback registrations.
  • Cooperative cancellation_stop_event injection with per-job threading.Event. See Cancellation docs.
  • Progress callbacks with four dispatch paths — Async/sync callbacks work with or without an event loop. See Progress Callbacks docs.
  • Lazy event loop resolutionQuiv() can be instantiated at module level before any asyncio loop exists. The event loop is resolved on first progress callback dispatch.
  • Backpressure — Scheduler defers dispatch when the thread pool is full. Late-starting jobs log a warning suggesting to increase pool_size.
  • TaskStatus.RUNNING — Tasks are marked RUNNING during execution, preventing concurrent runs of the same task.

Bug Fixes

  • Removed logger.setLevel(logging.DEBUG) — the library no longer forces a log level
  • Removed asyncio.get_event_loop() at init — fixes deprecation warnings and module-level instantiation
  • shutdown() now cleans up SQLite WAL (-wal, -shm) sidecar files
  • Cancellation detection no longer depends on handler accepting _stop_event

Improvements

  • TaskStatus / JobStatus are now proper (str, Enum) classes
  • History cleanup uses SQL-level filtering with col() wrapper (runs every 60s, not every tick)
  • Next run scheduled from job completion time, not dispatch time
  • All log timestamps use the configured display timezone consistently

Documentation

  • New pages: Bigger Applications, Progress Callbacks, Cancellation (with mermaid diagrams)
  • Architecture page now has a sequence diagram
  • API page: added pool size guidance, logger/timezone note blocks
  • Tabbed uv/pip install commands across all pages
  • GitHub repo link in docs header with edit/view source buttons

Tests

  • 8 new tests covering backpressure, late start warnings, concurrent run prevention, remove_task, progress callbacks without event loop, and task lifecycle
  • All existing tests updated for API changes

Repository

  • Added CODEOWNERS, issue templates, CONTRIBUTING.md, CODE_OF_CONDUCT.md
  • CI workflows now include pyproject.toml in path filters
  • Removed master branch reference from docs deploy workflow

Changes

Initial Release (v0.1.0) - 2026-03-08

Initial Release

Changes