Skip to main content

driftfdr

tests

English · Русский

English

Drift monitoring for fleets of ML models without a flood of false alarms

With tens or hundreds of models in production, drift detectors (Page-Hinkley, DDM, ADWIN, KS) at their default thresholds produce a stream of false alarms and needless retrains: every detector has an unknown false-alarm rate, and across many models the false alarms add up. driftfdr turns a detector's signal into a calibrated p-value that accounts for autocorrelation in the data, and decides which models to retrain across the whole fleet at once, at a chosen false-alarm level.

error of model 1 ─► detector ─► bootstrap calibration ─► p-value ─┐
error of model 2 ─► detector ─► bootstrap calibration ─► p-value ─┼─► correction for the number of models ─► retrain?
error of model K ─► detector ─► bootstrap calibration ─► p-value ─┘

Why

On real data — four public data sets (INSECTS, Electricity, Airlines, Covertype; 50 models each, rows in their original order) and hourly exchange rates of five currency pairs — the share of false retrains among all alarms:

data river Page-Hinkley, defaults driftfdr
INSECTS 50% 0%
Electricity 56% 0–8%
Covertype 75% 15%
Airlines 26% 0–6% (hourly steps)
FX rates, 40 volatility models, 2010–2026 no alarms at all, no degradation caught 0–10%, 63–100% of degradations above 30% caught

On a fixed suite of 100 synthetic scenarios the best driftfdr configuration reaches a detection F1 of 0.88, river's Page-Hinkley at its defaults 0.06: it catches every drift, but 97% of its alarms are false (experiment 21).

Evidently's standard drift test on the error series alarms in 20–78% of windows where the model did not get worse. NannyML's performance monitoring is about as accurate as driftfdr, but its ±3σ threshold is fixed and ignores the number of models; in driftfdr the false-alarm level, the correction for fleet size and the tolerated degradation are explicit. Monitoring is as fast as the river detector itself (about 1 µs per step per model); calibration takes a fraction of a second per model after each retrain (experiment 19). Details and all 24 experiments are in docs/experiments.md.

Installation

pip install driftfdr                 # numpy, scipy, pandas, matplotlib
pip install "driftfdr[prometheus]"   # + an integration: prometheus, mlflow or nannyml

From a clone of the repository: pip install -e ".[dev]" (tests) or pip install -e ".[datasets]" (river and scikit-learn for the real data sets used in the experiments).

Quick start

At every step the monitor takes the error (or loss) of each model and returns the models that should be retrained:

from driftfdr import CalibrationConfig, MeanShift, StreamingMonitor

monitor = StreamingMonitor(
    model_ids=["pricing", "eta", "demand"],
    detector_factory=lambda: MeanShift(3),     # alarm only if the degradation lasts 3 windows
    procedure="bonferroni",                    # correction for the number of models
    alpha=0.05,                                # false-alarm rate per window across the fleet
    n_ref=300, window=100, horizon=5,          # in steps; a step can be an hour (see bucket_means)
    calibration=CalibrationConfig(tolerance=0.05),  # retrain if the error rose by > 5 points
)

for errors in stream:                          # e.g. {"pricing": 0.21, "eta": 0.35}
    for model_id in monitor.update(errors):
        retrain(model_id)                      # the monitor starts collecting a new reference itself

monitor.save("monitor.npz")                    # the state survives a service restart
monitor = StreamingMonitor.load("monitor.npz", detector_factory=lambda: MeanShift(3))
  • If the models' errors move together (shared data source, shared features), set split_common=True. The detectors then watch each model's deviation from the fleet median, and the median itself is tested separately: its alarm sets monitor.fleet_alarm ("something shared broke"). Without it a common spike produces a burst of simultaneous false alarms (experiment 18). On all four real data sets the models are strongly correlated, and split_common removes most of that dependence (experiment 20). In this mode every model must report at every step.
  • To react without waiting for the end of a window, use detector_factory=ECUSUM with sequential=True: every model is checked at every step, with the same false-alarm budget per window (Bonferroni). At the same budget this caught drift 26% faster than the windowed PH (exp. 23); a step costs about 7 µs per model instead of 2.
  • Models may report irregularly or skip steps: each has its own window clock. Models can be added and removed on the fly (add_model, remove_model).
  • With delayed labels, pass a model's error when its label arrives.
  • A detector can be taken from river with its settings: detector_factory=lambda: from_river(drift.PageHinkley(mode="up")).
  • Full example: examples/streaming_demo.py. Visual demo: examples/live_demo.py — 40 correlated models, a common spike and a fleet-wide event; river at its defaults retrains 293 times (248 wasted), driftfdr 10 times (1 wasted) plus one fleet alarm. The output, results/demo.html, replays step by step in a browser.

Demo

Integrations

Optional adapters in driftfdr.integrations; each needs its own extra and nothing else changes.

where what install
Prometheus PrometheusExporter(monitor).serve(8000) publishes p-values, alarms and fleet alarms; alert rules in examples/prometheus/alerts.yml driftfdr[prometheus]
MLflow MLflowReporter(monitor, run_id=..., model_versions={id: (name, version)}) logs p-values to a run and tags the registered version of an alarmed model with driftfdr_retrain driftfdr[mlflow]
NannyML cbpe_estimated_error(reference, analysis, chunk_size): a label-free error estimate to monitor while labels are delayed; it relies on calibrated probabilities and cannot see changes in p(y|X), and on Electricity it failed (exp. 24): check it against the true error first driftfdr[nannyml]

examples/prometheus_service.py runs the monitor as a small service: it reads errors, exposes metrics, saves its state on exit and resumes from it. Evidently's drift tests are not wrapped: they assume independent observations and alarm in 20–78% of the windows where the model did not get worse (exp. 17), and a multiplicity correction over invalid p-values does not fix that.

what recommendation why
signal the model's error or loss, not its input features feature detectors alarm on harmless drift and miss harmful drift (exp. 11)
detector ECUSUM for speed (with a reference of a few hundred points or more, exp. 24), MeanShift(3) for robustness to short spikes; PageHinkley at the same false-alarm budget e-CUSUM catches drift 26% faster than PH and MeanShift(3), twice as fast on abrupt shifts (exp. 23); MeanShift(3) ignores one-window spikes (exp. 9, 14)
step and window time units that are multiples of the data's cycle (hour, day) otherwise a daily cycle looks like drift (exp. 16); bucket_means
null hypothesis tolerance tolerance > 0: "the error rose materially" on real data "nothing changed" never holds (exp. 8, 14)
tolerance δ tolerance_from_cost(retrain cost, horizon) retraining pays off when a rise δ over the horizon costs more than the retrain itself
rule "bonferroni" or "bh_window"; with split_common=True for correlated models Bonferroni holds its level under any correlation (exp. 13), BH is faster when many models drift at once (exp. 4); with split_common the best F1 on the whole benchmark, and the gain grows with correlation (exp. 18, 21)
not recommended ADWIN at strict levels, DDM, LORD/SAFFRON, e-BH ADWIN's calibrated tail is anti-conservative (exp. 10); DDM is weak; online FDR is slower at an equal number of false alarms (exp. 3, 12); e-BH misses a third of drifts (exp. 13)

How it works

  1. The detector as a continuous score. Every detector has one sensitivity parameter, and its binary signal is a threshold on an internal statistic. driftfdr uses that statistic; for Page-Hinkley and DDM it reproduces river exactly.
  2. Calibration. The null distribution of the statistic is estimated by bootstrap from the reference segment (data right after the model was trained): AR-sieve with parameter uncertainty for continuous signals, block bootstrap for 0/1 errors, a GPD tail for small p-values. A plain bootstrap under autocorrelation gives several times more false alarms than it promises (exp. 1).
  3. A fleet-level decision. The p-values of all models whose window has ended go through a multiplicity correction; alarmed models are retrained and collect a new reference.

The method: docs/method.md; the relation to the literature: docs/related_work.md; the reference for every class, method and function: docs/api.md. Every document except the API reference also has a Russian version (*.ru.md).

Limitations

  • The false-alarm level approximately holds for PH, KS and MeanShift (actual 0.044–0.064 at a nominal 0.05); ADWIN's tail calibration is about three times anti-conservative.
  • The results come from synthetic data, four public data sets and hourly FX rates; there has been no validation on production logs, and the models are not actually retrained (an alarm only starts a new reference).
  • The corrections' guarantees assume independent p-values; with strongly correlated models Bonferroni is the safer choice.
  • On noisy signals (for example, the daily loss of FX volatility models) only material degradations are caught: rises of less than ~15% are indistinguishable from noise (exp. 22).
  • The full list is in docs/experiments.md.

Repository layout

src/driftfdr/
  streaming.py     StreamingMonitor (split_common, sequential mode), CalibratedDetector, from_river
  detectors.py     Page-Hinkley, DDM, ADWIN, KS, sliding KS, MeanShift, e-CUSUM; AR whitening
  calibration.py   null distributions, p-values, tail, tolerance δ
  bootstrap.py     block, stationary and AR-sieve bootstrap
  online_fdr.py    Bonferroni, BH, Storey-BH, e-BH per window; BatchBH, LORD++, SAFFRON, LOND, alpha-investing
  preprocess.py    time-bucket means, tolerance from retrain cost, split_common
  monitor.py       batch monitoring of ready-made series (for the experiments)
  metrics.py       FDR, delays, misses, cost of delay, event-level precision / recall / F1
  streams.py       synthetic scenarios with known drift points, the 100-scenario benchmark_suite
  datasets.py      model fleets on INSECTS, Electricity, Airlines, Covertype and hourly FX rates
  integrations/    Prometheus exporter, MLflow reporter, NannyML CBPE signal (optional)
experiments/       24 experiments (exp1…exp24)
results/           tables and figures of the experiments, the demo page
docs/              method, related work, experiment log (English and *.ru.md), API reference (generated: python docs/gen_api.py)
tests/             83 tests (CI on Python 3.10–3.12): agreement with river, bootstrap, calibration, procedures, streaming
examples/          online monitoring, the replayable demo, a Prometheus service with alert rules

Tests and reproduction

pytest                                         # ~15 s
python experiments/exp1_calibration.py --quick # any experiment; without --quick, the full run

The experiments use fixed seeds and pinned settings, so they are reproducible; a full run of each takes from a few minutes to about an hour on 4 cores.

License

Apache License 2.0, see LICENSE and NOTICE. Copyright 2026 Artem Deviatov. The data sets are downloaded by the loaders and keep their own licenses; they are not part of this repository.


Русский

Мониторинг дрейфа для парка ML-моделей без лавины ложных тревог

Когда в эксплуатации десятки и сотни моделей, детекторы дрейфа (Page-Hinkley, DDM, ADWIN, KS) на порогах по умолчанию дают поток ложных тревог и лишних переобучений: у каждого детектора неизвестная доля ложных срабатываний, и при проверке многих моделей они складываются. driftfdr превращает сигнал детектора в откалиброванное p-значение с учётом автокорреляции данных и принимает решение о переобучении по всему парку моделей сразу, с заданным уровнем ложных тревог.

ошибка модели 1 ─► детектор ─► бутстреп-калибровка ─► p-значение ─┐
ошибка модели 2 ─► детектор ─► бутстреп-калибровка ─► p-значение ─┼─► поправка на число моделей ─► переобучить?
ошибка модели K ─► детектор ─► бутстреп-калибровка ─► p-значение ─┘

Зачем

На реальных данных — четыре открытых набора (INSECTS, Electricity, Airlines, Covertype; по 50 моделей, строки в исходном порядке) и часовые курсы пяти валютных пар — доля ложных переобучений среди всех тревог:

набор Page-Hinkley river по умолчанию driftfdr
INSECTS 50% 0%
Electricity 56% 0–8%
Covertype 75% 15%
Airlines 26% 0–6% (шаг — час)
Курсы валют, 40 моделей волатильности, 2010–2026 0 тревог, не поймано ни одно ухудшение 0–10%, поймано 63–100% ухудшений больше 30%

На фиксированном наборе из 100 синтетических сценариев F1 обнаружения у лучшей конфигурации driftfdr 0.88, у Page-Hinkley river по умолчанию 0.06: он ловит все дрейфы, но 97% его тревог ложные (эксп. 21).

Стандартный тест дрейфа Evidently на ряде ошибок тревожит в 20–78% окон, где модель не стала хуже. Мониторинг качества NannyML сопоставим с driftfdr по точности, но его порог ±3σ фиксирован и не зависит от числа моделей; в driftfdr уровень ложных тревог, поправка на размер парка и допустимое ухудшение задаются явно. По скорости мониторинг не медленнее самого детектора river (около 1 мкс на шаг на модель); калибровка занимает доли секунды на модель после каждого переобучения (эксп. 19). Подробности и все 24 эксперимента — в docs/experiments.ru.md.

Установка

pip install driftfdr                 # numpy, scipy, pandas, matplotlib
pip install "driftfdr[prometheus]"   # + интеграция: prometheus, mlflow или nannyml

Из клона репозитория: pip install -e ".[dev]" (тесты) или pip install -e ".[datasets]" (river и scikit-learn для реальных наборов данных из экспериментов).

Быстрый старт

Монитор принимает на каждом шаге ошибку (или потерю) каждой модели и возвращает модели, которые пора переобучать:

from driftfdr import CalibrationConfig, MeanShift, StreamingMonitor

monitor = StreamingMonitor(
    model_ids=["pricing", "eta", "demand"],
    detector_factory=lambda: MeanShift(3),     # тревога, только если ухудшение держится 3 окна
    procedure="bonferroni",                    # поправка на число моделей
    alpha=0.05,                                # доля ложных тревог на окно по всему парку
    n_ref=300, window=100, horizon=5,          # в шагах; шаг может быть часом (см. bucket_means)
    calibration=CalibrationConfig(tolerance=0.05),  # переобучать, если ошибка выросла > 5 п.п.
)

for errors in stream:                          # например {"pricing": 0.21, "eta": 0.35}
    for model_id in monitor.update(errors):
        retrain(model_id)                      # монитор сам начнёт собирать новый опорный отрезок

monitor.save("monitor.npz")                    # состояние переживает перезапуск сервиса
monitor = StreamingMonitor.load("monitor.npz", detector_factory=lambda: MeanShift(3))
  • Если ошибки моделей движутся вместе (общий источник данных, общие признаки), включите split_common=True. Тогда детекторы смотрят на отклонение каждой модели от медианы по парку, а сама медиана проверяется отдельно: её тревога выставляет monitor.fleet_alarm («сломалось что-то общее»). Без этого общий всплеск даёт пачку одновременных ложных тревог (эксп. 18). На всех четырёх реальных наборах модели сильно коррелированы, а split_common снимает большую часть этой зависимости (эксп. 20). В этом режиме каждая модель должна присылать значение на каждом шаге.
  • Чтобы реагировать, не дожидаясь конца окна, используйте detector_factory=ECUSUM с sequential=True: каждая модель проверяется на каждом шаге при том же бюджете ложных тревог на окно (Бонферрони). При том же бюджете это ловило дрейф на 26% быстрее оконного PH (эксп. 23); шаг стоит около 7 мкс на модель вместо 2.
  • Модели могут присылать данные нерегулярно или с пропусками: у каждой свои часы окон. Модели добавляются и убираются на лету (add_model, remove_model).
  • При задержке меток передавайте ошибку модели тогда, когда пришла метка.
  • Детектор можно взять из river с его настройками: detector_factory=lambda: from_river(drift.PageHinkley(mode="up")).
  • Полный пример — examples/streaming_demo.py. Наглядная демонстрация — examples/live_demo.py: 40 связанных моделей, общий всплеск и событие во всём парке; river по умолчанию делает 293 переобучения (248 впустую), driftfdr — 10 (1 впустую) и одну тревогу парка. Результат — results/demo.html, открывается в браузере с проигрыванием по шагам.

Демонстрация

Интеграции

Опциональные адаптеры в driftfdr.integrations; каждому нужна своя дополнительная зависимость, основной пакет не меняется.

куда что установка
Prometheus PrometheusExporter(monitor).serve(8000) публикует p-значения, тревоги и тревоги парка; правила алертов — examples/prometheus/alerts.yml driftfdr[prometheus]
MLflow MLflowReporter(monitor, run_id=..., model_versions={id: (name, version)}) пишет p-значения в run и ставит тег driftfdr_retrain на зарегистрированную версию модели с тревогой driftfdr[mlflow]
NannyML cbpe_estimated_error(reference, analysis, chunk_size): оценка ошибки без меток, чтобы мониторить, пока метки задерживаются; опирается на откалиброванные вероятности, изменений p(y|X) не видит, на Electricity не сработала (эксп. 24) — сначала сверьте её с настоящей ошибкой driftfdr[nannyml]

examples/prometheus_service.py запускает монитор как небольшой сервис: читает ошибки, отдаёт метрики, сохраняет состояние при остановке и продолжает с него. Тесты дрейфа Evidently не оборачиваются: они предполагают независимые наблюдения и тревожат в 20–78% окон, где модель не стала хуже (эксп. 17), а поправка на множественность поверх невалидных p-значений этого не исправит.

Рекомендуемая конфигурация

что рекомендация почему
сигнал ошибка или потеря модели, не входные признаки детекторы на признаках тревожат на безвредном дрейфе и не видят вредного (эксп. 11)
детектор ECUSUM для скорости (при опорном отрезке от нескольких сотен точек, эксп. 24), MeanShift(3) для устойчивости к кратким всплескам; PageHinkley при том же бюджете ложных тревог e-CUSUM ловит дрейф на 26% быстрее PH и MeanShift(3), на резких сдвигах вдвое (эксп. 23); MeanShift(3) не реагирует на однооконные всплески (эксп. 9, 14)
шаг и окно в единицах времени, кратных циклу данных (час, сутки) иначе суточный цикл выглядит как дрейф (эксп. 16); bucket_means
нулевая гипотеза допуск tolerance > 0: «ошибка выросла существенно» на реальных данных «ничего не изменилось» не бывает (эксп. 8, 14)
допуск δ tolerance_from_cost(цена переобучения, горизонт) переобучать выгодно, если рост ошибки δ за горизонт стоит больше самого переобучения
правило "bonferroni" или "bh_window"; при связанных моделях — с split_common=True Бонферрони держит уровень при любой корреляции моделей (эксп. 13), BH быстрее при массовых дрейфах (эксп. 4); с split_common лучший F1 на всём бенчмарке, выигрыш растёт с корреляцией (эксп. 18, 21)
не рекомендуется ADWIN при строгих уровнях, DDM, LORD/SAFFRON, e-BH хвост ADWIN антиконсервативен (эксп. 10); DDM слабый; онлайн-FDR медленнее при равном числе ложных (эксп. 3, 12); e-BH пропускает треть дрейфов (эксп. 13)

Как это работает

  1. Детектор как непрерывный скор. У каждого детектора один параметр чувствительности, а бинарный сигнал — это порог на внутренней статистике. Эту статистику и берём; для Page-Hinkley и DDM она воспроизводит river точно.
  2. Калибровка. Нулевое распределение статистики оценивается бутстрепом из опорного отрезка (данные сразу после обучения модели): AR-sieve с учётом неопределённости параметров для непрерывных сигналов, блочный бутстреп для ошибок 0/1, GPD-хвост для малых p-значений. Обычный бутстреп при автокорреляции даёт в разы больше ложных тревог, чем обещает (эксп. 1).
  3. Решение по парку. p-значения всех моделей, у которых закончилось окно, проходят через поправку на множественность; модели с тревогой переобучаются и собирают новый опорный отрезок.

Подробное описание — docs/method.ru.md; связь с литературой — docs/related_work.ru.md; справочник по всем классам, методам и функциям (только на английском) — docs/api.md. У каждого документа, кроме справочника API, есть английская версия (без .ru в имени).

Ограничения

  • Уровень ложных тревог примерно выдерживается для PH, KS и MeanShift (фактически 0.044–0.064 при номинале 0.05); у ADWIN калибровка хвоста антиконсервативна примерно втрое.
  • Выводы получены на синтетике, четырёх открытых наборах данных и часовых курсах валют; проверки на продовых логах не было, и модели не переобучаются по-настоящему (тревога лишь запускает сбор нового опорного отрезка).
  • Гарантии поправок выведены для независимых p-значений; при сильно коррелированных моделях надёжнее Бонферрони.
  • На шумном сигнале (например, дневная потеря моделей волатильности на курсах валют) ловятся только существенные ухудшения: рост меньше ~15% неотличим от шума (эксп. 22).
  • Полный список — в docs/experiments.ru.md.

Структура репозитория

src/driftfdr/
  streaming.py     StreamingMonitor (split_common, последовательный режим), CalibratedDetector, from_river
  detectors.py     Page-Hinkley, DDM, ADWIN, KS, скользящий KS, MeanShift, e-CUSUM; отбеливание AR
  calibration.py   нулевые распределения, p-значения, хвост, допуск δ
  bootstrap.py     блочный, стационарный и AR-sieve бутстреп
  online_fdr.py    Бонферрони, BH, BH Стори, e-BH в окне; BatchBH, LORD++, SAFFRON, LOND, alpha-investing
  preprocess.py    усреднение по временным корзинам, допуск из стоимости переобучения, split_common
  monitor.py       пакетный прогон мониторинга по готовым рядам (для экспериментов)
  metrics.py       FDR, задержки, пропуски, цена задержки, событийные точность / полнота / F1
  streams.py       синтетические сценарии с известными точками дрейфа, бенчмарк benchmark_suite
  datasets.py      парки моделей на INSECTS, Electricity, Airlines, Covertype и часовых курсах валют
  integrations/    экспорт в Prometheus, отчёты в MLflow, сигнал NannyML CBPE (опционально)
experiments/       24 эксперимента (exp1…exp24)
results/           таблицы и графики экспериментов, страница демонстрации
docs/              метод, связанные работы, журнал экспериментов (английский и *.ru.md), справочник API (генерируется: python docs/gen_api.py)
tests/             83 теста (CI на Python 3.10–3.12): совпадение с river, бутстреп, калибровка, процедуры, потоковый режим
examples/          онлайн-мониторинг, демонстрация с проигрыванием, сервис для Prometheus с правилами алертов

Тесты и воспроизведение

pytest                                         # ~15 с
python experiments/exp1_calibration.py --quick # любой эксперимент; без --quick — полный прогон

Эксперименты используют фиксированные сиды и закреплённые настройки, поэтому воспроизводятся; полный прогон каждого занимает от нескольких минут до часа на 4 ядрах.

Лицензия

Apache License 2.0, см. LICENSE и NOTICE. Copyright 2026 Artem Deviatov. Наборы данных скачиваются загрузчиками, остаются под своими лицензиями и в репозиторий не входят.

Release files for driftfdr 0.1.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 driftfdr 0.1.0
File Size Uploaded
driftfdr-0.1.0.tar.gz 90.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for driftfdr 0.1.0
File Interpreter ABI Platform
driftfdr-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 155.7 kB

Release files / driftfdr-0.1.0.tar.gz

Download URL driftfdr-0.1.0.tar.gz
Size 90.1 kB
Tags Source
SHA-256 checksum
How to use checksums
fc8d06eee7b613667fc502f7629cf47bdb9ac6098fa5ba717463edece2429a38
BLAKE2b-256 checksum
How to use checksums
de18c9972dda93f2276f1be9dffe504dcb047ff3b6fb607ff39157586f26ecdc
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 25, 2026.

Transparency log

Release files / driftfdr-0.1.0-py3-none-any.whl

Download URL driftfdr-0.1.0-py3-none-any.whl
Size 65.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
debcc2e913c38de011014ea8e3f3ac8b7ef8626b2ca9d34c0d7e777412c880d3
BLAKE2b-256 checksum
How to use checksums
b8aa941b4b180998c870df506e79b8eac8971dd4cd7ee8db32ee36b04280909b
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 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

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