Skip to main content

hdfe-stream

Regression with several high-dimensional fixed effects, on data that does not fit in memory: linear (feols_stream), and Poisson, logit and probit (fepois_stream, feglm_stream).

from hdfe_stream import feols_stream

fit = feols_stream(
    "log_earn ~ age_squared + age_cubed | worker_id + firm_id + year",
    "data/*.parquet",              # a Parquet path or glob, never fully loaded
    workdir="scratch",             # where intermediates go: disk, not memory
    vcov={"CRV1": "worker_id"},
)
fit.summary()

Formulas follow pyfixest/fixest syntax and are parsed by pyfixest's own parser, so the syntax, the coefficient names and the results all match. The difference is where the work happens: rows are streamed off disk in batches and never held as a design matrix.

When to use this, and when not to

Use pyfixest. It is the better tool whenever your data fits in memory: mature, broader in scope, more inference options, and no scratch directory to think about. If a pandas DataFrame of your data fits comfortably in RAM, stop reading.

Use this when it doesn't. The case it was built for is a matched employer–employee panel — tens of millions of rows, millions of worker effects, an AKM variance decomposition — on a machine or a secure enclave where the data is larger than the memory you are allowed. Concretely, reach for it when:

  • the data does not fit in memory, or fits so tightly that nothing else does;
  • you have one very high-cardinality dimension (workers, patients, students) whose groups each touch only a few levels of the others;
  • you want worker-specific slopes as well as intercepts (worker_id[t]), which multiplies the parameter count by the number of slopes;
  • the design is wide: hundreds of covariates, such as a categorical expanded into fine indicators or many interactions. An in-memory library needs rows × covariates in memory before it starts; this needs one batch of rows × covariates at a time, with any number of fixed effects, including one or none (see many covariates);
  • you need a hard, predictable memory ceiling — a shared cluster, or a job that must not be the one that gets killed.

You are trading memory for disk, and you need scratch space: about five times the size of your Parquet input while the fit runs, and only the result files once it finishes. See benchmarks for what that buys. Point workdir= at it, or set the HDFE_STREAM_WORKDIR environment variable once (for example to a cluster's scratch filesystem) and leave workdir out; a workdir passed to a call takes precedence, and with neither, the system temporary directory is used.

How it works, in one paragraph

One fixed-effect dimension — normally the largest — is streamed: it is never represented as a vector in memory. Every other dimension is held as a vector sized by its number of levels, not rows. Rows are read in batches, hash-partitioned into buckets by the streamed dimension, sorted one bucket at a time, and reduced to a table of cells (one per combination of fixed-effect levels). The reduced normal equations for the non-streamed dimensions are then solved by conjugate gradient, and the streamed effects are recovered group by group in a final pass that writes residuals and fixed effects straight to Parquet. So memory scales with firms × years, not with workers × rows, and that asymmetry is the whole design. The covariates travel with the rows: what is held at once is one bucket or batch of rows × covariates, never all of them, which is why a wide design fits where an in-memory one does not. Full detail is in hdfe_stream/__init__.py.

Install

pip install hdfe-stream[formula]     # formula syntax needs pyfixest

polars, numpy, numba, pyarrow and scipy are required. The extras:

extra what it adds
formula feols_stream formula syntax and pyfixest reporting (pyfixest, formulaic, pandas)
within the solver="within" backend
amg algebraic multigrid preconditioning (precond="amg") for hard geometries
all all of the above
test the test suite

Without formula you still get StreamingHDFE, the lower-level interface that takes column names or Polars expressions instead of a formula string.

Features

Fixed effects. Any number of dimensions, including one (a within regression, y ~ x | worker_id) and none (OLS with an intercept, y ~ x). Interactions with ^ (firm_id^year). Varying slopes on the streamed dimension in fixest syntax (worker_id[t], worker_id[t, t2]). Connected components of the worker–firm graph are computed and reported, and the degrees-of-freedom correction counts the fixed-effect parameters that are actually identified (fe_dof="exact") or follows pyfixest's convention (fe_dof="pyfixest").

Covariates. Transformations (I(age**2), log(x)), categoricals (C(x), string columns), interactions (:, *), event-study terms (i(year, treat, ref=2009)). Terms spanned by the fixed effects are dropped exactly as pyfixest drops them. Every term compiles to a Polars expression, so the design is built while the data streams, one bucket at a time: a categorical expanded into hundreds of indicators is carried through the partitioning as the one column it comes from.

Standard errors. iid, heteroskedasticity-robust (hetero/HC1), and CRV1 clustered on any column, any ^ interaction, or several dimensions at once (multi-way Cameron–Gelbach–Miller, any number of ways). Ask for several at fit time with cluster= and switch between them afterwards with with_vcov() — they are all computed in the one residual pass. CRV3, the cluster jackknife (vcov={"CRV3": var}), for OLS when every fixed effect is nested within the clusters or there are none; see limitations.

Estimators. OLS, weighted least squares (weights=, analytic or frequency), and 2SLS (y ~ exog | fe | endog ~ instruments) with a first-stage F. Poisson (fepois_stream, with an offset) and logit and probit (feglm_stream), with weights; see below.

Multiple models. Several outcomes (y1 + y2 ~ ...), stepwise covariate sets (sw(), csw()) and stepwise fixed-effect sets. Models sharing a fixed-effect set share one pass over the data and one solve.

Leave-out variance components. The Kline–Saggio–Sølvsten bias correction for the AKM decomposition, via leave_out_kss — including the leave-one-out connected set, Johnson–Lindenstrauss leverages, weights, standard errors with 95% intervals (se=True), KSS's weak-identification diagnostic saying whether those intervals are justified, and the interval that stays valid when they are not. See below and docs/kss.md.

Output. Coefficients as a Polars DataFrame (tidy()); the model-level information of summary() (observations, fixed-effect counts, fit statistics, solver) as JSON with summary_json(), optionally written to a file with summary_json("fit.json") (summary_dict() returns the same as a dict); residuals and per-row fixed effects as lazy Polars scans, so aggregates like a variance decomposition run as a streaming pass; estimated effects per dimension via fixef(). to_pyfixest() converts a result into a pyfixest model for pf.etable, pf.summary, pf.coefplot and pf.iplot, and hdfe_stream.etable mixes streaming and in-memory models in one table.

Operations. Progress, warnings and summaries to a logging logger as they happen. Each fit works in its own run directory; intermediates are deleted as soon as they are no longer needed, everything is removed if the fit fails, and hdfe_stream.cleanup() sweeps up after killed jobs.

Benchmarks

A standard three-way AKM specification, log_earn ~ age_squared + age_cubed | worker_id + firm_id + year, clustered by worker, on the simulated panel that ships with the library. It is compared with pyfixest, under both of its demeaners, and with xhdfe, on its CPU backend. Sizes run from 25,000 to 5,000,000 workers, with firms = workers / 15 at every size and about 8.5 rows per worker, so the largest panel has 42.5 million rows. Every configuration agrees with every other on the coefficients to within 4e-10, so these are measurements of equally good answers. Reproduce with:

pip install -e ".[benchmark]"      # xhdfe installs separately: see benchmarks/README.md
python benchmarks/akm_benchmark.py
python benchmarks/plot_benchmarks.py

The numbers behind the figures are in benchmarks/results/akm.csv, and benchmarks/README.md says how each is measured. In brief: wall time includes reading the data, because pyfixest and xhdfe need it in memory before they can start and hdfe_stream reads it itself — that difference is the comparison, not an artifact. Peak memory is VmHWM for the whole process, including about 0.5 GB of imports. Each configuration runs in its own process.

AKM regression: wall time against the number of workers, one line per configuration, log-log AKM regression: peak memory against the number of workers, one line per configuration, log-log AKM regression: peak disk use of hdfe_stream against the number of workers, log-log

At 5 million workers:

configuration wall time peak memory peak disk
pyfixest (MAP, its default) 888 s 20.7 GB —
pyfixest (LSMR) 150 s 25.8 GB —
xhdfe 126 s 19.1 GB —
hdfe_stream (stream_cg) 40 s 5.4 GB 3.9 GB
hdfe_stream (stream_cg, low memory) 48 s 3.7 GB 4.0 GB

Reading the figures

Memory is the point. At 5 million workers the in-memory libraries peak at 19–26 GB: pyfixest's LSMR run needed almost all of this machine's 26 GB. hdfe_stream peaks at 5.4 GB with its defaults and 3.7 GB with the batch and bucket sizes turned down ("low memory": rows_per_bucket=250_000, batch_rows=200_000), for about 20% more time and the same answer. What it spends instead is disk: about 90 bytes per row, 4 GB at 42.5 million rows, freed when the fit finishes. The in-memory libraries' peak grows in proportion to the data. hdfe_stream's low-memory setting grows far more slowly: 0.8 GB at 25,000 workers, 1.3 GB at a million, 3.7 GB at five million.

It is not slower for it. From about 400,000 workers up, hdfe_stream is the fastest configuration measured. At 5 million workers it takes 40 s against xhdfe's 126 s and pyfixest's 150 s (LSMR) or 888 s (MAP). With AKM panel data, reducing rows to worker-firm cells before solving more than pays for the disk traffic. The simulated panel has about 8.5 rows per worker and one cell per row, which suits this approach; a specification where the cell table is no smaller than the data, and the fixed effects are less local, may do worse.

Below about 400,000 workers, the in-memory libraries use less memory. Running any hdfe_stream fit costs about 0.8 GB, most of it importing polars, numba and scipy and starting Polars' streaming engine and numba's thread pools. On small data that floor dominates: at 25,000 workers xhdfe peaks at 0.27 GB and pyfixest at about 0.5 GB. This is the concrete version of "use pyfixest (or xhdfe) if your data fits".

The default pyfixest demeaner is not the one to compare against. MapDemeaner (alternating projections) is about 6x slower than LsmrDemeaner from 400,000 workers up, since many worker effects are the case alternating projections struggles with. If you are comparing, compare against LSMR.

hdfe_stream's within solver uses more memory at scale: 14.7 GB at 5 million workers, where stream_cg and explicit stay near 5.4 GB. Prefer those when memory is the constraint.

Many covariates

The same kind of comparison, holding the data fixed and varying its width: the million-worker panel (8.5 million rows, 66,665 firms) with age entered as indicators, log_earn ~ i(age_bin) | worker_id + firm_id, clustered by worker, in bins from five years wide (8 indicators) down to one month (503). hdfe_stream runs at its defaults and "sized to the design", with bucket, batch and row-group sizes scaled down as the design widens (the rule is below). The numbers are in benchmarks/results/covariates.csv; every configuration that finished agrees with every other to within 1e-8.

python benchmarks/covariates_benchmark.py
Many covariates: peak memory against the number of covariates, one line per configuration, log-log Many covariates: wall time against the number of covariates, one line per configuration, log-log Many covariates: peak disk use of hdfe_stream against the number of covariates, log-log

Wall time and peak memory ("—": ran out of this machine's 26 GB):

covariates pyfixest (MAP) pyfixest (LSMR) xhdfe hdfe_stream hdfe_stream, sized
8 219 s, 5.7 GB 17 s, 7.3 GB 18 s, 9.0 GB 7 s, 2.8 GB 7 s, 2.9 GB
41 522 s, 13.1 GB 56 s, 17.3 GB 55 s, 18.1 GB 21 s, 7.0 GB 20 s, 3.7 GB
83 1,026 s, 24.8 GB 189 s, 25.4 GB — 71 s, 12.5 GB 56 s, 4.5 GB
167 — — — 114 s, 23.6 GB 112 s, 5.1 GB
503 — — — — 351 s, 8.6 GB

The in-memory libraries run out first. Their peak grows with rows × covariates. At 8.5 million rows pyfixest was within 1 GB of this machine's 26 GB at 83 indicators and out of memory at 167; xhdfe was already out of memory at 83. hdfe_stream sized to the design goes from 2.9 GB at 8 indicators to 5.1 GB at 167 and 8.6 GB at 503. Of that last figure, about 4.9 GB is the memory-mapped cell table: file pages the kernel can drop under pressure. The memory the fit itself allocated peaked at 4.6 GB (a separate profile, reading the process's anonymous and file-backed pages apart).

Size the batches to the design. At its defaults hdfe_stream holds a whole bucket of up to 20 million rows, with every covariate, while it sorts; that is fine for a handful of covariates and not for hundreds (23.6 GB at 167, out of memory at 503). Every step that holds rows holds all the covariates, so scale rows_per_bucket, batch_rows and row_group_size down with the width. The benchmark aims at about 1 GB of design per bucket and 250 MB per batch and row group, never above the defaults:

per_row = 8 * (k + 4)                   # k covariates, plus outcome and ids
batch = min(2_000_000, int(250e6 // per_row))
fit = feols_stream(fml, data, rows_per_bucket=min(20_000_000, int(1e9 // per_row)),
                   batch_rows=batch, row_group_size=min(500_000, batch))

Time grows with the square of the width. The cross-products cost k × k per row; they are formed chunk by chunk as BLAS matrix products, and 503 indicators take 6 minutes where 167 take 2. hdfe_stream is the fastest configuration at every width measured.

Timings on this machine are noisy where memory is tight. Each point is one run. Repeated runs of the default configuration at 83 indicators, which holds 12.5 GB, took between 53 and 83 s; the sized configuration varied by about 15%. The differences between libraries above are far larger than that.

Disk grows with the width too, since the working copy of the rows carries every covariate: 0.3 GB at 8 indicators, 6 GB at 503.

How the design is built matters. A formula term such as i(age_bin) is carried through the partitioning as the one column it is computed from, and the indicators are built one bucket at a time. That works for any expression whose value on a row depends on that row alone, which formula terms always do. With the low-level StreamingHDFE, an expression that needs the whole column (pl.col("x") - pl.col("x").mean(), a window, a shift) is detected and the design is built before partitioning instead, with a warning: the estimates are the same, the memory is not. Define such a column in the input LazyFrame to avoid it. result.diagnostics["design_evaluated"] says which happened. examples/wide_designs.py puts this together.

Trading memory for time

Same data, same solver, same answer — only how much is in flight at once:

python benchmarks/akm_benchmark.py --memory-sweep 1000000

8,500,353 rows, 1,000,000 worker effects

rows_per_bucket batch_rows wall time peak memory peak disk
20,000,000 (default) 2,000,000 7.1 s 3,861 MB 779 MB
1,000,000 500,000 7.4 s 1,560 MB 781 MB
250,000 200,000 7.8 s 1,343 MB 795 MB
100,000 100,000 9.3 s 1,280 MB 789 MB

Most of the saving comes from the first step down. The defaults are tuned for a machine with room to spare; if memory is the binding constraint, set rows_per_bucket to something near what you can afford and leave the rest alone (with many covariates, scale batch_rows and row_group_size down too; see many covariates). rhs_block bounds the other big array (levels × variables), and max_s_gb caps the explicit reduced matrix, above which solver="auto" falls back to stream_cg by itself.

Leave-out estimation has a memory knob of its own, scratch_mb; see docs/kss.md.

Measured on an Intel Core Ultra 7 258V, 8 cores, 26 GB RAM, Linux (WSL2); Python 3.13.5, polars 1.44.2, numpy 2.5.3, numba 0.67.0, scipy 1.18.1, pyfixest 0.60.0, xhdfe 2.28.0 (CPU backend). The exact versions are in benchmarks/results/machine.json. Numba kernels are compiled on first use and cached to disk; the benchmark runs a warm-up fit so compilation is not charged to any configuration.

Choosing a solver

solver what it does when
"auto" (default) explicit, falling back to stream_cg if the reduced matrix would exceed max_s_gb leave it alone
"explicit" builds the reduced matrix once and solves it in memory fastest when it fits; its size depends on how levels co-occur, not on rows
"stream_cg" applies the reduced matrix by streaming the cell table each iteration when the reduced matrix is the thing that does not fit
"within" hands the reduced system to within-py an alternative; no varying-slopes support

The streamed dimension is chosen for you: an explicit stream= if given, else the dimension carrying varying slopes, else the highest approximate cardinality. result.diagnostics["stream"] says which and why.

Examples

Runnable, on simulated data, no setup — see examples/:

quickstart.py fit a model and read the results
akm_variance.py the variance decomposition, in a streaming pass
formulas.py the formula syntax, end to end
weights_and_iv.py weights and 2SLS
fewer_fixed_effects.py one fixed effect, or none
glm.py Poisson with an offset, logit and probit, separation, and the incidental parameter bias
wide_designs.py hundreds of covariates, and sizing the batches to them
varying_slopes.py worker-specific trends, and the low-level interface
out_of_core.py memory, disk, solvers, logging
reporting.py tables and plots via pyfixest
leave_out_kss.py the KSS bias correction, checked against the effects the data was built from

Simulated data

hdfe_stream.simulate ships with the package, so you can try the library, or check your own code, against data whose answer you know:

from hdfe_stream.simulate import simulate_akm

simulate_akm(n_workers=1_000_000).write_parquet("sim.parquet")

simulate_akm draws worker and firm effects explicitly and positively assorted, with enough mobility to connect the firms into one component. simulate_rich adds categoricals, non-fixed-effect cluster variables, weights, an IV block and missing values; simulate_trends adds worker-specific time trends. All are deterministic given a seed.

Poisson, logit and probit

from hdfe_stream import fepois_stream, feglm_stream

pois = fepois_stream("visits ~ x | worker_id + firm_id + year", "data/*.parquet",
                     offset="log_exposure", workdir="scratch")
logit = feglm_stream("promoted ~ x | firm_id + year", "data/*.parquet", "logit",
                     workdir="scratch")

These are fitted by iteratively reweighted least squares, each step a weighted version of the linear regression above. Pass 0 sorts the rows once. Each step then reads them once, rebuilding every row's linear predictor from the current coefficients, and solves the reduced system, starting from the previous step's solution.

Nothing row-sized is rewritten between steps, so the disk needed is about what OLS needs. A fit takes a few times as long as OLS on the same data, since it usually needs 6 to 10 steps. Benchmarks against pyfixest, over the AKM benchmark's panel sizes and over the number of covariates, are in docs/glm.md.

Fixed-effect levels whose effect would be infinite are dropped first: all-zero outcomes for Poisson, constant ones for logit and probit. Estimates, standard errors and deviance match pyfixest to about 1e-12. The few places where they deliberately differ are frequency weights, which here mean repeated rows, and logit and probit levels whose outcome is all 1, which are dropped. Both are described in docs/glm.md, along with the options and results.

Leave-out variance components (KSS)

The plug-in AKM decomposition is biased: worker and firm effects are estimated with error, which inflates their variances and attenuates their covariance. leave_out_kss applies the bias correction of Kline, Saggio and Sølvsten (2020), streaming like the rest of the package:

from hdfe_stream import leave_out_kss

lo = leave_out_kss("log_earn ~ age_squared | worker_id + firm_id",
                   "data/*.parquet", workdir="scratch", se=True)
print(lo.summary())

It follows Saggio's reference implementation, LeaveOutTwoWay. It prunes to the leave-one-out connected set, leaves out a worker–firm match by default, and approximates leverages by random projection. It supports weights, standard errors, and KSS's weak-identification diagnostic and q = 1 interval.

docs/kss.md covers the options, the standard errors and their measured coverage, weak identification, validation, reproducibility, and performance against xhdfe: at 5 million workers, 5.3 GB of memory against xhdfe's 20 GB, with standard errors in 46 minutes where xhdfe's did not finish in three hours. docs/kss_methodological_differences.md lists every way this implementation differs from LeaveOutTwoWay, VarianceComponentsHDFE.jl, xhdfe and pytwoway, with the motivation and measured effect of each.

Reproducibility

Everything in a fit is deterministic: the same data and the same options give the same numbers, with no random component anywhere — to floating-point rounding. Repeated fits normally agree bit for bit, but aggregation runs in parallel, and once, under heavy CPU load from another process, two identical fits differed in the last digit. Treat agreement to about 1e-12 as the guarantee, not bitwise identity.

The exception is leave-out estimation, which uses random projection and random draws. Its results are reproducible from the seed but not deterministic; docs/kss.md says exactly what they depend on and what to report in a paper.

Correctness

The test suite checks every feature against pyfixest on simulated data — coefficients, standard errors under each vcov, residuals, fixed effects, fit statistics, the sample kept after dropping missing values, and the exact set of coefficient names and dropped collinear terms. Varying slopes have no pyfixest equivalent, so they are checked against a brute-force regression with explicit worker-by-slope dummies; three-way clustering is checked against an inclusion–exclusion sum built from pyfixest's own score matrix. Poisson, logit and probit are checked against pyfixest's fepois and feglm, and their frequency weights against pyfixest on the data with each row repeated.

pip install -e ".[test]"
pytest

Limitations

  • Varying slopes only on the streamed dimension, and only one dimension may carry them.
  • CRV3 only with nested fixed effects. The cluster jackknife is computed by downdating the fit, which is exact when every fixed effect is nested within the clusters (worker effects clustered by worker, firm × year effects by firm) or there are none. With fixed effects that are not nested, as in an AKM model clustered by worker, the jackknife has to re-estimate the fixed effects once per cluster, and the fit refuses; use CRV1, or pyfixest, which refits. CRV3 is also one-way and OLS only (pyfixest has no IV CRV3 either). No CRV2 and no wild bootstrap.
  • Poisson, logit and probit: no CRV3, IV, varying slopes or leave-out estimation; no detection of separation by the covariates (pyfixest's "ir" check), only by the fixed effects; and no correction for the incidental parameter bias of logit and probit with fixed effects estimated from few observations, as in pyfixest and fixest. During the iterations the streamed dimension's effects are held in memory, one float per group per coefficient set. See docs/glm.md.
  • A converted result cannot recompute its own vcov, because it holds no data. Ask for what you need at fit time via cluster=.
  • Formula support pins a pyfixest range (>=0.50,<0.61), because it uses pyfixest's internal formula modules. StreamingHDFE has no such dependency.
  • Needs scratch disk, roughly the size of your data during the fit. Leave-out estimation adds to that: it memory-maps its row-sized accumulators rather than holding them, which is what keeps it inside a bounded memory footprint, at about 90 bytes per row of scratch while it runs.
  • Wide designs need the batch sizes scaled down (see many covariates): the defaults hold a whole bucket of rows with every covariate. And the cross-products cost k × k per row, so time grows with the square of the number of covariates.
  • Leave-out estimation handles one outcome at a time, and its standard errors omit the split-sample refinement of KSS §4.2 (so they are conservative, as their Lemma 5 provides for). Under weak identification it supplies the q = 1 interval, not KSS's q > 1 generalization. Leaving out a match, as it does by default, var(alpha) has no standard error and the covariance no q = 1 interval. See docs/kss.md.

Metadata

Release files for hdfe-stream 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 hdfe-stream 0.1.0
File Size Uploaded
hdfe_stream-0.1.0.tar.gz 349.3 kB Details

Built distribution (wheel)

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

Total release size: 513.7 kB

Release files / hdfe_stream-0.1.0.tar.gz

Download URL hdfe_stream-0.1.0.tar.gz
Size 349.3 kB
Tags Source
SHA-256 checksum
How to use checksums
1c432d82d228ad117f4d4599737cc3a520c761e058f9202b4f237678c62e922c
BLAKE2b-256 checksum
How to use checksums
57220dc22113d79ef82a29acb4d58064a8149366cefe25762ad551f5914d3e90
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 Oct 1, 2026.

Transparency log

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

Download URL hdfe_stream-0.1.0-py3-none-any.whl
Size 164.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
57c405751f7ffbccaa1865eb7df439ee43bcba6bc92e67c15cd5a206dde98a36
BLAKE2b-256 checksum
How to use checksums
fdd79572420d09fdbcd4a29e24715ab08d2133b5b992d28ed45a3055d0ccf767
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 Oct 1, 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