Release notes
v0.3.4 - Job and Task attrs datetime aware - 2026-04-10
Bug fixes
- Job datetime normalization:
Jobdatetimes (started_at,ended_at) loaded from SQLite are now correctly normalized to UTC-aware. Previously, the@model_validatoronJobwas dead code because SQLAlchemy bypasses Pydantic validators when hydrating table classes.
Improvements
- Generic datetime normalization via
@reconstructor: Added a@reconstructormethod onQuivModelBasethat automatically normalizes alldatetimefields to UTC-aware on every DB load. This applies to bothTaskDBandJobmodels (and any future models), replacing the per-model dead-code validators. - Removed dead
@model_validatorfromTaskDBandJobmodels. delete_task()now has test coverage forTaskNotFoundErroron 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.ymlnow 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.
v0.3.3 - fixed inteval option for tasks - 2026-04-09
Breaking changes
- Default interval scheduling changed to fixed intervals:
fixed_intervaldefaults toTrue, meaning next run is now scheduled from the job start time rather than completion time. Setfixed_interval=Falseto restore the previous wait-between-runs behavior.
What's new
-
fixed_intervalper-task scheduling mode:add_task()accepts a newfixed_intervalparameter:True(default) — next run at fixed intervals from job start time. If a run exceeds the interval, missed intervals are skipped.False— next runintervalseconds after job completion (old behavior).
Other changes
finalize_task_after_job()acceptsjob_started_atfor fixed-interval scheduling.
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.2...v0.3.3
v0.3.2 - Removed unique task name constraint - 2026-04-09
Breaking changes
-
Task operations now use
task_idinstead oftask_name: All public methods that previously accepted atask_namestring now accept thetask_id(UUID string) returned byadd_task(). This removes the uniqueness constraint on task names — multiple tasks can now share the sametask_name.Affected methods: -
remove_task(task_id)— previouslyremove_task(task_name)-pause_task(task_id)— previouslypause_task(task_name)-resume_task(task_id)— previouslyresume_task(task_name)-run_task_immediately(task_id)— previouslyrun_task_immediately(task_name)-get_task(task_id)— previouslyget_task(task_name)(by-name lookup)Removed methods: -
get_task_by_id()— merged intoget_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
TaskandJobmodel objects directly instead of untypeddict[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 raisesConfigurationErroron duplicatetask_name. Each call returns a uniquetask_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_secondsanderror_messageon the Job modelThe
Jobmodel now includes two new fields:duration_seconds: float | None— job runtime in seconds, set when the job finisheserror_message: str | None— error description, set when a job fails
Both fields are persisted in the database and available via
get_job()andget_all_jobs(), making it easy to inspect job history without parsing logs. -
Typed event listener callbacks
Event listeners now receive real
TaskandJobmodel objects with full IDE autocomplete and type checking.JOB_*events include the parentTaskalongside theJob, 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_idfor runtime operations. - Added admonitions and footnotes throughout docs for better readability.
- Added
_job_idtracing 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_idinstead oftask_name. prepare_invocation()in the execution layer usestask_idfor progress callback dispatch.- Removed
get_task_by_name(),get_task_id_by_name()from the persistence layer. - Renamed
get_task_by_id()toget_task()in the persistence layer. delete_task()andqueue_task_for_immediate_run()in the persistence layer now accepttask_idinstead oftask_name.finalize_job()now accepts optionalduration_secondsanderror_messageparameters.- Removed unused
timezoneimport from persistence module.
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.1...v0.3.2
v0.3.1 - Better task pickling - 2026-04-07
Breaking Changes
- The public methods
get_task(),get_task_by_id(), andget_all_tasks()returnTaskobjects with unpickledargs(tuple) andkwargs(dict) — ready for JSON serialization in FastAPI endpoints. - Internal model renamed: The SQLModel database model is now
TaskDB(internal only, not exported). External modelTaskis 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
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.3.0...v0.3.1
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)andremove_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 forstart(), andstop()as an alias forshutdown(), makingstart/stoppairs more natural in user code by @nandyalu in #19 Job.idnow usesUUIDand 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
v0.2.4 - Preserve task args order - 2026-04-07
What's Changed
add_taskargs as tuple to preserve order by @nandyalu in #18loggeracceptslogging.Loggeras well aslogging.LoggerAdapterby @nandyalu in #18
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.3...v0.2.4
v0.2.3 - Exception logging improvements - 2026-04-06
What's Changed
- Bump zensical from 0.0.24 to 0.0.27 by @dependabot[bot] in #8
- Bump zensical from 0.0.27 to 0.0.28 by @dependabot[bot] in #9
- Bump pytest-cov from 7.0.0 to 7.1.0 by @dependabot[bot] in #10
- Bump actions/configure-pages from 5 to 6 by @dependabot[bot] in #11
- Bump actions/deploy-pages from 4 to 5 by @dependabot[bot] in #12
- Bump zensical from 0.0.28 to 0.0.30 by @dependabot[bot] in #13
- Bump sqlmodel from 0.0.37 to 0.0.38 by @dependabot[bot] in #14
- Bump tzdata from 2025.3 to 2026.1 by @dependabot[bot] in #15
- Bump mypy from 1.19.1 to 1.20.0 by @dependabot[bot] in #16
- Improve job logging with task names and error details by @nandyalu in #17
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.2...v0.2.3
v0.2.2 - SQLModel Registry Fix - 2026-03-13
What's Changed
-
fix: quiv registry to not include user models. Updated the private
registryusage forSQLModelmodels ofQuivto the method from https://github.com/fastapi/sqlmodel/discussions/1539#discussioncomment-14229572 by @nandyalu in #7 -
Added a test to ensure user's
SQLModelwithtable=Truedoes not get created in Quiv database by @nandyalu in #7
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.0...v0.2.2
v0.2.1 - Fix Release Build - 2026-03-11
What's Changed
Full Changelog: https://github.com/nandyalu/quiv/compare/v0.2.0...v0.2.1
v0.2.0-Bug Fixes and minor updates - 2026-03-09
Breaking Changes
timezone_namerenamed totimezone— BothQuiv()andQuivConfig()now usetimezonefor the display timezone parameter. Update anytimezone_name=keyword arguments.register_handler/register_progress_callbackare now private — Renamed to_register_handler/_register_progress_callback. Useadd_task()instead, which handles registration internally.add_task()raises on duplicate task names — Callremove_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_eventinjection with per-jobthreading.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 resolution —
Quiv()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 markedRUNNINGduring 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/JobStatusare 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.tomlin path filters - Removed
masterbranch reference from docs deploy workflow
Initial Release (v0.1.0) - 2026-03-08
Initial Release