Metadata-Version: 2.5
Name: z4j-scheduler
Version: 1.12.1
Summary: Engine-agnostic dynamic scheduler for the z4j stack (Apache 2.0)
Project-URL: Changelog, https://github.com/z4jdev/z4j-scheduler/blob/main/CHANGELOG.md
Project-URL: Documentation, https://docs.z4j.com/scheduler
Project-URL: Homepage, https://z4j.com
Project-URL: Issues, https://github.com/z4jdev/z4j-scheduler/issues
Project-URL: Source, https://github.com/z4jdev/z4j-scheduler
Author: z4j contributors
License: Apache-2.0
License-File: LICENSE
Keywords: celery,cron,distributed,dramatiq,queue,rq,scheduler,task,z4j
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: System :: Distributed Computing
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: astral==3.2
Requires-Dist: asyncpg>=0.30
Requires-Dist: croniter==6.2.4
Requires-Dist: fastapi>=0.135
Requires-Dist: grpcio>=1.80.0
Requires-Dist: httpx>=0.28
Requires-Dist: prometheus-client>=0.21
Requires-Dist: protobuf>=6.33.5
Requires-Dist: pydantic-settings>=2.14.2
Requires-Dist: pydantic>=2.9
Requires-Dist: python-dateutil==2.9.0.post0
Requires-Dist: six==1.17.0
Requires-Dist: structlog>=25.1
Requires-Dist: typer>=0.13
Requires-Dist: tzdata==2026.5
Requires-Dist: uvicorn[standard]>=0.32
Requires-Dist: uvloop>=0.21; sys_platform != 'win32'
Requires-Dist: z4j-core<2,>=1.12.0
Provides-Extra: apscheduler-import
Requires-Dist: apscheduler[sqlalchemy]>=3.10; extra == 'apscheduler-import'
Provides-Extra: arq-import
Requires-Dist: arq>=0.26; extra == 'arq-import'
Provides-Extra: celery-import
Requires-Dist: celery>=5.6; extra == 'celery-import'
Requires-Dist: django-celery-beat>=2.7; extra == 'celery-import'
Provides-Extra: huey-import
Requires-Dist: huey>=2.4; extra == 'huey-import'
Provides-Extra: rq-import
Requires-Dist: rq-scheduler>=0.14; extra == 'rq-import'
Requires-Dist: rq>=2.0; extra == 'rq-import'
Provides-Extra: taskiq-import
Requires-Dist: taskiq>=0.11; extra == 'taskiq-import'
Description-Content-Type: text/markdown

# z4j-scheduler

[![PyPI version](https://img.shields.io/pypi/v/z4j-scheduler.svg)](https://pypi.org/project/z4j-scheduler/)
[![Python](https://img.shields.io/pypi/pyversions/z4j-scheduler.svg)](https://pypi.org/project/z4j-scheduler/)
[![License](https://img.shields.io/pypi/l/z4j-scheduler.svg)](https://github.com/z4jdev/z4j-scheduler/blob/main/LICENSE)

The engine-agnostic dynamic scheduler for [z4j](https://z4j.com).

One service drives Celery, RQ, Dramatiq, Huey, arq, and TaskIQ from
a single dashboard. Schedules live in z4j's database, you edit
them live without restarting anything, every change made through
z4j is recorded in an HMAC-chained audit log, and importers +
exporters keep the door open in either direction. This is the canonical scheduler when you
want one place to manage cron / interval / one-shot / solar
schedules across mixed engines.

## Compatibility

Python 3.11+. PostgreSQL 17+ for shared-database HA (SQLite is supported
for single-node brain deployments). The scheduler sends each prepared fire
to the brain, which routes it to a connected Celery, RQ, Dramatiq, Huey,
arq, or TaskIQ agent; engine packages do not need to be installed beside the
scheduler service.

Full per-adapter matrix at <https://docs.z4j.com/reference/compatibility/>.

## What makes z4j-scheduler different

z4j-scheduler is a deliberate alternative to in-language schedulers
like celery-beat, rq-scheduler, and APScheduler. The differences
that matter day to day:

- **Engine-agnostic.** One scheduler service for every supported
  Python task engine. A project running Celery for legacy services
  and arq for a FastAPI rewrite uses the same scheduler for both,
  with one dashboard and one audit trail.
- **Live editing.** Schedules live in the brain's database.
  Create, edit, pause, resume, rename, and delete from the dashboard
  or REST API without restarting the scheduler service.
- **HMAC-chained audit log.** Every schedule mutation that goes
  through z4j records the actor, change, and time (plus the source IP
  for request-originated changes) in an
  HMAC-chained audit trail alongside the brain's other audit rows,
  and the database refuses a schedule change that arrives without a
  fresh revision and a matching change-log entry. That covers the
  dashboard, the API, config, and any adapter, including an older
  one. It does not cover a database role writing those tables
  directly, which can supply the revision and the change-log entry
  itself; guard those credentials accordingly.
- **HA-ready.** Multiple scheduler instances can run against the
  same Postgres database. Depending on the configured backend, Postgres
  advisory locks elect one global leader or one leader per project; followers
  stay warm. Failover latency follows the configured leader heartbeat, and
  slots that age during a handoff are handled by the per-schedule catch-up
  policy.
- **Migration tooling.** The CLI imports celery-beat (static and
  django-celery-beat), rq-scheduler, APScheduler SQLAlchemy jobstores, system
  crontab, Huey periodic tasks, arq cron jobs, and taskiq schedule labels. It
  exports reviewable Celery, RQ, APScheduler, crontab, Huey, arq, or taskiq
  configuration; Dramatiq has no native scheduler, so `--from dramatiq` and
  `--to dramatiq` print migration guidance instead. Generated output is
  advisory and must be reviewed and tested
  against the target scheduler by the operator. This is not a
  lossless rollback: target formats cannot represent every z4j schedule kind
  or policy, and unsupported rows render as comments for manual handling.

## What it ships

| Capability | Notes |
|---|---|
| Schedule kinds | cron, interval, one-shot, solar (sunrise / sunset / dawn / dusk / noon / midnight at a given lat / lon) |
| Live editing | dashboard and REST API require no restart; declarative config reconciles when a configured framework startup hook or helper/CLI invokes it |
| Engine fan-out | Celery, RQ, Dramatiq, Huey, arq, TaskIQ |
| Importers | celery / django-celery-beat / rq-scheduler / apscheduler / cron / huey / arq / taskiq / dramatiq; the dramatiq value prints guidance only, since Dramatiq has no native scheduler |
| Exporters | celery / rq / apscheduler / cron / huey / arq / taskiq / dramatiq; generated output is advisory, target limitations can require manual handling, and the dramatiq value renders guidance only |
| HA leader election | Postgres advisory locks; global or per-project leadership, with warm followers |
| Audit log | every mutation through z4j is HMAC-chained; the database refuses a schedule change with no matching change-log entry, though a role holding direct write access to those tables can supply both |
| Catch-up policy | per-schedule: skip, fire one missed, fire all missed |
| Timezones | IANA zones validated at the boundary; DST fall-back fold fixed (no double-fires); spring-forward gap handled |
| Trigger surface | brain validates and dispatches operator-triggered fires directly; manual fires do not advance the cadence cursor |

## Install

Standalone (recommended for production):

```bash
pip install z4j-scheduler
export Z4J_SCHEDULER_BRAIN_GRPC_URL=brain.internal:7701
export Z4J_SCHEDULER_BRAIN_REST_URL=https://brain.internal
export Z4J_SCHEDULER_TLS_CERT=/etc/z4j/scheduler.crt
export Z4J_SCHEDULER_TLS_KEY=/etc/z4j/scheduler.key
export Z4J_SCHEDULER_TLS_CA=/etc/z4j/ca.crt
export Z4J_SCHEDULER_BIND_HOST=127.0.0.1
z4j-scheduler serve
```

On the brain host, install `z4j[scheduler-grpc]` and enable the brain's mTLS
scheduler listener. A separate scheduler installation does not add the gRPC
runtime to the brain environment. The loopback operational bind above keeps
`/metrics` local to that host; a non-loopback production bind also requires a
metrics auth token or explicitly disabled metrics.

Embedded inside z4j (recommended for homelab and small teams):

```bash
pip install z4j z4j-scheduler
# then enable in brain settings: Z4J_EMBEDDED_SCHEDULER=true
```

With the flag on, the brain spawns and supervises a
`z4j-scheduler serve` subprocess for you, no separate deployment
unit to manage.

Migrate existing schedules in:

```bash
pip install 'z4j-scheduler[celery-import]'
z4j-scheduler import \
  --from celery \
  --celery-app myapp.celery:app \
  --project myproject \
  --brain-url https://brain.example.com \
  --api-token "$Z4J_SCHEDULER_BRAIN_API_TOKEN"
```

The other importer subcommands follow the same shape (`--from rq`,
`--from apscheduler`, `--from django-celery-beat`, `--from huey --huey-app
pkg.module:huey`, `--from arq --arq-settings pkg.worker:WorkerSettings`,
`--from taskiq --taskiq-broker pkg.tkq:broker`); each has its own extra
(`rq-import`, `apscheduler-import`, `huey-import`, `arq-import`,
`taskiq-import`). Add
`--dry-run` to print the parsed schedules as JSONL for review
instead of writing them to the brain. `--verify` also implies dry-run; a normal
run with neither flag pushes the imported schedules immediately.

## When to choose z4j-scheduler

You probably want it if:

- You run more than one Python task engine and want one schedule
  surface across all of them.
- An auditor or a security review asks who paused the nightly
  billing job last Tuesday and you don't have a clean answer.
- You want to edit schedules without restarting a static-config scheduler.
- You want HA scheduling without standing up a second control
  plane.
- You're considering a one-time migration from celery-beat /
  rq-scheduler / APScheduler / Huey / arq / taskiq and want reviewable import
  and export tooling while
  accepting the target format's limitations.

You probably don't need it if:

- You run a single engine (typically Celery), have no compliance
  pressure, and the in-language scheduler already meets your needs.
  Stay where you are; we ship z4j-celerybeat / z4j-rqscheduler /
  z4j-apscheduler as adapters that surface those schedules in the z4j
  dashboard and expose the controls each backend supports, without replacing
  the native scheduler or taking ownership of its cadence.

## Documentation

Full docs at [docs.z4j.com/scheduler/](https://docs.z4j.com/scheduler/).
The migration guide at
[docs.z4j.com/scheduler/migrating-from-celery-beat/](https://docs.z4j.com/scheduler/migrating-from-celery-beat/)
walks the importer + dashboard verification path step by step.

## License

Apache-2.0, see [LICENSE](LICENSE). `z4j-scheduler` is independently
installable from the AGPL-licensed `z4j` server distribution; consult the
license terms for the obligations that apply to your deployment.
The accepted `Z4J_SCHEDULER_PRO_LICENSE_KEY` setting is a reserved, inert
compatibility field; setting it enables no feature.

## Links

- Homepage: https://z4j.com
- Documentation: https://docs.z4j.com
- PyPI: https://pypi.org/project/z4j-scheduler/
- Issues: https://github.com/z4jdev/z4j-scheduler/issues
- Changelog: [CHANGELOG.md](CHANGELOG.md)
- Security: security@z4j.com (see [SECURITY.md](SECURITY.md))
