diff --git a/docs/design/restricted-diagram.md b/docs/design/restricted-diagram.md
new file mode 100644
index 000000000..93341d166
--- /dev/null
+++ b/docs/design/restricted-diagram.md
@@ -0,0 +1,217 @@
+# Restricted Diagrams
+
+**Issues:** [#865](https://github.com/datajoint/datajoint-python/issues/865), [#1110](https://github.com/datajoint/datajoint-python/issues/1110)
+
+## Motivation
+
+### Error-driven cascade is fragile
+
+The original cascade delete worked by trial-and-error: attempt `DELETE` on the parent, catch the FK integrity error, parse the MySQL error message to discover which child table is blocking, then recursively delete from that child first.
+
+This approach has several problems:
+
+- **MySQL 8 with limited privileges:** Returns error 1217 (`ROW_IS_REFERENCED`) instead of 1451 (`ROW_IS_REFERENCED_2`), which provides no table name. The cascade crashes ([#1110](https://github.com/datajoint/datajoint-python/issues/1110)).
+- **PostgreSQL overhead:** PostgreSQL aborts the entire transaction on any error. Each failed delete attempt requires `SAVEPOINT` / `ROLLBACK TO SAVEPOINT` round-trips.
+- **Fragile parsing:** Different MySQL versions and privilege levels produce different error message formats.
+
+### Graph-driven approach
+
+`drop()` already uses graph-driven traversal — walking the dependency graph in reverse topological order, dropping leaves first. The same pattern applies to cascade delete, with the addition of **restriction propagation** through FK attribute mappings.
+
+### Data subsetting
+
+`dj.Diagram` provides set operators for specifying subsets of *tables*. Per-node restrictions complete the functionality for specifying cross-sections of *data* — enabling delete, export, backup, and sharing.
+
+## Architecture
+
+Single `class Diagram(nx.DiGraph)` with all operational methods always available. Only visualization methods (`draw`, `make_dot`, `make_svg`, `make_png`, `make_image`, `make_mermaid`, `save`, `_repr_svg_`) are gated on `diagram_active`.
+
+`Dependencies` is the canonical store of the FK graph. `Diagram` copies from it and constructs derived views.
+
+### Instance attributes
+
+```python
+self._connection # Connection
+self._cascade_restrictions # dict[str, list] — per-node OR restrictions (cascade mode)
+self._restrict_conditions # dict[str, AndList] — per-node AND restrictions (restrict mode)
+self._restriction_attrs # dict[str, set] — restriction attribute names per node
+self._part_integrity # str — "enforce", "ignore", or "cascade" (set by cascade())
+```
+
+### Restriction modes
+
+A diagram operates in one of three states: **unrestricted** (initial), **cascade**, or **restrict**. The modes are mutually exclusive. `cascade` is applied once; `restrict` can be chained.
+
+```python
+# cascade: applied once, OR at convergence, for delete
+rd = dj.Diagram(schema).cascade(Session & 'subject_id=1')
+
+# restrict: chainable, AND at convergence, for export
+rd = dj.Diagram(schema).restrict(Session & cond).restrict(Stimulus & cond2)
+
+# Mixing raises DataJointError
+```
+
+## Restriction Propagation
+
+A restriction applied to one table node propagates downstream through FK edges in topological order. Each downstream node accumulates a restriction derived from its restricted parent(s).
+
+### Propagation rules
+
+For edge `Parent → Child` with `attr_map`:
+
+| Condition | Child restriction |
+|-----------|-------------------|
+| Non-aliased AND `parent_attrs ⊆ child.primary_key` | Copy parent restriction directly |
+| Aliased FK (`attr_map` renames columns) | `parent_ft.proj(**{fk: pk for fk, pk in attr_map.items()})` |
+| Non-aliased AND `parent_attrs ⊄ child.primary_key` | `parent_ft.proj()` |
+
+Restrictions are applied via `restrict()` → `make_condition()`, ensuring `AndList` and `QueryExpression` objects are properly converted to SQL. Direct assignment to `_restriction` is never used, as `where_clause()` would produce invalid SQL from `str(AndList)` or `str(QueryExpression)`.
+
+### Converging paths
+
+A child node may have multiple restricted ancestors. The combination rule depends on the operator:
+
+```
+Session ──→ Recording ←── Stimulus
+ ↓ ↓
+subject=1 type="visual"
+```
+
+`Recording` receives two propagated restrictions: R1 from Session, R2 from Stimulus.
+
+**`cascade` — OR (union):** A recording is deleted if tainted by *any* restricted parent. Correct for referential integrity: if the parent row is being deleted, all child rows referencing it must go. Implemented by passing the full restriction list to `restrict()`, which creates an OrList.
+
+**`restrict` — AND (intersection):** A recording is included only if it satisfies *all* restricted ancestors. Correct for subsetting: only rows matching every condition are selected. Implemented by iterating restrictions and calling `restrict()` for each.
+
+| DataJoint type | Python type | SQL meaning |
+|----------------|-------------|-------------|
+| OR-combined restrictions | `list` | `WHERE (r1) OR (r2) OR ...` |
+| AND-combined restrictions | `AndList` | `WHERE (r1) AND (r2) AND ...` |
+| No restriction | empty `list` or `AndList()` | No WHERE clause (all rows) |
+
+### Multiple FK paths from same parent
+
+A child may reference the same parent through multiple FKs (e.g., `source_mouse` and `target_mouse` both referencing `Mouse`). These are represented as alias nodes in the dependency graph. Multiple FK paths from the same restricted parent always combine with **OR** — structural, not operation-dependent.
+
+### `part_integrity`
+
+| Mode | Behavior |
+|------|----------|
+| `"enforce"` | Data-driven post-check: raises only when rows were actually deleted from a Part without its master also being deleted. Avoids false positives when a Part appears in the cascade but has zero affected rows. |
+| `"ignore"` | Allow deleting parts without masters |
+| `"cascade"` | Propagate restriction upward from part to master, then re-propagate downstream |
+
+### Unloaded schemas
+
+If a child table lives in a schema not loaded into the dependency graph, the graph-driven delete won't know about it. The final parent `delete_quick()` fails with an FK error. Error-message parsing is retained as a **diagnostic fallback** to produce an actionable error: "activate schema X."
+
+## Methods
+
+### `cascade(self, table_expr, part_integrity="enforce") -> Diagram`
+
+Apply cascade restriction and propagate downstream. Returns a new `Diagram`. One-shot — cannot be called twice or mixed with `restrict()`.
+
+1. Verify no existing cascade or restrict restrictions
+2. Copy diagram, seed `_cascade_restrictions[root]` with `list(table_expr.restriction)`
+3. Propagate via `_propagate_restrictions(root, mode="cascade", part_integrity=part_integrity)`
+
+### `restrict(self, table_expr) -> Diagram`
+
+Apply restrict condition and propagate downstream. Returns a new `Diagram`. Chainable — can be called multiple times. Cannot be mixed with `cascade()`.
+
+1. Verify no existing cascade restrictions
+2. Copy diagram, seed/extend `_restrict_conditions[root]` with `table_expr.restriction`
+3. Propagate via `_propagate_restrictions(root, mode="restrict")`
+
+### `delete(self, transaction=True, prompt=None, dry_run=False) -> int | dict`
+
+Execute cascading delete. Requires `cascade()` first.
+
+1. If `dry_run`: return `preview()` without modifying data
+2. Get non-alias nodes with restrictions in topological order
+3. If `prompt`: show preview (table name + row count for each)
+4. Start transaction
+5. Delete in **reverse** topological order (leaves first) via `_restrict_freetable()` + `delete_quick()`
+6. On `IntegrityError`: cancel transaction, parse FK error for actionable message about unloaded schemas
+7. Post-check `part_integrity="enforce"`: if any part table had rows deleted but its master did not, cancel transaction and raise
+8. Confirm/commit, return count from the root table
+
+### `drop(self, prompt=None, part_integrity="enforce", dry_run=False)`
+
+Drop all tables in `nodes_to_show` in reverse topological order. Pre-checks `part_integrity` structurally (tables, not rows). If `dry_run`, returns row counts without dropping.
+
+### `preview(self) -> dict[str, int]`
+
+Return `{full_table_name: row_count}` for each node with a restriction. Requires `cascade()` or `restrict()` first. Uses `_restrict_freetable()` to apply restrictions with correct OR/AND semantics.
+
+### `prune(self) -> Diagram`
+
+Remove tables with zero matching rows. With restrictions, removes nodes where the restricted query yields zero rows. Without restrictions, removes physically empty tables. Idempotent and chainable.
+
+### `_restrict_freetable(ft, restrictions, mode="cascade") -> FreeTable`
+
+Static helper. Applies restrictions to a `FreeTable` using `restrict()` for proper SQL generation.
+
+- **cascade mode:** Passes the entire restriction list to `restrict()`, creating an OrList (OR semantics).
+- **restrict mode:** Iterates restrictions, calling `restrict()` for each (AND semantics).
+
+### `_from_table(cls, table_expr) -> Diagram`
+
+Classmethod factory for `Table.delete()` and `Table.drop()`. Creates a Diagram containing `table_expr` and all its descendants.
+
+## `Table` Integration
+
+```python
+def delete(self, transaction=True, prompt=None, part_integrity="enforce", dry_run=False):
+ diagram = Diagram._from_table(self)
+ diagram = diagram.cascade(self, part_integrity=part_integrity)
+ return diagram.delete(transaction=transaction, prompt=prompt, dry_run=dry_run)
+
+def drop(self, prompt=None, part_integrity="enforce", dry_run=False):
+ if self.restriction:
+ raise DataJointError("A restricted Table cannot be dropped.")
+ diagram = Diagram._from_table(self)
+ diagram.drop(prompt=prompt, part_integrity=part_integrity, dry_run=dry_run)
+```
+
+## API Examples
+
+```python
+# cascade: OR propagation for delete
+rd = dj.Diagram(schema).cascade(Session & 'subject_id=1')
+rd.preview() # show affected tables and row counts
+rd.delete() # downstream only, OR at convergence
+
+# restrict: AND propagation for data subsetting
+rd = (dj.Diagram(schema)
+ .restrict(Session & 'subject_id=1')
+ .restrict(Stimulus & 'type="visual"'))
+rd.preview() # show selected tables and row counts
+
+# prune: remove tables with zero matching rows
+rd = (dj.Diagram(schema)
+ .restrict(Subject & {'species': 'mouse'})
+ .restrict(Session & 'session_date > "2024-01-01"')
+ .prune())
+rd.preview() # only tables with matching rows
+
+# dry_run: preview without executing
+counts = (Session & 'subject_id=1').delete(dry_run=True)
+# returns {full_table_name: affected_row_count}
+
+# Table.delete() delegates to Diagram internally
+(Session & 'subject_id=1').delete()
+```
+
+## Advantages
+
+| | Error-driven | Graph-driven |
+|---|---|---|
+| MySQL 8 + limited privileges | Crashes ([#1110](https://github.com/datajoint/datajoint-python/issues/1110)) | Works — no error parsing needed |
+| PostgreSQL | Savepoint overhead per attempt | No errors triggered |
+| Multiple FKs to same child | One-at-a-time via retry loop | All paths resolved upfront |
+| part_integrity enforcement | Post-hoc check after delete | Data-driven post-check (no false positives) |
+| Unloaded schemas | Crash with opaque error | Clear error: "activate schema X" |
+| Reusability | Delete-only | Delete, drop, export, prune |
+| Inspectability | Opaque recursive cascade | `preview()` / `dry_run` before executing |
diff --git a/pixi.lock b/pixi.lock
index dcc82c2b5..02e7fbeee 100644
--- a/pixi.lock
+++ b/pixi.lock
@@ -5,6 +5,8 @@ environments:
- url: https://conda.anaconda.org/conda-forge/
indexes:
- https://pypi.org/simple
+ options:
+ pypi-prerelease-mode: if-necessary-or-explicit
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -98,58 +100,26 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/f5/11/02ebebb09ff2104b690457cb7bc6ed700c9e0ce88cf581486bb0a5d3c88b/faker-37.8.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/08/2a/5628a99d04acb2d2f2e749cdf4ea571d2575e898df0528a090948018b726/ipython-9.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/89/a3/00260f8df72b51afa1f182dd609533c77fa2407918c4c2813d87b4a56725/minio-7.2.16-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl
- pypi: ./
linux-aarch64:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2
@@ -254,58 +224,26 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-xorgproto-2024.1-h86ecc28_1.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_2.conda
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/7d/ae/f32695da4f93de50dd7075100dab8cf689a9d96270f58ce6f940fd044a3e/minio-7.2.18-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl
- pypi: ./
osx-arm64:
- conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda
@@ -365,64 +303,34 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl
- - pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/7d/ae/f32695da4f93de50dd7075100dab8cf689a9d96270f58ce6f940fd044a3e/minio-7.2.18-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/16/32/f8e3c85d1d5250232a5d3477a2a28cc291968ff175caeadaf3cc19ce0e4a/parso-0.8.5-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7e/32/a7125fb28c4261a627f999d5fb4afff25b523800faed2c30979949d6facd/pydot-4.0.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7c/4c/ad33b92b9864cbde84f259d5df035a6447f91891f5be77788e2a3892bce3/pymysql-1.1.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl
- pypi: ./
dev:
channels:
- url: https://conda.anaconda.org/conda-forge/
indexes:
- https://pypi.org/simple
+ options:
+ pypi-prerelease-mode: if-necessary-or-explicit
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -515,33 +423,49 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
+ - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f5/11/02ebebb09ff2104b690457cb7bc6ed700c9e0ce88cf581486bb0a5d3c88b/faker-37.8.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e5/ae/2ad30f4652712c82f1c23423d79136fbce338932ad166d70c1efb86a5998/identify-2.6.14-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/08/2a/5628a99d04acb2d2f2e749cdf4ea571d2575e898df0528a090948018b726/ipython-9.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/89/a3/00260f8df72b51afa1f182dd609533c77fa2407918c4c2813d87b4a56725/minio-7.2.16-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
@@ -553,10 +477,15 @@ environments:
- pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
@@ -572,10 +501,13 @@ environments:
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f3/51/0489a6a5595b7760b5dbac0dd82852b510326e7d88d51dbffcd2e07e3ff3/ruff-0.14.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
@@ -584,6 +516,8 @@ environments:
- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: ./
linux-aarch64:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2
@@ -687,33 +621,49 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxxf86vm-1.1.6-h86ecc28_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-xorgproto-2024.1-h86ecc28_1.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_2.conda
+ - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/2e/7a/34c9402ad12bce609be4be1146a7d22a7fae8e9d752684b6315cce552a65/coverage-7.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7d/ae/f32695da4f93de50dd7075100dab8cf689a9d96270f58ce6f940fd044a3e/minio-7.2.18-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
@@ -725,10 +675,15 @@ environments:
- pypi: https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/27/11/574fe7d13acf30bfd0a8dd7fa1647040f2b8064f13f43e8c963b1e65093b/pre_commit-4.4.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
@@ -744,10 +699,13 @@ environments:
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/9e/e9/08840ff5127916bb989c86f18924fd568938b06f58b60e206176f327c0fe/ruff-0.14.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
@@ -756,6 +714,8 @@ environments:
- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: ./
osx-arm64:
- conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda
@@ -814,33 +774,48 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda
- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda
+ - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl
- pypi: https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/e3/26/57c6fb270950d476074c087527a558ccb6f4436657314bfb6cdf484114c4/docker-7.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7d/ae/f32695da4f93de50dd7075100dab8cf689a9d96270f58ce6f940fd044a3e/minio-7.2.18-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl
@@ -852,10 +827,15 @@ environments:
- pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
@@ -871,10 +851,13 @@ environments:
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7d/f8/2be49047f929d6965401855461e697ab185e1a6a683d914c5c19c7962d9e/ruff-0.14.9-py3-none-macosx_11_0_arm64.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
@@ -883,12 +866,16 @@ environments:
- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/79/0c/c05523fa3181fdf0c9c52a6ba91a23fbf3246cc095f26f6516f9c60e6771/virtualenv-20.35.4-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl
- pypi: ./
test:
channels:
- url: https://conda.anaconda.org/conda-forge/
indexes:
- https://pypi.org/simple
+ options:
+ pypi-prerelease-mode: if-necessary-or-explicit
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -981,15 +968,23 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb9d3cd8_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-hb9d3cd8_3.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
+ - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/73/dd/508420fb47d09d904d962f123221bc249f64b5e56aa93d5f5f7603be475f/coverage-7.10.6-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
@@ -997,16 +992,21 @@ environments:
- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f5/11/02ebebb09ff2104b690457cb7bc6ed700c9e0ce88cf581486bb0a5d3c88b/faker-37.8.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f2/9f/bf231c2a3fac99d1d7f1d89c76594f158693f981a4aa02be406e9f036832/fonttools-4.59.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/08/2a/5628a99d04acb2d2f2e749cdf4ea571d2575e898df0528a090948018b726/ipython-9.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/e5/b8/9eea6630198cb303d131d95d285a024b3b8645b1763a2916fddb44ca8760/matplotlib-3.10.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/89/a3/00260f8df72b51afa1f182dd609533c77fa2407918c4c2813d87b4a56725/minio-7.2.16-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl
@@ -1016,9 +1016,14 @@ environments:
- pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/5f/e9/a09476d436d0ff1402ac3867d933c61805ec2326c6ea557aeeac3825604e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
@@ -1030,14 +1035,15 @@ environments:
- pypi: https://files.pythonhosted.org/packages/53/b8/fbab973592e23ae313042d450fc26fa24282ebffba21ba373786e1ce63b4/pyparsing-3.2.4-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/de/b8/87cfb16045c9d4092cfcf526135d73b88101aac83bc1adcf82dfb5fd3833/pytest_env-1.1.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
@@ -1045,6 +1051,8 @@ environments:
- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ - pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
- pypi: ./
linux-aarch64:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/_openmp_mutex-4.5-2_gnu.tar.bz2
@@ -1148,15 +1156,23 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-libxxf86vm-1.1.6-h86ecc28_0.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/xorg-xorgproto-2024.1-h86ecc28_1.conda
- conda: https://conda.anaconda.org/conda-forge/linux-aarch64/zstd-1.5.7-hbcf94c1_2.conda
+ - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/73/23/90e31ceeed1de63058a02cb04b12f2de4b40e3bef5e082a7c18d9c8ae281/contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/2e/7a/34c9402ad12bce609be4be1146a7d22a7fae8e9d752684b6315cce552a65/coverage-7.11.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
@@ -1164,16 +1180,21 @@ environments:
- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/05/aa/62893d6a591d337aa59dcc4c6f6c842f1fe20cd72c8c5c1f980255243252/ipython-9.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/d0/7f/ccdca06f4c2e6c7989270ed7829b8679466682f4cfc0f8c9986241c023b6/matplotlib-3.10.7-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7d/ae/f32695da4f93de50dd7075100dab8cf689a9d96270f58ce6f940fd044a3e/minio-7.2.18-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl
@@ -1183,9 +1204,14 @@ environments:
- pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/50/52/adaf4c8c100a8c49d2bd058e5b551f73dfd8cb89eb4911e25a0c469b6b4e/pycryptodome-3.23.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
@@ -1197,14 +1223,15 @@ environments:
- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/72/99/cafef234114a3b6d9f3aaed0723b437c40c57bdb7b3e4c3a575bc4890052/pytest-9.0.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/27/98/822b924a4a3eb58aacba84444c7439fce32680592f394de26af9c76e2569/pytest_env-1.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
@@ -1212,6 +1239,8 @@ environments:
- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ - pypi: https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
- pypi: ./
osx-arm64:
- conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda
@@ -1270,15 +1299,23 @@ environments:
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h892fb3f_2.conda
- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025b-h78e105d_0.conda
- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-h6491c7d_2.conda
+ - pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl
- pypi: https://files.pythonhosted.org/packages/96/e4/7adcd9c8362745b2210728f209bfbcf7d91ba868a2c5f40d8b58f54c509b/contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/96/5d/dc5fa98fea3c175caf9d360649cb1aa3715e391ab00dc78c4c66fabd7356/coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/f7/e6/efe534ef0952b531b630780e19cabd416e2032697019d5295defc6ef9bd9/deepdiff-8.6.1-py3-none-any.whl
@@ -1286,16 +1323,20 @@ environments:
- pypi: https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/98/2c050dec90e295a524c9b65c4cb9e7c302386a296b2938710448cbd267d5/faker-37.12.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl
+ - pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/91/4c/e0ce1ef95d4000ebc1c11801f9b944fa5910ecc15b5e351865763d8657f8/graphviz-0.21-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/48/c5/d5e07995077e48220269c28a221e168c91123ad5ceee44d548f54a057fc0/ipython-9.6.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/bc/d0/b3d3338d467d3fc937f0bb7f256711395cae6f78e22cef0656159950adf0/matplotlib-3.10.7-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/af/33/ee4519fa02ed11a94aef9559552f3b17bb863f2ecfe1a35dc7f548cde231/matplotlib_inline-0.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/7d/ae/f32695da4f93de50dd7075100dab8cf689a9d96270f58ce6f940fd044a3e/minio-7.2.18-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl
@@ -1305,9 +1346,14 @@ environments:
- pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/db/6c/a1f71542c969912bb0e106f64f60a56cc1f0fabecf9396f45accbe63fa68/pycryptodome-3.23.0-cp37-abi3-macosx_10_9_universal2.whl
- pypi: https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl
@@ -1319,14 +1365,15 @@ environments:
- pypi: https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/27/98/822b924a4a3eb58aacba84444c7439fce32680592f394de26af9c76e2569/pytest_env-1.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl
- - pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl
@@ -1334,6 +1381,8 @@ environments:
- pypi: https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl
- pypi: https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl
+ - pypi: https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl
+ - pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl
- pypi: ./
packages:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
@@ -1394,6 +1443,95 @@ packages:
purls: []
size: 631452
timestamp: 1758743294412
+- pypi: https://files.pythonhosted.org/packages/02/78/79aa8169408996f5a71150abdea2e5e0f364df250c9e54ac385f115c7436/aiobotocore-3.2.1-py3-none-any.whl
+ name: aiobotocore
+ version: 3.2.1
+ sha256: 68b7474af3e7124666b8e191805db5a7255d14e6187e0472481c845b6062e42e
+ requires_dist:
+ - aiohttp>=3.12.0,<4.0.0
+ - aioitertools>=0.5.1,<1.0.0
+ - botocore>=1.42.53,<1.42.62
+ - python-dateutil>=2.1,<3.0.0
+ - jmespath>=0.7.1,<2.0.0
+ - multidict>=6.0.0,<7.0.0
+ - typing-extensions>=4.14.0,<5.0.0 ; python_full_version < '3.11'
+ - wrapt>=1.10.10,<3.0.0
+ - httpx>=0.25.1,<0.29 ; extra == 'httpx'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl
+ name: aiohappyeyeballs
+ version: 2.6.1
+ sha256: f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/17/f8/8dd2cf6112a5a76f81f81a5130c57ca829d101ad583ce57f889179accdda/aiohttp-3.13.3-cp313-cp313-macosx_11_0_arm64.whl
+ name: aiohttp
+ version: 3.13.3
+ sha256: 425c126c0dc43861e22cb1c14ba4c8e45d09516d0a3ae0a3f7494b79f5f233a3
+ requires_dist:
+ - aiohappyeyeballs>=2.5.0
+ - aiosignal>=1.4.0
+ - async-timeout>=4.0,<6.0 ; python_full_version < '3.11'
+ - attrs>=17.3.0
+ - frozenlist>=1.1.1
+ - multidict>=4.5,<7.0
+ - propcache>=0.2.0
+ - yarl>=1.17.0,<2.0
+ - aiodns>=3.3.0 ; extra == 'speedups'
+ - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups'
+ - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups'
+ - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/6d/40/a46b03ca03936f832bc7eaa47cfbb1ad012ba1be4790122ee4f4f8cba074/aiohttp-3.13.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: aiohttp
+ version: 3.13.3
+ sha256: 7f9120f7093c2a32d9647abcaf21e6ad275b4fbec5b55969f978b1a97c7c86bf
+ requires_dist:
+ - aiohappyeyeballs>=2.5.0
+ - aiosignal>=1.4.0
+ - async-timeout>=4.0,<6.0 ; python_full_version < '3.11'
+ - attrs>=17.3.0
+ - frozenlist>=1.1.1
+ - multidict>=4.5,<7.0
+ - propcache>=0.2.0
+ - yarl>=1.17.0,<2.0
+ - aiodns>=3.3.0 ; extra == 'speedups'
+ - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups'
+ - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups'
+ - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/7c/24/75d274228acf35ceeb2850b8ce04de9dd7355ff7a0b49d607ee60c29c518/aiohttp-3.13.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ name: aiohttp
+ version: 3.13.3
+ sha256: f76c1e3fe7d7c8afad7ed193f89a292e1999608170dcc9751a7462a87dfd5bc0
+ requires_dist:
+ - aiohappyeyeballs>=2.5.0
+ - aiosignal>=1.4.0
+ - async-timeout>=4.0,<6.0 ; python_full_version < '3.11'
+ - attrs>=17.3.0
+ - frozenlist>=1.1.1
+ - multidict>=4.5,<7.0
+ - propcache>=0.2.0
+ - yarl>=1.17.0,<2.0
+ - aiodns>=3.3.0 ; extra == 'speedups'
+ - brotli>=1.2 ; platform_python_implementation == 'CPython' and extra == 'speedups'
+ - brotlicffi>=1.2 ; platform_python_implementation != 'CPython' and extra == 'speedups'
+ - backports-zstd ; python_full_version < '3.14' and platform_python_implementation == 'CPython' and extra == 'speedups'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/10/a1/510b0a7fadc6f43a6ce50152e69dbd86415240835868bb0bd9b5b88b1e06/aioitertools-0.13.0-py3-none-any.whl
+ name: aioitertools
+ version: 0.13.0
+ sha256: 0be0292b856f08dfac90e31f4739432f4cb6d7520ab9eb73e143f4f2fa5259be
+ requires_dist:
+ - typing-extensions>=4.0 ; python_full_version < '3.10'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl
+ name: aiosignal
+ version: 1.4.0
+ sha256: 053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e
+ requires_dist:
+ - frozenlist>=1.1.0
+ - typing-extensions>=4.2 ; python_full_version < '3.13'
+ requires_python: '>=3.9'
- pypi: https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl
name: annotated-types
version: 0.7.0
@@ -1544,6 +1682,22 @@ packages:
purls: []
size: 347530
timestamp: 1713896411580
+- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl
+ name: attrs
+ version: 25.4.0
+ sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/88/46/98a01139f318b7a2f0ad1d1e3be2a028d13aeb7e05aaa340a27cdc47fdf0/botocore-1.42.61-py3-none-any.whl
+ name: botocore
+ version: 1.42.61
+ sha256: 476059beb3f462042742950cf195d26bc313461a77189c16e37e205b0a924b26
+ requires_dist:
+ - jmespath>=0.7.1,<2.0.0
+ - python-dateutil>=2.1,<3.0.0
+ - urllib3>=1.25.4,<1.27 ; python_full_version < '3.10'
+ - urllib3>=1.25.4,!=2.2.0,<3 ; python_full_version >= '3.10'
+ - awscrt==0.31.2 ; extra == 'crt'
+ requires_python: '>=3.9'
- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_8.conda
sha256: c30daba32ddebbb7ded490f0e371eae90f51e72db620554089103b4a6934b0d5
md5: 51a19bba1b8ebfb60df25cde030b7ebc
@@ -1833,6 +1987,96 @@ packages:
requires_dist:
- tomli ; python_full_version <= '3.11' and extra == 'toml'
requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl
+ name: cryptography
+ version: 46.0.5
+ sha256: 50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263
+ requires_dist:
+ - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy'
+ - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy'
+ - typing-extensions>=4.13.2 ; python_full_version < '3.11'
+ - bcrypt>=3.1.5 ; extra == 'ssh'
+ - nox[uv]>=2024.4.15 ; extra == 'nox'
+ - cryptography-vectors==46.0.5 ; extra == 'test'
+ - pytest>=7.4.0 ; extra == 'test'
+ - pytest-benchmark>=4.0 ; extra == 'test'
+ - pytest-cov>=2.10.1 ; extra == 'test'
+ - pytest-xdist>=3.5.0 ; extra == 'test'
+ - pretend>=0.7 ; extra == 'test'
+ - certifi>=2024 ; extra == 'test'
+ - pytest-randomly ; extra == 'test-randomorder'
+ - sphinx>=5.3.0 ; extra == 'docs'
+ - sphinx-rtd-theme>=3.0.0 ; extra == 'docs'
+ - sphinx-inline-tabs ; extra == 'docs'
+ - pyenchant>=3 ; extra == 'docstest'
+ - readme-renderer>=30.0 ; extra == 'docstest'
+ - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest'
+ - build>=1.0.0 ; extra == 'sdist'
+ - ruff>=0.11.11 ; extra == 'pep8test'
+ - mypy>=1.14 ; extra == 'pep8test'
+ - check-sdist ; extra == 'pep8test'
+ - click>=8.0.1 ; extra == 'pep8test'
+ requires_python: '>=3.8,!=3.9.0,!=3.9.1'
+- pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl
+ name: cryptography
+ version: 46.0.5
+ sha256: 3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed
+ requires_dist:
+ - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy'
+ - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy'
+ - typing-extensions>=4.13.2 ; python_full_version < '3.11'
+ - bcrypt>=3.1.5 ; extra == 'ssh'
+ - nox[uv]>=2024.4.15 ; extra == 'nox'
+ - cryptography-vectors==46.0.5 ; extra == 'test'
+ - pytest>=7.4.0 ; extra == 'test'
+ - pytest-benchmark>=4.0 ; extra == 'test'
+ - pytest-cov>=2.10.1 ; extra == 'test'
+ - pytest-xdist>=3.5.0 ; extra == 'test'
+ - pretend>=0.7 ; extra == 'test'
+ - certifi>=2024 ; extra == 'test'
+ - pytest-randomly ; extra == 'test-randomorder'
+ - sphinx>=5.3.0 ; extra == 'docs'
+ - sphinx-rtd-theme>=3.0.0 ; extra == 'docs'
+ - sphinx-inline-tabs ; extra == 'docs'
+ - pyenchant>=3 ; extra == 'docstest'
+ - readme-renderer>=30.0 ; extra == 'docstest'
+ - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest'
+ - build>=1.0.0 ; extra == 'sdist'
+ - ruff>=0.11.11 ; extra == 'pep8test'
+ - mypy>=1.14 ; extra == 'pep8test'
+ - check-sdist ; extra == 'pep8test'
+ - click>=8.0.1 ; extra == 'pep8test'
+ requires_python: '>=3.8,!=3.9.0,!=3.9.1'
+- pypi: https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl
+ name: cryptography
+ version: 46.0.5
+ sha256: 351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad
+ requires_dist:
+ - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy'
+ - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy'
+ - typing-extensions>=4.13.2 ; python_full_version < '3.11'
+ - bcrypt>=3.1.5 ; extra == 'ssh'
+ - nox[uv]>=2024.4.15 ; extra == 'nox'
+ - cryptography-vectors==46.0.5 ; extra == 'test'
+ - pytest>=7.4.0 ; extra == 'test'
+ - pytest-benchmark>=4.0 ; extra == 'test'
+ - pytest-cov>=2.10.1 ; extra == 'test'
+ - pytest-xdist>=3.5.0 ; extra == 'test'
+ - pretend>=0.7 ; extra == 'test'
+ - certifi>=2024 ; extra == 'test'
+ - pytest-randomly ; extra == 'test-randomorder'
+ - sphinx>=5.3.0 ; extra == 'docs'
+ - sphinx-rtd-theme>=3.0.0 ; extra == 'docs'
+ - sphinx-inline-tabs ; extra == 'docs'
+ - pyenchant>=3 ; extra == 'docstest'
+ - readme-renderer>=30.0 ; extra == 'docstest'
+ - sphinxcontrib-spelling>=7.3.1 ; extra == 'docstest'
+ - build>=1.0.0 ; extra == 'sdist'
+ - ruff>=0.11.11 ; extra == 'pep8test'
+ - mypy>=1.14 ; extra == 'pep8test'
+ - check-sdist ; extra == 'pep8test'
+ - click>=8.0.1 ; extra == 'pep8test'
+ requires_python: '>=3.8,!=3.9.0,!=3.9.1'
- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl
name: cycler
version: 0.12.1
@@ -1848,31 +2092,46 @@ packages:
requires_python: '>=3.8'
- pypi: ./
name: datajoint
- version: 0.14.6
- sha256: f761bb719d6afe0361d7e564bcc950ea76c79fbee9c334032459d0d4437a6423
+ version: 2.1.1
+ sha256: 267defaa9ea7f22a8497568e8a14679be178f78cd3b34a4132609a57f0f71227
requires_dist:
+ - deepdiff
+ - fsspec>=2023.1.0
+ - networkx
- numpy
+ - pandas
+ - pydantic-settings>=2.0.0
+ - pydot
- pymysql>=0.7.2
- - deepdiff
- pyparsing
- - ipython
- - pandas
- tqdm
- - networkx
- - pydot
- - minio>=7.0.0
- - matplotlib
- - faker
- - urllib3
- - setuptools
- - pydantic-settings>=2.0.0
- - pre-commit ; extra == 'dev'
- - ruff ; extra == 'dev'
+ - pyarrow>=14.0.0 ; extra == 'arrow'
+ - adlfs>=2023.1.0 ; extra == 'azure'
- codespell ; extra == 'dev'
+ - polars>=0.20.0 ; extra == 'dev'
+ - pre-commit ; extra == 'dev'
+ - pyarrow>=14.0.0 ; extra == 'dev'
- pytest ; extra == 'dev'
- pytest-cov ; extra == 'dev'
- requires_python: '>=3.9,<3.14'
- editable: true
+ - ruff ; extra == 'dev'
+ - gcsfs>=2023.1.0 ; extra == 'gcs'
+ - polars>=0.20.0 ; extra == 'polars'
+ - psycopg2-binary>=2.9.0 ; extra == 'postgres'
+ - s3fs>=2023.1.0 ; extra == 's3'
+ - faker ; extra == 'test'
+ - ipython ; extra == 'test'
+ - matplotlib ; extra == 'test'
+ - polars>=0.20.0 ; extra == 'test'
+ - psycopg2-binary>=2.9.0 ; extra == 'test'
+ - pyarrow>=14.0.0 ; extra == 'test'
+ - pytest ; extra == 'test'
+ - pytest-cov ; extra == 'test'
+ - requests ; extra == 'test'
+ - s3fs>=2023.1.0 ; extra == 'test'
+ - testcontainers[minio,mysql,postgres]>=4.0 ; extra == 'test'
+ - ipython ; extra == 'viz'
+ - matplotlib ; extra == 'viz'
+ requires_python: '>=3.10,<3.14'
- conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h3c4dab8_0.conda
sha256: 3b988146a50e165f0fa4e839545c679af88e4782ec284cc7b6d07dd226d6a068
md5: 679616eb5ad4e521c83da4650860aba7
@@ -2314,6 +2573,129 @@ packages:
purls: []
size: 59391
timestamp: 1757438897523
+- pypi: https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl
+ name: frozenlist
+ version: 1.8.0
+ sha256: f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: frozenlist
+ version: 1.8.0
+ sha256: eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ name: frozenlist
+ version: 1.8.0
+ sha256: fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl
+ name: fsspec
+ version: 2026.2.0
+ sha256: 98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437
+ requires_dist:
+ - adlfs ; extra == 'abfs'
+ - adlfs ; extra == 'adl'
+ - pyarrow>=1 ; extra == 'arrow'
+ - dask ; extra == 'dask'
+ - distributed ; extra == 'dask'
+ - pre-commit ; extra == 'dev'
+ - ruff>=0.5 ; extra == 'dev'
+ - numpydoc ; extra == 'doc'
+ - sphinx ; extra == 'doc'
+ - sphinx-design ; extra == 'doc'
+ - sphinx-rtd-theme ; extra == 'doc'
+ - yarl ; extra == 'doc'
+ - dropbox ; extra == 'dropbox'
+ - dropboxdrivefs ; extra == 'dropbox'
+ - requests ; extra == 'dropbox'
+ - adlfs ; extra == 'full'
+ - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'full'
+ - dask ; extra == 'full'
+ - distributed ; extra == 'full'
+ - dropbox ; extra == 'full'
+ - dropboxdrivefs ; extra == 'full'
+ - fusepy ; extra == 'full'
+ - gcsfs>2024.2.0 ; extra == 'full'
+ - libarchive-c ; extra == 'full'
+ - ocifs ; extra == 'full'
+ - panel ; extra == 'full'
+ - paramiko ; extra == 'full'
+ - pyarrow>=1 ; extra == 'full'
+ - pygit2 ; extra == 'full'
+ - requests ; extra == 'full'
+ - s3fs>2024.2.0 ; extra == 'full'
+ - smbprotocol ; extra == 'full'
+ - tqdm ; extra == 'full'
+ - fusepy ; extra == 'fuse'
+ - gcsfs>2024.2.0 ; extra == 'gcs'
+ - pygit2 ; extra == 'git'
+ - requests ; extra == 'github'
+ - gcsfs ; extra == 'gs'
+ - panel ; extra == 'gui'
+ - pyarrow>=1 ; extra == 'hdfs'
+ - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'http'
+ - libarchive-c ; extra == 'libarchive'
+ - ocifs ; extra == 'oci'
+ - s3fs>2024.2.0 ; extra == 's3'
+ - paramiko ; extra == 'sftp'
+ - smbprotocol ; extra == 'smb'
+ - paramiko ; extra == 'ssh'
+ - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test'
+ - numpy ; extra == 'test'
+ - pytest ; extra == 'test'
+ - pytest-asyncio!=0.22.0 ; extra == 'test'
+ - pytest-benchmark ; extra == 'test'
+ - pytest-cov ; extra == 'test'
+ - pytest-mock ; extra == 'test'
+ - pytest-recording ; extra == 'test'
+ - pytest-rerunfailures ; extra == 'test'
+ - requests ; extra == 'test'
+ - aiobotocore>=2.5.4,<3.0.0 ; extra == 'test-downstream'
+ - dask[dataframe,test] ; extra == 'test-downstream'
+ - moto[server]>4,<5 ; extra == 'test-downstream'
+ - pytest-timeout ; extra == 'test-downstream'
+ - xarray ; extra == 'test-downstream'
+ - adlfs ; extra == 'test-full'
+ - aiohttp!=4.0.0a0,!=4.0.0a1 ; extra == 'test-full'
+ - backports-zstd ; python_full_version < '3.14' and extra == 'test-full'
+ - cloudpickle ; extra == 'test-full'
+ - dask ; extra == 'test-full'
+ - distributed ; extra == 'test-full'
+ - dropbox ; extra == 'test-full'
+ - dropboxdrivefs ; extra == 'test-full'
+ - fastparquet ; extra == 'test-full'
+ - fusepy ; extra == 'test-full'
+ - gcsfs ; extra == 'test-full'
+ - jinja2 ; extra == 'test-full'
+ - kerchunk ; extra == 'test-full'
+ - libarchive-c ; extra == 'test-full'
+ - lz4 ; extra == 'test-full'
+ - notebook ; extra == 'test-full'
+ - numpy ; extra == 'test-full'
+ - ocifs ; extra == 'test-full'
+ - pandas<3.0.0 ; extra == 'test-full'
+ - panel ; extra == 'test-full'
+ - paramiko ; extra == 'test-full'
+ - pyarrow ; extra == 'test-full'
+ - pyarrow>=1 ; extra == 'test-full'
+ - pyftpdlib ; extra == 'test-full'
+ - pygit2 ; extra == 'test-full'
+ - pytest ; extra == 'test-full'
+ - pytest-asyncio!=0.22.0 ; extra == 'test-full'
+ - pytest-benchmark ; extra == 'test-full'
+ - pytest-cov ; extra == 'test-full'
+ - pytest-mock ; extra == 'test-full'
+ - pytest-recording ; extra == 'test-full'
+ - pytest-rerunfailures ; extra == 'test-full'
+ - python-snappy ; extra == 'test-full'
+ - requests ; extra == 'test-full'
+ - smbprotocol ; extra == 'test-full'
+ - tqdm ; extra == 'test-full'
+ - urllib3 ; extra == 'test-full'
+ - zarr ; extra == 'test-full'
+ - zstandard ; python_full_version < '3.14' and extra == 'test-full'
+ - tqdm ; extra == 'tqdm'
+ requires_python: '>=3.10'
- conda: https://conda.anaconda.org/conda-forge/linux-64/gdk-pixbuf-2.44.1-h2b0a6b4_0.conda
sha256: b827285fe001806beeddcc30953d2bd07869aeb0efe4581d56432c92c06b0c48
md5: 2935d9c0526277bd42373cf23d49d51f
@@ -2520,6 +2902,28 @@ packages:
purls: []
size: 2201370
timestamp: 1754732518951
+- pypi: https://files.pythonhosted.org/packages/49/8d/9771d03e7a8b1ee456511961e1b97a6d77ae1dea4a34a5b98eee706689d3/greenlet-3.3.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
+ name: greenlet
+ version: 3.3.2
+ sha256: ab0c7e7901a00bc0a7284907273dc165b32e0d109a6713babd04471327ff7986
+ requires_dist:
+ - sphinx ; extra == 'docs'
+ - furo ; extra == 'docs'
+ - objgraph ; extra == 'test'
+ - psutil ; extra == 'test'
+ - setuptools ; extra == 'test'
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/7a/34/259b28ea7a2a0c904b11cd36c79b8cef8019b26ee5dbe24e73b469dea347/greenlet-3.3.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
+ name: greenlet
+ version: 3.3.2
+ sha256: b6997d360a4e6a4e936c0f9625b1c20416b8a0ea18a8e19cabbefc712e7397ab
+ requires_dist:
+ - sphinx ; extra == 'docs'
+ - furo ; extra == 'docs'
+ - objgraph ; extra == 'test'
+ - psutil ; extra == 'test'
+ - setuptools ; extra == 'test'
+ requires_python: '>=3.10'
- conda: https://conda.anaconda.org/conda-forge/linux-64/gtk3-3.24.43-h0c6a113_5.conda
sha256: d36263cbcbce34ec463ce92bd72efa198b55d987959eab6210cc256a0e79573b
md5: 67d00e9cfe751cfe581726c5eff7c184
@@ -2999,6 +3403,11 @@ packages:
- docopt ; extra == 'testing'
- pytest<9.0.0 ; extra == 'testing'
requires_python: '>=3.6'
+- pypi: https://files.pythonhosted.org/packages/14/2f/967ba146e6d58cf6a652da73885f52fc68001525b4197effc174321d70b4/jmespath-1.1.0-py3-none-any.whl
+ name: jmespath
+ version: 1.1.0
+ sha256: a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64
+ requires_python: '>=3.9'
- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda
sha256: 0960d06048a7185d3542d850986d807c6e37ca2e644342dd0c72feefcf26c2a4
md5: b38117a3c920364aff79f870c984b4a3
@@ -4272,6 +4681,27 @@ packages:
- typing-extensions
- urllib3
requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/79/76/55cd7186f498ed080a18440c9013011eb548f77ae1b297206d030eb1180a/multidict-6.7.1-cp313-cp313-macosx_11_0_arm64.whl
+ name: multidict
+ version: 6.7.1
+ sha256: 935434b9853c7c112eee7ac891bc4cb86455aa631269ae35442cb316790c1445
+ requires_dist:
+ - typing-extensions>=4.1.0 ; python_full_version < '3.11'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/b0/73/6e1b01cbeb458807aa0831742232dbdd1fa92bfa33f52a3f176b4ff3dc11/multidict-6.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ name: multidict
+ version: 6.7.1
+ sha256: 9d624335fd4fa1c08a53f8b4be7676ebde19cd092b3895c421045ca87895b429
+ requires_dist:
+ - typing-extensions>=4.1.0 ; python_full_version < '3.11'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/f6/32/befed7f74c458b4a525e60519fe8d87eef72bb1e99924fa2b0f9d97a221e/multidict-6.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: multidict
+ version: 6.7.1
+ sha256: e82d14e3c948952a1a85503817e038cba5905a3352de76b9a465075d072fba23
+ requires_dist:
+ - typing-extensions>=4.1.0 ; python_full_version < '3.11'
+ requires_python: '>=3.9'
- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda
sha256: 3fde293232fa3fca98635e1167de6b7c7fda83caf24b9d6c91ec9eefb4f4d586
md5: 47e340acb35de30501a76c7c799c41d7
@@ -4972,6 +5402,58 @@ packages:
- pytest-benchmark ; extra == 'testing'
- coverage ; extra == 'testing'
requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/0a/49/737c1a6273c585719858261753da0b688454d1b634438ccba8a9c4eb5aab/polars-1.38.1-py3-none-any.whl
+ name: polars
+ version: 1.38.1
+ sha256: a29479c48fed4984d88b656486d221f638cba45d3e961631a50ee5fdde38cb2c
+ requires_dist:
+ - polars-runtime-32==1.38.1
+ - polars-runtime-64==1.38.1 ; extra == 'rt64'
+ - polars-runtime-compat==1.38.1 ; extra == 'rtcompat'
+ - polars-cloud>=0.4.0 ; extra == 'polars-cloud'
+ - numpy>=1.16.0 ; extra == 'numpy'
+ - pandas ; extra == 'pandas'
+ - polars[pyarrow] ; extra == 'pandas'
+ - pyarrow>=7.0.0 ; extra == 'pyarrow'
+ - pydantic ; extra == 'pydantic'
+ - fastexcel>=0.9 ; extra == 'calamine'
+ - openpyxl>=3.0.0 ; extra == 'openpyxl'
+ - xlsx2csv>=0.8.0 ; extra == 'xlsx2csv'
+ - xlsxwriter ; extra == 'xlsxwriter'
+ - polars[calamine,openpyxl,xlsx2csv,xlsxwriter] ; extra == 'excel'
+ - adbc-driver-manager[dbapi] ; extra == 'adbc'
+ - adbc-driver-sqlite[dbapi] ; extra == 'adbc'
+ - connectorx>=0.3.2 ; extra == 'connectorx'
+ - sqlalchemy ; extra == 'sqlalchemy'
+ - polars[pandas] ; extra == 'sqlalchemy'
+ - polars[adbc,connectorx,sqlalchemy] ; extra == 'database'
+ - fsspec ; extra == 'fsspec'
+ - deltalake>=1.0.0 ; extra == 'deltalake'
+ - pyiceberg>=0.7.1 ; extra == 'iceberg'
+ - gevent ; extra == 'async'
+ - cloudpickle ; extra == 'cloudpickle'
+ - matplotlib ; extra == 'graph'
+ - altair>=5.4.0 ; extra == 'plot'
+ - great-tables>=0.8.0 ; extra == 'style'
+ - tzdata ; sys_platform == 'win32' and extra == 'timezone'
+ - cudf-polars-cu12 ; extra == 'gpu'
+ - polars[async,cloudpickle,database,deltalake,excel,fsspec,graph,iceberg,numpy,pandas,plot,pyarrow,pydantic,style,timezone] ; extra == 'all'
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/a7/fb/599ff3709e6a303024efd7edfd08cf8de55c6ac39527d8f41cbc4399385f/polars_runtime_32-1.38.1-cp310-abi3-macosx_11_0_arm64.whl
+ name: polars-runtime-32
+ version: 1.38.1
+ sha256: c49acac34cc4049ed188f1eb67d6ff3971a39b4af7f7b734b367119970f313ac
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/dc/8c/3ac18d6f89dc05fe2c7c0ee1dc5b81f77a5c85ad59898232c2500fe2ebbf/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
+ name: polars-runtime-32
+ version: 1.38.1
+ sha256: fef2ef2626a954e010e006cc8e4de467ecf32d08008f130cea1c78911f545323
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/f2/5a/61d60ec5cc0ab37cbd5a699edb2f9af2875b7fdfdfb2a4608ca3cc5f0448/polars_runtime_32-1.38.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
+ name: polars-runtime-32
+ version: 1.38.1
+ sha256: e8a5f7a8125e2d50e2e060296551c929aec09be23a9edcb2b12ca923f555a5ba
+ requires_python: '>=3.10'
- pypi: https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl
name: pre-commit
version: 4.3.0
@@ -5001,6 +5483,36 @@ packages:
requires_dist:
- wcwidth
requires_python: '>=3.8'
+- pypi: https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: propcache
+ version: 0.4.1
+ sha256: 333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl
+ name: propcache
+ version: 0.4.1
+ sha256: cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ name: propcache
+ version: 0.4.1
+ sha256: d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
+ name: psycopg2-binary
+ version: 2.9.11
+ sha256: 8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
+ name: psycopg2-binary
+ version: 2.9.11
+ sha256: 5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl
+ name: psycopg2-binary
+ version: 2.9.11
+ sha256: 366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee
+ requires_python: '>=3.9'
- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb9d3cd8_1002.conda
sha256: 9c88f8c64590e9567c6c80823f0328e58d3b1efb0e1c539c0315ceca764e0973
md5: b3c17d95b5a10c6e64a21fa17573e70e
@@ -5032,6 +5544,21 @@ packages:
sha256: 1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0
requires_dist:
- pytest ; extra == 'tests'
+- pypi: https://files.pythonhosted.org/packages/47/10/2cbe4c6f0fb83d2de37249567373d64327a5e4d8db72f486db42875b08f6/pyarrow-23.0.1-cp313-cp313-macosx_12_0_arm64.whl
+ name: pyarrow
+ version: 23.0.1
+ sha256: 6b8fda694640b00e8af3c824f99f789e836720aa8c9379fb435d4c4953a756b8
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/b3/93/10a48b5e238de6d562a411af6467e71e7aedbc9b87f8d3a35f1560ae30fb/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_x86_64.whl
+ name: pyarrow
+ version: 23.0.1
+ sha256: 9b6f4f17b43bc39d56fec96e53fe89d94bac3eb134137964371b45352d40d0c2
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/f9/63/d2747d930882c9d661e9398eefc54f15696547b8983aaaf11d4a2e8b5426/pyarrow-23.0.1-cp313-cp313-manylinux_2_28_aarch64.whl
+ name: pyarrow
+ version: 23.0.1
+ sha256: 71c5be5cbf1e1cb6169d2a0980850bccb558ddc9b747b6206435313c47c37677
+ requires_python: '>=3.10'
- pypi: https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl
name: pycparser
version: '2.23'
@@ -5203,28 +5730,6 @@ packages:
- pytest-xdist ; extra == 'testing'
- virtualenv ; extra == 'testing'
requires_python: '>=3.9'
-- pypi: https://files.pythonhosted.org/packages/de/b8/87cfb16045c9d4092cfcf526135d73b88101aac83bc1adcf82dfb5fd3833/pytest_env-1.1.5-py3-none-any.whl
- name: pytest-env
- version: 1.1.5
- sha256: ce90cf8772878515c24b31cd97c7fa1f4481cd68d588419fd45f10ecaee6bc30
- requires_dist:
- - pytest>=8.3.3
- - tomli>=2.0.1 ; python_full_version < '3.11'
- - covdefaults>=2.3 ; extra == 'testing'
- - coverage>=7.6.1 ; extra == 'testing'
- - pytest-mock>=3.14 ; extra == 'testing'
- requires_python: '>=3.8'
-- pypi: https://files.pythonhosted.org/packages/27/98/822b924a4a3eb58aacba84444c7439fce32680592f394de26af9c76e2569/pytest_env-1.2.0-py3-none-any.whl
- name: pytest-env
- version: 1.2.0
- sha256: d7e5b7198f9b83c795377c09feefa45d56083834e60d04767efd64819fc9da00
- requires_dist:
- - pytest>=8.4.2
- - tomli>=2.2.1 ; python_full_version < '3.11'
- - covdefaults>=2.3 ; extra == 'testing'
- - coverage>=7.10.7 ; extra == 'testing'
- - pytest-mock>=3.15.1 ; extra == 'testing'
- requires_python: '>=3.10'
- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.7-h2b335a9_100_cp313.conda
build_number: 100
sha256: 16cc30a5854f31ca6c3688337d34e37a79cdc518a06375fe3482ea8e2d6b34c8
@@ -5405,68 +5910,134 @@ packages:
version: 0.14.9
sha256: 72034534e5b11e8a593f517b2f2f2b273eb68a30978c6a2d40473ad0aaa4cb4a
requires_python: '>=3.7'
-- pypi: https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl
- name: setuptools
- version: 80.9.0
- sha256: 062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922
+- pypi: https://files.pythonhosted.org/packages/57/e1/64c264db50b68de8a438b60ceeb921b2f22da3ebb7ad6255150225d0beac/s3fs-2026.2.0-py3-none-any.whl
+ name: s3fs
+ version: 2026.2.0
+ sha256: 65198835b86b1d5771112b0085d1da52a6ede36508b1aaa6cae2aedc765dfe10
requires_dist:
- - pytest>=6,!=8.1.* ; extra == 'test'
- - virtualenv>=13.0.0 ; extra == 'test'
- - wheel>=0.44.0 ; extra == 'test'
- - pip>=19.1 ; extra == 'test'
- - packaging>=24.2 ; extra == 'test'
- - jaraco-envs>=2.2 ; extra == 'test'
- - pytest-xdist>=3 ; extra == 'test'
- - jaraco-path>=3.7.2 ; extra == 'test'
- - build[virtualenv]>=1.0.3 ; extra == 'test'
- - filelock>=3.4.0 ; extra == 'test'
- - ini2toml[lite]>=0.14 ; extra == 'test'
- - tomli-w>=1.0.0 ; extra == 'test'
- - pytest-timeout ; extra == 'test'
- - pytest-perf ; sys_platform != 'cygwin' and extra == 'test'
- - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test'
- - pytest-home>=0.5 ; extra == 'test'
- - pytest-subprocess ; extra == 'test'
- - pyproject-hooks!=1.1 ; extra == 'test'
- - jaraco-test>=5.5 ; extra == 'test'
- - sphinx>=3.5 ; extra == 'doc'
- - jaraco-packaging>=9.3 ; extra == 'doc'
- - rst-linker>=1.9 ; extra == 'doc'
- - furo ; extra == 'doc'
- - sphinx-lint ; extra == 'doc'
- - jaraco-tidelift>=1.4 ; extra == 'doc'
- - pygments-github-lexers==0.0.5 ; extra == 'doc'
- - sphinx-favicon ; extra == 'doc'
- - sphinx-inline-tabs ; extra == 'doc'
- - sphinx-reredirects ; extra == 'doc'
- - sphinxcontrib-towncrier ; extra == 'doc'
- - sphinx-notfound-page>=1,<2 ; extra == 'doc'
- - pyproject-hooks!=1.1 ; extra == 'doc'
- - towncrier<24.7 ; extra == 'doc'
- - packaging>=24.2 ; extra == 'core'
- - more-itertools>=8.8 ; extra == 'core'
- - jaraco-text>=3.7 ; extra == 'core'
- - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core'
- - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core'
- - wheel>=0.43.0 ; extra == 'core'
- - platformdirs>=4.2.2 ; extra == 'core'
- - jaraco-functools>=4 ; extra == 'core'
- - more-itertools ; extra == 'core'
- - pytest-checkdocs>=2.4 ; extra == 'check'
- - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check'
- - ruff>=0.8.0 ; sys_platform != 'cygwin' and extra == 'check'
- - pytest-cov ; extra == 'cover'
- - pytest-enabler>=2.2 ; extra == 'enabler'
- - pytest-mypy ; extra == 'type'
- - mypy==1.14.* ; extra == 'type'
- - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type'
- - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type'
- requires_python: '>=3.9'
+ - aiobotocore>=2.19.0,<4.0.0
+ - fsspec==2026.2.0
+ - aiohttp!=4.0.0a0,!=4.0.0a1
+ requires_python: '>=3.10'
- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl
name: six
version: 1.17.0
sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274
requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*'
+- pypi: https://files.pythonhosted.org/packages/6d/ff/f4e04a4bd5a24304f38cb0d4aa2ad4c0fb34999f8b884c656535e1b2b74c/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: sqlalchemy
+ version: 2.0.48
+ sha256: 2645b7d8a738763b664a12a1542c89c940daa55196e8d73e55b169cc5c99f65f
+ requires_dist:
+ - importlib-metadata ; python_full_version < '3.8'
+ - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'
+ - typing-extensions>=4.6.0
+ - greenlet>=1 ; extra == 'asyncio'
+ - mypy>=0.910 ; extra == 'mypy'
+ - pyodbc ; extra == 'mssql'
+ - pymssql ; extra == 'mssql-pymssql'
+ - pyodbc ; extra == 'mssql-pyodbc'
+ - mysqlclient>=1.4.0 ; extra == 'mysql'
+ - mysql-connector-python ; extra == 'mysql-connector'
+ - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector'
+ - cx-oracle>=8 ; extra == 'oracle'
+ - oracledb>=1.0.1 ; extra == 'oracle-oracledb'
+ - psycopg2>=2.7 ; extra == 'postgresql'
+ - pg8000>=1.29.1 ; extra == 'postgresql-pg8000'
+ - greenlet>=1 ; extra == 'postgresql-asyncpg'
+ - asyncpg ; extra == 'postgresql-asyncpg'
+ - psycopg2-binary ; extra == 'postgresql-psycopg2binary'
+ - psycopg2cffi ; extra == 'postgresql-psycopg2cffi'
+ - psycopg>=3.0.7 ; extra == 'postgresql-psycopg'
+ - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary'
+ - pymysql ; extra == 'pymysql'
+ - greenlet>=1 ; extra == 'aiomysql'
+ - aiomysql>=0.2.0 ; extra == 'aiomysql'
+ - greenlet>=1 ; extra == 'aioodbc'
+ - aioodbc ; extra == 'aioodbc'
+ - greenlet>=1 ; extra == 'asyncmy'
+ - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy'
+ - greenlet>=1 ; extra == 'aiosqlite'
+ - aiosqlite ; extra == 'aiosqlite'
+ - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite'
+ - sqlcipher3-binary ; extra == 'sqlcipher'
+ requires_python: '>=3.7'
+- pypi: https://files.pythonhosted.org/packages/d1/c6/569dc8bf3cd375abc5907e82235923e986799f301cd79a903f784b996fca/sqlalchemy-2.0.48-cp313-cp313-macosx_11_0_arm64.whl
+ name: sqlalchemy
+ version: 2.0.48
+ sha256: e3070c03701037aa418b55d36532ecb8f8446ed0135acb71c678dbdf12f5b6e4
+ requires_dist:
+ - importlib-metadata ; python_full_version < '3.8'
+ - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'
+ - typing-extensions>=4.6.0
+ - greenlet>=1 ; extra == 'asyncio'
+ - mypy>=0.910 ; extra == 'mypy'
+ - pyodbc ; extra == 'mssql'
+ - pymssql ; extra == 'mssql-pymssql'
+ - pyodbc ; extra == 'mssql-pyodbc'
+ - mysqlclient>=1.4.0 ; extra == 'mysql'
+ - mysql-connector-python ; extra == 'mysql-connector'
+ - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector'
+ - cx-oracle>=8 ; extra == 'oracle'
+ - oracledb>=1.0.1 ; extra == 'oracle-oracledb'
+ - psycopg2>=2.7 ; extra == 'postgresql'
+ - pg8000>=1.29.1 ; extra == 'postgresql-pg8000'
+ - greenlet>=1 ; extra == 'postgresql-asyncpg'
+ - asyncpg ; extra == 'postgresql-asyncpg'
+ - psycopg2-binary ; extra == 'postgresql-psycopg2binary'
+ - psycopg2cffi ; extra == 'postgresql-psycopg2cffi'
+ - psycopg>=3.0.7 ; extra == 'postgresql-psycopg'
+ - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary'
+ - pymysql ; extra == 'pymysql'
+ - greenlet>=1 ; extra == 'aiomysql'
+ - aiomysql>=0.2.0 ; extra == 'aiomysql'
+ - greenlet>=1 ; extra == 'aioodbc'
+ - aioodbc ; extra == 'aioodbc'
+ - greenlet>=1 ; extra == 'asyncmy'
+ - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy'
+ - greenlet>=1 ; extra == 'aiosqlite'
+ - aiosqlite ; extra == 'aiosqlite'
+ - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite'
+ - sqlcipher3-binary ; extra == 'sqlcipher'
+ requires_python: '>=3.7'
+- pypi: https://files.pythonhosted.org/packages/fe/88/cb59509e4668d8001818d7355d9995be90c321313078c912420603a7cb95/sqlalchemy-2.0.48-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ name: sqlalchemy
+ version: 2.0.48
+ sha256: b19151e76620a412c2ac1c6f977ab1b9fa7ad43140178345136456d5265b32ed
+ requires_dist:
+ - importlib-metadata ; python_full_version < '3.8'
+ - greenlet>=1 ; platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'
+ - typing-extensions>=4.6.0
+ - greenlet>=1 ; extra == 'asyncio'
+ - mypy>=0.910 ; extra == 'mypy'
+ - pyodbc ; extra == 'mssql'
+ - pymssql ; extra == 'mssql-pymssql'
+ - pyodbc ; extra == 'mssql-pyodbc'
+ - mysqlclient>=1.4.0 ; extra == 'mysql'
+ - mysql-connector-python ; extra == 'mysql-connector'
+ - mariadb>=1.0.1,!=1.1.2,!=1.1.5,!=1.1.10 ; extra == 'mariadb-connector'
+ - cx-oracle>=8 ; extra == 'oracle'
+ - oracledb>=1.0.1 ; extra == 'oracle-oracledb'
+ - psycopg2>=2.7 ; extra == 'postgresql'
+ - pg8000>=1.29.1 ; extra == 'postgresql-pg8000'
+ - greenlet>=1 ; extra == 'postgresql-asyncpg'
+ - asyncpg ; extra == 'postgresql-asyncpg'
+ - psycopg2-binary ; extra == 'postgresql-psycopg2binary'
+ - psycopg2cffi ; extra == 'postgresql-psycopg2cffi'
+ - psycopg>=3.0.7 ; extra == 'postgresql-psycopg'
+ - psycopg[binary]>=3.0.7 ; extra == 'postgresql-psycopgbinary'
+ - pymysql ; extra == 'pymysql'
+ - greenlet>=1 ; extra == 'aiomysql'
+ - aiomysql>=0.2.0 ; extra == 'aiomysql'
+ - greenlet>=1 ; extra == 'aioodbc'
+ - aioodbc ; extra == 'aioodbc'
+ - greenlet>=1 ; extra == 'asyncmy'
+ - asyncmy>=0.2.3,!=0.2.4,!=0.2.6 ; extra == 'asyncmy'
+ - greenlet>=1 ; extra == 'aiosqlite'
+ - aiosqlite ; extra == 'aiosqlite'
+ - typing-extensions!=3.10.0.1 ; extra == 'aiosqlite'
+ - sqlcipher3-binary ; extra == 'sqlcipher'
+ requires_python: '>=3.7'
- pypi: https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl
name: stack-data
version: 0.6.3
@@ -5480,6 +6051,50 @@ packages:
- pygments ; extra == 'tests'
- littleutils ; extra == 'tests'
- cython ; extra == 'tests'
+- pypi: https://files.pythonhosted.org/packages/c8/31/5e7b23f9e43ff7fd46d243808d70c5e8daf3bc08ecf5a7fb84d5e38f7603/testcontainers-4.14.1-py3-none-any.whl
+ name: testcontainers
+ version: 4.14.1
+ sha256: 03dfef4797b31c82e7b762a454b6afec61a2a512ad54af47ab41e4fa5415f891
+ requires_dist:
+ - azure-cosmos>=4,<5 ; extra == 'cosmosdb'
+ - azure-storage-blob>=12,<13 ; extra == 'azurite'
+ - bcrypt>=5,<6 ; extra == 'registry'
+ - boto3>=1,<2 ; extra == 'aws' or extra == 'localstack'
+ - cassandra-driver>=3,<4 ; extra == 'scylla'
+ - chromadb-client>=1,<2 ; extra == 'chroma'
+ - cryptography ; extra == 'mailpit' or extra == 'sftp'
+ - docker
+ - google-cloud-datastore>=2,<3 ; extra == 'google'
+ - google-cloud-pubsub>=2,<3 ; extra == 'google'
+ - httpx ; extra == 'aws' or extra == 'generic' or extra == 'test-module-import'
+ - ibm-db-sa ; platform_machine != 'aarch64' and platform_machine != 'arm64' and extra == 'db2'
+ - influxdb>=5,<6 ; extra == 'influxdb'
+ - influxdb-client>=1,<2 ; extra == 'influxdb'
+ - kubernetes ; extra == 'k3s'
+ - minio>=7,<8 ; extra == 'minio'
+ - nats-py>=2,<3 ; extra == 'nats'
+ - neo4j>=6,<7 ; extra == 'neo4j'
+ - openfga-sdk ; extra == 'openfga'
+ - opensearch-py>=3,<4 ; python_full_version < '4' and extra == 'opensearch'
+ - oracledb>=3,<4 ; extra == 'oracle' or extra == 'oracle-free'
+ - pika>=1,<2 ; extra == 'rabbitmq'
+ - pymongo>=4,<5 ; extra == 'mongodb'
+ - pymssql>=2,<3 ; extra == 'mssql'
+ - pymysql[rsa]>=1,<2 ; extra == 'mysql'
+ - python-arango>=8,<9 ; extra == 'arangodb'
+ - python-dotenv
+ - python-keycloak>=6,<7 ; python_full_version < '4' and extra == 'keycloak'
+ - pyyaml>=6.0.3 ; extra == 'k3s'
+ - qdrant-client>=1,<2 ; extra == 'qdrant'
+ - redis>=7,<8 ; extra == 'generic' or extra == 'redis'
+ - selenium>=4,<5 ; extra == 'selenium'
+ - sqlalchemy>=2,<3 ; extra == 'db2' or extra == 'mssql' or extra == 'mysql' or extra == 'oracle' or extra == 'oracle-free'
+ - trino ; extra == 'trino'
+ - typing-extensions
+ - urllib3
+ - weaviate-client>=4,<5 ; extra == 'weaviate'
+ - wrapt
+ requires_python: '>=3.10'
- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_hd72426e_102.conda
sha256: a84ff687119e6d8752346d1d408d5cf360dee0badd487a472aa8ddedfdc219e1
md5: a0116df4f4ed05c303811a837d5b39d8
@@ -5678,6 +6293,30 @@ packages:
version: 0.2.14
sha256: a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1
requires_python: '>=3.6'
+- pypi: https://files.pythonhosted.org/packages/03/a9/5b7d6a16fd6533fed2756900fc8fc923f678179aea62ada6d65c92718c00/wrapt-2.1.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl
+ name: wrapt
+ version: 2.1.2
+ sha256: bbac24d879aa22998e87f6b3f481a5216311e7d53c7db87f189a7a0266dafffb
+ requires_dist:
+ - pytest ; extra == 'dev'
+ - setuptools ; extra == 'dev'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/45/bb/34c443690c847835cfe9f892be78c533d4f32366ad2888972c094a897e39/wrapt-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: wrapt
+ version: 2.1.2
+ sha256: 16997dfb9d67addc2e3f41b62a104341e80cac52f91110dece393923c0ebd5ca
+ requires_dist:
+ - pytest ; extra == 'dev'
+ - setuptools ; extra == 'dev'
+ requires_python: '>=3.9'
+- pypi: https://files.pythonhosted.org/packages/5e/88/9a9b9a90ac8ca11c2fdb6a286cb3a1fc7dd774c00ed70929a6434f6bc634/wrapt-2.1.2-cp313-cp313-macosx_11_0_arm64.whl
+ name: wrapt
+ version: 2.1.2
+ sha256: 4bdf26e03e6d0da3f0e9422fd36bcebf7bc0eeb55fdf9c727a09abc6b9fe472e
+ requires_dist:
+ - pytest ; extra == 'dev'
+ - setuptools ; extra == 'dev'
+ requires_python: '>=3.9'
- conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.45-hb9d3cd8_0.conda
sha256: a5d4af601f71805ec67403406e147c48d6bad7aaeae92b0622b7e2396842d3fe
md5: 397a013c2dc5145a70737871aaa87e98
@@ -6090,6 +6729,33 @@ packages:
purls: []
size: 566948
timestamp: 1726847598167
+- pypi: https://files.pythonhosted.org/packages/66/fe/b1e10b08d287f518994f1e2ff9b6d26f0adeecd8dd7d533b01bab29a3eda/yarl-1.23.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl
+ name: yarl
+ version: 1.23.0
+ sha256: 34b6cf500e61c90f305094911f9acc9c86da1a05a7a3f5be9f68817043f486e4
+ requires_dist:
+ - idna>=2.0
+ - multidict>=4.0
+ - propcache>=0.2.1
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/ae/50/06d511cc4b8e0360d3c94af051a768e84b755c5eb031b12adaaab6dec6e5/yarl-1.23.0-cp313-cp313-macosx_11_0_arm64.whl
+ name: yarl
+ version: 1.23.0
+ sha256: 7c6b9461a2a8b47c65eef63bb1c76a4f1c119618ffa99ea79bc5bb1e46c5821b
+ requires_dist:
+ - idna>=2.0
+ - multidict>=4.0
+ - propcache>=0.2.1
+ requires_python: '>=3.10'
+- pypi: https://files.pythonhosted.org/packages/c4/f4/4e30b250927ffdab4db70da08b9b8d2194d7c7b400167b8fbeca1e4701ca/yarl-1.23.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl
+ name: yarl
+ version: 1.23.0
+ sha256: 2569b67d616eab450d262ca7cb9f9e19d2f718c70a8b88712859359d0ab17035
+ requires_dist:
+ - idna>=2.0
+ - multidict>=4.0
+ - propcache>=0.2.1
+ requires_python: '>=3.10'
- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb8e6e7a_2.conda
sha256: a4166e3d8ff4e35932510aaff7aa90772f84b4d07e9f6f83c614cba7ceefe0eb
md5: 6432cb5d4ac0046c3ac0a8a0f95842f9
diff --git a/src/datajoint/builtin_codecs/attach.py b/src/datajoint/builtin_codecs/attach.py
index aa10f2424..9100db5b0 100644
--- a/src/datajoint/builtin_codecs/attach.py
+++ b/src/datajoint/builtin_codecs/attach.py
@@ -107,6 +107,7 @@ def decode(self, stored: bytes, *, key: dict | None = None) -> str:
config = (key or {}).get("_config")
if config is None:
from ..settings import config
+ assert config is not None
download_path = Path(config.get("download_path", "."))
download_path.mkdir(parents=True, exist_ok=True)
local_path = download_path / filename
diff --git a/src/datajoint/builtin_codecs/filepath.py b/src/datajoint/builtin_codecs/filepath.py
index a0400499b..cb03f4783 100644
--- a/src/datajoint/builtin_codecs/filepath.py
+++ b/src/datajoint/builtin_codecs/filepath.py
@@ -103,6 +103,7 @@ def encode(self, value: Any, *, key: dict | None = None, store_name: str | None
config = (key or {}).get("_config")
if config is None:
from ..settings import config
+ assert config is not None
path = str(value)
diff --git a/src/datajoint/diagram.py b/src/datajoint/diagram.py
index 75e00c21c..230d53b39 100644
--- a/src/datajoint/diagram.py
+++ b/src/datajoint/diagram.py
@@ -1,12 +1,18 @@
"""
-Diagram visualization for DataJoint schemas.
+Diagram for DataJoint schemas.
-This module provides the Diagram class for visualizing schema structure
-as directed acyclic graphs showing tables and their foreign key relationships.
+This module provides the Diagram class for constructing derived views of the
+dependency graph. Diagram supports set operators (+, -, *) for selecting subsets
+of tables, restriction propagation (cascade, restrict) for selecting subsets of
+data, and operations (delete, drop, preview) for acting on those selections.
+
+Visualization methods (draw, make_dot, make_svg, etc.) require matplotlib and
+pygraphviz. All other methods are always available.
"""
from __future__ import annotations
+import copy as copy_module
import functools
import inspect
import io
@@ -14,10 +20,12 @@
import networkx as nx
-from .dependencies import topo_sort
-from .errors import DataJointError
+from .condition import AndList
+from .dependencies import extract_master, topo_sort
+from .errors import DataJointError, IntegrityError
from .table import Table, lookup_class_name
from .user_tables import Computed, Imported, Lookup, Manual, Part, _AliasNode, _get_tier
+from .utils import user_choice
try:
from matplotlib import pyplot as plt
@@ -37,1002 +45,1511 @@
logger = logging.getLogger(__name__.split(".")[0])
-if not diagram_active: # noqa: C901
-
- class Diagram:
+class Diagram(nx.DiGraph): # noqa: C901
+ """
+ Schema diagram as a directed acyclic graph (DAG).
+
+ Visualizes tables and foreign key relationships derived from
+ ``connection.dependencies``.
+
+ Parameters
+ ----------
+ source : Table, Schema, or module
+ A table object, table class, schema, or module with a schema.
+ context : dict, optional
+ Namespace for resolving table class names. If None, uses caller's
+ frame globals/locals.
+
+ Examples
+ --------
+ >>> diag = dj.Diagram(schema.MyTable)
+ >>> diag.draw()
+
+ Operators:
+
+ - ``diag1 + diag2`` - union of diagrams
+ - ``diag1 - diag2`` - difference of diagrams
+ - ``diag1 * diag2`` - intersection of diagrams
+ - ``diag + n`` - expand n levels of successors (children)
+ - ``diag - n`` - expand n levels of predecessors (parents)
+
+ >>> dj.Diagram(schema.Table) + 1 - 1 # immediate ancestors and descendants
+
+ Notes
+ -----
+ ``diagram + 1 - 1`` may differ from ``diagram - 1 + 1``.
+ Only tables loaded in the connection are displayed.
+
+ Layout direction is controlled via ``dj.config.display.diagram_direction``
+ (default ``"TB"``). Use ``dj.config.override()`` to change temporarily::
+
+ with dj.config.override(display_diagram_direction="LR"):
+ dj.Diagram(schema).draw()
+ """
+
+ def __init__(self, source, context=None) -> None:
+ if isinstance(source, Diagram):
+ # copy constructor
+ self.nodes_to_show = set(source.nodes_to_show)
+ self._expanded_nodes = set(source._expanded_nodes)
+ self.context = source.context
+ self._connection = source._connection
+ self._cascade_restrictions = copy_module.deepcopy(source._cascade_restrictions)
+ self._restrict_conditions = copy_module.deepcopy(source._restrict_conditions)
+ self._restriction_attrs = copy_module.deepcopy(source._restriction_attrs)
+ self._part_integrity = getattr(source, "_part_integrity", "enforce")
+ super().__init__(source)
+ return
+
+ # get the caller's context
+ if context is None:
+ frame = inspect.currentframe().f_back
+ self.context = dict(frame.f_globals, **frame.f_locals)
+ del frame
+ else:
+ self.context = context
+
+ # find connection in the source
+ try:
+ connection = source.connection
+ except AttributeError:
+ try:
+ connection = source.schema.connection
+ except AttributeError:
+ raise DataJointError("Could not find database connection in %s" % repr(source[0]))
+
+ # initialize graph from dependencies
+ connection.dependencies.load()
+ super().__init__(connection.dependencies)
+ self._connection = connection
+ self._cascade_restrictions = {}
+ self._restrict_conditions = {}
+ self._restriction_attrs = {}
+
+ # Enumerate nodes from all the items in the list
+ self.nodes_to_show = set()
+ try:
+ self.nodes_to_show.add(source.full_table_name)
+ except AttributeError:
+ try:
+ database = source.database
+ except AttributeError:
+ try:
+ database = source.schema.database
+ except AttributeError:
+ raise DataJointError("Cannot plot Diagram for %s" % repr(source))
+ for node in self:
+ # Handle both MySQL backticks and PostgreSQL double quotes
+ if node.startswith("`%s`" % database) or node.startswith('"%s"' % database):
+ self.nodes_to_show.add(node)
+ # All nodes start as expanded
+ self._expanded_nodes = set(self.nodes_to_show)
+
+ @classmethod
+ def from_sequence(cls, sequence) -> "Diagram":
"""
- Schema diagram (disabled).
+ Create combined Diagram from a sequence of sources.
- Diagram visualization requires matplotlib and pygraphviz packages.
- Install them to enable this feature.
+ Parameters
+ ----------
+ sequence : iterable
+ Sequence of table objects, classes, or schemas.
- See Also
- --------
- https://docs.datajoint.com/how-to/installation/
+ Returns
+ -------
+ Diagram
+ Union of diagrams: ``Diagram(arg1) + ... + Diagram(argn)``.
"""
+ return functools.reduce(lambda x, y: x + y, map(Diagram, sequence))
- def __init__(self, *args, **kwargs) -> None:
- logger.warning("Please install matplotlib and pygraphviz libraries to enable the Diagram feature.")
-
-else:
-
- class Diagram(nx.DiGraph):
+ @classmethod
+ def _from_table(cls, table_expr) -> "Diagram":
"""
- Schema diagram as a directed acyclic graph (DAG).
+ Create a Diagram containing table_expr and all its descendants.
- Visualizes tables and foreign key relationships derived from
- ``connection.dependencies``.
+ Internal factory for ``Table.delete()`` and ``Table.drop()``.
+ Bypasses the normal ``__init__`` which does caller-frame introspection
+ and source-type resolution.
Parameters
----------
- source : Table, Schema, or module
- A table object, table class, schema, or module with a schema.
- context : dict, optional
- Namespace for resolving table class names. If None, uses caller's
- frame globals/locals.
+ table_expr : Table
+ A table instance with ``connection`` and ``full_table_name``.
- Examples
- --------
- >>> diag = dj.Diagram(schema.MyTable)
- >>> diag.draw()
+ Returns
+ -------
+ Diagram
+ """
+ conn = table_expr.connection
+ conn.dependencies.load()
+ descendants = set(conn.dependencies.descendants(table_expr.full_table_name))
+ result = cls.__new__(cls)
+ nx.DiGraph.__init__(result, conn.dependencies)
+ result._connection = conn
+ result.context = {}
+ result.nodes_to_show = descendants
+ result._expanded_nodes = set(descendants)
+ result._cascade_restrictions = {}
+ result._restrict_conditions = {}
+ result._restriction_attrs = {}
+ return result
+
+ def add_parts(self) -> "Diagram":
+ """
+ Add part tables of all masters already in the diagram.
- Operators:
+ Returns
+ -------
+ Diagram
+ New diagram with part tables included.
+ """
- - ``diag1 + diag2`` - union of diagrams
- - ``diag1 - diag2`` - difference of diagrams
- - ``diag1 * diag2`` - intersection of diagrams
- - ``diag + n`` - expand n levels of successors (children)
- - ``diag - n`` - expand n levels of predecessors (parents)
+ def is_part(part, master):
+ part = [s.strip("`") for s in part.split(".")]
+ master = [s.strip("`") for s in master.split(".")]
+ return master[0] == part[0] and master[1] + "__" == part[1][: len(master[1]) + 2]
- >>> dj.Diagram(schema.Table) + 1 - 1 # immediate ancestors and descendants
+ self = Diagram(self) # copy
+ self.nodes_to_show.update(n for n in self.nodes() if any(is_part(n, m) for m in self.nodes_to_show))
+ return self
- Notes
- -----
- ``diagram + 1 - 1`` may differ from ``diagram - 1 + 1``.
- Only tables loaded in the connection are displayed.
+ def collapse(self) -> "Diagram":
+ """
+ Mark all nodes in this diagram as collapsed.
- Layout direction is controlled via ``dj.config.display.diagram_direction``
- (default ``"TB"``). Use ``dj.config.override()`` to change temporarily::
+ Collapsed nodes are shown as a single node per schema. When combined
+ with other diagrams using ``+``, expanded nodes win: if a node is
+ expanded in either operand, it remains expanded in the result.
- with dj.config.override(display_diagram_direction="LR"):
- dj.Diagram(schema).draw()
+ Returns
+ -------
+ Diagram
+ A copy of this diagram with all nodes collapsed.
+
+ Examples
+ --------
+ >>> # Show schema1 expanded, schema2 collapsed into single nodes
+ >>> dj.Diagram(schema1) + dj.Diagram(schema2).collapse()
+
+ >>> # Collapse all three schemas together
+ >>> (dj.Diagram(schema1) + dj.Diagram(schema2) + dj.Diagram(schema3)).collapse()
+
+ >>> # Expand one table from collapsed schema
+ >>> dj.Diagram(schema).collapse() + dj.Diagram(SingleTable)
"""
+ result = Diagram(self)
+ result._expanded_nodes = set() # All nodes collapsed
+ return result
- def __init__(self, source, context=None) -> None:
- if isinstance(source, Diagram):
- # copy constructor
- self.nodes_to_show = set(source.nodes_to_show)
- self._expanded_nodes = set(source._expanded_nodes)
- self.context = source.context
- self._connection = source._connection
- super().__init__(source)
- return
-
- # get the caller's context
- if context is None:
- frame = inspect.currentframe().f_back
- self.context = dict(frame.f_globals, **frame.f_locals)
- del frame
- else:
- self.context = context
+ def __add__(self, arg) -> "Diagram":
+ """
+ Union or downstream expansion.
+
+ Parameters
+ ----------
+ arg : Diagram or int
+ Another Diagram for union, or positive int for downstream expansion.
- # find connection in the source
+ Returns
+ -------
+ Diagram
+ Combined or expanded diagram.
+ """
+ result = Diagram(self) # copy
+ try:
+ # Merge nodes and edges from the other diagram
+ result.add_nodes_from(arg.nodes(data=True))
+ result.add_edges_from(arg.edges(data=True))
+ result.nodes_to_show.update(arg.nodes_to_show)
+ # Merge contexts for class name lookups
+ result.context = {**result.context, **arg.context}
+ # Expanded wins: union of expanded nodes from both operands
+ result._expanded_nodes = self._expanded_nodes | arg._expanded_nodes
+ except AttributeError:
try:
- connection = source.connection
+ result.nodes_to_show.add(arg.full_table_name)
+ result._expanded_nodes.add(arg.full_table_name)
except AttributeError:
- try:
- connection = source.schema.connection
- except AttributeError:
- raise DataJointError("Could not find database connection in %s" % repr(source[0]))
+ for i in range(arg):
+ new = nx.algorithms.boundary.node_boundary(result, result.nodes_to_show)
+ if not new:
+ break
+ # add nodes referenced by aliased nodes
+ new.update(nx.algorithms.boundary.node_boundary(result, (a for a in new if a.isdigit())))
+ result.nodes_to_show.update(new)
+ # New nodes from expansion are expanded
+ result._expanded_nodes = result._expanded_nodes | result.nodes_to_show
+ return result
+
+ def __sub__(self, arg) -> "Diagram":
+ """
+ Difference or upstream expansion.
- # initialize graph from dependencies
- self._connection = connection
- connection.dependencies.load()
- super().__init__(connection.dependencies)
+ Parameters
+ ----------
+ arg : Diagram or int
+ Another Diagram for difference, or positive int for upstream expansion.
- # Enumerate nodes from all the items in the list
- self.nodes_to_show = set()
+ Returns
+ -------
+ Diagram
+ Reduced or expanded diagram.
+ """
+ self = Diagram(self) # copy
+ try:
+ self.nodes_to_show.difference_update(arg.nodes_to_show)
+ except AttributeError:
try:
- self.nodes_to_show.add(source.full_table_name)
+ self.nodes_to_show.remove(arg.full_table_name)
except AttributeError:
- try:
- database = source.database
- except AttributeError:
- try:
- database = source.schema.database
- except AttributeError:
- raise DataJointError("Cannot plot Diagram for %s" % repr(source))
- for node in self:
- # Handle both MySQL backticks and PostgreSQL double quotes
- if node.startswith("`%s`" % database) or node.startswith('"%s"' % database):
- self.nodes_to_show.add(node)
- # All nodes start as expanded
- self._expanded_nodes = set(self.nodes_to_show)
-
- @classmethod
- def from_sequence(cls, sequence) -> "Diagram":
- """
- Create combined Diagram from a sequence of sources.
-
- Parameters
- ----------
- sequence : iterable
- Sequence of table objects, classes, or schemas.
-
- Returns
- -------
- Diagram
- Union of diagrams: ``Diagram(arg1) + ... + Diagram(argn)``.
- """
- return functools.reduce(lambda x, y: x + y, map(Diagram, sequence))
-
- def add_parts(self) -> "Diagram":
- """
- Add part tables of all masters already in the diagram.
-
- Returns
- -------
- Diagram
- New diagram with part tables included.
- """
-
- def is_part(part, master):
- part = [s.strip("`") for s in part.split(".")]
- master = [s.strip("`") for s in master.split(".")]
- return master[0] == part[0] and master[1] + "__" == part[1][: len(master[1]) + 2]
-
- self = Diagram(self) # copy
- self.nodes_to_show.update(n for n in self.nodes() if any(is_part(n, m) for m in self.nodes_to_show))
- return self
-
- def collapse(self) -> "Diagram":
- """
- Mark all nodes in this diagram as collapsed.
-
- Collapsed nodes are shown as a single node per schema. When combined
- with other diagrams using ``+``, expanded nodes win: if a node is
- expanded in either operand, it remains expanded in the result.
-
- Returns
- -------
- Diagram
- A copy of this diagram with all nodes collapsed.
-
- Examples
- --------
- >>> # Show schema1 expanded, schema2 collapsed into single nodes
- >>> dj.Diagram(schema1) + dj.Diagram(schema2).collapse()
-
- >>> # Collapse all three schemas together
- >>> (dj.Diagram(schema1) + dj.Diagram(schema2) + dj.Diagram(schema3)).collapse()
-
- >>> # Expand one table from collapsed schema
- >>> dj.Diagram(schema).collapse() + dj.Diagram(SingleTable)
- """
- result = Diagram(self)
- result._expanded_nodes = set() # All nodes collapsed
- return result
+ for i in range(arg):
+ graph = nx.DiGraph(self).reverse()
+ new = nx.algorithms.boundary.node_boundary(graph, self.nodes_to_show)
+ if not new:
+ break
+ # add nodes referenced by aliased nodes
+ new.update(nx.algorithms.boundary.node_boundary(graph, (a for a in new if a.isdigit())))
+ self.nodes_to_show.update(new)
+ return self
+
+ def __mul__(self, arg) -> "Diagram":
+ """
+ Intersection of two diagrams.
- def __add__(self, arg) -> "Diagram":
- """
- Union or downstream expansion.
-
- Parameters
- ----------
- arg : Diagram or int
- Another Diagram for union, or positive int for downstream expansion.
-
- Returns
- -------
- Diagram
- Combined or expanded diagram.
- """
- result = Diagram(self) # copy
- try:
- # Merge nodes and edges from the other diagram
- result.add_nodes_from(arg.nodes(data=True))
- result.add_edges_from(arg.edges(data=True))
- result.nodes_to_show.update(arg.nodes_to_show)
- # Merge contexts for class name lookups
- result.context = {**result.context, **arg.context}
- # Expanded wins: union of expanded nodes from both operands
- result._expanded_nodes = self._expanded_nodes | arg._expanded_nodes
- except AttributeError:
- try:
- result.nodes_to_show.add(arg.full_table_name)
- result._expanded_nodes.add(arg.full_table_name)
- except AttributeError:
- for i in range(arg):
- new = nx.algorithms.boundary.node_boundary(result, result.nodes_to_show)
- if not new:
- break
- # add nodes referenced by aliased nodes
- new.update(nx.algorithms.boundary.node_boundary(result, (a for a in new if a.isdigit())))
- result.nodes_to_show.update(new)
- # New nodes from expansion are expanded
- result._expanded_nodes = result._expanded_nodes | result.nodes_to_show
- return result
+ Parameters
+ ----------
+ arg : Diagram
+ Another Diagram.
- def __sub__(self, arg) -> "Diagram":
- """
- Difference or upstream expansion.
-
- Parameters
- ----------
- arg : Diagram or int
- Another Diagram for difference, or positive int for upstream expansion.
-
- Returns
- -------
- Diagram
- Reduced or expanded diagram.
- """
- self = Diagram(self) # copy
- try:
- self.nodes_to_show.difference_update(arg.nodes_to_show)
- except AttributeError:
- try:
- self.nodes_to_show.remove(arg.full_table_name)
- except AttributeError:
- for i in range(arg):
- graph = nx.DiGraph(self).reverse()
- new = nx.algorithms.boundary.node_boundary(graph, self.nodes_to_show)
- if not new:
- break
- # add nodes referenced by aliased nodes
- new.update(nx.algorithms.boundary.node_boundary(graph, (a for a in new if a.isdigit())))
- self.nodes_to_show.update(new)
- return self
-
- def __mul__(self, arg) -> "Diagram":
- """
- Intersection of two diagrams.
-
- Parameters
- ----------
- arg : Diagram
- Another Diagram.
-
- Returns
- -------
- Diagram
- Diagram with nodes present in both operands.
- """
- self = Diagram(self) # copy
- self.nodes_to_show.intersection_update(arg.nodes_to_show)
- return self
-
- def topo_sort(self) -> list[str]:
- """
- Return nodes in topological order.
-
- Returns
- -------
- list[str]
- Node names in topological order.
- """
- return topo_sort(self)
-
- def _make_graph(self) -> nx.DiGraph:
- """
- Build graph object ready for drawing.
-
- Returns
- -------
- nx.DiGraph
- Graph with nodes relabeled to class names.
- """
- # mark "distinguished" tables, i.e. those that introduce new primary key
- # attributes
- # Filter nodes_to_show to only include nodes that exist in the graph
- valid_nodes = self.nodes_to_show.intersection(set(self.nodes()))
- for name in valid_nodes:
- foreign_attributes = set(
- attr for p in self.in_edges(name, data=True) for attr in p[2]["attr_map"] if p[2]["primary"]
- )
- self.nodes[name]["distinguished"] = (
- "primary_key" in self.nodes[name] and foreign_attributes < self.nodes[name]["primary_key"]
- )
- # include aliased nodes that are sandwiched between two displayed nodes
- gaps = set(nx.algorithms.boundary.node_boundary(self, valid_nodes)).intersection(
- nx.algorithms.boundary.node_boundary(nx.DiGraph(self).reverse(), valid_nodes)
+ Returns
+ -------
+ Diagram
+ Diagram with nodes present in both operands.
+ """
+ self = Diagram(self) # copy
+ self.nodes_to_show.intersection_update(arg.nodes_to_show)
+ return self
+
+ def topo_sort(self) -> list[str]:
+ """
+ Return nodes in topological order.
+
+ Returns
+ -------
+ list[str]
+ Node names in topological order.
+ """
+ return topo_sort(self)
+
+ def cascade(self, table_expr, part_integrity="enforce"):
+ """
+ Apply cascade restriction and propagate downstream.
+
+ OR at convergence — a child row is affected if *any* restricted
+ ancestor taints it. Used for delete.
+
+ Can only be called once on an unrestricted Diagram. Cannot be
+ mixed with ``restrict()``.
+
+ Parameters
+ ----------
+ table_expr : QueryExpression
+ A restricted table expression
+ (e.g., ``Session & 'subject_id=1'``).
+ part_integrity : str, optional
+ ``"enforce"`` (default), ``"ignore"``, or ``"cascade"``.
+
+ Returns
+ -------
+ Diagram
+ New Diagram with cascade restrictions applied.
+ """
+ if self._cascade_restrictions or self._restrict_conditions:
+ raise DataJointError(
+ "cascade() can only be called once on an unrestricted Diagram. "
+ "cascade and restrict modes are mutually exclusive."
)
- nodes = valid_nodes.union(a for a in gaps if a.isdigit())
- # construct subgraph and rename nodes to class names
- graph = nx.DiGraph(nx.DiGraph(self).subgraph(nodes))
- nx.set_node_attributes(graph, name="node_type", values={n: _get_tier(n) for n in graph})
- # relabel nodes to class names
- mapping = {node: lookup_class_name(node, self.context) or node for node in graph.nodes()}
- new_names = list(mapping.values())
- if len(new_names) > len(set(new_names)):
- raise DataJointError("Some classes have identical names. The Diagram cannot be plotted.")
- nx.relabel_nodes(graph, mapping, copy=False)
- return graph
-
- def _apply_collapse(self, graph: nx.DiGraph) -> tuple[nx.DiGraph, dict[str, str]]:
- """
- Apply collapse logic to the graph.
-
- Nodes in nodes_to_show but not in _expanded_nodes are collapsed into
- single schema nodes.
-
- Parameters
- ----------
- graph : nx.DiGraph
- The graph from _make_graph().
-
- Returns
- -------
- tuple[nx.DiGraph, dict[str, str]]
- Modified graph and mapping of collapsed schema labels to their table count.
- """
- # Filter to valid nodes (those that exist in the underlying graph)
- valid_nodes = self.nodes_to_show.intersection(set(self.nodes()))
- valid_expanded = self._expanded_nodes.intersection(set(self.nodes()))
-
- # If all nodes are expanded, no collapse needed
- if valid_expanded >= valid_nodes:
- return graph, {}
-
- # Map full_table_names to class_names
- full_to_class = {node: lookup_class_name(node, self.context) or node for node in valid_nodes}
- class_to_full = {v: k for k, v in full_to_class.items()}
-
- # Identify expanded class names
- expanded_class_names = {full_to_class.get(node, node) for node in valid_expanded}
-
- # Identify nodes to collapse (class names)
- nodes_to_collapse = set(graph.nodes()) - expanded_class_names
-
- if not nodes_to_collapse:
- return graph, {}
-
- # Group collapsed nodes by schema
- collapsed_by_schema = {} # schema_name -> list of class_names
- for class_name in nodes_to_collapse:
- full_name = class_to_full.get(class_name)
- if full_name:
- parts = full_name.replace('"', "`").split("`")
- if len(parts) >= 2:
- schema_name = parts[1]
- if schema_name not in collapsed_by_schema:
- collapsed_by_schema[schema_name] = []
- collapsed_by_schema[schema_name].append(class_name)
-
- if not collapsed_by_schema:
- return graph, {}
-
- # Determine labels for collapsed schemas
- schema_modules = {}
- for schema_name, class_names in collapsed_by_schema.items():
- schema_modules[schema_name] = set()
- for class_name in class_names:
- cls = self._resolve_class(class_name)
- if cls is not None and hasattr(cls, "__module__"):
- module_name = cls.__module__.split(".")[-1]
- schema_modules[schema_name].add(module_name)
-
- # Collect module names for ALL schemas in the diagram (not just collapsed)
- all_schema_modules = {} # schema_name -> module_name
- for node in graph.nodes():
- full_name = class_to_full.get(node)
- if full_name:
- parts = full_name.replace('"', "`").split("`")
- if len(parts) >= 2:
- db_schema = parts[1]
- cls = self._resolve_class(node)
- if cls is not None and hasattr(cls, "__module__"):
- module_name = cls.__module__.split(".")[-1]
- all_schema_modules[db_schema] = module_name
-
- # Check which module names are shared by multiple schemas
- module_to_schemas = {}
- for db_schema, module_name in all_schema_modules.items():
- if module_name not in module_to_schemas:
- module_to_schemas[module_name] = []
- module_to_schemas[module_name].append(db_schema)
-
- ambiguous_modules = {m for m, schemas in module_to_schemas.items() if len(schemas) > 1}
-
- # Determine labels for collapsed schemas
- collapsed_labels = {} # schema_name -> label
- for schema_name, modules in schema_modules.items():
- if len(modules) == 1:
- module_name = next(iter(modules))
- # Use database schema name if module is ambiguous
- if module_name in ambiguous_modules:
- label = schema_name
- else:
- label = module_name
- else:
- label = schema_name
- collapsed_labels[schema_name] = label
-
- # Build counts using final labels
- collapsed_counts = {} # label -> count of tables
- for schema_name, class_names in collapsed_by_schema.items():
- label = collapsed_labels[schema_name]
- collapsed_counts[label] = len(class_names)
-
- # Create new graph with collapsed nodes
- new_graph = nx.DiGraph()
-
- # Map old node names to new names (collapsed nodes -> schema label)
- node_mapping = {}
- for node in graph.nodes():
- full_name = class_to_full.get(node)
- if full_name:
- parts = full_name.replace('"', "`").split("`")
- if len(parts) >= 2 and node in nodes_to_collapse:
- schema_name = parts[1]
- node_mapping[node] = collapsed_labels[schema_name]
+ result = Diagram(self)
+ result._part_integrity = part_integrity
+ node = table_expr.full_table_name
+ if node not in result.nodes():
+ raise DataJointError(f"Table {node} is not in the diagram.")
+ # Seed restriction
+ restriction = AndList(table_expr.restriction)
+ result._cascade_restrictions[node] = [restriction] if restriction else []
+ result._restriction_attrs[node] = set(table_expr.restriction_attributes)
+ # Propagate downstream
+ result._propagate_restrictions(node, mode="cascade", part_integrity=part_integrity)
+ return result
+
+ def _restricted_table(self, node):
+ """
+ Return a FreeTable for ``node`` with this diagram's restrictions applied.
+
+ Cascade restrictions are OR-combined (a row is affected if ANY
+ FK reference points to a deleted row). Restrict conditions are
+ AND-combined (a row is included only when ALL ancestor conditions
+ are satisfied).
+ """
+ from .table import FreeTable
+
+ ft = FreeTable(self._connection, node)
+ restrictions = (self._cascade_restrictions or self._restrict_conditions).get(node, [])
+ if not restrictions:
+ return ft
+ if self._cascade_restrictions:
+ # OR semantics — passing a list to restrict() creates an OrList
+ return ft.restrict(restrictions)
+ else:
+ # AND semantics — each restriction narrows further
+ for r in restrictions:
+ ft = ft.restrict(r)
+ return ft
+
+ def restrict(self, table_expr):
+ """
+ Apply restrict condition and propagate downstream.
+
+ AND at convergence — a child row is included only if it satisfies
+ *all* restricted ancestors. Used for export. Can be chained.
+
+ Cannot be called on a cascade-restricted Diagram.
+
+ Parameters
+ ----------
+ table_expr : QueryExpression
+ A restricted table expression.
+
+ Returns
+ -------
+ Diagram
+ New Diagram with restrict conditions applied.
+ """
+ if self._cascade_restrictions:
+ raise DataJointError(
+ "Cannot apply restrict() on a cascade-restricted Diagram. "
+ "cascade and restrict modes are mutually exclusive."
+ )
+ result = Diagram(self)
+ node = table_expr.full_table_name
+ if node not in result.nodes():
+ raise DataJointError(f"Table {node} is not in the diagram.")
+ # Seed restriction (AND accumulation)
+ result._restrict_conditions.setdefault(node, AndList()).extend(table_expr.restriction)
+ result._restriction_attrs.setdefault(node, set()).update(table_expr.restriction_attributes)
+ # Propagate downstream
+ result._propagate_restrictions(node, mode="restrict")
+ return result
+
+ def _propagate_restrictions(self, start_node, mode, part_integrity="enforce"):
+ """
+ Propagate restrictions from start_node to all its descendants.
+
+ Walks the dependency graph in topological order, applying
+ propagation rules at each edge. Only processes descendants of
+ start_node to avoid duplicate propagation when chaining.
+ """
+ from .table import FreeTable
+
+ sorted_nodes = topo_sort(self)
+ # Only propagate through descendants of start_node
+ allowed_nodes = {start_node} | set(nx.descendants(self, start_node))
+ propagated_edges = set()
+ visited_masters = set()
+
+ restrictions = self._cascade_restrictions if mode == "cascade" else self._restrict_conditions
+
+ # Multiple passes to handle part_integrity="cascade" upward propagation
+ max_passes = 10
+ for _ in range(max_passes):
+ any_new = False
+
+ for node in sorted_nodes:
+ if node not in restrictions or node not in allowed_nodes:
+ continue
+
+ # Build parent FreeTable with current restriction
+ parent_ft = self._restricted_table(node)
+
+ parent_attrs = self._restriction_attrs.get(node, set())
+
+ for _, target, edge_props in self.out_edges(node, data=True):
+ attr_map = edge_props.get("attr_map", {})
+ aliased = edge_props.get("aliased", False)
+
+ if target.isdigit():
+ # Alias node — follow through to real child
+ for _, child_node, _ in self.out_edges(target, data=True):
+ edge_key = (node, target, child_node)
+ if edge_key in propagated_edges:
+ continue
+ propagated_edges.add(edge_key)
+ was_new = child_node not in restrictions
+ self._apply_propagation_rule(
+ parent_ft,
+ parent_attrs,
+ child_node,
+ attr_map,
+ True,
+ mode,
+ restrictions,
+ )
+ if was_new and child_node in restrictions:
+ any_new = True
else:
- node_mapping[node] = node
+ edge_key = (node, target)
+ if edge_key in propagated_edges:
+ continue
+ propagated_edges.add(edge_key)
+ was_new = target not in restrictions
+ self._apply_propagation_rule(
+ parent_ft,
+ parent_attrs,
+ target,
+ attr_map,
+ aliased,
+ mode,
+ restrictions,
+ )
+ if was_new and target in restrictions:
+ any_new = True
+
+ # part_integrity="cascade": propagate up from part to master
+ if part_integrity == "cascade" and mode == "cascade":
+ master_name = extract_master(target)
+ if (
+ master_name
+ and master_name in self.nodes()
+ and master_name not in restrictions
+ and master_name not in visited_masters
+ ):
+ visited_masters.add(master_name)
+ child_ft = self._restricted_table(target)
+ master_ft = FreeTable(self._connection, master_name)
+ from .condition import make_condition
+
+ master_restr = make_condition(
+ master_ft,
+ (master_ft.proj() & child_ft.proj()).to_arrays(),
+ master_ft.restriction_attributes,
+ )
+ restrictions[master_name] = [master_restr]
+ self._restriction_attrs[master_name] = set()
+ allowed_nodes.add(master_name)
+ allowed_nodes.update(nx.descendants(self, master_name))
+ any_new = True
+
+ if not any_new:
+ break
+
+ def _apply_propagation_rule(
+ self,
+ parent_ft,
+ parent_attrs,
+ child_node,
+ attr_map,
+ aliased,
+ mode,
+ restrictions,
+ ):
+ """
+ Apply one of the 3 propagation rules to a parent→child edge.
+
+ Rules (from table.py restriction propagation):
+
+ 1. Non-aliased AND parent restriction attrs ⊆ child PK:
+ Copy parent restriction directly.
+ 2. Aliased FK (attr_map renames columns):
+ ``parent.proj(**{fk: pk for fk, pk in attr_map.items()})``
+ 3. Non-aliased AND parent restriction attrs ⊄ child PK:
+ ``parent.proj()``
+ """
+ child_pk = self.nodes[child_node].get("primary_key", set())
+
+ if not aliased and parent_attrs and parent_attrs <= child_pk:
+ # Rule 1: copy parent restriction directly
+ parent_restr = restrictions.get(
+ parent_ft.full_table_name,
+ [] if mode == "cascade" else AndList(),
+ )
+ if mode == "cascade":
+ restrictions.setdefault(child_node, []).extend(parent_restr)
+ else:
+ restrictions.setdefault(child_node, AndList()).extend(parent_restr)
+ child_attrs = set(parent_attrs)
+ elif aliased:
+ # Rule 2: aliased FK — project with renaming
+ child_item = parent_ft.proj(**{fk: pk for fk, pk in attr_map.items()})
+ if mode == "cascade":
+ restrictions.setdefault(child_node, []).append(child_item)
+ else:
+ restrictions.setdefault(child_node, AndList()).append(child_item)
+ child_attrs = set(attr_map.keys())
+ else:
+ # Rule 3: non-aliased, restriction attrs ⊄ child PK — project
+ child_item = parent_ft.proj()
+ if mode == "cascade":
+ restrictions.setdefault(child_node, []).append(child_item)
+ else:
+ restrictions.setdefault(child_node, AndList()).append(child_item)
+ child_attrs = set(attr_map.values())
+
+ self._restriction_attrs.setdefault(child_node, set()).update(child_attrs)
+
+ def delete(self, transaction=True, prompt=None, dry_run=False):
+ """
+ Execute cascading delete using cascade restrictions.
+
+ Parameters
+ ----------
+ transaction : bool, optional
+ Wrap in a transaction. Default True.
+ prompt : bool or None, optional
+ Show preview and ask confirmation. Default ``dj.config['safemode']``.
+ dry_run : bool, optional
+ If True, return affected row counts without deleting. Default False.
+
+ Returns
+ -------
+ int or dict[str, int]
+ Number of rows deleted from the root table, or (if ``dry_run``)
+ a mapping of full table name to affected row count.
+ """
+ if dry_run:
+ return self.preview()
+
+ prompt = self._connection._config["safemode"] if prompt is None else prompt
+
+ if not self._cascade_restrictions:
+ raise DataJointError("No cascade restrictions applied. Call cascade() first.")
+
+ conn = self._connection
+
+ # Get non-alias nodes with restrictions in topological order
+ all_sorted = topo_sort(self)
+ tables = [t for t in all_sorted if not t.isdigit() and t in self._cascade_restrictions]
+
+ # Preview
+ if prompt:
+ for t in tables:
+ ft = self._restricted_table(t)
+ logger.info("{table} ({count} tuples)".format(table=t, count=len(ft)))
+
+ # Start transaction
+ if transaction:
+ if not conn.in_transaction:
+ conn.start_transaction()
+ else:
+ if not prompt:
+ transaction = False
else:
- # Alias nodes - check if they should be collapsed
- # An alias node should be collapsed if ALL its neighbors are collapsed
- neighbors = set(graph.predecessors(node)) | set(graph.successors(node))
- if neighbors and neighbors <= nodes_to_collapse:
- # Get schema from first neighbor
- neighbor = next(iter(neighbors))
- full_name = class_to_full.get(neighbor)
- if full_name:
- parts = full_name.replace('"', "`").split("`")
- if len(parts) >= 2:
- schema_name = parts[1]
- node_mapping[node] = collapsed_labels[schema_name]
- continue
- node_mapping[node] = node
+ raise DataJointError(
+ "Delete cannot use a transaction within an "
+ "ongoing transaction. Set transaction=False "
+ "or prompt=False."
+ )
+
+ # Execute deletes in reverse topological order (leaves first)
+ root_count = 0
+ deleted_tables = set()
+ try:
+ for table_name in reversed(tables):
+ ft = self._restricted_table(table_name)
+ count = ft.delete_quick(get_count=True)
+ if count > 0:
+ deleted_tables.add(table_name)
+ logger.info("Deleting {count} rows from {table}".format(count=count, table=table_name))
+ if table_name == tables[0]:
+ root_count = count
+ except IntegrityError as error:
+ if transaction:
+ conn.cancel_transaction()
+ match = conn.adapter.parse_foreign_key_error(error.args[0])
+ if match:
+ raise DataJointError(
+ "Delete blocked by table {child} in an unloaded "
+ "schema. Activate all dependent schemas before "
+ "deleting.".format(child=match["child"])
+ ) from None
+ raise DataJointError("Delete blocked by FK in unloaded/inaccessible schema.") from None
+ except:
+ if transaction:
+ conn.cancel_transaction()
+ raise
+
+ # Post-check part_integrity="enforce": roll back if a part table
+ # had rows deleted without its master also having rows deleted.
+ if getattr(self, "_part_integrity", "enforce") == "enforce" and deleted_tables:
+ for table_name in deleted_tables:
+ master = extract_master(table_name)
+ if master and master not in deleted_tables:
+ if transaction:
+ conn.cancel_transaction()
+ raise DataJointError(
+ f"Attempt to delete part table {table_name} before "
+ f"its master {master}. Delete from the master first, "
+ f"or use part_integrity='ignore' or 'cascade'."
+ )
+
+ # Confirm and commit
+ if root_count == 0:
+ if prompt:
+ logger.warning("Nothing to delete.")
+ if transaction:
+ conn.cancel_transaction()
+ elif not transaction:
+ logger.info("Delete completed")
+ else:
+ if not prompt or user_choice("Commit deletes?", default="no") == "yes":
+ if transaction:
+ conn.commit_transaction()
+ if prompt:
+ logger.info("Delete committed.")
+ else:
+ if transaction:
+ conn.cancel_transaction()
+ if prompt:
+ logger.warning("Delete cancelled")
+ root_count = 0
+ return root_count
+
+ def drop(self, prompt=None, part_integrity="enforce", dry_run=False):
+ """
+ Drop all tables in the diagram in reverse topological order.
+
+ Parameters
+ ----------
+ prompt : bool or None, optional
+ Show preview and ask confirmation. Default ``dj.config['safemode']``.
+ part_integrity : str, optional
+ ``"enforce"`` (default) or ``"ignore"``.
+ dry_run : bool, optional
+ If True, return row counts without dropping. Default False.
+
+ Returns
+ -------
+ dict[str, int] or None
+ If ``dry_run``, mapping of full table name to row count.
+ """
+ from .table import FreeTable
+
+ prompt = self._connection._config["safemode"] if prompt is None else prompt
+ conn = self._connection
- # Build reverse mapping: label -> schema_name
- label_to_schema = {label: schema for schema, label in collapsed_labels.items()}
-
- # Add nodes
- added_collapsed = set()
- for old_node, new_node in node_mapping.items():
- if new_node in collapsed_counts:
- # This is a collapsed schema node
- if new_node not in added_collapsed:
- schema_name = label_to_schema.get(new_node, new_node)
- new_graph.add_node(
- new_node,
- node_type=None,
- collapsed=True,
- table_count=collapsed_counts[new_node],
- schema_name=schema_name,
+ tables = [t for t in topo_sort(self) if not t.isdigit() and t in self.nodes_to_show]
+
+ if part_integrity == "enforce":
+ for part in tables:
+ master = extract_master(part)
+ if master and master not in tables:
+ raise DataJointError(
+ "Attempt to drop part table {part} before its " "master {master}. Drop the master first.".format(
+ part=part, master=master
)
- added_collapsed.add(new_node)
- else:
- new_graph.add_node(new_node, **graph.nodes[old_node])
-
- # Add edges (avoiding self-loops and duplicates)
- for src, dest, data in graph.edges(data=True):
- new_src = node_mapping[src]
- new_dest = node_mapping[dest]
- if new_src != new_dest and not new_graph.has_edge(new_src, new_dest):
- new_graph.add_edge(new_src, new_dest, **data)
-
- return new_graph, collapsed_counts
-
- def _resolve_class(self, name: str):
- """
- Safely resolve a table class from a dotted name without eval().
-
- Parameters
- ----------
- name : str
- Dotted class name like "MyTable" or "Module.MyTable".
-
- Returns
- -------
- type or None
- The table class if found, otherwise None.
- """
- parts = name.split(".")
- obj = self.context.get(parts[0])
- for part in parts[1:]:
- if obj is None:
- return None
- obj = getattr(obj, part, None)
- if obj is not None and isinstance(obj, type) and issubclass(obj, Table):
- return obj
- return None
-
- @staticmethod
- def _encapsulate_edge_attributes(graph: nx.DiGraph) -> None:
- """
- Encapsulate edge attr_map in double quotes for pydot compatibility.
-
- Modifies graph in place.
-
- See Also
- --------
- https://github.com/pydot/pydot/issues/258#issuecomment-795798099
- """
- for u, v, *_, edgedata in graph.edges(data=True):
- if "attr_map" in edgedata:
- graph.edges[u, v]["attr_map"] = '"{0}"'.format(edgedata["attr_map"])
-
- @staticmethod
- def _encapsulate_node_names(graph: nx.DiGraph) -> None:
- """
- Encapsulate node names in double quotes for pydot compatibility.
-
- Modifies graph in place.
-
- See Also
- --------
- https://github.com/datajoint/datajoint-python/pull/1176
- """
- nx.relabel_nodes(
- graph,
- {node: '"{0}"'.format(node) for node in graph.nodes()},
- copy=False,
+ )
+
+ if dry_run:
+ result = {}
+ for t in tables:
+ count = len(FreeTable(conn, t))
+ result[t] = count
+ logger.info("{table} ({count} tuples)".format(table=t, count=count))
+ return result
+
+ do_drop = True
+ if prompt:
+ for t in tables:
+ logger.info("{table} ({count} tuples)".format(table=t, count=len(FreeTable(conn, t))))
+ do_drop = user_choice("Proceed?", default="no") == "yes"
+ if do_drop:
+ for t in reversed(tables):
+ FreeTable(conn, t).drop_quick()
+ logger.info("Tables dropped. Restart kernel.")
+
+ def preview(self):
+ """
+ Show affected tables and row counts without modifying data.
+
+ Returns
+ -------
+ dict[str, int]
+ Mapping of full table name to affected row count.
+ """
+ restrictions = self._cascade_restrictions or self._restrict_conditions
+ if not restrictions:
+ raise DataJointError("No restrictions applied. " "Call cascade() or restrict() first.")
+
+ result = {}
+ for node in topo_sort(self):
+ if node.isdigit() or node not in restrictions:
+ continue
+ result[node] = len(self._restricted_table(node))
+
+ for t, count in result.items():
+ logger.info("{table} ({count} tuples)".format(table=t, count=count))
+ return result
+
+ def prune(self):
+ """
+ Remove tables with zero matching rows from the diagram.
+
+ Without prior restrictions, removes physically empty tables.
+ With restrictions (``cascade()`` or ``restrict()``), removes
+ tables where the restricted query yields zero rows.
+
+ Returns
+ -------
+ Diagram
+ New Diagram with empty tables removed.
+ """
+ from .table import FreeTable
+
+ result = Diagram(self)
+ restrictions = result._cascade_restrictions or result._restrict_conditions
+
+ if restrictions:
+ # Restricted: check row counts under restriction
+ for node in list(restrictions):
+ if node.isdigit():
+ continue
+ if len(result._restricted_table(node)) == 0:
+ restrictions.pop(node)
+ result._restriction_attrs.pop(node, None)
+ result.nodes_to_show.discard(node)
+ else:
+ # Unrestricted: check physical row counts
+ for node in list(result.nodes_to_show):
+ if node.isdigit():
+ continue
+ ft = FreeTable(self._connection, node)
+ if len(ft) == 0:
+ result.nodes_to_show.discard(node)
+
+ return result
+
+ def _make_graph(self) -> nx.DiGraph:
+ """
+ Build graph object ready for drawing.
+
+ Returns
+ -------
+ nx.DiGraph
+ Graph with nodes relabeled to class names.
+ """
+ # mark "distinguished" tables, i.e. those that introduce new primary key
+ # attributes
+ # Filter nodes_to_show to only include nodes that exist in the graph
+ valid_nodes = self.nodes_to_show.intersection(set(self.nodes()))
+ for name in valid_nodes:
+ foreign_attributes = set(
+ attr for p in self.in_edges(name, data=True) for attr in p[2]["attr_map"] if p[2]["primary"]
)
+ self.nodes[name]["distinguished"] = (
+ "primary_key" in self.nodes[name] and foreign_attributes < self.nodes[name]["primary_key"]
+ )
+ # include aliased nodes that are sandwiched between two displayed nodes
+ gaps = set(nx.algorithms.boundary.node_boundary(self, valid_nodes)).intersection(
+ nx.algorithms.boundary.node_boundary(nx.DiGraph(self).reverse(), valid_nodes)
+ )
+ nodes = valid_nodes.union(a for a in gaps if a.isdigit())
+ # construct subgraph and rename nodes to class names
+ graph = nx.DiGraph(nx.DiGraph(self).subgraph(nodes))
+ nx.set_node_attributes(graph, name="node_type", values={n: _get_tier(n) for n in graph})
+ # relabel nodes to class names
+ mapping = {node: lookup_class_name(node, self.context) or node for node in graph.nodes()}
+ new_names = list(mapping.values())
+ if len(new_names) > len(set(new_names)):
+ raise DataJointError("Some classes have identical names. The Diagram cannot be plotted.")
+ nx.relabel_nodes(graph, mapping, copy=False)
+ return graph
+
+ def _apply_collapse(self, graph: nx.DiGraph) -> tuple[nx.DiGraph, dict[str, str]]:
+ """
+ Apply collapse logic to the graph.
+
+ Nodes in nodes_to_show but not in _expanded_nodes are collapsed into
+ single schema nodes.
+
+ Parameters
+ ----------
+ graph : nx.DiGraph
+ The graph from _make_graph().
+
+ Returns
+ -------
+ tuple[nx.DiGraph, dict[str, str]]
+ Modified graph and mapping of collapsed schema labels to their table count.
+ """
+ # Filter to valid nodes (those that exist in the underlying graph)
+ valid_nodes = self.nodes_to_show.intersection(set(self.nodes()))
+ valid_expanded = self._expanded_nodes.intersection(set(self.nodes()))
- def make_dot(self):
- """
- Generate a pydot graph object.
-
- Returns
- -------
- pydot.Dot
- The graph object ready for rendering.
-
- Notes
- -----
- Layout direction is controlled via ``dj.config.display.diagram_direction``.
- Tables are grouped by schema, with the Python module name shown as the
- group label when available.
- """
- direction = self._connection._config.display.diagram_direction
- graph = self._make_graph()
-
- # Apply collapse logic if needed
- graph, collapsed_counts = self._apply_collapse(graph)
-
- # Build schema mapping: class_name -> schema_name
- # Group by database schema, label with Python module name if 1:1 mapping
- schema_map = {} # class_name -> schema_name
- schema_modules = {} # schema_name -> set of module names
-
- for full_name in self.nodes_to_show:
- # Extract schema from full table name like `schema`.`table` or "schema"."table"
+ # If all nodes are expanded, no collapse needed
+ if valid_expanded >= valid_nodes:
+ return graph, {}
+
+ # Map full_table_names to class_names
+ full_to_class = {node: lookup_class_name(node, self.context) or node for node in valid_nodes}
+ class_to_full = {v: k for k, v in full_to_class.items()}
+
+ # Identify expanded class names
+ expanded_class_names = {full_to_class.get(node, node) for node in valid_expanded}
+
+ # Identify nodes to collapse (class names)
+ nodes_to_collapse = set(graph.nodes()) - expanded_class_names
+
+ if not nodes_to_collapse:
+ return graph, {}
+
+ # Group collapsed nodes by schema
+ collapsed_by_schema = {} # schema_name -> list of class_names
+ for class_name in nodes_to_collapse:
+ full_name = class_to_full.get(class_name)
+ if full_name:
+ parts = full_name.replace('"', "`").split("`")
+ if len(parts) >= 2:
+ schema_name = parts[1]
+ if schema_name not in collapsed_by_schema:
+ collapsed_by_schema[schema_name] = []
+ collapsed_by_schema[schema_name].append(class_name)
+
+ if not collapsed_by_schema:
+ return graph, {}
+
+ # Determine labels for collapsed schemas
+ schema_modules = {}
+ for schema_name, class_names in collapsed_by_schema.items():
+ schema_modules[schema_name] = set()
+ for class_name in class_names:
+ cls = self._resolve_class(class_name)
+ if cls is not None and hasattr(cls, "__module__"):
+ module_name = cls.__module__.split(".")[-1]
+ schema_modules[schema_name].add(module_name)
+
+ # Collect module names for ALL schemas in the diagram (not just collapsed)
+ all_schema_modules = {} # schema_name -> module_name
+ for node in graph.nodes():
+ full_name = class_to_full.get(node)
+ if full_name:
parts = full_name.replace('"', "`").split("`")
if len(parts) >= 2:
- schema_name = parts[1] # schema is between first pair of backticks
- class_name = lookup_class_name(full_name, self.context) or full_name
- schema_map[class_name] = schema_name
-
- # Collect all module names for this schema
- if schema_name not in schema_modules:
- schema_modules[schema_name] = set()
- cls = self._resolve_class(class_name)
+ db_schema = parts[1]
+ cls = self._resolve_class(node)
if cls is not None and hasattr(cls, "__module__"):
module_name = cls.__module__.split(".")[-1]
- schema_modules[schema_name].add(module_name)
-
- # Determine cluster labels: use module name if 1:1, else database schema name
- cluster_labels = {} # schema_name -> label
- for schema_name, modules in schema_modules.items():
- if len(modules) == 1:
- cluster_labels[schema_name] = next(iter(modules))
- else:
- cluster_labels[schema_name] = schema_name
-
- # Disambiguate labels if multiple schemas share the same module name
- # (e.g., all defined in __main__ in a notebook)
- label_counts = {}
- for label in cluster_labels.values():
- label_counts[label] = label_counts.get(label, 0) + 1
-
- for schema_name, label in cluster_labels.items():
- if label_counts[label] > 1:
- # Multiple schemas share this module name - add schema name
- cluster_labels[schema_name] = f"{label} ({schema_name})"
-
- # Assign alias nodes (orange dots) to the same schema as their child table
- for node, data in graph.nodes(data=True):
- if data.get("node_type") is _AliasNode:
- # Find the child (successor) - the table that declares the renamed FK
- successors = list(graph.successors(node))
- if successors and successors[0] in schema_map:
- schema_map[node] = schema_map[successors[0]]
-
- # Assign collapsed nodes to their schema so they appear in the cluster
- for node, data in graph.nodes(data=True):
- if data.get("collapsed") and data.get("schema_name"):
- schema_map[node] = data["schema_name"]
-
- scale = 1.2 # scaling factor for fonts and boxes
- label_props = { # http://matplotlib.org/examples/color/named_colors.html
- None: dict(
- shape="circle",
- color="#FFFF0040",
- fontcolor="yellow",
- fontsize=round(scale * 8),
- size=0.4 * scale,
- fixed=False,
- ),
- _AliasNode: dict(
- shape="circle",
- color="#FF880080",
- fontcolor="#FF880080",
- fontsize=round(scale * 0),
- size=0.05 * scale,
- fixed=True,
- ),
- Manual: dict(
- shape="box",
- color="#00FF0030",
- fontcolor="darkgreen",
- fontsize=round(scale * 10),
- size=0.4 * scale,
- fixed=False,
- ),
- Lookup: dict(
- shape="plaintext",
- color="#00000020",
- fontcolor="black",
- fontsize=round(scale * 8),
- size=0.4 * scale,
- fixed=False,
- ),
- Computed: dict(
- shape="ellipse",
- color="#FF000020",
- fontcolor="#7F0000A0",
- fontsize=round(scale * 10),
- size=0.4 * scale,
- fixed=False,
- ),
- Imported: dict(
- shape="ellipse",
- color="#00007F40",
- fontcolor="#00007FA0",
- fontsize=round(scale * 10),
- size=0.4 * scale,
- fixed=False,
- ),
- Part: dict(
- shape="plaintext",
- color="#00000000",
- fontcolor="black",
- fontsize=round(scale * 8),
- size=0.1 * scale,
- fixed=False,
- ),
- "collapsed": dict(
- shape="box3d",
- color="#80808060",
- fontcolor="#404040",
- fontsize=round(scale * 10),
- size=0.5 * scale,
- fixed=False,
- ),
- }
- # Build node_props, handling collapsed nodes specially
- node_props = {}
- for node, d in graph.nodes(data=True):
- if d.get("collapsed"):
- node_props[node] = label_props["collapsed"]
+ all_schema_modules[db_schema] = module_name
+
+ # Check which module names are shared by multiple schemas
+ module_to_schemas = {}
+ for db_schema, module_name in all_schema_modules.items():
+ if module_name not in module_to_schemas:
+ module_to_schemas[module_name] = []
+ module_to_schemas[module_name].append(db_schema)
+
+ ambiguous_modules = {m for m, schemas in module_to_schemas.items() if len(schemas) > 1}
+
+ # Determine labels for collapsed schemas
+ collapsed_labels = {} # schema_name -> label
+ for schema_name, modules in schema_modules.items():
+ if len(modules) == 1:
+ module_name = next(iter(modules))
+ # Use database schema name if module is ambiguous
+ if module_name in ambiguous_modules:
+ label = schema_name
else:
- node_props[node] = label_props[d["node_type"]]
-
- self._encapsulate_node_names(graph)
- self._encapsulate_edge_attributes(graph)
- dot = nx.drawing.nx_pydot.to_pydot(graph)
- dot.set_rankdir(direction)
- for node in dot.get_nodes():
- node.set_shape("circle")
- name = node.get_name().strip('"')
- props = node_props[name]
- node.set_fontsize(props["fontsize"])
- node.set_fontcolor(props["fontcolor"])
- node.set_shape(props["shape"])
- node.set_fontname("arial")
- node.set_fixedsize("shape" if props["fixed"] else False)
- node.set_width(props["size"])
- node.set_height(props["size"])
-
- # Handle collapsed nodes specially
- node_data = graph.nodes.get(f'"{name}"', {})
- if node_data.get("collapsed"):
- table_count = node_data.get("table_count", 0)
- label = f"({table_count} tables)" if table_count != 1 else "(1 table)"
- node.set_label(label)
- node.set_tooltip(f"Collapsed schema: {table_count} tables")
+ label = module_name
+ else:
+ label = schema_name
+ collapsed_labels[schema_name] = label
+
+ # Build counts using final labels
+ collapsed_counts = {} # label -> count of tables
+ for schema_name, class_names in collapsed_by_schema.items():
+ label = collapsed_labels[schema_name]
+ collapsed_counts[label] = len(class_names)
+
+ # Create new graph with collapsed nodes
+ new_graph = nx.DiGraph()
+
+ # Map old node names to new names (collapsed nodes -> schema label)
+ node_mapping = {}
+ for node in graph.nodes():
+ full_name = class_to_full.get(node)
+ if full_name:
+ parts = full_name.replace('"', "`").split("`")
+ if len(parts) >= 2 and node in nodes_to_collapse:
+ schema_name = parts[1]
+ node_mapping[node] = collapsed_labels[schema_name]
else:
- cls = self._resolve_class(name)
- if cls is not None:
- description = cls().describe(context=self.context).split("\n")
- description = (
- (
- "-" * 30
- if q.startswith("---")
- else (q.replace("->", "→") if "->" in q else q.split(":")[0])
- )
- for q in description
- if not q.startswith("#")
- )
- node.set_tooltip("
".join(description))
- # Strip module prefix from label if it matches the cluster label
- display_name = name
- schema_name = schema_map.get(name)
- if schema_name and "." in name:
- cluster_label = cluster_labels.get(schema_name)
- if cluster_label and name.startswith(cluster_label + "."):
- display_name = name[len(cluster_label) + 1 :]
- node.set_label("<" + display_name + ">" if node.get("distinguished") == "True" else display_name)
- node.set_color(props["color"])
- node.set_style("filled")
-
- for edge in dot.get_edges():
- # see https://graphviz.org/doc/info/attrs.html
- src = edge.get_source()
- dest = edge.get_destination()
- props = graph.get_edge_data(src, dest)
- if props is None:
- raise DataJointError("Could not find edge with source '{}' and destination '{}'".format(src, dest))
- edge.set_color("#00000040")
- edge.set_style("solid" if props.get("primary") else "dashed")
- dest_node_type = graph.nodes[dest].get("node_type")
- master_part = dest_node_type is Part and dest.startswith(src + ".")
- edge.set_weight(3 if master_part else 1)
- edge.set_arrowhead("none")
- edge.set_penwidth(0.75 if props.get("multi") else 2)
-
- # Group nodes into schema clusters (always on)
- if schema_map:
- import pydot
-
- # Group nodes by schema
- schemas = {}
- for node in list(dot.get_nodes()):
- name = node.get_name().strip('"')
- schema_name = schema_map.get(name)
- if schema_name:
- if schema_name not in schemas:
- schemas[schema_name] = []
- schemas[schema_name].append(node)
-
- # Create clusters for each schema
- # Use Python module name if 1:1 mapping, otherwise database schema name
- for schema_name, nodes in schemas.items():
- label = cluster_labels.get(schema_name, schema_name)
- cluster = pydot.Cluster(
- f"cluster_{schema_name}",
- label=label,
- style="dashed",
- color="gray",
- fontcolor="gray",
+ node_mapping[node] = node
+ else:
+ # Alias nodes - check if they should be collapsed
+ # An alias node should be collapsed if ALL its neighbors are collapsed
+ neighbors = set(graph.predecessors(node)) | set(graph.successors(node))
+ if neighbors and neighbors <= nodes_to_collapse:
+ # Get schema from first neighbor
+ neighbor = next(iter(neighbors))
+ full_name = class_to_full.get(neighbor)
+ if full_name:
+ parts = full_name.replace('"', "`").split("`")
+ if len(parts) >= 2:
+ schema_name = parts[1]
+ node_mapping[node] = collapsed_labels[schema_name]
+ continue
+ node_mapping[node] = node
+
+ # Build reverse mapping: label -> schema_name
+ label_to_schema = {label: schema for schema, label in collapsed_labels.items()}
+
+ # Add nodes
+ added_collapsed = set()
+ for old_node, new_node in node_mapping.items():
+ if new_node in collapsed_counts:
+ # This is a collapsed schema node
+ if new_node not in added_collapsed:
+ schema_name = label_to_schema.get(new_node, new_node)
+ new_graph.add_node(
+ new_node,
+ node_type=None,
+ collapsed=True,
+ table_count=collapsed_counts[new_node],
+ schema_name=schema_name,
)
- for node in nodes:
- cluster.add_node(node)
- dot.add_subgraph(cluster)
+ added_collapsed.add(new_node)
+ else:
+ new_graph.add_node(new_node, **graph.nodes[old_node])
+
+ # Add edges (avoiding self-loops and duplicates)
+ for src, dest, data in graph.edges(data=True):
+ new_src = node_mapping[src]
+ new_dest = node_mapping[dest]
+ if new_src != new_dest and not new_graph.has_edge(new_src, new_dest):
+ new_graph.add_edge(new_src, new_dest, **data)
+
+ return new_graph, collapsed_counts
+
+ def _resolve_class(self, name: str):
+ """
+ Safely resolve a table class from a dotted name without eval().
- return dot
+ Parameters
+ ----------
+ name : str
+ Dotted class name like "MyTable" or "Module.MyTable".
- def make_svg(self):
- from IPython.display import SVG
+ Returns
+ -------
+ type or None
+ The table class if found, otherwise None.
+ """
+ parts = name.split(".")
+ obj = self.context.get(parts[0])
+ for part in parts[1:]:
+ if obj is None:
+ return None
+ obj = getattr(obj, part, None)
+ if obj is not None and isinstance(obj, type) and issubclass(obj, Table):
+ return obj
+ return None
+
+ @staticmethod
+ def _encapsulate_edge_attributes(graph: nx.DiGraph) -> None:
+ """
+ Encapsulate edge attr_map in double quotes for pydot compatibility.
- return SVG(self.make_dot().create_svg())
+ Modifies graph in place.
- def make_png(self):
- return io.BytesIO(self.make_dot().create_png())
+ See Also
+ --------
+ https://github.com/pydot/pydot/issues/258#issuecomment-795798099
+ """
+ for u, v, *_, edgedata in graph.edges(data=True):
+ if "attr_map" in edgedata:
+ graph.edges[u, v]["attr_map"] = '"{0}"'.format(edgedata["attr_map"])
- def make_image(self):
- if plot_active:
- return plt.imread(self.make_png())
- else:
- raise DataJointError("pyplot was not imported")
-
- def make_mermaid(self) -> str:
- """
- Generate Mermaid diagram syntax.
-
- Produces a flowchart in Mermaid syntax that can be rendered in
- Markdown documentation, GitHub, or https://mermaid.live.
-
- Returns
- -------
- str
- Mermaid flowchart syntax.
-
- Notes
- -----
- Layout direction is controlled via ``dj.config.display.diagram_direction``.
- Tables are grouped by schema using Mermaid subgraphs, with the Python
- module name shown as the group label when available.
-
- Examples
- --------
- >>> print(dj.Diagram(schema).make_mermaid())
- flowchart TB
- subgraph my_pipeline
- Mouse[Mouse]:::manual
- Session[Session]:::manual
- Neuron([Neuron]):::computed
- end
- Mouse --> Session
- Session --> Neuron
- """
- graph = self._make_graph()
- direction = self._connection._config.display.diagram_direction
-
- # Apply collapse logic if needed
- graph, collapsed_counts = self._apply_collapse(graph)
-
- # Build schema mapping for grouping
- schema_map = {} # class_name -> schema_name
- schema_modules = {} # schema_name -> set of module names
-
- for full_name in self.nodes_to_show:
- parts = full_name.replace('"', "`").split("`")
- if len(parts) >= 2:
- schema_name = parts[1]
- class_name = lookup_class_name(full_name, self.context) or full_name
- schema_map[class_name] = schema_name
+ @staticmethod
+ def _encapsulate_node_names(graph: nx.DiGraph) -> None:
+ """
+ Encapsulate node names in double quotes for pydot compatibility.
- # Collect all module names for this schema
- if schema_name not in schema_modules:
- schema_modules[schema_name] = set()
- cls = self._resolve_class(class_name)
- if cls is not None and hasattr(cls, "__module__"):
- module_name = cls.__module__.split(".")[-1]
- schema_modules[schema_name].add(module_name)
+ Modifies graph in place.
- # Determine cluster labels: use module name if 1:1, else database schema name
- cluster_labels = {}
- for schema_name, modules in schema_modules.items():
- if len(modules) == 1:
- cluster_labels[schema_name] = next(iter(modules))
- else:
- cluster_labels[schema_name] = schema_name
-
- # Assign alias nodes to the same schema as their child table
- for node, data in graph.nodes(data=True):
- if data.get("node_type") is _AliasNode:
- successors = list(graph.successors(node))
- if successors and successors[0] in schema_map:
- schema_map[node] = schema_map[successors[0]]
-
- lines = [f"flowchart {direction}"]
-
- # Define class styles matching Graphviz colors
- lines.append(" classDef manual fill:#90EE90,stroke:#006400")
- lines.append(" classDef lookup fill:#D3D3D3,stroke:#696969")
- lines.append(" classDef computed fill:#FFB6C1,stroke:#8B0000")
- lines.append(" classDef imported fill:#ADD8E6,stroke:#00008B")
- lines.append(" classDef part fill:#FFFFFF,stroke:#000000")
- lines.append(" classDef collapsed fill:#808080,stroke:#404040")
- lines.append("")
-
- # Shape mapping: Manual=box, Computed/Imported=stadium, Lookup/Part=box
- shape_map = {
- Manual: ("[", "]"), # box
- Lookup: ("[", "]"), # box
- Computed: ("([", "])"), # stadium/pill
- Imported: ("([", "])"), # stadium/pill
- Part: ("[", "]"), # box
- _AliasNode: ("((", "))"), # circle
- None: ("((", "))"), # circle
- }
-
- tier_class = {
- Manual: "manual",
- Lookup: "lookup",
- Computed: "computed",
- Imported: "imported",
- Part: "part",
- _AliasNode: "",
- None: "",
- }
-
- # Group nodes by schema into subgraphs (including collapsed nodes)
+ See Also
+ --------
+ https://github.com/datajoint/datajoint-python/pull/1176
+ """
+ nx.relabel_nodes(
+ graph,
+ {node: '"{0}"'.format(node) for node in graph.nodes()},
+ copy=False,
+ )
+
+ def make_dot(self):
+ """
+ Generate a pydot graph object.
+
+ Returns
+ -------
+ pydot.Dot
+ The graph object ready for rendering.
+
+ Raises
+ ------
+ DataJointError
+ If pygraphviz/pydot is not installed.
+
+ Notes
+ -----
+ Layout direction is controlled via ``dj.config.display.diagram_direction``.
+ Tables are grouped by schema, with the Python module name shown as the
+ group label when available.
+ """
+ if not diagram_active:
+ raise DataJointError("Install pygraphviz and pydot libraries to enable diagram visualization.")
+ direction = self._connection._config.display.diagram_direction
+ graph = self._make_graph()
+
+ # Apply collapse logic if needed
+ graph, collapsed_counts = self._apply_collapse(graph)
+
+ # Build schema mapping: class_name -> schema_name
+ # Group by database schema, label with Python module name if 1:1 mapping
+ schema_map = {} # class_name -> schema_name
+ schema_modules = {} # schema_name -> set of module names
+
+ for full_name in self.nodes_to_show:
+ # Extract schema from full table name like `schema`.`table` or "schema"."table"
+ parts = full_name.replace('"', "`").split("`")
+ if len(parts) >= 2:
+ schema_name = parts[1] # schema is between first pair of backticks
+ class_name = lookup_class_name(full_name, self.context) or full_name
+ schema_map[class_name] = schema_name
+
+ # Collect all module names for this schema
+ if schema_name not in schema_modules:
+ schema_modules[schema_name] = set()
+ cls = self._resolve_class(class_name)
+ if cls is not None and hasattr(cls, "__module__"):
+ module_name = cls.__module__.split(".")[-1]
+ schema_modules[schema_name].add(module_name)
+
+ # Determine cluster labels: use module name if 1:1, else database schema name
+ cluster_labels = {} # schema_name -> label
+ for schema_name, modules in schema_modules.items():
+ if len(modules) == 1:
+ cluster_labels[schema_name] = next(iter(modules))
+ else:
+ cluster_labels[schema_name] = schema_name
+
+ # Disambiguate labels if multiple schemas share the same module name
+ # (e.g., all defined in __main__ in a notebook)
+ label_counts = {}
+ for label in cluster_labels.values():
+ label_counts[label] = label_counts.get(label, 0) + 1
+
+ for schema_name, label in cluster_labels.items():
+ if label_counts[label] > 1:
+ # Multiple schemas share this module name - add schema name
+ cluster_labels[schema_name] = f"{label} ({schema_name})"
+
+ # Assign alias nodes (orange dots) to the same schema as their child table
+ for node, data in graph.nodes(data=True):
+ if data.get("node_type") is _AliasNode:
+ # Find the child (successor) - the table that declares the renamed FK
+ successors = list(graph.successors(node))
+ if successors and successors[0] in schema_map:
+ schema_map[node] = schema_map[successors[0]]
+
+ # Assign collapsed nodes to their schema so they appear in the cluster
+ for node, data in graph.nodes(data=True):
+ if data.get("collapsed") and data.get("schema_name"):
+ schema_map[node] = data["schema_name"]
+
+ scale = 1.2 # scaling factor for fonts and boxes
+ label_props = { # http://matplotlib.org/examples/color/named_colors.html
+ None: dict(
+ shape="circle",
+ color="#FFFF0040",
+ fontcolor="yellow",
+ fontsize=round(scale * 8),
+ size=0.4 * scale,
+ fixed=False,
+ ),
+ _AliasNode: dict(
+ shape="circle",
+ color="#FF880080",
+ fontcolor="#FF880080",
+ fontsize=round(scale * 0),
+ size=0.05 * scale,
+ fixed=True,
+ ),
+ Manual: dict(
+ shape="box",
+ color="#00FF0030",
+ fontcolor="darkgreen",
+ fontsize=round(scale * 10),
+ size=0.4 * scale,
+ fixed=False,
+ ),
+ Lookup: dict(
+ shape="plaintext",
+ color="#00000020",
+ fontcolor="black",
+ fontsize=round(scale * 8),
+ size=0.4 * scale,
+ fixed=False,
+ ),
+ Computed: dict(
+ shape="ellipse",
+ color="#FF000020",
+ fontcolor="#7F0000A0",
+ fontsize=round(scale * 10),
+ size=0.4 * scale,
+ fixed=False,
+ ),
+ Imported: dict(
+ shape="ellipse",
+ color="#00007F40",
+ fontcolor="#00007FA0",
+ fontsize=round(scale * 10),
+ size=0.4 * scale,
+ fixed=False,
+ ),
+ Part: dict(
+ shape="plaintext",
+ color="#00000000",
+ fontcolor="black",
+ fontsize=round(scale * 8),
+ size=0.1 * scale,
+ fixed=False,
+ ),
+ "collapsed": dict(
+ shape="box3d",
+ color="#80808060",
+ fontcolor="#404040",
+ fontsize=round(scale * 10),
+ size=0.5 * scale,
+ fixed=False,
+ ),
+ }
+ # Build node_props, handling collapsed nodes specially
+ node_props = {}
+ for node, d in graph.nodes(data=True):
+ if d.get("collapsed"):
+ node_props[node] = label_props["collapsed"]
+ else:
+ node_props[node] = label_props[d["node_type"]]
+
+ self._encapsulate_node_names(graph)
+ self._encapsulate_edge_attributes(graph)
+ dot = nx.drawing.nx_pydot.to_pydot(graph)
+ dot.set_rankdir(direction)
+ for node in dot.get_nodes():
+ node.set_shape("circle")
+ name = node.get_name().strip('"')
+ props = node_props[name]
+ node.set_fontsize(props["fontsize"])
+ node.set_fontcolor(props["fontcolor"])
+ node.set_shape(props["shape"])
+ node.set_fontname("arial")
+ node.set_fixedsize("shape" if props["fixed"] else False)
+ node.set_width(props["size"])
+ node.set_height(props["size"])
+
+ # Handle collapsed nodes specially
+ node_data = graph.nodes.get(f'"{name}"', {})
+ if node_data.get("collapsed"):
+ table_count = node_data.get("table_count", 0)
+ label = f"({table_count} tables)" if table_count != 1 else "(1 table)"
+ node.set_label(label)
+ node.set_tooltip(f"Collapsed schema: {table_count} tables")
+ else:
+ cls = self._resolve_class(name)
+ if cls is not None:
+ description = cls().describe(context=self.context).split("\n")
+ description = (
+ ("-" * 30 if q.startswith("---") else (q.replace("->", "→") if "->" in q else q.split(":")[0]))
+ for q in description
+ if not q.startswith("#")
+ )
+ node.set_tooltip("
".join(description))
+ # Strip module prefix from label if it matches the cluster label
+ display_name = name
+ schema_name = schema_map.get(name)
+ if schema_name and "." in name:
+ cluster_label = cluster_labels.get(schema_name)
+ if cluster_label and name.startswith(cluster_label + "."):
+ display_name = name[len(cluster_label) + 1 :]
+ node.set_label("<" + display_name + ">" if node.get("distinguished") == "True" else display_name)
+ node.set_color(props["color"])
+ node.set_style("filled")
+
+ for edge in dot.get_edges():
+ # see https://graphviz.org/doc/info/attrs.html
+ src = edge.get_source()
+ dest = edge.get_destination()
+ props = graph.get_edge_data(src, dest)
+ if props is None:
+ raise DataJointError("Could not find edge with source '{}' and destination '{}'".format(src, dest))
+ edge.set_color("#00000040")
+ edge.set_style("solid" if props.get("primary") else "dashed")
+ dest_node_type = graph.nodes[dest].get("node_type")
+ master_part = dest_node_type is Part and dest.startswith(src + ".")
+ edge.set_weight(3 if master_part else 1)
+ edge.set_arrowhead("none")
+ edge.set_penwidth(0.75 if props.get("multi") else 2)
+
+ # Group nodes into schema clusters (always on)
+ if schema_map:
+ import pydot
+
+ # Group nodes by schema
schemas = {}
- for node, data in graph.nodes(data=True):
- if data.get("collapsed"):
- # Collapsed nodes use their schema_name attribute
- schema_name = data.get("schema_name")
- else:
- schema_name = schema_map.get(node)
+ for node in list(dot.get_nodes()):
+ name = node.get_name().strip('"')
+ schema_name = schema_map.get(name)
if schema_name:
if schema_name not in schemas:
schemas[schema_name] = []
- schemas[schema_name].append((node, data))
+ schemas[schema_name].append(node)
- # Add nodes grouped by schema subgraphs
+ # Create clusters for each schema
+ # Use Python module name if 1:1 mapping, otherwise database schema name
for schema_name, nodes in schemas.items():
label = cluster_labels.get(schema_name, schema_name)
- lines.append(f" subgraph {label}")
- for node, data in nodes:
- safe_id = node.replace(".", "_").replace(" ", "_")
- if data.get("collapsed"):
- # Collapsed node - show only table count
- table_count = data.get("table_count", 0)
- count_text = f"{table_count} tables" if table_count != 1 else "1 table"
- lines.append(f' {safe_id}[["({count_text})"]]:::collapsed')
- else:
- # Regular node
- tier = data.get("node_type")
- left, right = shape_map.get(tier, ("[", "]"))
- cls = tier_class.get(tier, "")
- # Strip module prefix from display name if it matches the cluster label
- display_name = node
- if "." in node and node.startswith(label + "."):
- display_name = node[len(label) + 1 :]
- class_suffix = f":::{cls}" if cls else ""
- lines.append(f" {safe_id}{left}{display_name}{right}{class_suffix}")
- lines.append(" end")
-
- lines.append("")
-
- # Add edges
- for src, dest, data in graph.edges(data=True):
- safe_src = src.replace(".", "_").replace(" ", "_")
- safe_dest = dest.replace(".", "_").replace(" ", "_")
- # Solid arrow for primary FK, dotted for non-primary
- style = "-->" if data.get("primary") else "-.->"
- lines.append(f" {safe_src} {style} {safe_dest}")
-
- return "\n".join(lines)
-
- def _repr_svg_(self):
- return self.make_svg()._repr_svg_()
-
- def draw(self):
- if plot_active:
- plt.imshow(self.make_image())
- plt.gca().axis("off")
- plt.show()
+ cluster = pydot.Cluster(
+ f"cluster_{schema_name}",
+ label=label,
+ style="dashed",
+ color="gray",
+ fontcolor="gray",
+ )
+ for node in nodes:
+ cluster.add_node(node)
+ dot.add_subgraph(cluster)
+
+ return dot
+
+ def make_svg(self):
+ from IPython.display import SVG
+
+ return SVG(self.make_dot().create_svg())
+
+ def make_png(self):
+ return io.BytesIO(self.make_dot().create_png())
+
+ def make_image(self):
+ if plot_active:
+ return plt.imread(self.make_png())
+ else:
+ raise DataJointError("pyplot was not imported")
+
+ def make_mermaid(self) -> str:
+ """
+ Generate Mermaid diagram syntax.
+
+ Produces a flowchart in Mermaid syntax that can be rendered in
+ Markdown documentation, GitHub, or https://mermaid.live.
+
+ Returns
+ -------
+ str
+ Mermaid flowchart syntax.
+
+ Notes
+ -----
+ Layout direction is controlled via ``dj.config.display.diagram_direction``.
+ Tables are grouped by schema using Mermaid subgraphs, with the Python
+ module name shown as the group label when available.
+
+ Examples
+ --------
+ >>> print(dj.Diagram(schema).make_mermaid())
+ flowchart TB
+ subgraph my_pipeline
+ Mouse[Mouse]:::manual
+ Session[Session]:::manual
+ Neuron([Neuron]):::computed
+ end
+ Mouse --> Session
+ Session --> Neuron
+ """
+ graph = self._make_graph()
+ direction = self._connection._config.display.diagram_direction
+
+ # Apply collapse logic if needed
+ graph, collapsed_counts = self._apply_collapse(graph)
+
+ # Build schema mapping for grouping
+ schema_map = {} # class_name -> schema_name
+ schema_modules = {} # schema_name -> set of module names
+
+ for full_name in self.nodes_to_show:
+ parts = full_name.replace('"', "`").split("`")
+ if len(parts) >= 2:
+ schema_name = parts[1]
+ class_name = lookup_class_name(full_name, self.context) or full_name
+ schema_map[class_name] = schema_name
+
+ # Collect all module names for this schema
+ if schema_name not in schema_modules:
+ schema_modules[schema_name] = set()
+ cls = self._resolve_class(class_name)
+ if cls is not None and hasattr(cls, "__module__"):
+ module_name = cls.__module__.split(".")[-1]
+ schema_modules[schema_name].add(module_name)
+
+ # Determine cluster labels: use module name if 1:1, else database schema name
+ cluster_labels = {}
+ for schema_name, modules in schema_modules.items():
+ if len(modules) == 1:
+ cluster_labels[schema_name] = next(iter(modules))
else:
- raise DataJointError("pyplot was not imported")
-
- def save(self, filename: str, format: str | None = None) -> None:
- """
- Save diagram to file.
-
- Parameters
- ----------
- filename : str
- Output filename.
- format : str, optional
- File format (``'png'``, ``'svg'``, or ``'mermaid'``).
- Inferred from extension if None.
-
- Raises
- ------
- DataJointError
- If format is unsupported.
-
- Notes
- -----
- Layout direction is controlled via ``dj.config.display.diagram_direction``.
- Tables are grouped by schema, with the Python module name shown as the
- group label when available.
- """
- if format is None:
- if filename.lower().endswith(".png"):
- format = "png"
- elif filename.lower().endswith(".svg"):
- format = "svg"
- elif filename.lower().endswith((".mmd", ".mermaid")):
- format = "mermaid"
- if format is None:
- raise DataJointError("Could not infer format from filename. Specify format explicitly.")
- if format.lower() == "png":
- with open(filename, "wb") as f:
- f.write(self.make_png().getbuffer().tobytes())
- elif format.lower() == "svg":
- with open(filename, "w") as f:
- f.write(self.make_svg().data)
- elif format.lower() == "mermaid":
- with open(filename, "w") as f:
- f.write(self.make_mermaid())
+ cluster_labels[schema_name] = schema_name
+
+ # Assign alias nodes to the same schema as their child table
+ for node, data in graph.nodes(data=True):
+ if data.get("node_type") is _AliasNode:
+ successors = list(graph.successors(node))
+ if successors and successors[0] in schema_map:
+ schema_map[node] = schema_map[successors[0]]
+
+ lines = [f"flowchart {direction}"]
+
+ # Define class styles matching Graphviz colors
+ lines.append(" classDef manual fill:#90EE90,stroke:#006400")
+ lines.append(" classDef lookup fill:#D3D3D3,stroke:#696969")
+ lines.append(" classDef computed fill:#FFB6C1,stroke:#8B0000")
+ lines.append(" classDef imported fill:#ADD8E6,stroke:#00008B")
+ lines.append(" classDef part fill:#FFFFFF,stroke:#000000")
+ lines.append(" classDef collapsed fill:#808080,stroke:#404040")
+ lines.append("")
+
+ # Shape mapping: Manual=box, Computed/Imported=stadium, Lookup/Part=box
+ shape_map = {
+ Manual: ("[", "]"), # box
+ Lookup: ("[", "]"), # box
+ Computed: ("([", "])"), # stadium/pill
+ Imported: ("([", "])"), # stadium/pill
+ Part: ("[", "]"), # box
+ _AliasNode: ("((", "))"), # circle
+ None: ("((", "))"), # circle
+ }
+
+ tier_class = {
+ Manual: "manual",
+ Lookup: "lookup",
+ Computed: "computed",
+ Imported: "imported",
+ Part: "part",
+ _AliasNode: "",
+ None: "",
+ }
+
+ # Group nodes by schema into subgraphs (including collapsed nodes)
+ schemas = {}
+ for node, data in graph.nodes(data=True):
+ if data.get("collapsed"):
+ # Collapsed nodes use their schema_name attribute
+ schema_name = data.get("schema_name")
else:
- raise DataJointError("Unsupported file format")
+ schema_name = schema_map.get(node)
+ if schema_name:
+ if schema_name not in schemas:
+ schemas[schema_name] = []
+ schemas[schema_name].append((node, data))
+
+ # Add nodes grouped by schema subgraphs
+ for schema_name, nodes in schemas.items():
+ label = cluster_labels.get(schema_name, schema_name)
+ lines.append(f" subgraph {label}")
+ for node, data in nodes:
+ safe_id = node.replace(".", "_").replace(" ", "_")
+ if data.get("collapsed"):
+ # Collapsed node - show only table count
+ table_count = data.get("table_count", 0)
+ count_text = f"{table_count} tables" if table_count != 1 else "1 table"
+ lines.append(f' {safe_id}[["({count_text})"]]:::collapsed')
+ else:
+ # Regular node
+ tier = data.get("node_type")
+ left, right = shape_map.get(tier, ("[", "]"))
+ cls = tier_class.get(tier, "")
+ # Strip module prefix from display name if it matches the cluster label
+ display_name = node
+ if "." in node and node.startswith(label + "."):
+ display_name = node[len(label) + 1 :]
+ class_suffix = f":::{cls}" if cls else ""
+ lines.append(f" {safe_id}{left}{display_name}{right}{class_suffix}")
+ lines.append(" end")
+
+ lines.append("")
+
+ # Add edges
+ for src, dest, data in graph.edges(data=True):
+ safe_src = src.replace(".", "_").replace(" ", "_")
+ safe_dest = dest.replace(".", "_").replace(" ", "_")
+ # Solid arrow for primary FK, dotted for non-primary
+ style = "-->" if data.get("primary") else "-.->"
+ lines.append(f" {safe_src} {style} {safe_dest}")
+
+ return "\n".join(lines)
+
+ def _repr_svg_(self):
+ return self.make_svg()._repr_svg_()
+
+ def draw(self):
+ if plot_active:
+ plt.imshow(self.make_image())
+ plt.gca().axis("off")
+ plt.show()
+ else:
+ raise DataJointError("pyplot was not imported")
+
+ def save(self, filename: str, format: str | None = None) -> None:
+ """
+ Save diagram to file.
- @staticmethod
- def _layout(graph, **kwargs):
- return pydot_layout(graph, prog="dot", **kwargs)
+ Parameters
+ ----------
+ filename : str
+ Output filename.
+ format : str, optional
+ File format (``'png'``, ``'svg'``, or ``'mermaid'``).
+ Inferred from extension if None.
+
+ Raises
+ ------
+ DataJointError
+ If format is unsupported.
+
+ Notes
+ -----
+ Layout direction is controlled via ``dj.config.display.diagram_direction``.
+ Tables are grouped by schema, with the Python module name shown as the
+ group label when available.
+ """
+ if format is None:
+ if filename.lower().endswith(".png"):
+ format = "png"
+ elif filename.lower().endswith(".svg"):
+ format = "svg"
+ elif filename.lower().endswith((".mmd", ".mermaid")):
+ format = "mermaid"
+ if format is None:
+ raise DataJointError("Could not infer format from filename. Specify format explicitly.")
+ if format.lower() == "png":
+ with open(filename, "wb") as f:
+ f.write(self.make_png().getbuffer().tobytes())
+ elif format.lower() == "svg":
+ with open(filename, "w") as f:
+ f.write(self.make_svg().data)
+ elif format.lower() == "mermaid":
+ with open(filename, "w") as f:
+ f.write(self.make_mermaid())
+ else:
+ raise DataJointError("Unsupported file format")
+
+ @staticmethod
+ def _layout(graph, **kwargs):
+ return pydot_layout(graph, prog="dot", **kwargs)
diff --git a/src/datajoint/hash_registry.py b/src/datajoint/hash_registry.py
index 331c836cd..00ed35386 100644
--- a/src/datajoint/hash_registry.py
+++ b/src/datajoint/hash_registry.py
@@ -130,7 +130,7 @@ def build_hash_path(
return f"_hash/{schema_name}/{content_hash}"
-def get_store_backend(store_name: str | None = None, config=None) -> StorageBackend:
+def get_store_backend(store_name: str | None = None, config: Any = None) -> StorageBackend:
"""
Get a StorageBackend for hash-addressed storage.
@@ -153,7 +153,7 @@ def get_store_backend(store_name: str | None = None, config=None) -> StorageBack
return StorageBackend(spec)
-def get_store_subfolding(store_name: str | None = None, config=None) -> tuple[int, ...] | None:
+def get_store_subfolding(store_name: str | None = None, config: Any = None) -> tuple[int, ...] | None:
"""
Get the subfolding configuration for a store.
@@ -182,7 +182,7 @@ def put_hash(
data: bytes,
schema_name: str,
store_name: str | None = None,
- config=None,
+ config: Any = None,
) -> dict[str, Any]:
"""
Store content using hash-addressed storage.
@@ -231,7 +231,7 @@ def put_hash(
}
-def get_hash(metadata: dict[str, Any], config=None) -> bytes:
+def get_hash(metadata: dict[str, Any], config: Any = None) -> bytes:
"""
Retrieve content using stored metadata.
@@ -275,7 +275,7 @@ def get_hash(metadata: dict[str, Any], config=None) -> bytes:
def delete_path(
path: str,
store_name: str | None = None,
- config=None,
+ config: Any = None,
) -> bool:
"""
Delete content at the specified path from storage.
diff --git a/src/datajoint/table.py b/src/datajoint/table.py
index 256fab6e9..6907cc7c4 100644
--- a/src/datajoint/table.py
+++ b/src/datajoint/table.py
@@ -18,13 +18,12 @@
AccessError,
DataJointError,
DuplicateError,
- IntegrityError,
UnknownAttributeError,
)
from .expression import QueryExpression
from .heading import Heading
from .staged_insert import staged_insert1 as _staged_insert1
-from .utils import get_master, is_camel_case, user_choice
+from .utils import is_camel_case, user_choice
logger = logging.getLogger(__name__.split(".")[0])
@@ -974,10 +973,15 @@ def delete(
transaction: bool = True,
prompt: bool | None = None,
part_integrity: str = "enforce",
- ) -> int:
+ dry_run: bool = False,
+ ) -> int | dict[str, int]:
"""
Deletes the contents of the table and its dependent tables, recursively.
+ Uses graph-driven cascade: builds a dependency diagram, propagates
+ restrictions to all descendants, then deletes in reverse topological
+ order (leaves first).
+
Args:
transaction: If `True`, use of the entire delete becomes an atomic transaction.
This is the default and recommended behavior. Set to `False` if this delete is
@@ -988,187 +992,25 @@ def delete(
- ``"enforce"`` (default): Error if parts would be deleted without masters.
- ``"ignore"``: Allow deleting parts without masters (breaks integrity).
- ``"cascade"``: Also delete masters when parts are deleted (maintains integrity).
+ dry_run: If `True`, return a dict mapping full table names to affected
+ row counts without deleting any data. Default False.
Returns:
- Number of deleted rows (excluding those from dependent tables).
+ Number of deleted rows (excluding those from dependent tables), or
+ (if ``dry_run``) a dict mapping full table name to affected row count.
Raises:
- DataJointError: Delete exceeds maximum number of delete attempts.
DataJointError: When deleting within an existing transaction.
DataJointError: Deleting a part table before its master (when part_integrity="enforce").
ValueError: Invalid part_integrity value.
"""
if part_integrity not in ("enforce", "ignore", "cascade"):
- raise ValueError(f"part_integrity must be 'enforce', 'ignore', or 'cascade', got {part_integrity!r}")
- deleted = set()
- visited_masters = set()
-
- def cascade(table):
- """service function to perform cascading deletes recursively."""
- max_attempts = 50
- for _ in range(max_attempts):
- # Set savepoint before delete attempt (for PostgreSQL transaction handling)
- savepoint_name = f"cascade_delete_{id(table)}"
- if transaction:
- table.connection.query(f"SAVEPOINT {savepoint_name}")
-
- try:
- delete_count = table.delete_quick(get_count=True)
- except IntegrityError as error:
- # Rollback to savepoint so we can continue querying (PostgreSQL requirement)
- if transaction:
- table.connection.query(f"ROLLBACK TO SAVEPOINT {savepoint_name}")
- # Use adapter to parse FK error message
- match = table.connection.adapter.parse_foreign_key_error(error.args[0])
- if match is None:
- raise DataJointError(
- "Cascading deletes failed because the error message is missing foreign key information. "
- "Make sure you have REFERENCES privilege to all dependent tables."
- ) from None
-
- # Strip quotes from parsed values for backend-agnostic processing
- quote_chars = ("`", '"')
-
- def strip_quotes(s):
- if s and any(s.startswith(q) for q in quote_chars):
- return s.strip('`"')
- return s
-
- # Extract schema and table name from child (work with unquoted names)
- child_table_raw = strip_quotes(match["child"])
- if "." in child_table_raw:
- child_parts = child_table_raw.split(".")
- child_schema = strip_quotes(child_parts[0])
- child_table_name = strip_quotes(child_parts[1])
- else:
- # Add schema from current table
- schema_parts = table.full_table_name.split(".")
- child_schema = strip_quotes(schema_parts[0])
- child_table_name = child_table_raw
-
- # If FK/PK attributes not in error message, query information_schema
- if match["fk_attrs"] is None or match["pk_attrs"] is None:
- constraint_query = table.connection.adapter.get_constraint_info_sql(
- strip_quotes(match["name"]),
- child_schema,
- child_table_name,
- )
-
- results = table.connection.query(
- constraint_query,
- args=(strip_quotes(match["name"]), child_schema, child_table_name),
- ).fetchall()
- if results:
- match["fk_attrs"], match["parent"], match["pk_attrs"] = list(map(list, zip(*results)))
- match["parent"] = match["parent"][0] # All rows have same parent
-
- # Build properly quoted full table name for FreeTable
- child_full_name = (
- f"{table.connection.adapter.quote_identifier(child_schema)}."
- f"{table.connection.adapter.quote_identifier(child_table_name)}"
- )
-
- # Restrict child by table if
- # 1. if table's restriction attributes are not in child's primary key
- # 2. if child renames any attributes
- # Otherwise restrict child by table's restriction.
- child = FreeTable(table.connection, child_full_name)
- if set(table.restriction_attributes) <= set(child.primary_key) and match["fk_attrs"] == match["pk_attrs"]:
- child._restriction = table._restriction
- child._restriction_attributes = table.restriction_attributes
- elif match["fk_attrs"] != match["pk_attrs"]:
- child &= table.proj(**dict(zip(match["fk_attrs"], match["pk_attrs"])))
- else:
- child &= table.proj()
-
- master_name = get_master(child.full_table_name, table.connection.adapter)
- if (
- part_integrity == "cascade"
- and master_name
- and master_name != table.full_table_name
- and master_name not in visited_masters
- ):
- master = FreeTable(table.connection, master_name)
- master._restriction_attributes = set()
- master._restriction = [
- make_condition( # &= may cause in target tables in subquery
- master,
- (master.proj() & child.proj()).to_arrays(),
- master._restriction_attributes,
- )
- ]
- visited_masters.add(master_name)
- cascade(master)
- else:
- cascade(child)
- else:
- # Successful delete - release savepoint
- if transaction:
- table.connection.query(f"RELEASE SAVEPOINT {savepoint_name}")
- deleted.add(table.full_table_name)
- logger.info("Deleting {count} rows from {table}".format(count=delete_count, table=table.full_table_name))
- break
- else:
- raise DataJointError("Exceeded maximum number of delete attempts.")
- return delete_count
-
- prompt = self.connection._config["safemode"] if prompt is None else prompt
-
- # Start transaction
- if transaction:
- if not self.connection.in_transaction:
- self.connection.start_transaction()
- else:
- if not prompt:
- transaction = False
- else:
- raise DataJointError(
- "Delete cannot use a transaction within an ongoing transaction. Set transaction=False or prompt=False."
- )
+ raise ValueError(f"part_integrity must be 'enforce', 'ignore', or 'cascade', " f"got {part_integrity!r}")
+ from .diagram import Diagram
- # Cascading delete
- try:
- delete_count = cascade(self)
- except:
- if transaction:
- self.connection.cancel_transaction()
- raise
-
- if part_integrity == "enforce":
- # Avoid deleting from part before master (See issue #151)
- for part in deleted:
- master = get_master(part, self.connection.adapter)
- if master and master not in deleted:
- if transaction:
- self.connection.cancel_transaction()
- raise DataJointError(
- "Attempt to delete part table {part} before deleting from its master {master} first. "
- "Use part_integrity='ignore' to allow, or part_integrity='cascade' to also delete master.".format(
- part=part, master=master
- )
- )
-
- # Confirm and commit
- if delete_count == 0:
- if prompt:
- logger.warning("Nothing to delete.")
- if transaction:
- self.connection.cancel_transaction()
- elif not transaction:
- logger.info("Delete completed")
- else:
- if not prompt or user_choice("Commit deletes?", default="no") == "yes":
- if transaction:
- self.connection.commit_transaction()
- if prompt:
- logger.info("Delete committed.")
- else:
- if transaction:
- self.connection.cancel_transaction()
- if prompt:
- logger.warning("Delete cancelled")
- delete_count = 0 # Reset count when delete is cancelled
- return delete_count
+ diagram = Diagram._from_table(self)
+ diagram = diagram.cascade(self, part_integrity=part_integrity)
+ return diagram.delete(transaction=transaction, prompt=prompt, dry_run=dry_run)
def drop_quick(self):
"""
@@ -1208,42 +1050,34 @@ def drop_quick(self):
else:
logger.info("Nothing to drop: table %s is not declared" % self.full_table_name)
- def drop(self, prompt: bool | None = None):
+ def drop(self, prompt: bool | None = None, part_integrity: str = "enforce", dry_run: bool = False):
"""
Drop the table and all tables that reference it, recursively.
+ Uses graph-driven traversal: builds a dependency diagram and drops
+ in reverse topological order (leaves first).
+
Args:
prompt: If `True`, show what will be dropped and ask for confirmation.
If `False`, drop without confirmation. Default is `dj.config['safemode']`.
+ part_integrity: Policy for master-part integrity. One of:
+ - ``"enforce"`` (default): Error if parts would be dropped without masters.
+ - ``"ignore"``: Allow dropping parts without masters.
+ dry_run: If `True`, return a dict mapping full table names to row
+ counts without dropping any tables. Default False.
+
+ Returns:
+ dict[str, int] or None: If ``dry_run``, mapping of full table name
+ to row count. Otherwise None.
"""
if self.restriction:
raise DataJointError(
- "A table with an applied restriction cannot be dropped. Call drop() on the unrestricted Table."
+ "A table with an applied restriction cannot be dropped. " "Call drop() on the unrestricted Table."
)
- prompt = self.connection._config["safemode"] if prompt is None else prompt
-
- self.connection.dependencies.load()
- do_drop = True
- tables = [table for table in self.connection.dependencies.descendants(self.full_table_name) if not table.isdigit()]
-
- # avoid dropping part tables without their masters: See issue #374
- for part in tables:
- master = get_master(part, self.connection.adapter)
- if master and master not in tables:
- raise DataJointError(
- "Attempt to drop part table {part} before dropping its master. Drop {master} first.".format(
- part=part, master=master
- )
- )
+ from .diagram import Diagram
- if prompt:
- for table in tables:
- logger.info(table + " (%d tuples)" % len(FreeTable(self.connection, table)))
- do_drop = user_choice("Proceed?", default="no") == "yes"
- if do_drop:
- for table in reversed(tables):
- FreeTable(self.connection, table).drop_quick()
- logger.info("Tables dropped. Restart kernel.")
+ diagram = Diagram._from_table(self)
+ return diagram.drop(prompt=prompt, part_integrity=part_integrity, dry_run=dry_run)
def describe(self, context=None, printout=False):
"""
diff --git a/src/datajoint/user_tables.py b/src/datajoint/user_tables.py
index 4c2ba8d4c..ced5f4c25 100644
--- a/src/datajoint/user_tables.py
+++ b/src/datajoint/user_tables.py
@@ -239,7 +239,7 @@ def delete(self, part_integrity: str = "enforce", **kwargs):
)
super().delete(part_integrity=part_integrity, **kwargs)
- def drop(self, part_integrity: str = "enforce"):
+ def drop(self, part_integrity: str = "enforce", dry_run: bool = False):
"""
Drop a Part table.
@@ -248,12 +248,13 @@ def drop(self, part_integrity: str = "enforce"):
- ``"enforce"`` (default): Error - drop master instead.
- ``"ignore"``: Allow direct drop (breaks master-part structure).
Note: ``"cascade"`` is not supported for drop (too destructive).
+ dry_run: If `True`, return row counts without dropping. Default False.
Raises:
DataJointError: If part_integrity="enforce" (direct Part drops prohibited)
"""
if part_integrity == "ignore":
- super().drop()
+ return super().drop(part_integrity="ignore", dry_run=dry_run)
elif part_integrity == "enforce":
raise DataJointError("Cannot drop a Part directly. Drop master instead, or use part_integrity='ignore' to force.")
else:
diff --git a/src/datajoint/version.py b/src/datajoint/version.py
index 871a28cbb..9a1d4aff2 100644
--- a/src/datajoint/version.py
+++ b/src/datajoint/version.py
@@ -1,4 +1,4 @@
# version bump auto managed by Github Actions:
# label_prs.yaml(prep), release.yaml(bump), post_release.yaml(edit)
# manually set this version will be eventually overwritten by the above actions
-__version__ = "2.1.1"
+__version__ = "2.2.0.dev0"
diff --git a/tests/integration/test_cascade_delete.py b/tests/integration/test_cascade_delete.py
index caf5f331b..2964bb877 100644
--- a/tests/integration/test_cascade_delete.py
+++ b/tests/integration/test_cascade_delete.py
@@ -188,3 +188,86 @@ class Observation(dj.Manual):
assert remaining_obs[0]["obs_id"] == 3
assert remaining_obs[0]["subject_id"] == 2
assert remaining_obs[0]["measurement"] == 15.3
+
+
+def test_delete_dry_run(schema_by_backend):
+ """dry_run=True returns affected row counts without deleting data."""
+
+ @schema_by_backend
+ class Parent(dj.Manual):
+ definition = """
+ parent_id : int
+ ---
+ name : varchar(255)
+ """
+
+ @schema_by_backend
+ class Child(dj.Manual):
+ definition = """
+ -> Parent
+ child_id : int
+ ---
+ data : varchar(255)
+ """
+
+ Parent.insert1((1, "P1"))
+ Parent.insert1((2, "P2"))
+ Child.insert1((1, 1, "C1-1"))
+ Child.insert1((1, 2, "C1-2"))
+ Child.insert1((2, 1, "C2-1"))
+
+ # dry_run on restricted delete
+ counts = (Parent & {"parent_id": 1}).delete(dry_run=True)
+
+ assert isinstance(counts, dict)
+ assert counts[Parent.full_table_name] == 1
+ assert counts[Child.full_table_name] == 2
+
+ # Data must still be intact
+ assert len(Parent()) == 2
+ assert len(Child()) == 3
+
+ # dry_run on unrestricted delete
+ counts_all = Parent.delete(dry_run=True)
+ assert counts_all[Parent.full_table_name] == 2
+ assert counts_all[Child.full_table_name] == 3
+
+ # Still intact
+ assert len(Parent()) == 2
+ assert len(Child()) == 3
+
+
+def test_drop_dry_run(schema_by_backend):
+ """dry_run=True returns row counts without dropping tables."""
+
+ @schema_by_backend
+ class Parent(dj.Manual):
+ definition = """
+ parent_id : int
+ ---
+ name : varchar(255)
+ """
+
+ @schema_by_backend
+ class Child(dj.Manual):
+ definition = """
+ -> Parent
+ child_id : int
+ ---
+ data : varchar(255)
+ """
+
+ Parent.insert1((1, "P1"))
+ Child.insert1((1, 1, "C1"))
+
+ counts = Parent.drop(dry_run=True)
+
+ assert isinstance(counts, dict)
+ assert counts[Parent.full_table_name] == 1
+ assert counts[Child.full_table_name] == 1
+
+ # Tables must still exist and have data
+ assert Parent.is_declared
+ assert Child.is_declared
+ assert len(Parent()) == 1
+ assert len(Child()) == 1
diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py
index 35230ea4e..1f8144f0f 100644
--- a/tests/integration/test_cli.py
+++ b/tests/integration/test_cli.py
@@ -3,6 +3,7 @@
"""
import subprocess
+import sys
import pytest
@@ -31,7 +32,7 @@ def test_cli_help(capsys):
def test_cli_config():
process = subprocess.Popen(
- ["dj"],
+ [sys.executable, "-m", "datajoint.cli"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -50,7 +51,7 @@ def test_cli_config():
def test_cli_args():
process = subprocess.Popen(
- ["dj", "-u", "test_user", "-p", "test_pass", "--host", "test_host"],
+ [sys.executable, "-m", "datajoint.cli", "-u", "test_user", "-p", "test_pass", "--host", "test_host"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
@@ -82,7 +83,9 @@ class IJ(dj.Lookup):
# Pass credentials via CLI args to avoid prompting for username
process = subprocess.Popen(
[
- "dj",
+ sys.executable,
+ "-m",
+ "datajoint.cli",
"-u",
db_creds_root["user"],
"-p",
diff --git a/tests/integration/test_erd.py b/tests/integration/test_erd.py
index 95077da50..92a8ad682 100644
--- a/tests/integration/test_erd.py
+++ b/tests/integration/test_erd.py
@@ -1,6 +1,8 @@
+import pytest as _pytest
+
import datajoint as dj
-from tests.schema_simple import LOCALS_SIMPLE, A, B, D, E, G, L
+from tests.schema_simple import LOCALS_SIMPLE, A, B, D, E, G, L, Profile, Website
def test_decorator(schema_simp):
@@ -61,3 +63,105 @@ def test_part_table_parsing(schema_simp):
graph = erd._make_graph()
assert "OutfitLaunch" in graph.nodes()
assert "OutfitLaunch.OutfitPiece" in graph.nodes()
+
+
+# --- prune() tests ---
+
+
+@_pytest.fixture
+def schema_simp_pop(schema_simp):
+ """Populate the simple schema for prune tests."""
+ Profile().delete()
+ Website().delete()
+ G().delete()
+ E().delete()
+ D().delete()
+ B().delete()
+ L().delete()
+ A().delete()
+
+ A().insert(A.contents, skip_duplicates=True)
+ L().insert(L.contents, skip_duplicates=True)
+ B().populate()
+ D().populate()
+ E().populate()
+ G().populate()
+ yield schema_simp
+
+
+def test_prune_unrestricted(schema_simp_pop):
+ """Prune on unrestricted diagram removes physically empty tables."""
+ diag = dj.Diagram(schema_simp_pop, context=LOCALS_SIMPLE)
+ original_count = len(diag.nodes_to_show)
+ pruned = diag.prune()
+
+ # Populated tables (A, L, B, B.C, D, E, E.F, G, etc.) should survive
+ for cls in (A, B, D, E, L):
+ assert cls.full_table_name in pruned.nodes_to_show, f"{cls.__name__} should not be pruned"
+
+ # Empty tables like Profile should be removed
+ assert Profile.full_table_name not in pruned.nodes_to_show, "empty Profile should be pruned"
+
+ # Pruned diagram should have fewer nodes
+ assert len(pruned.nodes_to_show) < original_count
+
+
+def test_prune_after_restrict(schema_simp_pop):
+ """Prune after restrict removes tables with zero matching rows."""
+ diag = dj.Diagram(schema_simp_pop, context=LOCALS_SIMPLE)
+ restricted = diag.restrict(A & "id_a=0")
+ counts = restricted.preview()
+
+ pruned = restricted.prune()
+ pruned_counts = pruned.preview()
+
+ # Every table in pruned preview should have > 0 rows
+ assert all(c > 0 for c in pruned_counts.values()), "pruned diagram should have no zero-count tables"
+
+ # Tables with zero rows in the original preview should be gone
+ for table, count in counts.items():
+ if count == 0:
+ assert table not in pruned._restrict_conditions, f"{table} had 0 rows but was not pruned"
+
+
+def test_prune_after_cascade(schema_simp_pop):
+ """Prune after cascade removes tables with zero matching rows."""
+ diag = dj.Diagram(schema_simp_pop, context=LOCALS_SIMPLE)
+ cascaded = diag.cascade(A & "id_a=0")
+ counts = cascaded.preview()
+
+ pruned = cascaded.prune()
+ pruned_counts = pruned.preview()
+
+ assert all(c > 0 for c in pruned_counts.values())
+
+ for table, count in counts.items():
+ if count == 0:
+ assert table not in pruned._cascade_restrictions, f"{table} had 0 rows but was not pruned"
+
+
+def test_prune_idempotent(schema_simp_pop):
+ """Pruning twice gives the same result."""
+ diag = dj.Diagram(schema_simp_pop, context=LOCALS_SIMPLE)
+ restricted = diag.restrict(A & "id_a=0")
+ pruned_once = restricted.prune()
+ pruned_twice = pruned_once.prune()
+
+ assert pruned_once.nodes_to_show == pruned_twice.nodes_to_show
+ assert set(pruned_once._restrict_conditions) == set(pruned_twice._restrict_conditions)
+
+
+def test_prune_then_restrict(schema_simp_pop):
+ """Restrict can be called after prune."""
+ diag = dj.Diagram(schema_simp_pop, context=LOCALS_SIMPLE)
+ pruned = diag.restrict(A & "id_a < 5").prune()
+ # Restrict again on the same seed table with a tighter condition
+ further = pruned.restrict(A & "id_a=0")
+
+ # Should not raise; further restriction should narrow results
+ counts = further.preview()
+ assert all(c >= 0 for c in counts.values())
+ # Tighter restriction should produce fewer or equal rows
+ pruned_counts = pruned.preview()
+ for table in counts:
+ assert counts[table] <= pruned_counts.get(table, 0)