stapel-calendar
Calendar and scheduling: events with participants and RSVP, RFC 5545 recurring series (virtual expansion, on-demand materialization, per-instant cancellation and reschedule), availability windows with free/busy and bookable slot computation, ICS export, and event-driven reminders.
Part of the Stapel framework — composable Django apps that deploy as a monolith or as microservices without changing module code.
Install
pip install stapel-calendar
At a glance
| Fact | Value |
|---|---|
| Version | 0.7.2 |
| Python | >=3.11 (3.11, 3.12, 3.13, 3.14) |
| HTTP operations | 10 |
| Config axes | 1 |
| Usage surface | 31 |
| Extension points | 5 |
| Error codes | 49 |
| Fleet dependencies | stapel-auth (optional) · stapel-core · stapel-notifications (optional) |
Documentation
OpenAPI · capabilities.json · llms.txt (for agents)
What this is
A generic calendar core — Event / Participant / RSVP, an RFC 5545
recurrence engine (RRULE via python-dateutil, virtual expansion +
on-demand materialization), availability (working windows, free/busy,
slots) and ICS export. Two flavors of one domain — meetings and
bookings — share this core; everything app-specific lives behind seams.
Quick start
INSTALLED_APPS = [
# ...
"stapel_calendar",
]
# urls.py
path("calendar/", include("stapel_calendar.urls"))
Concepts
- Event — a scheduled thing (meeting/booking/appointment) with tz-aware
start/end, anownerand an opaquescope_key. No FK to any host concept (org, room) — scoping and resources are seams. - Recurrence — the series master stores a canonical RRULE. Occurrences are virtual until they gain their own state (an RSVP or a resource), at which point they are materialized (persisted) on demand.
- Availability — recurring working windows minus busy events give open booking slots.
from stapel_calendar import services
series = services.create_event(
owner=user, title="Standup",
start=start, end=end,
recurrence_type="weekdays", # -> FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR
)
occurrences = services.expand_event(series, range_start, range_end) # virtual
services.materialize(series, occurrence_start) # persist one + emit hook
busy = services.free_busy(user, range_start, range_end)
slots = services.compute_slots(user, range_start, range_end, slot_minutes=30)
Settings
All configuration lives in the STAPEL_CALENDAR namespace (dict setting,
flat setting, or env var — resolved lazily):
| Key | Default | Meaning |
|---|---|---|
SCOPE_PROVIDER |
…scope.DefaultScopeProvider |
Resolve/filter the opaque scope_key |
REMINDER_POLICY |
…reminders.DefaultReminderPolicy |
When/what to remind |
PRESETS |
{} |
Custom recurrence presets (merged over built-ins) |
REMINDER_OFFSETS |
[10] |
Minutes before start to remind |
REMINDER_SCAN_WINDOW_SECONDS |
60 |
Reminder cron granularity |
DEFAULT_EXPANSION_HORIZON_DAYS |
90 |
Default range end |
MAX_EXPANSION_OCCURRENCES |
1000 |
Expansion safety cap |
DEFAULT_SLOT_MINUTES |
30 |
Default slot length |
comm surface
| Kind | Name | Contract |
|---|---|---|
| Emit | calendar.occurrence.materialized |
An occurrence was persisted — subscribe to attach a resource |
| Emit | calendar.event.reminder_due |
A reminder is due — deliver it |
| Function | calendar.free_busy |
{user_id, start, end, scope_key?} -> {busy: [...]} |
Extension points
See MODULE.md — the agent-facing map of every fork-free seam (the resource hook, reminder policy, scope provider, recurrence presets, serializer seams, settings).
Development
pip install -e . && pip install pytest pytest-django ruff
./setup-hooks.sh
pytest tests/
License
MIT — see LICENSE.
This page is assembled by stapel-readme from docs/readme.md plus the contract artifacts in docs/. Edit the prose in docs/readme.md; the badges, facts and links above and below it are generated — do not hand-edit README.md.
Metadata
Release files for stapel-calendar 0.7.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| stapel_calendar-0.7.2.tar.gz | 110.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| stapel_calendar-0.7.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 194.6 kB
Release files / stapel_calendar-0.7.2.tar.gz
| Download URL | stapel_calendar-0.7.2.tar.gz |
|---|---|
| Size | 110.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d143c786aabfcf61bb2a1b11d0accb42969291deeacbc56b59adf3b6bd2f64e0
|
|
BLAKE2b-256 checksum How to use checksums |
1f210721d15cffd661fb2410565399de862f55a3de59e062fa35317851a63ad3
|
| 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 17, 2026.
Transparency logRelease files / stapel_calendar-0.7.2-py3-none-any.whl
| Download URL | stapel_calendar-0.7.2-py3-none-any.whl |
|---|---|
| Size | 84.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1a5926e29cb279ec8569d0dff3be0488df0d54d4db3f93824ad9934106352a71
|
|
BLAKE2b-256 checksum How to use checksums |
2f10440af05ed5adc0aaecc038e53e637258e37eea6c86ac8697de19968b7a11
|
| 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 17, 2026.
Transparency log