tabdgp 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ # Format check and the fast tests on three operating systems; the slow proofs run by hand or
2
+ # on the cluster (`uv run pytest -m slow`).
3
+ name: CI
4
+
5
+ on:
6
+ push:
7
+ branches: [main]
8
+ pull_request:
9
+
10
+ jobs:
11
+ test:
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest, windows-latest]
16
+ runs-on: ${{ matrix.os }}
17
+ steps:
18
+ - uses: actions/checkout@v7
19
+ - uses: astral-sh/setup-uv@v10.2.0 # setup-uv publishes no major tag
20
+ with:
21
+ python-version: '3.14'
22
+ - run: uv sync
23
+ - run: uv run ruff format --check .
24
+ - run: uv run pytest -rs
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ dist/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .hypothesis/
8
+ .ruff_cache/
9
+ .ipynb_checkpoints/
10
+ .DS_Store
11
+ # local agent instructions (docs/conventions.md is the public part)
12
+ CLAUDE.md
@@ -0,0 +1,99 @@
1
+ # TabDGP
2
+
3
+ A data-generating process for grouped and flat tables: what is drawn, how, and from which
4
+ distributions. Covariate generator, design matrix, within-group and group-level covariate,
5
+ group-confounded and quirk are metabeta's GLMM glossary's (`packages/mixed/CONTEXT.md` of
6
+ metabeta-v3) and are not redefined here; statistical terms (grouping factor, family, link)
7
+ too.
8
+
9
+ ## Language
10
+
11
+ ### The graph
12
+
13
+ **Node**:
14
+ One variable of a dataset's structural causal model (SCM): a grouping factor, a group-level or
15
+ within-group covariate, time, treatment, or the outcome node. A node has a parent set and a
16
+ mechanism; a node without parents is a root.
17
+ _Avoid_: feature, variable unqualified, unit (a neuron of an MLP)
18
+
19
+ **Mechanism**:
20
+ The function that computes a node's values from its parents' values and its own noise, drawn
21
+ per node from a mechanism family (linear, a small MLP, a tree). A root's mechanism is its noise.
22
+ _Avoid_: layer, activation (one part of it), edge function
23
+
24
+ **Generating graph**:
25
+ The directed acyclic graph of a dataset's nodes and their parent sets, returned with every
26
+ dataset.
27
+ _Avoid_: causal graph unqualified, DAG (one property of it), architecture
28
+
29
+ **Latent node**:
30
+ A node of the generating graph that is not a column of the table, kept in the output so that
31
+ the dataset stays white-box.
32
+ _Avoid_: hidden unit, confounder (a role a latent node can play)
33
+
34
+ **Column**:
35
+ A node observed in the table, with its metadata: type (continuous, binary, categorical,
36
+ ordered), levels, and role (grouping factor, covariate, time, treatment, outcome).
37
+ _Avoid_: feature, variable unqualified
38
+
39
+ **Marginal**:
40
+ The distribution a continuous column's values are mapped onto after the SCM, from a catalogue
41
+ of families (skewed, heavy-tailed, bounded, zero-inflated), keeping their ranks.
42
+ _Avoid_: noise distribution (the root's), prior
43
+
44
+ **Post-hoc type**:
45
+ The column type a continuous node is turned into after the SCM, by thresholds or bins on its
46
+ values: binary, categorical, ordered.
47
+ _Avoid_: dtype, encoding (the design matrix's)
48
+
49
+ ### The draw
50
+
51
+ **Draw**:
52
+ The one public call: given a key and a hyperprior, one dataset, a pure function of both. Who
53
+ draws many loops over indices.
54
+ _Avoid_: sample (a row or a value), generate, batch
55
+
56
+ **Key**:
57
+ A seed and an index, the identity of one dataset: the same key, hyperprior and TabDGP version
58
+ give the same dataset bitwise, whatever the batch, worker or shard it is drawn in.
59
+ _Avoid_: rng, random state, seed alone
60
+
61
+ **Dataset**:
62
+ What a draw returns: the columns as named arrays in their natural dtype, their metadata, the
63
+ generating graph, the latent nodes' values, the hyperparameters every field resolved to, and
64
+ the key. Never padded, never a file.
65
+ _Avoid_: batch, table (the columns alone), sample, corpus (the consumer's set of keys)
66
+
67
+ **Hyperprior**:
68
+ The one config of distributions from which every quantity of a dataset is drawn: sizes,
69
+ graph, mechanisms, marginals, types, quirks. Its defaults are matched to mixedbench's registry
70
+ and then widened; they are what the proofs certify.
71
+ _Avoid_: prior (the fitter's, over parameters), config unqualified, meta-distribution
72
+
73
+ **Override**:
74
+ A point value or a distribution passed for one field of the hyperprior, replacing its default
75
+ for that call. A standalone user passes none; metabeta passes its sizes.
76
+ _Avoid_: setting, parameter, hyperparameter
77
+
78
+ **Quirk axis**:
79
+ One field of the hyperprior that controls one quirk of the catalogue, granted only when
80
+ mixedbench's measurement pass shows that real tables have the quirk strongly enough.
81
+ _Avoid_: noise, perturbation, augmentation
82
+
83
+ **Outcome node**:
84
+ An optional node whose parents are drawn from the covariates, with a family, a link and terms
85
+ a GLMM lacks, that builds white-box misspecified datasets. Just another node; never drawn
86
+ unless asked for.
87
+ _Avoid_: y, target, label, response unqualified
88
+
89
+ ### The proofs
90
+
91
+ **Two-sample test**:
92
+ A classifier two-sample test (C2ST) on the summary vector of generated and registry tables:
93
+ the defaults pass when its accuracy stays below the bar.
94
+ _Avoid_: discriminator, realism score
95
+
96
+ **Containment test**:
97
+ The test that every registry row's summary vector lies inside the support of the generated
98
+ ones.
99
+ _Avoid_: coverage (the fitter's credible-interval property)
@@ -0,0 +1,63 @@
1
+ # Contributing to TabDGP
2
+
3
+ Thank you for your interest. TabDGP is early stage and help is welcome.
4
+
5
+ ## Ways to help
6
+
7
+ - **A real-data quirk the generator misses.** Open an issue naming the property, a public
8
+ table that shows it and, if you can, how to measure it per column or per grouping factor;
9
+ the quirk catalogue grows through the measurement pass, never by hand.
10
+ - **A bug in a mechanism or a proof.** Open an issue with the smallest example that shows it,
11
+ or a pull request with a failing test and the fix.
12
+ - **A use case.** A model family or a simulation study that needs something the hyperprior
13
+ cannot express: open an issue describing the dataset you cannot draw.
14
+
15
+ For anything bigger than a small fix, open an issue before writing code, so we can agree on
16
+ the approach first.
17
+
18
+ ## Set up
19
+
20
+ You need [uv](https://docs.astral.sh/uv/).
21
+
22
+ ```
23
+ git clone https://github.com/adkipnis/tabdgp
24
+ cd tabdgp
25
+ uv sync
26
+ uv run pytest # the fast tests
27
+ uv run pytest -m slow # the proofs against mixedbench's registry
28
+ ```
29
+
30
+ ## Find your way
31
+
32
+ ```
33
+ src/tabdgp/ the package; each module's docstring says what it defines and how
34
+ tests/ mirrors src/, fixtures beside the tests
35
+ docs/adr/ the design decisions and why they were taken
36
+ docs/ the conventions and the proofs' method
37
+ lab/ one notebook per small design choice, indexed in lab/README.md
38
+ examples/ the demo notebook
39
+ ```
40
+
41
+ [`CONTEXT.md`](CONTEXT.md) defines the words (node, mechanism, hyperprior, override, quirk
42
+ axis, outcome node); the covariate terms are metabeta's GLMM glossary's.
43
+
44
+ ## Pull requests
45
+
46
+ - Branch off `main`, one change per pull request, linked to its issue.
47
+ - Add a test with each change; mark a test that draws thousands of datasets `slow`.
48
+ - Run `uv run ruff format .` and `uv run pytest`. CI runs both.
49
+ - Please follow the style [conventions](docs/conventions.md).
50
+
51
+ Three rules keep the generator trustworthy:
52
+
53
+ - **The defaults are what the proofs certify.** A change to a default distribution of the
54
+ hyperprior, to a proof's seed or to its bar is discussed in an issue first and recorded in
55
+ the changelog; downstream training corpora depend on them.
56
+ - **Written from scratch.** scamd and TabICL's prior are read for ideas; no code is copied
57
+ from either. A mechanism they inspired says so in a comment.
58
+ - **Nothing from downstream.** TabDGP imports nothing from metabeta or mixedbench; the proofs
59
+ read mixedbench's registry as a test dependency only.
60
+
61
+ ## Licence
62
+
63
+ By contributing, you agree that your changes are released under [MIT](LICENSE).
tabdgp-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Kipnis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
tabdgp-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,76 @@
1
+ Metadata-Version: 2.5
2
+ Name: tabdgp
3
+ Version: 0.0.1
4
+ Summary: A data-generating process for grouped and flat tables, matched to a registry of real tables
5
+ Project-URL: Repository, https://github.com/adkipnis/tabdgp
6
+ Project-URL: Issues, https://github.com/adkipnis/tabdgp/issues
7
+ Project-URL: Roadmap, https://github.com/adkipnis/tabdgp/blob/main/ROADMAP.md
8
+ Author-email: Alex Kipnis <adkipnis@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 1 - Planning
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Topic :: Scientific/Engineering
16
+ Requires-Python: >=3.14
17
+ Description-Content-Type: text/markdown
18
+
19
+ [![CI](https://github.com/adkipnis/tabdgp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/adkipnis/tabdgp/actions/workflows/ci.yml)
20
+ [![Licence](https://img.shields.io/badge/licence-MIT-blue)](https://github.com/adkipnis/tabdgp/blob/main/LICENSE)
21
+
22
+ # TabDGP
23
+
24
+ A data-generating process (DGP) for grouped and flat tables: grouping structure and typed
25
+ covariates with the quirks of real data.
26
+
27
+ ## What and why
28
+
29
+ - **Measured against real tables.** The default hyperprior is matched to
30
+ [mixedbench](https://github.com/adkipnis/mixedbench)'s registry of real grouped tables, then
31
+ widened; two tests prove it: generated tables are indistinguishable from real ones on their
32
+ summary statistics, and every real table lies inside the generated support.
33
+ - **White-box.** Every dataset comes with its generating graph, its latent nodes and its column
34
+ metadata (type, levels, role).
35
+ - **Composable.** One structural causal model whose nodes are grouping factors, group-level and
36
+ within-group covariates, time and treatment; a flat table is the case without grouping
37
+ factors.
38
+ - **The quirks i.i.d. simulators lack.** Group confounding, empty cells, rare levels, ties,
39
+ bounds, irregular time, near dependence, leverage points: a catalogue, each quirk measured on
40
+ real tables before it gets an axis.
41
+ - **Covariates only.** The outcome belongs to whoever fits a model; an optional outcome node
42
+ builds white-box misspecified datasets.
43
+ - **Overridable.** Every quantity has a default distribution; pass a point value or another
44
+ distribution for any of them.
45
+
46
+ ## Install
47
+
48
+ ```
49
+ pip install tabdgp
50
+ ```
51
+
52
+ From source:
53
+
54
+ ```
55
+ git clone https://github.com/adkipnis/tabdgp
56
+ cd tabdgp
57
+ uv sync
58
+ ```
59
+
60
+ ## Quickstart
61
+
62
+ The first call arrives with roadmap item 1; see [`ROADMAP.md`](https://github.com/adkipnis/tabdgp/blob/main/ROADMAP.md).
63
+
64
+ ## Contents
65
+
66
+ - The package `tabdgp` under `src/`.
67
+ - [`CONTEXT.md`](https://github.com/adkipnis/tabdgp/blob/main/CONTEXT.md): the vocabulary;
68
+ [`docs/adr/`](https://github.com/adkipnis/tabdgp/tree/main/docs/adr): the design decisions.
69
+
70
+ ## Contributing
71
+
72
+ Interested in becoming part of TabDGP? Check out [`CONTRIBUTING.md`](https://github.com/adkipnis/tabdgp/blob/main/CONTRIBUTING.md).
73
+
74
+ ## Licence
75
+
76
+ [MIT](https://github.com/adkipnis/tabdgp/blob/main/LICENSE).
tabdgp-0.0.1/README.md ADDED
@@ -0,0 +1,58 @@
1
+ [![CI](https://github.com/adkipnis/tabdgp/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/adkipnis/tabdgp/actions/workflows/ci.yml)
2
+ [![Licence](https://img.shields.io/badge/licence-MIT-blue)](https://github.com/adkipnis/tabdgp/blob/main/LICENSE)
3
+
4
+ # TabDGP
5
+
6
+ A data-generating process (DGP) for grouped and flat tables: grouping structure and typed
7
+ covariates with the quirks of real data.
8
+
9
+ ## What and why
10
+
11
+ - **Measured against real tables.** The default hyperprior is matched to
12
+ [mixedbench](https://github.com/adkipnis/mixedbench)'s registry of real grouped tables, then
13
+ widened; two tests prove it: generated tables are indistinguishable from real ones on their
14
+ summary statistics, and every real table lies inside the generated support.
15
+ - **White-box.** Every dataset comes with its generating graph, its latent nodes and its column
16
+ metadata (type, levels, role).
17
+ - **Composable.** One structural causal model whose nodes are grouping factors, group-level and
18
+ within-group covariates, time and treatment; a flat table is the case without grouping
19
+ factors.
20
+ - **The quirks i.i.d. simulators lack.** Group confounding, empty cells, rare levels, ties,
21
+ bounds, irregular time, near dependence, leverage points: a catalogue, each quirk measured on
22
+ real tables before it gets an axis.
23
+ - **Covariates only.** The outcome belongs to whoever fits a model; an optional outcome node
24
+ builds white-box misspecified datasets.
25
+ - **Overridable.** Every quantity has a default distribution; pass a point value or another
26
+ distribution for any of them.
27
+
28
+ ## Install
29
+
30
+ ```
31
+ pip install tabdgp
32
+ ```
33
+
34
+ From source:
35
+
36
+ ```
37
+ git clone https://github.com/adkipnis/tabdgp
38
+ cd tabdgp
39
+ uv sync
40
+ ```
41
+
42
+ ## Quickstart
43
+
44
+ The first call arrives with roadmap item 1; see [`ROADMAP.md`](https://github.com/adkipnis/tabdgp/blob/main/ROADMAP.md).
45
+
46
+ ## Contents
47
+
48
+ - The package `tabdgp` under `src/`.
49
+ - [`CONTEXT.md`](https://github.com/adkipnis/tabdgp/blob/main/CONTEXT.md): the vocabulary;
50
+ [`docs/adr/`](https://github.com/adkipnis/tabdgp/tree/main/docs/adr): the design decisions.
51
+
52
+ ## Contributing
53
+
54
+ Interested in becoming part of TabDGP? Check out [`CONTRIBUTING.md`](https://github.com/adkipnis/tabdgp/blob/main/CONTRIBUTING.md).
55
+
56
+ ## Licence
57
+
58
+ [MIT](https://github.com/adkipnis/tabdgp/blob/main/LICENSE).
@@ -0,0 +1,19 @@
1
+ # TabDGP — Roadmap
2
+
3
+ Decided in metabeta-v3's Phase 1 grill (2026-10-11; its root ADR 0005 and
4
+ `packages/mixed` ADR 0005), moved here from metabeta-v3's `ROADMAP.md` when the repository
5
+ was created. Order reflects dependencies. Specs and tickets are GitHub Issues on this
6
+ repository.
7
+
8
+ - [ ] **1 The flat SCM.** The repository on mixedbench's template; the flat structural causal
9
+ model with a marginal catalogue and typed post-hoc columns, written from scratch with scamd
10
+ and TabICL's prior read for ideas; the hyperprior config with overrides; tests; the tensor
11
+ library decided in this spec
12
+ - [ ] **2 Grouping nodes.** Group-level and within-group covariates, the imbalance draw of
13
+ metabeta's envelope rule 7, within-group time, binary treatment; metabeta.mixed's slice A1
14
+ starts after this
15
+ - [ ] **3 Measurement pass and proofs.** The measurement pass over mixedbench's cached tables
16
+ (a mixedbench issue: the quirk statistics per column and per factor) and the two proof
17
+ tests, the two-sample test and the containment test
18
+ - [ ] **4 Quirk axes.** The quirk axes the pass justifies and the matched, widened hyperprior
19
+ - [ ] **5 Outcome node.** The optional outcome node for white-box misspecified datasets
File without changes
@@ -0,0 +1,79 @@
1
+ ---
2
+ status: accepted
3
+ date: 2026-10-11
4
+ ---
5
+
6
+ # 0001 — One dataset per draw, keyed by seed and index, in numpy
7
+
8
+ TabDGP's first consumer, `metabeta.mixed`, trains on simulated datasets whose sizes, groups
9
+ and columns all vary; its roadmap puts padding, masking, collate and the `.npz` corpus on
10
+ `metabeta.core`, and its Simulator adds the outcome before anything is written. Grilled on
11
+ 2026-10-11 (storage, batch layout, tensor library, item 1 of `ROADMAP.md`), the decision is:
12
+ **the seed is the corpus.** Dataset `i` is a pure function of its key `(seed, index)`, the
13
+ hyperprior with its overrides and the installed TabDGP version. The public call draws one
14
+ dataset, `draw(seed, index, hyperprior) -> Dataset`, in numpy, at the dataset's own shapes.
15
+ TabDGP never pads, never batches across datasets and never writes a file.
16
+
17
+ ## Considered options
18
+
19
+ - **Online generation or a stored corpus.** A stored corpus buys reproducibility and a frozen
20
+ schema at the cost of disk, plumbing and an incompatibility that stays hidden until the next
21
+ regeneration. With a deterministic generator the two are the same data: a consumer
22
+ generates per epoch from `(seed, index)`, keeps variety by changing the seed and snapshots
23
+ only what carries expensive references (its validation and test sets). Trimming stored full
24
+ tables was rejected: subsampling rows breaks the imbalance draw and the group-level
25
+ structure, and a draw at the needed size is cheap.
26
+ - **Determinism per dataset or per batch.** Per batch, `(seed, batch)`, is cheaper but gives a
27
+ dataset no identity outside its batch, so bucketing by size or resharding changes the data.
28
+ Per dataset, bitwise on CPU whatever the batch, worker or shard, is what a virtual corpus
29
+ needs. Bitwise identity across devices is not promised: it is a promise no library keeps
30
+ across versions, and the draw runs on CPU.
31
+ - **Batch layout: a loop over datasets, or one padded batch.** Datasets in a batch differ in
32
+ rows, columns, groups and in their generating graph (node count, edges, mechanism per node,
33
+ marginals, post-hoc types), so a padded batch means masks through every mechanism and a
34
+ padded adjacency, TabICL's road, which TabICL itself left. A padded batch also breaks
35
+ per-dataset determinism on a GPU, where matrix kernels are chosen by shape. A loop over
36
+ datasets, each vectorised over its own rows, groups and columns, has no masks and no way for
37
+ dataset `i` to depend on its neighbours. GPU generation was weighed and rejected for the
38
+ same reason: a dataset of about 2,000 rows by 20 columns is launch-bound, scatter and
39
+ index-add (group aggregations) are non-deterministic on CUDA by default, and a GPU node's
40
+ idle CPU cores draw in parallel instead.
41
+ - **Tensor library.** JAX's `vmap` and `jit` are what this layout does not use, and shapes
42
+ that differ per dataset leave it eager and slow. torch buys metabeta's stack at the cost of a
43
+ heavy dependency on every standalone user, a keyed sampler catalogue to maintain (its
44
+ `distributions` take no generator except `Gamma`), and five to ten times numpy's
45
+ per-operation overhead on small CPU arrays. numpy's `Generator` is a complete keyed
46
+ catalogue, `default_rng([seed, index])` is the key, dynamic shapes are free, and metabeta's
47
+ tensors are `torch.from_numpy`, zero copy. scipy may be added when it supplies a sampler
48
+ numpy lacks.
49
+ - **Public call: a batch or a dataset.** A stateful generator argument makes dataset 256
50
+ depend on how much randomness datasets 0 to 255 consumed, so the key must be in the
51
+ signature. A `Batch` indexable to a `Dataset` is a list comprehension the caller writes as
52
+ well as we do, and the consumer that buckets by size calls per dataset anyway, with its
53
+ sizes as point overrides on a copied hyperprior.
54
+ - **Container: named arrays or one matrix.** One `(N, p)` float matrix stores categoricals and
55
+ group indices as floats, and both consumers derive what they need from the metadata anyway.
56
+ `Dataset` holds the columns as named one-dimensional arrays in their natural dtype, the
57
+ column metadata, the generating graph with every node including the latent ones, the latent
58
+ nodes' values, the hyperparameters every field resolved to, and the key.
59
+
60
+ ## Consequences
61
+
62
+ - One public function, one container, no batch object, no storage code, no padding code and
63
+ no parallelism inside TabDGP: a draw is a pure function of its key, so a process pool over
64
+ indices is the caller's one line, and nesting a pool inside a consumer's dataloader workers
65
+ would be wrong.
66
+ - The end-to-end test draws dataset `i` alone and inside two different index sets and asserts
67
+ bitwise equality; the proofs draw their fixed-seed datasets by index and store nothing.
68
+ - The hyperprior and its resolution share one Pydantic type: an override is a point value or
69
+ a distribution on a field, a resolved hyperparameter a point value on the same field.
70
+ - Throughput is a bar, not a hope: the spec's first ticket is a lab note timing the core draw
71
+ (graph, MLP propagation, one rank-matched marginal, one binary threshold) on one core at
72
+ N in {100, 1k, 10k} and the scaling of a process pool over indices. If the core draw falls
73
+ under about 500 datasets per second per core at the mean v1 size, a padded torch batch
74
+ behind the same one-dataset interface is the fallback.
75
+ - A DataFrame is `pandas.DataFrame(dataset.columns)` and needs no dependency here.
76
+ - For `metabeta.mixed`'s slice A1: generate per epoch from `(seed, index)` in dataloader
77
+ workers, pin TabDGP, snapshot validation and test sets, draw an epoch's sizes first (its
78
+ envelope rule 8) and bucket by them before drawing contents, and keep a contract test on the
79
+ schema at the boundary. These are its decisions to record.
@@ -0,0 +1,164 @@
1
+ # Conventions
2
+
3
+ The rules a change is reviewed against. The words are [`CONTEXT.md`](../CONTEXT.md)'s; the
4
+ reasons behind the larger rules are in the [ADRs](adr).
5
+
6
+ ## Tooling
7
+
8
+ - Python 3.14 through `uv` only; `uv run` for everything, every command from the repository
9
+ root, without `cd`.
10
+ - `ruff format` and `pytest`, run by CI.
11
+ - Open an issue before adding a heavyweight dependency.
12
+
13
+ ## Modules
14
+
15
+ Each module owns one design decision that no other module depends on. The table fills as the
16
+ modules arrive:
17
+
18
+ | module | owns | imported by |
19
+ |---|---|---|
20
+
21
+ ## Design
22
+
23
+ After Ousterhout, *A Philosophy of Software Design*: complexity is anything that makes code
24
+ harder to understand or change; it accrues one small edit at a time.
25
+
26
+ - **Deep modules.** A small interface over much functionality; an interface as complex as its
27
+ implementation folds into its caller or callee.
28
+ - **Information hiding.** Each module owns one design decision (a node mechanism, a tensor
29
+ layout, the hyperprior's parameterisation). Split code by what it knows, not by the order
30
+ it runs in.
31
+ - **No pass-throughs.** A method that forwards its arguments unchanged adds nothing; call the
32
+ target directly.
33
+ - **Pull complexity down.** The module absorbs the awkward case so its callers stay simple. A
34
+ fixed sensible behaviour beats a new config parameter.
35
+ - **Define errors out of existence.** Shape the interface so the invalid case cannot arise
36
+ (accept the empty set, validate once at construction). The rest fails loudly: no fallbacks,
37
+ no defensive `try`/`except`, no speculative generality. Branch only for cases observed on
38
+ real inputs; add the next branch when use hits it.
39
+ - **Design it twice.** For a non-trivial interface, name two designs and why one wins before
40
+ writing code.
41
+ - **Obvious over clever.** The simplest form a reader guesses right on first read, in code
42
+ written, reviewed or rewritten. If 200 lines could be 50, rewrite them.
43
+
44
+ ## Changing code
45
+
46
+ - The smallest change that solves the problem and meets the design bar, no unrequested
47
+ extras. Leave adjacent code, comments and formatting untouched.
48
+ - Written from scratch: scamd and TabICL's prior are read for ideas, never copied. A mechanism
49
+ one of them inspired names it in a comment. Where a formula comes from a paper, the comment
50
+ cites it (a link, an equation number).
51
+ - A task that repeats, or is error-prone by hand, gets a `.py` or `.sh` script.
52
+
53
+ ## Names
54
+
55
+ - A module is one lowercase word. A package's `__init__.py` lists its public names in
56
+ `__all__`.
57
+ - `PascalCase` for classes and `Literal` aliases, `camelCase` for functions and methods,
58
+ `snake_case` for variables, `UPPER_SNAKE_CASE` for constants.
59
+ - A directory is `dir`, not `folder`. A DataFrame is a `table`, several are `tables`, never
60
+ `df` or `frame`.
61
+ - A leading `_` marks a pipeline step and anything no other module imports.
62
+ - Tests live in `tests/`, mirroring `src/`, one `test_<module>.py` per module, each test named
63
+ as a sentence (`test_<what_holds>`), fixtures in a `fixtures/` beside them.
64
+
65
+ ## Code
66
+
67
+ - Absolute imports: `from tabdgp.x import y`.
68
+ - Modern typing: `X | Y`, built-in generics, `from __future__ import annotations`.
69
+ `Literal[...]` for an argument with a fixed set of options, a type alias once it repeats.
70
+ Config models are Pydantic.
71
+ - f-strings, one statement per line, a long expression named on the line before.
72
+ - A method lists its steps in the order you would describe them; a step with its own `if`
73
+ ladder or loop becomes a method. A search is a plain loop with an explicit check after it,
74
+ not `for`/`else`, `while True` or `next()` on a generator.
75
+ - `a if c else b` only on a short line. Lookup tables are literal dicts or `if` chains, not
76
+ built with comprehensions, `dict.fromkeys` or `|`.
77
+ - A class per concept a reader should hold apart (a mechanism family, a pipeline), static
78
+ methods where it holds no state. A helper or constant used by one class lives on it; module
79
+ functions are for what spans classes.
80
+ - Wrap `argparse` blocks in `# fmt: off` / `# fmt: on`.
81
+
82
+ ### Arrays and tensors
83
+
84
+ - Annotate shapes inline (`# (B, N, p)` for B datasets of N rows and p columns), reshape with
85
+ `einops` where the library has it, never mutate a reused array in place. Vectorise with
86
+ broadcasting and advanced indexing, not loops; batch over datasets.
87
+ - Every draw takes an explicit generator or key; nothing reads global random state.
88
+
89
+ ## File layout
90
+
91
+ ```python
92
+ """grouping.py defines how a dataset's grouping factors and group sizes are drawn.
93
+
94
+ pipeline:
95
+ ---------
96
+ the overrides and the hyperprior
97
+ -> the number of factors and their relation _factors
98
+ -> groups per factor and the group sizes _sizes
99
+ -> one assignment array per factor draw
100
+ """
101
+
102
+ # =====================================================================================
103
+ # default hyperprior
104
+ # =====================================================================================
105
+ # the distributions every quantity falls back to when no override names one.
106
+ ```
107
+
108
+ - The module docstring: one sentence on what the module defines, optionally a short paragraph
109
+ on how, then the pipeline, one `-> result` line per step with its function on the right.
110
+ Who imports the module is the [map](#modules)'s.
111
+ - `# ====` banners separate globals, classes and main, `# ----` banners subsections. Each is
112
+ named in plain words, with one comment line on why the section exists unless a class
113
+ docstring below says it.
114
+ - One pipeline class per module: public methods first, then its steps in run order under a
115
+ `# ----` banner naming the pipeline.
116
+ - A script's `__main__` creates its pipeline class and reads as the run's table of contents.
117
+
118
+ ## Comments and docstrings
119
+
120
+ - Comments start in lower case; docstrings are capitalised sentences. Break the lines of both
121
+ at sentence or clause boundaries, not at the column limit.
122
+ - A docstring only where the behaviour or design is not obvious: one or two plain sentences,
123
+ none on a method its name and body already explain, never a chain of `;` clauses. Detail
124
+ goes into inline comments that say why and cite sources, and into trailing comments on
125
+ config fields.
126
+ - A comment explains the code, not its history: the case behind a rule goes to the test that
127
+ pins it, a definition to `CONTEXT.md`, how it was decided to an ADR or a lab note.
128
+ - Code in `src/` states its rules without citing an ADR; the ADR keeps the reason.
129
+
130
+ ## Tests
131
+
132
+ - Test first: the failing test, then the minimal code. `hypothesis` for property tests.
133
+ - Unit tests for every mechanism: marginal ranges, the coded shapes, determinism under a
134
+ fixed generator, every override honoured, the flat case as zero factors.
135
+ - The proofs against mixedbench's registry (the two-sample test and the containment test)
136
+ carry the `slow` mark and fixed seeds.
137
+ - Each test says in a sentence or two why it exists, unless obvious. Bind the object under
138
+ test, then call it; small named helpers, not lambdas or repeated comprehensions.
139
+ - No coverage gate.
140
+
141
+ ## Docs
142
+
143
+ Markdown meets the same bar as code: simple, and read top to bottom.
144
+
145
+ - Short bullets, the essentials first, detail in an appendix or a linked file.
146
+ - One home per fact, links everywhere else; a living rule sits with the method it governs.
147
+ Counts live in generated files, never in prose.
148
+ - `README.md` is for users and fits one screen: badges, a short what and why, install, one
149
+ quickstart box, contents, contributing, citing, licence. Its links are absolute to `main`,
150
+ so PyPI renders them.
151
+
152
+ ## Data
153
+
154
+ - The registry statistics the proofs read are mixedbench's, pinned by version, never edited
155
+ here.
156
+ - Stream or lazily load data that would not fit in memory.
157
+ - Figures are PDFs, never rasterised.
158
+
159
+ ## Lab notes and experiments
160
+
161
+ - `lab/YYYY-MM-DD-slug/`: one small design choice, its notebook committed with outputs and
162
+ indexed in `lab/README.md` with the decision. Never imported.
163
+ - `experiments/`: rerunnable scripted studies that feed a paper. Rerun with flags; never
164
+ one-off.
@@ -0,0 +1,65 @@
1
+ # Prior art
2
+
3
+ What scamd (`~/Code/scamd`, the prototype) and TabICL's prior (`~/Code/tabicl/src/tabicl/prior/`,
4
+ BSD-3, read at commit c91f00d) do, read for ideas only: no code is copied, and a mechanism one
5
+ of them inspired names it in a comment. Read 2026-10-11.
6
+
7
+ ## Shared by both
8
+
9
+ - PyTorch tensors, one dataset at a time: neither batches inside its SCM. TabICL batches by
10
+ padding features to a maximum and stacking, with a process pool over datasets.
11
+ - Both mix an explicit generator with global random state, so neither reproduces from one seed.
12
+ - Neither returns column types, the graph or the drawn hyperparameters.
13
+
14
+ ## scamd
15
+
16
+ - **SCM**: an MLP whose hidden units are the nodes (weights redrawn on every pass, dense with
17
+ dropout or block-diagonal), or a DAG option (Barabási–Albert or Erdős–Rényi, one activation
18
+ for all nodes). Activations: about 25 elementwise functions and random-Fourier GP functions.
19
+ - **Marginal catalogue**, applied to the root causes: normal, uniform, multinomial index,
20
+ Zipf, gamma, lognormal, beta, Student-t, Gaussian mixture; after the SCM, a power or log1p
21
+ warp per column.
22
+ - **Post-hoc column types**: threshold (binary), multi-threshold and quantile bins (ordered),
23
+ softmax categoricals, an ordinal block over a shared direction, Poisson and negative
24
+ binomial counts, clamps and censored floors. Each post-hoc column mixes all SCM columns.
25
+ - **Correlation**: block-wise weights, a Gaussian copula on the causes, a factor layer, and
26
+ shared noise (one noise vector added to a group of columns).
27
+ - **Config**: plain dict presets; extra keyword arguments routed by `inspect.signature`.
28
+ - **Weaknesses**: noise calibrated once while weights are redrawn; absolute noise scales; a
29
+ `sanityCheck` that only rejects all-non-finite data; column position reveals type; levels
30
+ fixed at 3.
31
+
32
+ ## TabICL
33
+
34
+ - **Hyperparameter meta-distributions**: a hyperparameter is drawn from a distribution whose
35
+ own parameters are drawn first (log-scaled truncated normal, Beta, choice over softmaxed
36
+ random logits). The parameters are drawn once per group of datasets, the values per dataset.
37
+ A spec entry is a constant or a distribution, recursively; defaults fill only what the user
38
+ left out.
39
+ - **Starting ranges**: layers 2 to about 8, hidden width about 9 to 134, init scale 0.01 to 10,
40
+ noise 1e-4 to 0.3, causes 2 to about 13, dropout 0.9·Beta, categorical share 0.2.
41
+ - **Graph SCM** (`graph_lib`): a Cauchy DAG, where an edge i→j (i < j) exists with probability
42
+ σ(offset + out_j + in_i) with Cauchy draws, so density ranges from near-empty to
43
+ near-complete and hubs arise. Node count 2–32, log scale. Vector-valued nodes; roots are base
44
+ points through a random function; parents are concatenated or combined by sum, product, max
45
+ or logsumexp. Function families: MLP, oblivious trees, nearest-centre discretisation, linear,
46
+ quadratic, random-Fourier GP, softmax, products.
47
+ - **Named draws** through one sampler with a mode per name: `global` (one value), `meta` (a
48
+ per-dataset Beta that correlates repeated draws of a name within a dataset), `local` (i.i.d.);
49
+ `boundary_mass` puts mass at the ends of an interval (all numeric, all categorical).
50
+ - **Categoricals**: k − 1 thresholds at random sample values with labels permuted (v1);
51
+ nearest-centre or softmax converters (graph SCM). Numeric columns get a Kumaraswamy warp.
52
+ - **Not taken over**: y as a node of the SCM, and the two-pass 4σ outlier clipping with z-scores
53
+ clipped to ±100.
54
+
55
+ ## Ideas carried into TabDGP's design (proposed, not yet decided)
56
+
57
+ - A recursive point-or-distribution field: for a per-column field, its distribution's
58
+ parameters drawn once per dataset and its values once per column (TabICL's meta draws), so
59
+ that corners such as an all-binary table get mass.
60
+ - The Cauchy DAG for edge density (TabICL); scamd's shared noise and block-wise correlation as
61
+ latent hub nodes and a within-block edge bonus in the graph draw.
62
+ - Marginals mapped onto the SCM's values by rank matching after the SCM (Iman and Conover,
63
+ 1982), instead of scamd's root marginals: each column gets exactly its drawn marginal and the
64
+ SCM supplies only the dependence.
65
+ - Categoricals by thresholds at drawn level shares with permuted labels (TabICL v1).
@@ -0,0 +1,7 @@
1
+ # Lab notes
2
+
3
+ One directory per small design choice, `lab/YYYY-MM-DD-slug/`, its notebook committed with
4
+ outputs and never imported. Each row names the decision it reached.
5
+
6
+ | date | note | decision |
7
+ |---|---|---|
@@ -0,0 +1,49 @@
1
+ [project]
2
+ name = "tabdgp"
3
+ version = "0.0.1"
4
+ description = "A data-generating process for grouped and flat tables, matched to a registry of real tables"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Alex Kipnis", email = "adkipnis@users.noreply.github.com" }]
9
+ requires-python = ">=3.14"
10
+ classifiers = [
11
+ "Development Status :: 1 - Planning",
12
+ "Intended Audience :: Science/Research",
13
+ "Topic :: Scientific/Engineering",
14
+ "Programming Language :: Python :: 3.14",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+ dependencies = []
18
+
19
+ [project.urls]
20
+ Repository = "https://github.com/adkipnis/tabdgp"
21
+ Issues = "https://github.com/adkipnis/tabdgp/issues"
22
+ Roadmap = "https://github.com/adkipnis/tabdgp/blob/main/ROADMAP.md"
23
+
24
+ [dependency-groups]
25
+ dev = [
26
+ "pytest",
27
+ "hypothesis",
28
+ "ruff",
29
+ ]
30
+
31
+ [build-system]
32
+ requires = ["hatchling>=1.27"] # PEP 639 licence expression and licence files
33
+ build-backend = "hatchling.build"
34
+
35
+ [tool.hatch.build.targets.wheel]
36
+ packages = ["src/tabdgp"]
37
+
38
+ [tool.ruff]
39
+ line-length = 100
40
+
41
+ [tool.ruff.format]
42
+ quote-style = "single"
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+ addopts = "-m 'not slow' --import-mode=importlib"
47
+ markers = [
48
+ "slow: draws thousands of datasets (the proofs against mixedbench's registry); runs by `-m slow`",
49
+ ]
@@ -0,0 +1,3 @@
1
+ """TabDGP, a data-generating process for grouped and flat tables."""
2
+
3
+ __all__ = []
@@ -0,0 +1,14 @@
1
+ """The README is rendered on PyPI, where relative links break, so every link points to main."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ README = Path(__file__).parents[1] / 'README.md'
9
+
10
+
11
+ def test_readme_links_are_absolute() -> None:
12
+ targets = re.findall(r'\]\(([^)]+)\)', README.read_text())
13
+ relative = [target for target in targets if not target.startswith('https://')]
14
+ assert relative == []
tabdgp-0.0.1/uv.lock ADDED
@@ -0,0 +1,181 @@
1
+ version = 1
2
+ revision = 3
3
+ requires-python = ">=3.14"
4
+
5
+ [[package]]
6
+ name = "colorama"
7
+ version = "0.4.6"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
10
+ wheels = [
11
+ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
12
+ ]
13
+
14
+ [[package]]
15
+ name = "hypothesis"
16
+ version = "6.169.0"
17
+ source = { registry = "https://pypi.org/simple" }
18
+ dependencies = [
19
+ { name = "sortedcontainers" },
20
+ ]
21
+ sdist = { url = "https://files.pythonhosted.org/packages/b7/b7/fcddfc235d1ab24b831e99ad3385361e87eb4fed427f527a7f15866214ad/hypothesis-6.169.0.tar.gz", hash = "sha256:b65749d7f7a2fddfb106bb57c9902db4ab25ce8724c821f4af50cc58891a6b7b", size = 510164, upload-time = "2026-10-11T06:30:11.324Z" }
22
+ wheels = [
23
+ { url = "https://files.pythonhosted.org/packages/46/77/f9618aea42a2130798678346c9ea7a8bba5698d87987e7df80e4287d663b/hypothesis-6.169.0-cp311-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e9e896e0175f0ccc4d3cabfdc704b363f0ccc84c7a3fee83ff7915015d9f8292", size = 790354, upload-time = "2026-10-11T06:29:11.368Z" },
24
+ { url = "https://files.pythonhosted.org/packages/c2/a3/1bc6f290a39e0d5d2111207cd6ad7a3fea3ea5b1e4ed7eecba2285e5dca1/hypothesis-6.169.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:7196caf24090cbacbff198d6a05c621b41cba6730240b06d0d70aebecec018a3", size = 785843, upload-time = "2026-10-11T06:29:22.091Z" },
25
+ { url = "https://files.pythonhosted.org/packages/4a/15/bce76740ac85d8554ca21667222e9c142058358df7fd189a5747672b255a/hypothesis-6.169.0-cp311-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5137579522957acd2ac0b75f63ab997d1606af133fa99e1f00e40c36352d6560", size = 1113614, upload-time = "2026-10-11T06:28:42.055Z" },
26
+ { url = "https://files.pythonhosted.org/packages/48/59/461ac4e614079c4762cc545f73cce0ab0b31d8cc10a3d942136b4c939442/hypothesis-6.169.0-cp311-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff4a20d78f9e9c1c5d2f8c70b0cd64b3e05be187dd78c9ceb53c1b35ca6c68c1", size = 1143771, upload-time = "2026-10-11T06:28:45.496Z" },
27
+ { url = "https://files.pythonhosted.org/packages/cf/b5/848f2d5b0447a3cf7c3d2de00701bce8a3d323ec6592baa2c64c857987f7/hypothesis-6.169.0-cp311-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:280ae28120be35792d8fe0ecdf8cd37978842b6646721e257100d24939377f21", size = 1138312, upload-time = "2026-10-11T06:29:56.305Z" },
28
+ { url = "https://files.pythonhosted.org/packages/0a/2b/eeac69999eeaa45354f6bc491ecd2ae163e6ac1bf3761cea21690625e48a/hypothesis-6.169.0-cp311-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76f04d874d2b3e0af583dbfefb6ba5a87059a4cc4ad07f74d4c1e35a350a6c02", size = 1190587, upload-time = "2026-10-11T06:28:07.344Z" },
29
+ { url = "https://files.pythonhosted.org/packages/53/63/1db41f8e3e4aa348b90e28e7059a75fa788f375cb2e06218684767a5df8c/hypothesis-6.169.0-cp311-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b9a681b0b1a11faccfc26947bf53c4b00eae7b1f49c435d7e1f76a9ea5ab224", size = 1156021, upload-time = "2026-10-11T06:29:18.175Z" },
30
+ { url = "https://files.pythonhosted.org/packages/0b/86/d60fe736ff11a31c3a908f50b2b1ef04d4746a9cd9ff8c9a89e09e166fcb/hypothesis-6.169.0-cp311-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:657ba124452b321c3e9fcb90d2ae7b1fa98a0584cde0790dd94359d1ad73a342", size = 1112353, upload-time = "2026-10-11T06:30:02.413Z" },
31
+ { url = "https://files.pythonhosted.org/packages/25/46/00f848d26bc013915dcf4427f229567b6a2760886d90a9aeb8694d5695d9/hypothesis-6.169.0-cp311-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:74c3af6a0dc9a6e15b8e875455aa790183524cbbb8a1bd64cb06a77c767c8d92", size = 1151311, upload-time = "2026-10-11T06:28:05.72Z" },
32
+ { url = "https://files.pythonhosted.org/packages/b9/b3/91ef45be347c8ae1a5602708ab29a11670b030ad78c67f516ace925187d4/hypothesis-6.169.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:90f928cdce3aa1252d5d2d02cd347535c9b8c4fad3aea5ea45c74a319197f654", size = 1288743, upload-time = "2026-10-11T06:28:56.972Z" },
33
+ { url = "https://files.pythonhosted.org/packages/f2/50/c0f12b457474a30034d48b8eed6345b6d36d6f14834082c2f29cf0d814d4/hypothesis-6.169.0-cp311-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:6f2b1a7512a8961d84ce92f33921fd297f12e3da5ebf490c9de383532307f56b", size = 1416963, upload-time = "2026-10-11T06:28:33.393Z" },
34
+ { url = "https://files.pythonhosted.org/packages/b5/26/6cdc5f10779af18abd847a195f0cbbb79661d9c4dcfe70d10210c5b396c0/hypothesis-6.169.0-cp311-abi3-musllinux_1_2_i686.whl", hash = "sha256:e0e597cbc93c2a8c7e4c7823039d291ba2c3b15f2105c346463a99c0cd41889c", size = 1370880, upload-time = "2026-10-11T06:28:47.213Z" },
35
+ { url = "https://files.pythonhosted.org/packages/41/0a/7c6aecb765ffa257bfe582efa7446c999c09459b7dcca2a60dade37a8b7f/hypothesis-6.169.0-cp311-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:149cd4905da8db8f7385b83dd73d8d1fa459ec327f369e9b8dcca5d3a3358549", size = 1267674, upload-time = "2026-10-11T06:29:23.766Z" },
36
+ { url = "https://files.pythonhosted.org/packages/c0/85/a958ca273d9436bb7fed05e62c5fb978238d5789165046f13154f1294b70/hypothesis-6.169.0-cp311-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:00b317f00bc41be393cb681b6684e6d912bff1da673be1e719d6ca7b314b78dd", size = 1282637, upload-time = "2026-10-11T06:29:50.717Z" },
37
+ { url = "https://files.pythonhosted.org/packages/3c/7a/a4d14c21b31e94ecc886ff5fbd68d534598796f848bfaaf3a9e7e13a1d90/hypothesis-6.169.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:96582616bb7de9533f8c5efdba4c5ea1b87457052148f04e53ff6da2e10f8fb8", size = 1322553, upload-time = "2026-10-11T06:29:05.887Z" },
38
+ { url = "https://files.pythonhosted.org/packages/a6/ec/77363e885adfea72e4a6e2f613cf7aea4e1107689666665c18e621cf609c/hypothesis-6.169.0-cp311-abi3-win32.whl", hash = "sha256:aa9cc053858d3a43f59569ca1203dbb2819b1738674fe426b8139229102e4286", size = 676707, upload-time = "2026-10-11T06:29:58.08Z" },
39
+ { url = "https://files.pythonhosted.org/packages/59/4f/0c586fabb76b30a643f5a9b3dbf4463909cac405bb44bfd8c72046d787c3/hypothesis-6.169.0-cp311-abi3-win_amd64.whl", hash = "sha256:43aeb55dbcae56e2dc91caa6bc3e6b1a2863f5ee0e1ba2a8c9a70ff453d6a42c", size = 683369, upload-time = "2026-10-11T06:28:21.568Z" },
40
+ { url = "https://files.pythonhosted.org/packages/e0/1a/ec298d9ee10d7c267e3d8bf886b2d27571628a65dee6238baf36e2275742/hypothesis-6.169.0-cp311-abi3-win_arm64.whl", hash = "sha256:4e00d21ce5e125e78c6ff43388c60f66969e2753e99dacaf2845c81f16b6adc1", size = 681146, upload-time = "2026-10-11T06:29:03.809Z" },
41
+ { url = "https://files.pythonhosted.org/packages/5a/c3/266159710ddf8d2ca594686cfe349597417f7e6d5cc8d299c5f179fb8ee6/hypothesis-6.169.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:ba0494c5be4c5aef90aae7bc6e5c7ee431f27f4594ab4829d4dd47c20d4ad2f9", size = 792160, upload-time = "2026-10-11T06:28:30.306Z" },
42
+ { url = "https://files.pythonhosted.org/packages/6c/a3/6ffbd303f1f6c2d5d6366024ce104bee175fd7d570ce795029f8f8506c54/hypothesis-6.169.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6f8c559b34c143bdb88ef4871e68747017050569313e42b68835b6e4e98f0acb", size = 783754, upload-time = "2026-10-11T06:28:00.236Z" },
43
+ { url = "https://files.pythonhosted.org/packages/f2/cf/7b61a2e12652cb11ec8f3b81b8ff5c227e4f211b845943d4e4a2d5e73f0a/hypothesis-6.169.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:078eeecc48d8361a39f63bab150f4098371e537bfd64c0cd1444912a7e269592", size = 1113176, upload-time = "2026-10-11T06:30:09.094Z" },
44
+ { url = "https://files.pythonhosted.org/packages/96/24/dced7321227420c63de73e57a48e1d2fd2732e32b0d2abb643c8630e1e09/hypothesis-6.169.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9ac3957d9b5da1d846f66ad17a793835b7e4b59892dc6f74005c709f16ad208", size = 1155753, upload-time = "2026-10-11T06:28:23.477Z" },
45
+ { url = "https://files.pythonhosted.org/packages/26/68/97ede862a9cf65e42338c0643b62d96bd02643b85d029b918aa357aeffe3/hypothesis-6.169.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:eb49c6433578ebc815d2a86315dcb2598c0d138ab4f674d59d9896d6fbc7102a", size = 1287760, upload-time = "2026-10-11T06:28:27.106Z" },
46
+ { url = "https://files.pythonhosted.org/packages/2b/97/03435e5d9f81e831e4b9b9bc88712b945ea4b8e48b52e76aa9c8a8d9cf8e/hypothesis-6.169.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:aa998bfdc1b13706e944219be55025fe4cdf63a8e30d97b15e6d0ce2ad14d57d", size = 1322223, upload-time = "2026-10-11T06:27:54.341Z" },
47
+ { url = "https://files.pythonhosted.org/packages/de/0c/79dc8be75c1eca2cfaa0ccbf36caef1f7ef18c73654b4d9b4e3cb276e568/hypothesis-6.169.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:d4edcb680604e5895577214395d01864f6c68adc2c007f5ad364653cc954fe93", size = 623152, upload-time = "2026-10-11T06:29:13.041Z" },
48
+ { url = "https://files.pythonhosted.org/packages/f0/4e/4c8e34699b0f79457245e15d7d9d6c0fb13881913a04740532b7fd5df5bc/hypothesis-6.169.0-cp314-cp314-win_amd64.whl", hash = "sha256:d0836e03ef8a3162d000d837deafbb1f0fc573078f46c7c0a8bdee0c4f289e41", size = 680803, upload-time = "2026-10-11T06:29:25.788Z" },
49
+ { url = "https://files.pythonhosted.org/packages/88/e2/4cb686970f3ffb0b0dc61a16c6a27f5029008373517671c443396c95bc85/hypothesis-6.169.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:575017acc9f12f5dc80a3f67089d40745ba95c218d60751bc0eaa25e0c42203c", size = 790544, upload-time = "2026-10-11T06:28:58.611Z" },
50
+ { url = "https://files.pythonhosted.org/packages/f9/41/a319aecd1dfe3d2f2cad3ea8e3ec7162cba6954d2f32eff79e91b51a5ae4/hypothesis-6.169.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:47c180e7176ed529232d8c74292c80c41837f5e5bd3e8dee687bf24a861ceb25", size = 782197, upload-time = "2026-10-11T06:29:09.431Z" },
51
+ { url = "https://files.pythonhosted.org/packages/86/6e/e7d2cacbdb4d29436bb822cba6ffdc35bf4976877f8c6b17a1c8e719f506/hypothesis-6.169.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7dd2bf18e569d0a36cccf7f25239e39e5fec0e81d48a1e65f9e8d0cce85ef9b", size = 1111061, upload-time = "2026-10-11T06:28:50.178Z" },
52
+ { url = "https://files.pythonhosted.org/packages/21/2b/f2bd549a927c70605c0a80e7003fb3e73a29d020de862cd4326b23de24a0/hypothesis-6.169.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031dc57f707f2d7aa64d652f582ee3cbb5d760c56db0268e10a93e4ba6a802f0", size = 1154548, upload-time = "2026-10-11T06:28:25.148Z" },
53
+ { url = "https://files.pythonhosted.org/packages/46/68/b7bbcd755b819988ed5dffb8e3c71c4e663f6db551409a1daefb12ceb6b2/hypothesis-6.169.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:eb45a192fcccd0220d980feeafdc89b9d7ce49b0343a31f34075dcac71432c2a", size = 1285944, upload-time = "2026-10-11T06:29:14.727Z" },
54
+ { url = "https://files.pythonhosted.org/packages/28/2e/b4cdf89eae136e7bb5052ee2b6a76c4a125f0a6317c7954f88a046090354/hypothesis-6.169.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f8be62e2c59055995353e929eeb01003796fbcde75a260d7f77ece88ee57be06", size = 1321091, upload-time = "2026-10-11T06:29:35.341Z" },
55
+ { url = "https://files.pythonhosted.org/packages/43/0d/9aee786b177aded81a5ea2f5a7ec5c0b3766b69b5cbb6ef23fb620d89a94/hypothesis-6.169.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fe0dfcd8cd9dd846d9c35c2a0d9fe697fae42ed25368c6aa7db4a6b4c2ea4a9", size = 680518, upload-time = "2026-10-11T06:28:40.535Z" },
56
+ { url = "https://files.pythonhosted.org/packages/48/32/85618cc42fc9088d0abeb90d62fa16fa52324855d59853a84437ecad0c78/hypothesis-6.169.0-cp315-abi3.abi3t-macosx_10_12_x86_64.whl", hash = "sha256:6bb65a6d0b327e3446baa535a86b645f68d09cf8e838d9b386ae26a2f4e7d829", size = 789864, upload-time = "2026-10-11T06:28:04.247Z" },
57
+ { url = "https://files.pythonhosted.org/packages/11/ac/2441c1a1db15d1e94659d02505d374c9e40932090c036b03d4c92bf5e41c/hypothesis-6.169.0-cp315-abi3.abi3t-macosx_11_0_arm64.whl", hash = "sha256:01f9c4660bf2627ef36558f3e0f20c746ba30d666e18a2f5af0abc7c71bad695", size = 781885, upload-time = "2026-10-11T06:29:31.743Z" },
58
+ { url = "https://files.pythonhosted.org/packages/b7/38/0ff5b49df3bf71cb7470bc47b3b9bb67c0ff90056f8de43df3208ac548df/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d754678d75d815c89a3ec0b174fb48df00671fc4ec157983a252f96a9b4872e8", size = 1110308, upload-time = "2026-10-11T06:29:45.02Z" },
59
+ { url = "https://files.pythonhosted.org/packages/06/36/64a2ea6272694b00352e5d9cd53901037477f7850d0be9fba4878ab14cd7/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c25e3458f6feedae16962790f58100b3f62c0c81f61c26bf091c55048e0c7b7", size = 1141630, upload-time = "2026-10-11T06:29:40.92Z" },
60
+ { url = "https://files.pythonhosted.org/packages/00/dc/a292b35d6563d9fff37410898cd39685d4f5dde16d96ace4e2b486e33a4f/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cfb0cb4964698c60b3756c74a4def1dd20e296cc622ec2313ccbce06e1a6f49", size = 1134877, upload-time = "2026-10-11T06:28:37.177Z" },
61
+ { url = "https://files.pythonhosted.org/packages/47/6c/cd0770da746c852251a98618abc46edabd2864f7ca9642f193dd694ccbae/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0ea13627863ee38040ce4bd2841a98f29d27bb404fb1460f0d750750da18a6d", size = 1188583, upload-time = "2026-10-11T06:29:59.951Z" },
62
+ { url = "https://files.pythonhosted.org/packages/aa/c7/ff5a591b32d2e7f3f1da09bcd81eee133bd23fce971dadeb51d3d87af718/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1d423b3d84357331e9cffb3d62c01cfbb08206e102005b858d096695d73210", size = 1153697, upload-time = "2026-10-11T06:28:35.397Z" },
63
+ { url = "https://files.pythonhosted.org/packages/7c/9c/178b6b9371c7d5beefef7cbf5e8746e48ed044852908feccd57db21d3b56/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_31_riscv64.whl", hash = "sha256:307f9aaf1eb3d323488cacd2b4f7c0b05ec637be1216b31aa47d0288a4ad163a", size = 1108825, upload-time = "2026-10-11T06:28:38.918Z" },
64
+ { url = "https://files.pythonhosted.org/packages/6f/26/19c06b74cae9949ff18f2bd9a6579310c37499ef46772ecb49d28a72fcd5/hypothesis-6.169.0-cp315-abi3.abi3t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:78b7b0ab7ccbfd8e6250573418859474ef0f8ef7906fcb3b639b6ceccb75af81", size = 1147441, upload-time = "2026-10-11T06:28:15.491Z" },
65
+ { url = "https://files.pythonhosted.org/packages/da/fa/d3638853d5bb2862545c34ba9b101211a5a1066e7a1c25679f828135d3b8/hypothesis-6.169.0-cp315-abi3.abi3t-musllinux_1_2_aarch64.whl", hash = "sha256:9e6d460c82340b18ad5b49e120df495f78b954c884d3c4f1ea0ca7b2d3bfe4ff", size = 1284816, upload-time = "2026-10-11T06:29:07.632Z" },
66
+ { url = "https://files.pythonhosted.org/packages/56/76/d6ecdd89b3ccbb7af89a0f2504e0bdb840848cc7fd9bffd0fbeee14b4218/hypothesis-6.169.0-cp315-abi3.abi3t-musllinux_1_2_armv7l.whl", hash = "sha256:1a321d2e407b21e63d5e10e657a5d5d0def640e3c4388918485bce328f066ccb", size = 1414409, upload-time = "2026-10-11T06:28:17.298Z" },
67
+ { url = "https://files.pythonhosted.org/packages/7f/94/12165c54ba410e3efe21cb4fdb24ca46f609e6b1fb5d170c5a1c07ab62ab/hypothesis-6.169.0-cp315-abi3.abi3t-musllinux_1_2_i686.whl", hash = "sha256:8e196d16686c9ee439aed446ae5dbfc67ff10f6596d27590f64ccb2952801dbb", size = 1367536, upload-time = "2026-10-11T06:29:37.166Z" },
68
+ { url = "https://files.pythonhosted.org/packages/e0/72/fae9de86e2dd876c8fd42caa3c33cc514b9426f5ed04d3d6044db818a797/hypothesis-6.169.0-cp315-abi3.abi3t-musllinux_1_2_ppc64le.whl", hash = "sha256:6ea93e30342ddb8a8f3e404718a0b51be5ec5b205aecdf9d900ca938c969a6e2", size = 1263366, upload-time = "2026-10-11T06:28:01.44Z" },
69
+ { url = "https://files.pythonhosted.org/packages/e4/c8/e82296f440ba5057fd89ab78f013463ac804bc546a80bc15ed870802f6d2/hypothesis-6.169.0-cp315-abi3.abi3t-musllinux_1_2_riscv64.whl", hash = "sha256:c1eab3b6b6aec4cec5c6f57f89d5d827d23ff8463ebd9296c63132579b0a79d3", size = 1280661, upload-time = "2026-10-11T06:28:43.914Z" },
70
+ { url = "https://files.pythonhosted.org/packages/3b/da/8bcd647d20fc4fa3d79a098d3f9a0672e31253605838278f37341873b896/hypothesis-6.169.0-cp315-abi3.abi3t-musllinux_1_2_x86_64.whl", hash = "sha256:4099543afdbb6c727ba823482b93329b8afff0d2b17d8888151592284c7c3971", size = 1320405, upload-time = "2026-10-11T06:30:06.898Z" },
71
+ { url = "https://files.pythonhosted.org/packages/a4/55/2e26e757aeea856ba7120fd8eca0cda40531e0847ac28c6937dc25b58f22/hypothesis-6.169.0-cp315-abi3.abi3t-win32.whl", hash = "sha256:764cdb2f9d5351bb40e459ff94f30ff271af8927a6e55a1b72db904794f002b8", size = 673870, upload-time = "2026-10-11T06:27:58.753Z" },
72
+ { url = "https://files.pythonhosted.org/packages/67/e6/5a780510ce2524aa778e30b729c5fc439d30e2a276856ccf50a19ae73bda/hypothesis-6.169.0-cp315-abi3.abi3t-win_amd64.whl", hash = "sha256:bb4643dd25af96749386d52b0cf7cf97d0a1abc5c4382e0835da9311f9c35112", size = 680232, upload-time = "2026-10-11T06:28:08.786Z" },
73
+ { url = "https://files.pythonhosted.org/packages/84/10/0869258af64a59319b42776cf22b1881b3183370ff1cbc2111466d595760/hypothesis-6.169.0-cp315-abi3.abi3t-win_arm64.whl", hash = "sha256:b65468d07f1f4483bd8c02581e2c03fd1dc9a1d21e3e9f053c4518cecf1e553b", size = 677992, upload-time = "2026-10-11T06:29:29.982Z" },
74
+ ]
75
+
76
+ [[package]]
77
+ name = "iniconfig"
78
+ version = "2.3.1"
79
+ source = { registry = "https://pypi.org/simple" }
80
+ sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" }
81
+ wheels = [
82
+ { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" },
83
+ ]
84
+
85
+ [[package]]
86
+ name = "packaging"
87
+ version = "26.3"
88
+ source = { registry = "https://pypi.org/simple" }
89
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" }
90
+ wheels = [
91
+ { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
92
+ ]
93
+
94
+ [[package]]
95
+ name = "pluggy"
96
+ version = "1.6.0"
97
+ source = { registry = "https://pypi.org/simple" }
98
+ sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
99
+ wheels = [
100
+ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
101
+ ]
102
+
103
+ [[package]]
104
+ name = "pygments"
105
+ version = "2.21.0"
106
+ source = { registry = "https://pypi.org/simple" }
107
+ sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" }
108
+ wheels = [
109
+ { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" },
110
+ ]
111
+
112
+ [[package]]
113
+ name = "pytest"
114
+ version = "9.1.1"
115
+ source = { registry = "https://pypi.org/simple" }
116
+ dependencies = [
117
+ { name = "colorama", marker = "sys_platform == 'win32'" },
118
+ { name = "iniconfig" },
119
+ { name = "packaging" },
120
+ { name = "pluggy" },
121
+ { name = "pygments" },
122
+ ]
123
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
124
+ wheels = [
125
+ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
126
+ ]
127
+
128
+ [[package]]
129
+ name = "ruff"
130
+ version = "0.17.0"
131
+ source = { registry = "https://pypi.org/simple" }
132
+ sdist = { url = "https://files.pythonhosted.org/packages/e9/a7/70debb024dfacda67b8e560cc7511f52b34b9348a8cc8c1ec23036dcd51d/ruff-0.17.0.tar.gz", hash = "sha256:5cd03240d8208a557c2a9655a5cb07ebe36aa6bb35065f97d48c1f6adef5a322", size = 5320554, upload-time = "2026-10-09T19:47:29.248Z" }
133
+ wheels = [
134
+ { url = "https://files.pythonhosted.org/packages/bc/f8/ee5ab9da6089eae2a33e6008b01deb1eda19992c1c8e10661e98cee1640f/ruff-0.17.0-py3-none-linux_armv6l.whl", hash = "sha256:0e271826af9a20d18c6cfae8c51e82959167c24859686ddd3eb9a7f0842ce81e", size = 10148907, upload-time = "2026-10-09T19:46:38.695Z" },
135
+ { url = "https://files.pythonhosted.org/packages/9f/d9/2f81fb5a9d580afbb11b1c8ff915233a11f2a1b27405d7991f183c5e1976/ruff-0.17.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5f0ca4a40f81403689c04f12966e22f44e329ae362072d8f1587b7bda87f603b", size = 10224208, upload-time = "2026-10-09T19:46:41.711Z" },
136
+ { url = "https://files.pythonhosted.org/packages/a7/20/643f3c8f75594f937b2bf74801241c56a2e2b8e139d24dff8b66b28cdd7f/ruff-0.17.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cbf7149e0927dc3295d5d64679a4765576eef71b00782b2ae969ef82274d6bb9", size = 9273324, upload-time = "2026-10-09T19:46:44.323Z" },
137
+ { url = "https://files.pythonhosted.org/packages/ec/91/627700b233d367736cb274f1bd0b47d1f2b12f68878192812bd875adadc3/ruff-0.17.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ee90156522998c3037059d8f66885c8adeeaf7643bdce2caceee196ecd23e0", size = 9953190, upload-time = "2026-10-09T19:46:47.155Z" },
138
+ { url = "https://files.pythonhosted.org/packages/cd/92/91f7b5ed39490f89d6cbf56e1f543c383667a725efa8e2c2dee0f01f5591/ruff-0.17.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3d8e4a002a94cd9d0dc48b51dc69d807a172b5b9bf2b668e656424dc5b55ead1", size = 9961746, upload-time = "2026-10-09T19:46:50.098Z" },
139
+ { url = "https://files.pythonhosted.org/packages/87/c5/7310f9fc63ce11ff6394edbd5e85433dfb0e14c9fbf6ccc97f1538491bc7/ruff-0.17.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0b8a60c06a218c337e1161638d34757f83449243e2db161483ddf948e53ad14", size = 10625483, upload-time = "2026-10-09T19:46:53.379Z" },
140
+ { url = "https://files.pythonhosted.org/packages/a5/8d/97443f0dca4a03a0bc7629fd396fd494a1cb6666121e38c5075acb217d8f/ruff-0.17.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a330178bdffc4205dbf3bda11d93e059e388fd6546f8cdd304501a9160363c0d", size = 11350129, upload-time = "2026-10-09T19:46:56.486Z" },
141
+ { url = "https://files.pythonhosted.org/packages/9c/0a/c525efd9777be4b6b012e6969a3012648468e7e6c4b3e5b46af69f46e8eb/ruff-0.17.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7bb08489e234876fa2da67ae3ea938e9a2156da80293e0e4365abd6973d98329", size = 10906223, upload-time = "2026-10-09T19:47:00.263Z" },
142
+ { url = "https://files.pythonhosted.org/packages/2c/3c/4a01195d93420cad1175bedad13a515dc8a56f95a6e39789b92e582682f5/ruff-0.17.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc73e7c133e82d55b5f15897b2a442d72c0cb4a0c886c46801ce3c247150b60c", size = 10386314, upload-time = "2026-10-09T19:47:03.057Z" },
143
+ { url = "https://files.pythonhosted.org/packages/c7/72/1a3951665485a921f6375f91e754a1854d5a645d41acc3642668064ff64d/ruff-0.17.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:db4f74c533403ab70fe4007873f6ae0c9f94a8b03158cf48d78788e47cdbe399", size = 10565464, upload-time = "2026-10-09T19:47:05.831Z" },
144
+ { url = "https://files.pythonhosted.org/packages/90/8c/539b4d8c082f57e18db8ae2be85a460d77861c79dcd5798e32b536a6a06f/ruff-0.17.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3d8cc360e666d1914e47b0777c6906d70cf18891a55532bd0a16844195d70859", size = 10079468, upload-time = "2026-10-09T19:47:08.617Z" },
145
+ { url = "https://files.pythonhosted.org/packages/e0/b8/84286966db79434e8c26b585b0a0f6897cb3ab1c51a4aa4df10c28488b62/ruff-0.17.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d66de796b726c4801e05fa99a2a8d7a780e107be222486c304ab61765561e866", size = 9958556, upload-time = "2026-10-09T19:47:11.324Z" },
146
+ { url = "https://files.pythonhosted.org/packages/69/50/27b6eed27b83fcdd5bfa0d52b83231e29094754374698da404d094487ae3/ruff-0.17.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c3f268baf004aea944f040623327119527ea231af15f7fb7890e82cea0679589", size = 10352173, upload-time = "2026-10-09T19:47:14.188Z" },
147
+ { url = "https://files.pythonhosted.org/packages/2a/fa/955399fd13044cd827862044117d784a59e3196f6cce7424908ac9a7f914/ruff-0.17.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:864b6c1acb6b0bccf94b5a3938a1531fd09aaca5e5659a2e7bf0f3cf2a685540", size = 10791761, upload-time = "2026-10-09T19:47:16.931Z" },
148
+ { url = "https://files.pythonhosted.org/packages/ae/bf/024e01e1f6aec87768696725e648ed5b438941341eea8f8100beb681961f/ruff-0.17.0-py3-none-win32.whl", hash = "sha256:5e50aa5b84decd9fe5b0bb0e6f71c3b592f1767ed09faa4b7207d933961e35cd", size = 9904112, upload-time = "2026-10-09T19:47:19.75Z" },
149
+ { url = "https://files.pythonhosted.org/packages/cc/77/1ee73df41dcc8d1cdb686ee4bc46ea29704ea175feb6b95c78420f631ab8/ruff-0.17.0-py3-none-win_amd64.whl", hash = "sha256:8ab76bcda86dfd28e13776cb5de3c7bcdcf1ae3d37ed761113d1a5a415dc134c", size = 10115585, upload-time = "2026-10-09T19:47:22.698Z" },
150
+ { url = "https://files.pythonhosted.org/packages/fd/71/eb4f0ccc844aece56963e8578df9c95d4c00f580547d52329d4035d3af18/ruff-0.17.0-py3-none-win_arm64.whl", hash = "sha256:c154c73ff43f9854395e24cac507af13078962e53d2b511605058d22af1fdb88", size = 9872035, upload-time = "2026-10-09T19:47:26.306Z" },
151
+ ]
152
+
153
+ [[package]]
154
+ name = "sortedcontainers"
155
+ version = "2.4.0"
156
+ source = { registry = "https://pypi.org/simple" }
157
+ sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" }
158
+ wheels = [
159
+ { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" },
160
+ ]
161
+
162
+ [[package]]
163
+ name = "tabdgp"
164
+ version = "0.0.1"
165
+ source = { editable = "." }
166
+
167
+ [package.dev-dependencies]
168
+ dev = [
169
+ { name = "hypothesis" },
170
+ { name = "pytest" },
171
+ { name = "ruff" },
172
+ ]
173
+
174
+ [package.metadata]
175
+
176
+ [package.metadata.requires-dev]
177
+ dev = [
178
+ { name = "hypothesis" },
179
+ { name = "pytest" },
180
+ { name = "ruff" },
181
+ ]