tablassert 9.0.0__tar.gz → 10.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {tablassert-9.0.0 → tablassert-10.0.0}/PKG-INFO +8 -2
  2. {tablassert-9.0.0 → tablassert-10.0.0}/README.md +5 -1
  3. {tablassert-9.0.0 → tablassert-10.0.0}/pyproject.toml +5 -1
  4. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/_lazy.py +13 -1
  5. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/agent.py +39 -28
  6. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/cli.py +325 -14
  7. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/enums.py +0 -1
  8. tablassert-10.0.0/src/tablassert/errors.py +161 -0
  9. tablassert-10.0.0/src/tablassert/extras.py +159 -0
  10. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/fullmap.py +18 -4
  11. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/lib.py +85 -7
  12. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/models.py +74 -68
  13. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/qc.py +10 -1
  14. tablassert-9.0.0/src/tablassert/errors.py +0 -95
  15. {tablassert-9.0.0 → tablassert-10.0.0}/LICENSE +0 -0
  16. {tablassert-9.0.0 → tablassert-10.0.0}/rust/Cargo.lock +0 -0
  17. {tablassert-9.0.0 → tablassert-10.0.0}/rust/Cargo.toml +0 -0
  18. {tablassert-9.0.0 → tablassert-10.0.0}/rust/examples/count_tables.rs +0 -0
  19. {tablassert-9.0.0 → tablassert-10.0.0}/rust/src/fullmap.rs +0 -0
  20. {tablassert-9.0.0 → tablassert-10.0.0}/rust/src/json.rs +0 -0
  21. {tablassert-9.0.0 → tablassert-10.0.0}/rust/src/lib.rs +0 -0
  22. {tablassert-9.0.0 → tablassert-10.0.0}/rust/src/ndjson.rs +0 -0
  23. {tablassert-9.0.0 → tablassert-10.0.0}/rust/src/uuid.rs +0 -0
  24. {tablassert-9.0.0 → tablassert-10.0.0}/rust/tests/build_golden.rs +0 -0
  25. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/__init__.py +0 -0
  26. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/biolink.py +0 -0
  27. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/coerce.py +0 -0
  28. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/graph_registry.py +0 -0
  29. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/ingests.py +0 -0
  30. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/log.py +0 -0
  31. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/nlp.py +0 -0
  32. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/progress.py +0 -0
  33. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/rig.py +0 -0
  34. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/rs.pyi +0 -0
  35. {tablassert-9.0.0 → tablassert-10.0.0}/src/tablassert/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tablassert
3
- Version: 9.0.0
3
+ Version: 10.0.0
4
4
  Classifier: License :: OSI Approved :: Apache Software License
5
5
  Classifier: Development Status :: 5 - Production/Stable
6
6
  Classifier: Intended Audience :: Science/Research
@@ -32,11 +32,13 @@ Requires-Dist: fastexcel>=0.20.2
32
32
  Requires-Dist: smolagents>=1.26.0 ; extra == 'agent'
33
33
  Requires-Dist: litellm>=1.93.0 ; extra == 'agent'
34
34
  Requires-Dist: pdfminer-six>=20221105 ; extra == 'agent'
35
+ Requires-Dist: aria2==0.0.1b0 ; extra == 'aria2'
35
36
  Requires-Dist: dspy>=3.2.1 ; extra == 'optimize'
36
37
  Requires-Dist: scikit-learn>=1.8.0 ; extra == 'qc'
37
38
  Requires-Dist: sentence-transformers>=5.3.0 ; extra == 'qc'
38
39
  Requires-Dist: polars[rtcompat]>=1.40.1 ; extra == 'rt'
39
40
  Provides-Extra: agent
41
+ Provides-Extra: aria2
40
42
  Provides-Extra: optimize
41
43
  Provides-Extra: qc
42
44
  Provides-Extra: rt
@@ -152,11 +154,15 @@ CSV/TSV/Excel sources; optional extras add runtime and pipeline capabilities:
152
154
  | Extra | Adds | Install |
153
155
  | ----- | ---- | ------- |
154
156
  | `rt` | CPU-compatible Polars runtime | `pip install "tablassert[rt]"` |
157
+ | `aria2` | bundled aria2c downloader for `build-fullmap --aria2c` (Linux/Windows wheels only) | `pip install "tablassert[aria2]"` |
155
158
  | `qc` | three-stage QC audit (exact → fuzzy → BioBERT embeddings) | `pip install "tablassert[qc]"` |
156
159
  | `agent` | autonomous agent (smolagents, litellm, PDF context) | `pip install "tablassert[agent]"` |
157
160
  | `optimize` | GEPA prompt optimization for `agent --optimize` (dspy) | `pip install "tablassert[optimize]"` |
158
161
 
159
- QC is opt-in at build time (`build-kg --qc`). See the
162
+ QC is opt-in at build time (`build-kg --qc`). Reaching a feature whose extra is not installed never
163
+ produces a bare `ModuleNotFoundError`: the failure names the missing package and the exact install
164
+ command, and for `build-kg --qc` and `tablassert agent` it arrives before the run starts rather than
165
+ partway through. See the
160
166
  [Installation guide](https://skyeav.github.io/Tablassert/installation/) for the full matrix and the
161
167
  [CLI Reference](https://skyeav.github.io/Tablassert/cli/) for every flag.
162
168
 
@@ -99,11 +99,15 @@ CSV/TSV/Excel sources; optional extras add runtime and pipeline capabilities:
99
99
  | Extra | Adds | Install |
100
100
  | ----- | ---- | ------- |
101
101
  | `rt` | CPU-compatible Polars runtime | `pip install "tablassert[rt]"` |
102
+ | `aria2` | bundled aria2c downloader for `build-fullmap --aria2c` (Linux/Windows wheels only) | `pip install "tablassert[aria2]"` |
102
103
  | `qc` | three-stage QC audit (exact → fuzzy → BioBERT embeddings) | `pip install "tablassert[qc]"` |
103
104
  | `agent` | autonomous agent (smolagents, litellm, PDF context) | `pip install "tablassert[agent]"` |
104
105
  | `optimize` | GEPA prompt optimization for `agent --optimize` (dspy) | `pip install "tablassert[optimize]"` |
105
106
 
106
- QC is opt-in at build time (`build-kg --qc`). See the
107
+ QC is opt-in at build time (`build-kg --qc`). Reaching a feature whose extra is not installed never
108
+ produces a bare `ModuleNotFoundError`: the failure names the missing package and the exact install
109
+ command, and for `build-kg --qc` and `tablassert agent` it arrives before the run starts rather than
110
+ partway through. See the
107
111
  [Installation guide](https://skyeav.github.io/Tablassert/installation/) for the full matrix and the
108
112
  [CLI Reference](https://skyeav.github.io/Tablassert/cli/) for every flag.
109
113
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tablassert"
3
- version = "9.0.0"
3
+ version = "10.0.0"
4
4
  description = "Extract knowledge assertions from tabular data into NCATS Translator-compliant KGX NDJSON — declaratively, with entity resolution and quality control built in."
5
5
  authors = [
6
6
  { name = "Skye Lane Goetz", email = "sgoetz@isbscience.org" }
@@ -78,6 +78,10 @@ tablassert = "tablassert.cli:APP"
78
78
  rt = [
79
79
  "polars[rtcompat]>=1.40.1",
80
80
  ]
81
+ # PyPI distribution "aria2" exposes import module "aria2c" with a bundled aria2c binary.
82
+ aria2 = [
83
+ "aria2==0.0.1b0",
84
+ ]
81
85
  qc = [
82
86
  "scikit-learn>=1.8.0",
83
87
  "sentence-transformers>=5.3.0",
@@ -34,7 +34,19 @@ class LazyModule:
34
34
  def _load(self) -> Any:
35
35
  module = self._module
36
36
  if module is None:
37
- module = import_module(self._name)
37
+ try:
38
+ module = import_module(self._name)
39
+ except ImportError as exc:
40
+ # Deferred: keeps this module import-light (everything imports it) and the
41
+ # registry lookup off the success path. A proxied optional dependency that
42
+ # is absent must name its extra here -- this is the LAST point that knows
43
+ # the module name, and the caller only sees "No module named 'x'".
44
+ from tablassert.extras import actionable_import_error
45
+
46
+ actionable = actionable_import_error(self._name)
47
+ if actionable is None:
48
+ raise
49
+ raise actionable from exc
38
50
  self._module = module
39
51
  return module
40
52
 
@@ -4,11 +4,11 @@ This module hosts a smolagents ``CodeAgent`` pipeline that autonomously builds
4
4
  and audits KGX knowledge graphs from PubMed Central articles. It is part of the
5
5
  OPTIONAL ``[agent]`` extra, so ``smolagents`` is imported LAZILY (via
6
6
  :class:`tablassert._lazy.LazyModule`) and the base package never requires it at
7
- import time. Install the extra with ``pip install tablassert[agent]``.
7
+ import time. Install the extra with ``pip install "tablassert[agent]"``.
8
8
 
9
9
  ``dspy`` powers ONLY the GEPA prompt-optimization path (``agent --optimize``)
10
10
  and lives in its own OPTIONAL ``[optimize]`` extra
11
- (``pip install tablassert[optimize]``); it is likewise lazy-imported and never
11
+ (``pip install "tablassert[optimize]"``); it is likewise lazy-imported and never
12
12
  required by ordinary agent runs.
13
13
  """
14
14
 
@@ -26,7 +26,6 @@ import xml.etree.ElementTree as ET
26
26
  from collections import Counter
27
27
  from collections.abc import Callable, Sequence
28
28
  from dataclasses import asdict, dataclass, field
29
- from importlib import import_module
30
29
  from pathlib import Path
31
30
  from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
32
31
  from urllib.request import Request, urlopen
@@ -38,6 +37,7 @@ from tablassert._lazy import LazyModule
38
37
  from tablassert.biolink import ENUM_RANGED_QUALIFIERS, Categories
39
38
  from tablassert.enums import EncodingMethods
40
39
  from tablassert.errors import GraphValidationError, QcRuntimeMissingError, SectionValidationError, TablassertValidationError
40
+ from tablassert.extras import install_command, require_module
41
41
  from tablassert.fullmap import distinct, fullmap_db_path, is_lock_contention, lookup_rows
42
42
  from tablassert.graph_registry import REGISTERED_STATUSES, register_build
43
43
  from tablassert.lib import Tcode
@@ -55,21 +55,23 @@ else:
55
55
  pl = LazyModule("polars")
56
56
  smolagents = LazyModule("smolagents")
57
57
 
58
- AGENT_EXTRA: str = "pip install tablassert[agent]"
59
- OPTIMIZE_EXTRA: str = "pip install tablassert[optimize]"
60
-
61
- # Package -> install hint for the extra that actually ships it (default: [agent]).
62
- _EXTRA_HINT: dict[str, str] = {"dspy": OPTIMIZE_EXTRA}
58
+ # Published install hints, derived from the extras registry so they cannot drift from
59
+ # either pyproject.toml or the messages users actually see.
60
+ AGENT_EXTRA: str = install_command("agent")
61
+ OPTIMIZE_EXTRA: str = install_command("optimize")
63
62
 
64
63
  logger = cat("AGENT")
65
64
 
66
65
 
67
66
  def _require(name: str) -> None:
68
- """Import an optional dependency or raise a loud, actionable ImportError."""
69
- try:
70
- import_module(name)
71
- except ImportError as exc:
72
- raise ImportError(f"tablassert agent features require the '{name}' package. Install with {_EXTRA_HINT.get(name, AGENT_EXTRA)}.") from exc
67
+ """Import an optional dependency or raise a loud, actionable ImportError.
68
+
69
+ Thin wrapper over :func:`tablassert.extras.require_module`, which owns the
70
+ package -> extra mapping (``dspy`` belongs to ``[optimize]``, everything else the
71
+ agent lazy-imports to ``[agent]``) so the hints cannot drift from
72
+ ``pyproject.toml``.
73
+ """
74
+ require_module(name, required_by="tablassert agent features")
73
75
 
74
76
 
75
77
  def is_lazy() -> bool:
@@ -455,43 +457,44 @@ DATA_GUARDRAIL: str = (
455
457
  def excel_sheet_names(path: Path) -> list[str]:
456
458
  """Return the worksheet names of an Excel workbook (calamine preferred, openpyxl fallback).
457
459
 
458
- Uses the SAME optional engines :func:`_read_excel` reads with (imported lazily), so a workbook is
459
- introspectable wherever it is readable. Raises a clear ``ValueError`` naming the install path when
460
- neither engine can open the workbook (a corrupt file or a missing engine).
460
+ Uses the SAME engines :func:`_read_excel` reads with (imported lazily), so a workbook is
461
+ introspectable wherever it is readable. Raises a clear ``ValueError`` naming the fix when
462
+ neither engine can open the workbook (a corrupt file, or a base install missing its engine).
461
463
  """
462
464
  try:
463
- import fastexcel # lazy optional engine (calamine), same as _read_excel
465
+ import fastexcel # lazy import of the core calamine engine, same as _read_excel
464
466
 
465
467
  return [str(name) for name in fastexcel.read_excel(path).sheet_names]
466
- except Exception as calamine_err: # missing fastexcel OR a genuinely unreadable workbook
468
+ except Exception as calamine_err: # unreadable workbook OR (rarely) a broken fastexcel install
467
469
  try:
468
470
  import openpyxl # lazy pure-Python fallback engine, same as _read_excel
469
471
 
470
472
  return [str(name) for name in openpyxl.load_workbook(path, read_only=True).sheetnames]
471
473
  except Exception:
472
474
  raise ValueError(
473
- f"Listing Excel sheets requires an excel engine (calamine/openpyxl); install tablassert[agent] or tablassert[rt]. ({calamine_err})"
475
+ f"Could not list Excel sheets with either engine. calamine (fastexcel) is a core dependency, so this is usually an unreadable "
476
+ f"workbook; `pip install openpyxl` adds the pure-Python fallback engine. ({calamine_err})"
474
477
  ) from calamine_err
475
478
 
476
479
 
477
480
  def _read_excel(path: Path, sheet: str | None = None) -> pl.DataFrame:
478
481
  """Read an Excel worksheet, preferring ``calamine`` and falling back to ``openpyxl``.
479
482
 
480
- WHY two engines: the fast ``calamine`` engine needs the optional ``fastexcel``
481
- package, which the base install lacks; ``openpyxl`` is a pure-Python fallback
482
- that is commonly present. ``sheet`` selects a worksheet BY NAME (``None`` reads
483
- the first/active sheet, matching polars' default). If neither engine can load the
484
- file (both missing, or the workbook is corrupt), raise a clear ``ValueError``
485
- naming the install path instead of leaking a raw engine error to the caller.
483
+ WHY two engines: the fast ``calamine`` engine (``fastexcel``) is a core dependency and
484
+ handles almost every workbook; ``openpyxl`` is a pure-Python fallback that reads some
485
+ files calamine rejects. ``sheet`` selects a worksheet BY NAME (``None`` reads the
486
+ first/active sheet, matching polars' default). If neither engine can load the file,
487
+ raise a clear ``ValueError`` naming the fix instead of leaking a raw engine error.
486
488
  """
487
489
  try:
488
490
  return pl.read_excel(path, engine="calamine", sheet_name=sheet)
489
- except Exception as calamine_err: # missing fastexcel OR a genuinely unreadable workbook
491
+ except Exception as calamine_err: # unreadable workbook OR (rarely) a broken fastexcel install
490
492
  try:
491
493
  return pl.read_excel(path, engine="openpyxl", sheet_name=sheet)
492
494
  except Exception:
493
495
  raise ValueError(
494
- f"Reading Excel requires an excel engine (calamine/openpyxl); install tablassert[agent] or tablassert[rt]. ({calamine_err})"
496
+ f"Could not read Excel with either engine. calamine (fastexcel) is a core dependency, so this is usually an unreadable "
497
+ f"workbook; `pip install openpyxl` adds the pure-Python fallback engine. ({calamine_err})"
495
498
  ) from calamine_err
496
499
 
497
500
 
@@ -572,7 +575,7 @@ def _extract_pdf_text(path: Path) -> str:
572
575
  try:
573
576
  from pdfminer.high_level import extract_text # pyright: ignore[reportMissingImports] # lazy optional dep ([agent] extra)
574
577
  except ImportError as exc:
575
- raise ValueError(f"Reading PDF main text requires pdfminer.six; install tablassert[agent]. ({exc})") from exc
578
+ raise ValueError(f"Reading PDF main text requires pdfminer.six. {install_command('agent')} ({exc})") from exc
576
579
  return str(extract_text(str(path)))
577
580
 
578
581
 
@@ -2042,6 +2045,9 @@ def build_model(model_id: str | None, api_base: str | None, api_key: str | None,
2042
2045
 
2043
2046
  _require("smolagents")
2044
2047
  if backend == "litellm":
2048
+ # smolagents imports litellm lazily inside LiteLLMModel; require it here so the
2049
+ # failure names the [agent] extra instead of surfacing smolagents' own message.
2050
+ _require("litellm")
2045
2051
  from smolagents import LiteLLMModel # local import keeps module import lazy # pyright: ignore[reportMissingImports]
2046
2052
 
2047
2053
  return LiteLLMModel(model_id=rid, api_base=rbase, api_key=rkey)
@@ -2166,6 +2172,11 @@ qualifier and evidence slot the specific class declared. build_and_audit reports
2166
2172
  description rather than emitted on the edge. `q_value`, `fold_change`, `z_score`, `beta` and
2167
2173
  similar are not association slots at all and are folded into `supporting_text`. Prefer
2168
2174
  `p_value`, `adjusted_p_value`, `effect_size`, `effect_type`, `has_evidence`.
2175
+ - MULTIVALUED slots (`has_evidence` and friends) take a real JSON array, never a joined string:
2176
+ declare the annotation `{method: column, encoding: <letter>, split_by: "|"}` so each cell's
2177
+ delimited text splits into its own per-row array. `split_by` is the ONLY multivalued encoding
2178
+ — there is no literal-list method, and a scalar bound for a multivalued slot ships to consumers
2179
+ as one unusable "a|b|c" blob.
2169
2180
  - `effect_size` / `effect_type` are deliberate Tablassert extras pending biolink-model#1774 and
2170
2181
  are EXEMPT from the validity score: a `biolink_valid_pct` below 1.0 is never caused by them.
2171
2182
  - QUALIFIERS: enum-ranged qualifiers take a literal TOKEN, never a CURIE