pyscarcopula
A Python library for dynamic copula modelling: bivariate, multivariate, vine, and stochastic copula models for financial time series and risk analytics.
About
pyscarcopula fits bivariate and multivariate dependence models using copulas in Python. Alongside classical constant-parameter copulas, it supports stochastic copula autoregressive (SCAR) models where the copula parameter is driven by a latent Ornstein-Uhlenbeck process or Kendall's tau follows a bounded Jacobi diffusion.
The package is aimed at financial time series, risk modelling, and experiments with dynamic dependence. It provides bivariate copulas, C-vines, R-vines, conditional sampling, prediction, goodness-of-fit diagnostics, and risk metrics.
Supported estimation methods:
| Method | Key | Description |
|---|---|---|
| Maximum likelihood | mle |
Static/constant model parameters |
| SCAR transfer matrix | scar-tm-ou |
Deterministic OU latent-state likelihood |
| SCAR Jacobi transfer matrix | scar-tm-jacobi |
Deterministic Kendall-tau diffusion likelihood |
| SCAR Monte Carlo | scar-p-ou, scar-m-ou |
Monte Carlo alternatives |
| GAS | gas |
Observation-driven score model |
Install
pip install pyscarcopula
Official wheels include the compiled numerical extension and do not need a local compiler. Source and editable installs require a C++17 compiler:
- Windows: Microsoft C++ Build Tools / Visual Studio Build Tools, or MinGW-w64 GCC (see below)
- Linux: GCC or Clang with the usual Python development headers
- macOS: Xcode Command Line Tools
On Windows, a MinGW-w64 GCC toolchain (for example the MSYS2 ucrt64 GCC) can
be used instead of the Microsoft compiler by opting in explicitly:
PYSCA_CPP_COMPILER=mingw32 pip install .
# or, from the source tree:
python setup.py build_ext --compiler=mingw32 --inplace
The GCC runtime is linked statically, so the resulting extension does not need MSYS2 DLLs at runtime. MSVC remains the default Windows toolchain.
For local development:
git clone https://github.com/AANovokhatskiy/pyscarcopula
cd pyscarcopula
pip install -e ".[test]"
To run the full test suite from the source tree, build the C++ extension in place first:
python setup.py build_ext --inplace
pytest --run-validation
pytest --run-validation enables optional validation tests. A source checkout
without a successfully built extension is incomplete for the default
bivariate GAS workflow.
The default command uses one pytest worker and runs tests sequentially. To run independent test modules on an explicit number of CPU cores, pass that number to pytest-xdist:
pytest -n 4 --run-validation
The repository configures xdist's loadscope scheduler, so tests within one
module remain sequential while separate modules can run concurrently. Use an
explicit core count rather than -n auto for reproducible resource usage.
Timing gates compare relative speedups or overhead ratios; absolute seconds are report-only. Opt-in benchmarks may run through xdist like the rest of the suite:
PYSCA_RUN_BENCHMARKS=1 pytest -n 10 -m benchmark
CPU placement remains under operating-system control. Relative alternatives are measured in paired, interleaved rounds so scheduler migration or transient load cannot systematically put the complete baseline and candidate phases on different classes of CPU core.
Validation and optional external groups use the same parallel runner:
pytest -n 10 --run-validation -m validation
pytest -n 10 --run-validation -m external # requires .[external]
loadscope intentionally keeps cases from one test module sequential. In
particular, alternatives inside one relative benchmark must not execute at the
same time, and the release/equicorrelation report writers use one shared JSON
artifact per module. Their modules still run concurrently with other modules.
Optional benchmark and large validation checks are disabled by default. Enable them explicitly:
PYSCA_RUN_BENCHMARKS=1 \
PYSCA_RUN_LARGE_BENCHMARKS=1 \
PYSCA_RUN_VINE_BENCHMARKS=1 \
pytest tests --run-validation
On Windows PowerShell:
$env:PYSCA_RUN_BENCHMARKS = "1"
$env:PYSCA_RUN_LARGE_BENCHMARKS = "1"
$env:PYSCA_RUN_VINE_BENCHMARKS = "1"
pytest tests --run-validation
Conditional sampling has separate PR, distributional, nightly external/d=50, and manual benchmark layers. See the conditional sampling validation guide for exact marker selections, oracle-only calibration, artifacts, and failure triage.
Core dependencies: numpy, numba, scipy, joblib, tqdm.
Verify the compiled extension with:
python -m pyscarcopula._native_smoke
Quick start
pyscarcopula expects a two-dimensional array with observations in rows and
variables in columns. Copula fitting uses pseudo-observations in the open unit
interval. Pass existing pseudo-observations directly, as below, or use
to_pobs=True to rank-transform raw continuous observations during fitting.
import numpy as np
from pyscarcopula import GumbelCopula
rng = np.random.default_rng(2026)
source = GumbelCopula(rotate=180)
u = source.sample_at_parameter(
400,
r=np.full(400, 1.8),
rng=rng,
)
model = GumbelCopula(rotate=180)
result = model.fit(u, method="mle")
forecast = model.predict(1_000, rng=np.random.default_rng(2027))
print(result.log_likelihood)
print(forecast.shape) # (1000, 2)
The fitted result is returned by fit and also stored as
model.fit_result. sample(...) reproduces the fitted model, while
predict(...) draws from its predictive distribution; this distinction
matters for dynamic models. See the complete
Quick Start and
Prediction Semantics.
Choose the model surface before tuning an estimation method:
| Task | Start with |
|---|---|
| Two-variable dependence | GumbelCopula, ClaytonCopula, FrankCopula, JoeCopula, or BivariateGaussianCopula |
| Static unrestricted multivariate dependence | GaussianCopula or StudentCopula |
| Scalar dynamic multivariate dependence | EquicorrGaussianCopula or StochasticStudentCopula |
| Flexible high-dimensional pair decomposition | VineCopula |
See Choosing a Model for the corresponding correlation modes, estimation methods, and limitations.
Features
Vine copulas
VineCopula is the primary API for regular vines. Omitting structure
selects an R-vine from data; C-vines and D-vines are fixed
RVineMatrix structures:
import numpy as np
from pyscarcopula import VineCopula
rng = np.random.default_rng(2028)
vine_data = rng.random((400, 4))
# Data-driven regular vine
auto = VineCopula().fit(vine_data, method="mle")
# Fixed standard structures
c_vine = VineCopula.cvine(d=4).fit(vine_data, method="mle")
d_vine = VineCopula.dvine(d=4).fit(vine_data, method="mle")
For an arbitrary valid regular-vine tree sequence, construct
RVineMatrix.from_trees(...) and pass it as VineCopula(structure=...).
The Vine guide defines the decoded tree format and
explains family selection, truncation, and conditional sampling.
Use vine.structure for the RVineMatrix and
vine.natural_order_matrix when an integration specifically needs the
natural-order runtime matrix. vine.matrix is a compatibility property.
The raw pyvinecopulib matrix uses one-based labels and the opposite
tree-level order above each anti-diagonal entry, so it is not obtained by
simply adding one. See the
matrix-layout conversion.
Copula families
- Archimedean: Gumbel, Frank, Clayton, Joe, including rotations where supported
- Elliptical: Gaussian and Student-t
- Independence copula for null models and vine pruning
- Multivariate Gaussian, Student-t, equicorrelation, and stochastic Student models
- Shared APIs for bivariate, multivariate, and vine models
Vine copulas
- One
VineCopularuntime for auto-selected and fixed regular vines - Fixed C-vine and D-vine factories backed by
RVineMatrix - Arbitrary valid structures built from decoded tree edges
- Automatic family and rotation selection per edge using AIC/BIC
- Tree-level and edge-level truncation
- Mixed MLE, SCAR, GAS, and independence edges within one vine
Sampling and prediction
- Unconditional sampling from fitted bivariate, multivariate, and vine models
- Conditional sampling for static and dynamic multivariate models and R-vines
- Exact and approximate conditional modes for R-vines
PredictConfigfor explicit prediction options- Reproducible random generation via
rng - JSON persistence through
model.save()andModelClass.load()(include_data=Falsecan omit stored training data)
Diagnostics and risk
- Rosenblatt-transform based goodness-of-fit tests
- Mixture Rosenblatt transform for stochastic models
- Predictive time-varying copula parameter paths
- VaR and CVaR utilities in
pyscarcopula.contrib
CPU parallelism
- Explicit native threading for eligible multivariate row, emission, conditional-sampling, static-likelihood, and Monte Carlo kernels
- Process-level
fit_independentand rollingrisk_metricsexecution - Absolute one-thread default: omitted
n_threadsalways means1, regardless of environment variables - Dependency-free C++17 linear algebra without hidden BLAS or OpenMP pools
Mathematical background
By Sklar's theorem, a joint distribution can be represented as
F(x_1, \ldots, x_d) = C(F_1(x_1), \ldots, F_d(x_d)),
where C is a copula and F_i are marginal distributions. This separates
marginal modelling from dependence modelling.
For a one-parameter Archimedean copula with generator phi,
C(u_1, \ldots, u_d; \theta)
= \phi^{-1}(\phi(u_1; \theta) + \cdots + \phi(u_d; \theta)).
In SCAR models the copula parameter is time-varying:
\theta_t = \Psi(x_t),
\qquad
dx_t = \kappa(\mu - x_t)dt + \nu dW_t,
where x_t is a latent Ornstein-Uhlenbeck process and Psi maps the latent
state to the valid parameter domain. scar-tm-jacobi instead evolves
Kendall's tau directly with a bounded Jacobi diffusion and maps tau back to the
copula parameter for families that implement tau_to_param.
The transfer matrix method evaluates the latent-state likelihood by exploiting the Markov structure of the latent process. The path integral is computed as a sequence of matrix-vector products on a discretized grid or spectral basis, avoiding Monte Carlo variance at the cost of numerical approximation.
For SCAR-TM-OU, transition_method='auto' uses a hybrid deterministic strategy:
Hermite spectral evaluation where it is reliable, matrix-based transition
evaluation for regimes better handled on a grid, and local Gauss-Hermite in
narrow-kernel OU cases. In broad terms, this keeps the latent path integral as
repeated deterministic linear-algebra updates while choosing the most suitable
transition representation automatically. See
docs/guide/performance.md for the details and
the available transition_method values.
result = fit(copula, u, method="scar-tm-ou")
result = fit(copula, u, method="gas")
Use the default scaling="unit" for production. scaling="fisher" remains an
experimental, numerically sensitive mode.
See docs/guide/performance.md for supported
families and numerical options.
Vine copulas decompose a d-dimensional dependence model into bivariate
copulas arranged in a sequence of trees. VineCopula() selects a regular-vine
structure from data subject to the proximity condition.
VineCopula.cvine(...) and VineCopula.dvine(...) use fixed standard
structures, while VineCopula(structure=RVineMatrix.from_trees(...)) accepts
an arbitrary valid decoded tree sequence.
Examples and docs
Worked notebooks are available in examples/:
01_basic_api.ipynb02_bivariate.ipynb03_multivariate.ipynb04_vine.ipynb05_risk_metrics.ipynb06_pyvinecopulib_comparison.ipynb
Additional documentation is in docs/. Estimation methods are
described in docs/guide/estimation-methods.md,
and performance-related details are kept in
docs/guide/performance.md. CPU threading,
process workers, thread safety, and scaling limits are documented in
docs/guide/parallelism.md. Release history is in
CHANGELOG.md.
License
MIT License. See LICENSE.txt.
Contacts
Contact me for any questions or discussion aanovokhatskiy@gmail.com
Release files for pyscarcopula 0.21.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyscarcopula-0.21.0.tar.gz | 1.0 MB | Details |
Built distributions (wheels)
Total release size:18.8 MB
Release files / pyscarcopula-0.21.0.tar.gz
| Download URL | pyscarcopula-0.21.0.tar.gz |
|---|---|
| Size | 1.0 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5aee23fd7198521f226e29cd56d6a9c3757b7f428c792bc47095964d489b0ee7
|
|
BLAKE2b-256 checksum How to use checksums |
27d5c5b5334044bc8bf456b1b3513d0564df59c50fb52eb94a53ab73ef8bd46f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp314-cp314-win_amd64.whl
| Download URL | pyscarcopula-0.21.0-cp314-cp314-win_amd64.whl |
|---|---|
| Size | 850.5 kB |
| Tags | CPython 3.14 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
7156799f876b70ee292eb193e2544118d45ea863bfec1fcb614657cdefda7b98
|
|
BLAKE2b-256 checksum How to use checksums |
e9460a070c95da0da19496cc315b1bc32bb813e07c5f33109c04f2112469ebdf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyscarcopula-0.21.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.14 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
dbd09ee2772e252eb025c4cda25723a7e777a1c247066b239b5aa3be64820ab0
|
|
BLAKE2b-256 checksum How to use checksums |
779491a3b8f20954e90ae3e0fca027521810b18f752d88939fe782450f7b1a4a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp314-cp314-macosx_10_15_universal2.whl
| Download URL | pyscarcopula-0.21.0-cp314-cp314-macosx_10_15_universal2.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.14 macOS 10.15+ universal2 (ARM64, x86-64) |
|
SHA-256 checksum How to use checksums |
d1b31a80b0563514f3dfd6b3f53e9ddab1b3749a6b9bb6fd8edd02986a0f563d
|
|
BLAKE2b-256 checksum How to use checksums |
b8c16979b023e65acc89cd5ccb39ffd6dcd697b2174ad2f71e8cfe012bf04382
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp313-cp313-win_amd64.whl
| Download URL | pyscarcopula-0.21.0-cp313-cp313-win_amd64.whl |
|---|---|
| Size | 838.5 kB |
| Tags | CPython 3.13 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
8978a70d132fc21da04ff984f2eb02d435a24c11841ce64187e7251f4adf28e4
|
|
BLAKE2b-256 checksum How to use checksums |
10c11d0f219d6dedfc84e1931858ed57fc77c833450a7e044465494dedaaf1fe
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyscarcopula-0.21.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
de8b19c6c51c94c1e9fc68e08ab72bb2dce8b398c51c8f5700962b491bc910a2
|
|
BLAKE2b-256 checksum How to use checksums |
e7a085b1e6d64ee6c41b4940c5a3b9a6e46fd1515f90fd989f177fb4d7dd8451
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp313-cp313-macosx_10_13_universal2.whl
| Download URL | pyscarcopula-0.21.0-cp313-cp313-macosx_10_13_universal2.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.13 macOS 10.13+ universal2 (ARM64, x86-64) |
|
SHA-256 checksum How to use checksums |
51a7ccb5799ee8a54a159692bcf36a604dbfe5df780c66a9d14306c544780a2f
|
|
BLAKE2b-256 checksum How to use checksums |
820da7dece919d46bdb552cd33d524052ad3784e68324226a3c377771158212b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp312-cp312-win_amd64.whl
| Download URL | pyscarcopula-0.21.0-cp312-cp312-win_amd64.whl |
|---|---|
| Size | 838.4 kB |
| Tags | CPython 3.12 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
78677681a6b7d10d0de55d19789d4aa750ea10e0e1d3663c7c6c7d2de53d6cdf
|
|
BLAKE2b-256 checksum How to use checksums |
b9afb58d837c6c9881b49398574799ac8a4eff4cb55f157f316e0c6eb18d3e9b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyscarcopula-0.21.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
935e5e202c4e2f52f88bacb5ce7810dea098ab155584a03ffed986eee91052d7
|
|
BLAKE2b-256 checksum How to use checksums |
8f17f7f4b5fc140415acb9403796cb7c95267d2341262aa9a64e2f34d798e8b1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp312-cp312-macosx_10_13_universal2.whl
| Download URL | pyscarcopula-0.21.0-cp312-cp312-macosx_10_13_universal2.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.12 macOS 10.13+ universal2 (ARM64, x86-64) |
|
SHA-256 checksum How to use checksums |
38f3cea03e8ca6ca3b95cbee4acaf2656f2ac13edf2f2802611307f981278323
|
|
BLAKE2b-256 checksum How to use checksums |
0ab300db0600c5fb43cccd19a98175b9ae4fcc5d292b2fc7c8665755fd1f0330
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp311-cp311-win_amd64.whl
| Download URL | pyscarcopula-0.21.0-cp311-cp311-win_amd64.whl |
|---|---|
| Size | 836.4 kB |
| Tags | CPython 3.11 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
8f2eb93f31c0a38f2356a059796778401e94385adf8384c74d88e3d94b437de9
|
|
BLAKE2b-256 checksum How to use checksums |
b60fb4384e126c183b708604477b518140bff7e4b17d94cb0ce02630aba135f1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyscarcopula-0.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.11 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
b17ed8a91dafa919570aeb9184e6be83d63dce0424e7d079edf5a05dedb8321c
|
|
BLAKE2b-256 checksum How to use checksums |
6c7a3a0aef67bf320fec40a2253f2f2fc1ae0c3ba27840170f94206d2bb1668e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp311-cp311-macosx_10_9_universal2.whl
| Download URL | pyscarcopula-0.21.0-cp311-cp311-macosx_10_9_universal2.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.11 macOS 10.9+ universal2 (ARM64, x86-64) |
|
SHA-256 checksum How to use checksums |
7788fa4460bfe2092b8a6b7fbe23e17ffe59997cbc33e34eef2ad954f4d9fec8
|
|
BLAKE2b-256 checksum How to use checksums |
05297d63a51bff03767f3e3dfa6f4967942a827c331048761edc4ba3f3d2cd4f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp310-cp310-win_amd64.whl
| Download URL | pyscarcopula-0.21.0-cp310-cp310-win_amd64.whl |
|---|---|
| Size | 835.8 kB |
| Tags | CPython 3.10 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
bd6f30c4f2b666df69397699b3d68fba0ef27544bf9b304b655712abd13d6844
|
|
BLAKE2b-256 checksum How to use checksums |
f62eb42b8b6a5caff16e9d78c5320fa62c221604e363b0704ea115df51a18940
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
| Download URL | pyscarcopula-0.21.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl |
|---|---|
| Size | 1.1 MB |
| Tags | CPython 3.10 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64 |
|
SHA-256 checksum How to use checksums |
a43f1c9bae78b95da9f51a3636367771838ee3dc1b2067ade38f906a58bb9341
|
|
BLAKE2b-256 checksum How to use checksums |
90a399416963a187dd01dd3a76162a5ad94e5ca61fc206426f08eced9cc20f7e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|
Release files / pyscarcopula-0.21.0-cp310-cp310-macosx_10_9_universal2.whl
| Download URL | pyscarcopula-0.21.0-cp310-cp310-macosx_10_9_universal2.whl |
|---|---|
| Size | 1.6 MB |
| Tags | CPython 3.10 macOS 10.9+ universal2 (ARM64, x86-64) |
|
SHA-256 checksum How to use checksums |
64230abba001b7834f61ddac31e0368369edb6666f845cc2aa2aaad00169605d
|
|
BLAKE2b-256 checksum How to use checksums |
0fae66b49a891ea0b815dd1cdb26fd1c2b308ce238d8bb557a15acc8e90d928c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.6
|