Skip to main content

3tears-scheduled-jobs

A generic, payload-agnostic, multipod-safe scheduled-jobs core. Every agent/skill/webhook/conversation-specific concept is stripped out, leaving only the scheduling machinery.

What it gives you:

  • scheduled_tick_job(...) -- one cross-pod-locked tick pump. Acquire the nats_distributed_lock at a caller-supplied key; on LockHeld skip silently; on KvError degrade open (the per-row optimistic-CAS is the real guard); sweep abandoned in-flight fires per kind group; enumerate due rows of the routed kinds; per-row CAS-claim + reschedule; invoke the handler registered for that row's kind; drift / missed-fire accounting; per-row failure isolation. Takes the store(s), a DispatchRoutes table, and the NATS client as parameters, with no domain knowledge.
  • Per-kind routing, and an unrouted kind is inert. The pump's kind -> handler table is matched EXACTLY -- no wildcard, no default handler, no fall-through -- and it also scopes the due-row scan (a SQL predicate, not a Python filter) and the reaper sweep. A row whose kind has no handler is refused with an unrouted_kind failure metric and an ERROR event, and is deliberately not claimed, so the occurrence survives until its handler is registered. Silent misdelivery is the failure this prevents: a row absorbed by another kind's dispatcher does that dispatcher's work and records it as a success.
  • Per-kind reap thresholds. dispatch_reap_after_seconds_by_kind overrides DEFAULT_DISPATCH_REAP_AFTER_SECONDS per kind, so a kind whose work legitimately runs for hours is not reaped on the 15-minute baseline. Kinds sharing a threshold sweep in one query. Note the age is measured from dispatch start, not last activity, so the threshold alone only moves the false-reap cliff -- pair it with progress-conditioned renewal in the executor.
  • A distinct lock key per pump. JobConfig.tick_lock_key defaults to DEFAULT_TICK_LOCK_KEY; consumers running more than one pump in a process MUST vary it, or the pumps serialise against each other for no reason.
  • BackgroundDispatch -- for fires that take long. The pump awaits each handler inline, so a tick lasts as long as all its fires together and a kind due every minute waits behind every slow one. Wrap the handlers (routes = {kind: background.wrap(h) ...}) and each fire is handed off (JobFireResult.handed_off: the tick leaves the row 'dispatching' and moves on), runs as its own task, and finalizes its own row with its real outcome. One fire per kind is in flight at a time: in-process, and across pods through nats_distributed_lock on in_flight_lock_key(kind). A fire that finds its kind still running is recorded as a success whose output carries IN_FLIGHT_SKIP_OUTPUT_KEY. max_concurrent bounds how many run at once. The reaper counts from the tick, so each fire runs under the smaller of its own timeout and the time left before its kind's reap threshold (less REAP_MARGIN_SECONDS), and a timeout that could never fit is refused at construction; pass the pump's own config. Each fire is recorded exactly once: a process that dies mid-fire leaves the row to the reaper, as before, and aclose() records what it cancels (a fire whose body already finished keeps its result). Opt-in: a pump that does not wrap its handlers behaves exactly as before. Exclusion groups keep kinds that share something (one client object, one file handle) off each other: exclusion_groups={kind: group}. Kinds in one group never run at the same time; they take turns in the order they reach the group. A fire waits for its group's turn before it takes a slot and before its timeout starts, so waiting costs neither; the reap clock does keep running, so the wait is bounded by it, and a fire still waiting when its time runs out is recorded as failed without running, naming its group. EVENT_FIRE_WAITING_EXCLUSION_GROUP logs each wait with the kind holding the turn. Groups hold within one BackgroundDispatch (one process). A waiting fire holds its kind's cross-pod in-flight lock, so another pod records that kind as skipped instead of running it twice.
  • compute_next_fire_at(...) -- the pure reschedule math for every schedule type (daily_at, every_n_hours, random_within_window, one_shot_at, cron, relative_delay, interval) and both missed-fire policies (coalesce, catch_up). The cron branch imports APScheduler lazily, so non-cron consumers pay nothing.
  • ScheduleStore / FireStore Protocols -- the exact surface the tick engine calls. The engine depends only on these, so a typed consumer collection can implement them.
  • A default store -- ScheduledJobEntity / JobFireEntity + collections + scheduled_jobs / job_fires table factories + a v001 migration, keyed on an opaque kind (TEXT) + payload (JSONB). A simple consumer can use it as-is with no table of its own.
  • Generic config / events / metrics -- a JobConfig protocol, the tick / fire / drift event-name constants, and cardinality-bounded Prometheus instruments.

The engine is pure-async, one tick per call with no internal polling. Drive cadence with whatever scheduler you like (an APScheduler IntervalTrigger, a while True: await asyncio.sleep(...), and so on). The engine does not own the scheduler.

Release files for 3tears-scheduled-jobs 0.49.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for 3tears-scheduled-jobs 0.49.0
File Size Uploaded
3tears_scheduled_jobs-0.49.0.tar.gz 98.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for 3tears-scheduled-jobs 0.49.0
File Interpreter ABI Platform
3tears_scheduled_jobs-0.49.0-py3-none-any.whl Python 3 none any Details

Total release size: 164.5 kB

Release files / 3tears_scheduled_jobs-0.49.0.tar.gz

Download URL 3tears_scheduled_jobs-0.49.0.tar.gz
Size 98.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f8e7ee0059a6db9ca2a3556218ba306a1bf7cf478eda08b70a7221381fef6b1b
BLAKE2b-256 checksum
How to use checksums
7730447e98c78aeffe0bf99eb5acc7fb467cfc0fe428d1942d7b8c5fee2fb5b8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release files / 3tears_scheduled_jobs-0.49.0-py3-none-any.whl

Download URL 3tears_scheduled_jobs-0.49.0-py3-none-any.whl
Size 66.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bc026a4dd3c9a1641deb37d3303829488d6365d096c2c635d5cc91b31af4526f
BLAKE2b-256 checksum
How to use checksums
e9caa833563c767ad98626340d2b3ab536582b7851d60779491bc0a1cab575cb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 23, 2026.

Transparency log

Release history Release notifications | RSS feed

0.52.1

2 release files

0.52.0

2 release files

0.51.1

2 release files

0.51.0

2 release files

0.50.0

2 release files

This release

0.49.0 This release

2 release files

0.48.0

2 release files

0.47.1

2 release files

0.47.0

2 release files

0.46.1

2 release files

0.46.0

2 release files

0.45.1

2 release files

0.45.0

2 release files

0.44.0

2 release files

0.43.0

2 release files

0.42.0

2 release files

0.41.4

2 release files

0.41.3

2 release files

0.41.2

2 release files

0.41.1

2 release files

0.41.0

2 release files

0.40.0

2 release files

0.39.0

2 release files

0.38.0

2 release files

0.37.0

2 release files

0.30.0

2 release files

0.29.0

2 release files

0.28.0

2 release files

0.27.0

2 release files

0.26.1

2 release files

0.26.0

2 release files

0.25.0

2 release files

0.24.7

2 release files

0.24.6

2 release files

0.24.5

2 release files

0.24.4

2 release files

0.24.3

2 release files

0.24.2

2 release files

0.24.1

2 release files

0.24.0

2 release files

0.23.9

2 release files

0.22.4

2 release files

0.22.3

2 release files

0.22.2

2 release files

0.22.1

2 release files

0.22.0

2 release files

0.21.0

2 release files

0.20.0

2 release files

0.19.4

2 release files

0.19.3

2 release files

0.19.2

2 release files

0.19.1

2 release files

0.19.0

2 release files

0.18.0

2 release files

0.17.9

2 release files

0.17.8

2 release files

0.17.7

2 release files

0.17.6

2 release files

0.17.5

2 release files

0.17.4

2 release files

0.17.3

2 release files

0.17.2

2 release files

0.17.1

2 release files

0.17.0

2 release files

0.16.1

2 release files

0.16.0

2 release files

0.15.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page