diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b2a99d4..beed3c89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ All notable changes to this project are documented in this file. ## Unreleased +### Breaking Changes +- **`source.url` is now a list of URLs (`url: list[HttpUrl]`).** A table-config section may declare one or more remote source URLs, all recorded as provenance (emitted in the edge `source_record_urls` list and the RIG). The legacy scalar form `url: https://example.com/x.tsv` is no longer accepted — wrap it in a list. Update existing configs from `url: https://...` to a sequence: + + ```yaml + source: + url: + - https://example.com/data.tsv + ``` + ### Added - **`tablassert build-fullmap --aria2c` / `-a`** opt-in downloader acceleration. When requested, the BABEL download stage uses the installed `aria2c` executable with segmented HTTP downloads plus resume/retry flags (`--continue=true`, `--max-tries`, `--retry-wait`) while keeping the existing Python downloader as the default. Missing or failing `aria2c` fails loud instead of silently falling back, and aria2 `.aria2` control files are preserved so interrupted downloads can resume on rerun. diff --git a/docs/configuration/advanced-example.md b/docs/configuration/advanced-example.md index 8b5a9690..cc8bd0f7 100644 --- a/docs/configuration/advanced-example.md +++ b/docs/configuration/advanced-example.md @@ -13,7 +13,8 @@ template: source: kind: excel local: ./DATALAKE/ALAM.XLSX - url: https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx + url: + - https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx row_slice: - 2 # Skip the first two rows (title + header) - auto # Read to end @@ -174,7 +175,8 @@ correlation tables linking two biological entities per row (e.g., metabolite ↔ template: source: kind: excel - url: https://pmc.ncbi.nlm.nih.gov/articles/instance/example/bin/data.xlsx + url: + - https://pmc.ncbi.nlm.nih.gov/articles/instance/example/bin/data.xlsx local: ./DATALAKE/AVUTHU1.xlsx sheet: signif_metab_microb_corre row_slice: [2, auto] @@ -221,7 +223,8 @@ the `object` (and optionally `row_slice`) per section — one section entry per template: source: kind: excel - url: https://pmc.ncbi.nlm.nih.gov/articles/instance/example/bin/data.xlsx + url: + - https://pmc.ncbi.nlm.nih.gov/articles/instance/example/bin/data.xlsx local: ./DATALAKE/BLANTON1.xlsx sheet: Sheet1 row_slice: [2, auto] diff --git a/docs/configuration/table.md b/docs/configuration/table.md index 312744d9..4dc6e4d7 100644 --- a/docs/configuration/table.md +++ b/docs/configuration/table.md @@ -90,7 +90,7 @@ Defines the data file location and format. |-------|------|----------|-------------| | `kind` | String | No | Source kind. Model default is `"excel"`, but specify it explicitly in configs. | | `local` | Path | Yes | Local file path the source is read from. The file must already exist here — Tablassert does not download it. | -| `url` | URL | Yes | Source URL recorded as provenance (emitted as the edge `source_record_urls` column and in the RIG). Format-validated only; not fetched. | +| `url` | List[URL] | Yes | One or more source URLs recorded as provenance (emitted as the edge `source_record_urls` list and in the RIG). At least one URL is required; supply multiple to back a single section with several links. Format-validated only; not fetched. | | `sheet` | String | No | Sheet name. Defaults to `"Sheet1"`. | | `row_slice` | List[PositiveInt\|"auto"] | No | Two-value zero-based crop bounds: `[start, stop]`. Each value may be a positive integer or `"auto"`. Mutually exclusive with `rows`. | | `rows` | List[PositiveInt] | No | Zero-based row indices to keep after any `row_slice` crop. Mutually exclusive with `row_slice`. | @@ -101,7 +101,8 @@ Defines the data file location and format. source: kind: excel local: ./data/mydata.xlsx - url: https://example.com/data.xlsx + url: + - https://example.com/data.xlsx sheet: "Sheet1" row_slice: [1, auto] # Start at the second physical row, read to end ``` @@ -114,7 +115,7 @@ source: |-------|------|----------|-------------| | `kind` | String | No | Source kind. Model default is `"text"`, but specify it explicitly in configs. | | `local` | Path | Yes | Local file path the source is read from. The file must already exist here — Tablassert does not download it. | -| `url` | URL | Yes | Source URL recorded as provenance (emitted as the edge `source_record_urls` column and in the RIG). Format-validated only; not fetched. | +| `url` | List[URL] | Yes | One or more source URLs recorded as provenance (emitted as the edge `source_record_urls` list and in the RIG). At least one URL is required; supply multiple to back a single section with several links. Format-validated only; not fetched. | | `delimiter` | String | No | Field delimiter. Defaults to `","`. | | `row_slice` | List[PositiveInt\|"auto"] | No | Two-value zero-based crop bounds: `[start, stop]`. Each value may be a positive integer or `"auto"`. Mutually exclusive with `rows`. | | `rows` | List[PositiveInt] | No | Zero-based row indices to keep after any `row_slice` crop. Mutually exclusive with `row_slice`. | @@ -125,7 +126,8 @@ source: source: kind: text local: ./data/mydata.tsv - url: https://example.com/data.tsv + url: + - https://example.com/data.tsv delimiter: "\t" row_slice: [1, auto] ``` diff --git a/docs/examples.md b/docs/examples.md index 1a7848bb..57564342 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -15,7 +15,8 @@ template: source: kind: text local: ./gene-disease.csv - url: https://example.com/gene-disease.csv + url: + - https://example.com/gene-disease.csv row_slice: [1, auto] delimiter: "," statement: @@ -57,7 +58,8 @@ template: source: kind: text local: ./drug-targets.tsv - url: https://example.com/drug-targets.tsv + url: + - https://example.com/drug-targets.tsv row_slice: [1, auto] delimiter: "\t" statement: @@ -102,7 +104,8 @@ template: source: kind: excel local: ./microbiome-correlations.xlsx - url: https://example.com/microbiome-data.xlsx + url: + - https://example.com/microbiome-data.xlsx sheet: correlations row_slice: [2, auto] statement: @@ -161,7 +164,8 @@ template: source: kind: text local: ./gene-pathways.csv - url: https://example.com/gene-pathways.csv + url: + - https://example.com/gene-pathways.csv row_slice: [1, auto] delimiter: "," statement: @@ -220,7 +224,8 @@ template: source: kind: text local: ./significant-associations.csv - url: https://example.com/associations.csv + url: + - https://example.com/associations.csv row_slice: [1, auto] delimiter: "," reindex: @@ -268,7 +273,8 @@ template: source: kind: text local: ./hierarchical-data.csv - url: https://example.com/hierarchical.csv + url: + - https://example.com/hierarchical.csv row_slice: [1, auto] delimiter: "," statement: diff --git a/docs/examples/tutorial-table.yaml b/docs/examples/tutorial-table.yaml index 6387fb39..e888dbe2 100644 --- a/docs/examples/tutorial-table.yaml +++ b/docs/examples/tutorial-table.yaml @@ -2,7 +2,8 @@ template: source: kind: text local: ./docs/examples/tutorial-data.csv - url: https://example.com/data.csv + url: + - https://example.com/data.csv row_slice: - 1 - auto diff --git a/docs/tutorial.md b/docs/tutorial.md index 5089673d..cb13485b 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -45,7 +45,8 @@ template: source: kind: text local: ./docs/examples/tutorial-data.csv - url: https://example.com/data.csv + url: + - https://example.com/data.csv row_slice: - 1 - auto diff --git a/examples/agent/QC_REPORT.md b/examples/agent/QC_REPORT.md index e9b5ea21..465ff0af 100644 --- a/examples/agent/QC_REPORT.md +++ b/examples/agent/QC_REPORT.md @@ -22,7 +22,7 @@ State dir: `.tablassert/qc-assay` · PMCs assayed: 10 - **object:** method=column encoding=D prioritize=['Gene', 'Protein'] - **source:** kind=excel sheet='Supp.Table 2A_cluster-1' local=/downloads/PMC8017771/PMC8017771.1/NIHMS1644812-supplement-1644812_Supp_Tab2.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC8017771', 'knowledge_level': 'statistical_association', 'agent_type': 'data_analysis_pipeline'} -- **config sha256:** `fb96e79edf16` +- **config sha256:** `676109938e71` ### Derived config (`configs/PMC8017771.yaml`) @@ -30,7 +30,8 @@ State dir: `.tablassert/qc-assay` · PMCs assayed: 10 source: kind: excel local: /downloads/PMC8017771/PMC8017771.1/NIHMS1644812-supplement-1644812_Supp_Tab2.xlsx - url: https://pmc-oa-opendata.s3.amazonaws.com/PMC8017771.1/NIHMS1644812-supplement-1644812_Supp_Tab2.xlsx + url: + - https://pmc-oa-opendata.s3.amazonaws.com/PMC8017771.1/NIHMS1644812-supplement-1644812_Supp_Tab2.xlsx sheet: "Supp.Table 2A_cluster-1" row_slice: [2, "auto"] reindex: @@ -91,7 +92,7 @@ annotations: - **object:** method=value encoding=MONDO:0007739 prioritize=None - **source:** kind=excel sheet='Cap Score - Ion Level' local=/downloads/PMC13161869/PMC13161869.1/ACN3-13-911-s001.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC13161869'} -- **config sha256:** `1aa5a8bf5ded` +- **config sha256:** `a316ead5d5ab` ### Derived config (`configs/PMC13161869.yaml`) @@ -104,7 +105,8 @@ sections: - source: kind: excel local: "/downloads/PMC13161869/PMC13161869.1/ACN3-13-911-s001.xlsx" - url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC13161869.1/ACN3-13-911-s001.xlsx" + url: + - "https://pmc-oa-opendata.s3.amazonaws.com/PMC13161869.1/ACN3-13-911-s001.xlsx" sheet: "Cap Score - Ion Level" row_slice: [1, "auto"] reindex: @@ -192,7 +194,7 @@ sections: - **object:** method=column encoding=B prioritize=['Cell'] - **source:** kind=excel sheet='Supp. Table 7' local=/downloads/PMC12900646/PMC12900646.1/41588_2025_2486_MOESM4_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC12900646'} -- **config sha256:** `560d11724293` +- **config sha256:** `c8745021082f` ### Derived config (`configs/PMC12900646.yaml`) @@ -205,7 +207,8 @@ sections: - source: kind: excel local: /downloads/PMC12900646/PMC12900646.1/41588_2025_2486_MOESM4_ESM.xlsx - url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC12900646.1/41588_2025_2486_MOESM4_ESM.xlsx" + url: + - "https://pmc-oa-opendata.s3.amazonaws.com/PMC12900646.1/41588_2025_2486_MOESM4_ESM.xlsx" sheet: "Supp. Table 7" row_slice: [7, "auto"] reindex: @@ -286,7 +289,7 @@ sections: - **object:** method=value encoding=MONDO:0004988 prioritize=None - **source:** kind=excel sheet='Percentiles - 16p11.2' local=/downloads/PMC9187732/PMC9187732.1/41467_2022_30968_MOESM16_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC9187732'} -- **config sha256:** `e8cb8eacc72d` +- **config sha256:** `468029c6b43d` ### Derived config (`configs/PMC9187732.yaml`) @@ -294,7 +297,8 @@ sections: source: kind: excel local: /downloads/PMC9187732/PMC9187732.1/41467_2022_30968_MOESM16_ESM.xlsx - url: https://pmc-oa-opendata.s3.amazonaws.com/PMC9187732.1/41467_2022_30968_MOESM16_ESM.xlsx + url: + - https://pmc-oa-opendata.s3.amazonaws.com/PMC9187732.1/41467_2022_30968_MOESM16_ESM.xlsx sheet: "Percentiles - 16p11.2" reindex: - column: A @@ -342,7 +346,7 @@ provenance: - **object:** method=column encoding=A prioritize=['BiologicalProcess'] - **source:** kind=excel sheet='Supplementary Table 7' local=/downloads/PMC13099431/PMC13099431.1/41591_2026_4228_MOESM2_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC13099431'} -- **config sha256:** `076cdc335924` +- **config sha256:** `90366dcd5469` ### Derived config (`configs/PMC13099431.yaml`) @@ -355,7 +359,8 @@ sections: - source: kind: excel local: /downloads/PMC13099431/PMC13099431.1/41591_2026_4228_MOESM2_ESM.xlsx - url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC13099431.1/41591_2026_4228_MOESM2_ESM.xlsx" + url: + - "https://pmc-oa-opendata.s3.amazonaws.com/PMC13099431.1/41591_2026_4228_MOESM2_ESM.xlsx" sheet: "Supplementary Table 7" row_slice: [2, "auto"] statement: @@ -400,7 +405,7 @@ sections: - **object:** method=value encoding=GO:0008380 prioritize=None - **source:** kind=excel sheet='vU1-8 KO v WT' local=/downloads/PMC13172311/PMC13172311.1/41467_2026_73121_MOESM5_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC13172311'} -- **config sha256:** `af8d05069765` +- **config sha256:** `e1d3542f1fda` ### Derived config (`configs/PMC13172311.yaml`) @@ -408,7 +413,8 @@ sections: source: kind: excel local: /downloads/PMC13172311/PMC13172311.1/41467_2026_73121_MOESM5_ESM.xlsx - url: https://pmc-oa-opendata.s3.amazonaws.com/PMC13172311.1/41467_2026_73121_MOESM5_ESM.xlsx + url: + - https://pmc-oa-opendata.s3.amazonaws.com/PMC13172311.1/41467_2026_73121_MOESM5_ESM.xlsx sheet: vU1-8 KO v WT statement: subject: @@ -453,7 +459,7 @@ provenance: - **object:** method=value encoding=NCBITaxon:4530 prioritize=None - **source:** kind=excel sheet='Map' local=/downloads/PMC12906585/PMC12906585.1/122_2026_5178_MOESM1_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC12906585'} -- **config sha256:** `d80ab3fe0ca0` +- **config sha256:** `6d5d4b88c16b` ### Derived config (`configs/PMC12906585.yaml`) @@ -466,7 +472,8 @@ sections: - source: kind: excel local: /downloads/PMC12906585/PMC12906585.1/122_2026_5178_MOESM1_ESM.xlsx - url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC12906585.1/122_2026_5178_MOESM1_ESM.xlsx" + url: + - "https://pmc-oa-opendata.s3.amazonaws.com/PMC12906585.1/122_2026_5178_MOESM1_ESM.xlsx" sheet: Map statement: subject: @@ -516,7 +523,7 @@ sections: - **object:** method=column encoding=F prioritize=['Pathway', 'BiologicalProcess'] - **source:** kind=excel sheet='SD15' local=/downloads/PMC13172025/PMC13172025.1/42003_2026_10045_MOESM3_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC13172025'} -- **config sha256:** `86448dd76087` +- **config sha256:** `ac9f8d96c81a` ### Derived config (`configs/PMC13172025.yaml`) @@ -529,7 +536,8 @@ sections: - source: kind: excel local: /downloads/PMC13172025/PMC13172025.1/42003_2026_10045_MOESM3_ESM.xlsx - url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC13172025.1/42003_2026_10045_MOESM3_ESM.xlsx" + url: + - "https://pmc-oa-opendata.s3.amazonaws.com/PMC13172025.1/42003_2026_10045_MOESM3_ESM.xlsx" sheet: "SD15" row_slice: [2, "auto"] statement: @@ -574,7 +582,7 @@ sections: - **object:** method=column encoding=A prioritize=['AnatomicalEntity', 'GrossAnatomicalStructure'] - **source:** kind=excel sheet='v68.lvedv.twas.alltissues' local=/downloads/PMC7206184/PMC7206184.1/41467_2020_15823_MOESM9_ESM.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC7206184', 'knowledge_level': 'statistical_association', 'agent_type': 'data_analysis_pipeline'} -- **config sha256:** `d6785bc724be` +- **config sha256:** `cedf7b1f7445` ### Derived config (`configs/PMC7206184.yaml`) @@ -589,7 +597,8 @@ sections: - source: kind: excel local: /downloads/PMC7206184/PMC7206184.1/41467_2020_15823_MOESM9_ESM.xlsx - url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC7206184.1/41467_2020_15823_MOESM9_ESM.xlsx" + url: + - "https://pmc-oa-opendata.s3.amazonaws.com/PMC7206184.1/41467_2020_15823_MOESM9_ESM.xlsx" sheet: "v68.lvedv.twas.alltissues" row_slice: [1, "auto"] statement: @@ -635,7 +644,7 @@ sections: - **object:** method=value encoding=MONDO:0004992 prioritize=None - **source:** kind=excel sheet='Table_S7' local=/downloads/PMC11947420/PMC11947420.1/mmc2.xlsx - **provenance:** {'repo': 'PMC', 'publication': 'PMC11947420'} -- **config sha256:** `8a1c74d4efc6` +- **config sha256:** `90413a33f8a9` ### Derived config (`configs/PMC11947420.yaml`) @@ -643,7 +652,8 @@ sections: source: kind: excel local: /downloads/PMC11947420/PMC11947420.1/mmc2.xlsx - url: https://pmc-oa-opendata.s3.amazonaws.com/PMC11947420.1/mmc2.xlsx + url: + - https://pmc-oa-opendata.s3.amazonaws.com/PMC11947420.1/mmc2.xlsx sheet: Table_S7 reindex: - column: A diff --git a/examples/agent/QC_REVIEW.md b/examples/agent/QC_REVIEW.md index 8f15f9e1..e17409b3 100644 --- a/examples/agent/QC_REVIEW.md +++ b/examples/agent/QC_REVIEW.md @@ -10,7 +10,7 @@ - other_mistakes: mean 1.40/3 (10 reviewed) - overall_quality counts: {'acceptable': 4, 'poor': 5, 'good': 1} -## PMC8017771 — **acceptable** (config sha256: `fb96e79edf16`) +## PMC8017771 — **acceptable** (config sha256: `676109938e71`) - **predicate_appropriateness** (2/3): The predicate is directionally plausible for a rapamycin up-regulation proteomics table, but it is broader than ideal and is applied uniformly without encoding the specific comparison or significance threshold. For protein abundance changes, a more specific abundance predicate would be preferable. → *Use a more specific directional predicate such as biolink:increases_abundance_of for rapamycin-associated protein abundance increases, or biolink:increases_expression_of only if expression is intended. Tie the assertion to the relevant comparison, e.g. KR/WT, and to positive log2 fold change plus significant q-value.* - **encoding_correctness** (2/3): Column D contains gene names and is a reasonable gene object source, but the table is explicitly a protein list with Protein IDs and Majority protein IDs. Using only gene names can collapse protein-level evidence, miss rows lacking gene names, and underuse the most direct measured entities. → *Prefer object encoding from Protein IDs or Majority protein IDs with Protein prioritization, or at least retain those identifiers as annotations/alternatives. If gene names are used, keep taxon 10090 and explode semicolon-separated values, but add fallback resolution for rows without gene names.* @@ -19,7 +19,7 @@ - **other_mistakes** (2/3): The config omits clearly present statistical columns: ANOVA q-value, log2(KO/WT), and t-test q-value KOvsWT. It also hardcodes an increases predicate without encoding direction/significance filters or comparison metadata beyond selected KR annotations. → *Add annotations for column E ANOVA q-value, column F log2_ko_wt, and column G q_value_ko_vs_wt. Include or qualify the predicate using the relevant log2 fold change and q-value columns, especially for the comparison supporting rapamycin up-regulation.* - **top issues:** Predicate is too generic for a proteomics abundance table and is not tied to a specific statistically supported comparison.; Measured protein identifier columns are ignored in favor of gene names, reducing entity precision and coverage.; Important statistical columns, including ANOVA q-value and KO/WT fold-change/q-value, are not captured as annotations. -## PMC13161869 — **acceptable** (config sha256: `1aa5a8bf5ded`) +## PMC13161869 — **acceptable** (config sha256: `a316ead5d5ab`) - **predicate_appropriateness** (2/3): The config uses the generic biolink:associated_with for ion-level proteomics associations with a disease term. The table contains abundance estimates and p-values, so a more specific relation is expected. → *Use biolink:correlated_with for protein/ion abundance association with the condition, or biolink:gene_associated_with_condition when subjects resolve to genes. If modeling the disease as affecting protein abundance, reverse the direction and use biolink:affects_amount_or_activity_of.* - **encoding_correctness** (2/3): Subject column A and Protein/Gene prioritization are plausible, and the regex cleanup is useful. However, the resulting subjects include an MGI identifier and duplicate UniProt identifiers, and the disease object is hard-coded rather than visible in the table. → *Constrain identifier resolution to human gene/protein namespaces such as NCBIGene, UniProtKB, and ENSEMBL; deduplicate subjects; and verify that MONDO:0007739 is the correct condition for this supplementary table.* @@ -28,7 +28,7 @@ - **other_mistakes** (2/3): The config omits clearly present SE and T-statistic columns for both analyses, and the sample edges do not expose the analysis-specific statistical annotations. → *Add annotations for SE and T-statistic columns for Analysis 1 and Analysis 2, verify the hidden adjusted p-value column, and ensure statistical values are attached to edges as attributes.* - **top issues:** Generic associated_with predicate used instead of a proteomics/condition-specific predicate.; Subject normalization leaks a non-human MGI identifier and produces duplicate UniProt edges despite human taxon 9606.; Important statistical columns, especially SE and T-statistic, are not annotated. -## PMC12900646 — **acceptable** (config sha256: `560d11724293`) +## PMC12900646 — **acceptable** (config sha256: `c8745021082f`) - **predicate_appropriateness** (2/3): The config uses the generic biolink:associated_with predicate for a statistical trait-to-cell-type association. This is not clearly wrong, but it is not maximally specific and does not explicitly reflect that the relationship is supported by quantitative association statistics. → *Keep biolink:associated_with if no more specific Biolink predicate is appropriate, but ensure all statistical columns are attached as annotations. If the z-score is intended as a correlation-like measure, consider biolink:correlated_with; otherwise add method/provenance qualifiers for gchromVAR/SuSiE/mvSuSiE.* - **encoding_correctness** (2/3): Subject column A and object column B are correct, and the prioritize categories are broadly appropriate. However, the object normalization does not force Cell Ontology identifiers, and the sample KG includes non-CL objects such as MONDO/UMLS terms. Trait regex normalization also misses some likely variants, especially misspelled count traits. → *Add explicit exact mappings from cell-type abbreviations to CL CURIEs, or otherwise constrain resolution to CL. Extend trait regexes to handle all suffixes and misspellings, e.g. Basophill_count, Eosinophill_count, Neutrophill_count.* @@ -37,7 +37,7 @@ - **other_mistakes** (1/3) ⚠️: The config annotates only mvsusie z-score and mvsusie lfsr, but the table also clearly contains susie z-score and susie lfsr columns. It also collapses GMP-A, GMP-B, and GMP-C into the same cell-type label, potentially losing biological granularity. → *Add annotations for the susie z-score and susie lfsr columns, likely columns C and E. Preserve GMP subtype distinctions if possible, or record them as qualifiers/labels rather than silently collapsing them.* - **top issues:** Missing annotations for the susie z-score and susie lfsr columns; Cell-type object resolution is not tightly constrained to CL, allowing UMLS/MONDO objects in the sample KG; Predicate is generic and does not fully reflect the statistical association nature of the table -## PMC9187732 — **poor** (config sha256: `e8cb8eacc72d`) +## PMC9187732 — **poor** (config sha256: `468029c6b43d`) - **predicate_appropriateness** (1/3) ⚠️: The config asserts gene_associated_with_condition between each gene and MONDO:0004988, but the visible table only contains mean expression in controls and expression percentile for genes; it does not provide a disease/condition association, nor a condition column. → *Use gene_associated_with_condition only when the table explicitly links genes to a condition. For this table, model gene expression/percentile annotations or omit the disease object unless an explicit table-supported condition is present.* - **encoding_correctness** (2/3): Subject encoding column A, prioritize Gene, and taxon 9606 are appropriate, and annotations use the correct B/C columns. However, the object is a hardcoded MONDO identifier not derived from any visible table column and may not match the 16p11.2 sheet context. → *Derive the object from an explicit table column/header or omit it. If an article-level disease must be used, verify the exact MONDO term and document the contextual source.* @@ -46,7 +46,7 @@ - **other_mistakes** (2/3): The configured mean_expression and percentile annotations are not visible in the sample edge JSON, and the fixed disease object appears invented relative to the displayed table. The second sheet is not mapped, though one sheet was explicitly selected. → *Ensure configured annotations are emitted as edge/node attributes. Do not add fixed disease objects unless justified by the table, and map or report additional sheets separately if required.* - **top issues:** Unsupported gene_associated_with_condition edges: the table reports expression percentiles in controls, not gene-condition associations.; Hardcoded MONDO:0004988 object is not grounded in the visible table and may be an over-inferred article context.; Configured annotations are not visible in the sample edges, so numeric table values may not be carried into the KG. -## PMC13099431 — **good** (config sha256: `076cdc335924`) +## PMC13099431 — **good** (config sha256: `90366dcd5469`) - **predicate_appropriateness** (3/3): actively_involved_in is the appropriate specific Biolink predicate for gene-to-GO Biological Process relationships. It is not a generic fallback. → *No change needed. If the intent is to represent statistical enrichment rather than direct functional evidence, retain the statistical annotations to make the evidence explicit.* - **encoding_correctness** (3/3): Subject column G is correct for gene symbols, explode_by ';' is correct, taxon 9606 is appropriate, and object column A is correctly regex-extracted to GO identifiers. Prioritize categories Gene and BiologicalProcess are correct. → *No change needed.* @@ -55,7 +55,7 @@ - **other_mistakes** (2/3): The Overlap column (B) is present in the table but was not captured as an annotation. It is a meaningful enrichment statistic and should be preserved. Term labels are also discarded, although GO IDs are sufficient for normalization. → *Add an annotation for column B, e.g. {annotation: overlap, method: column, encoding: B}. Optionally preserve the GO term label as an annotation if supported.* - **top issues:** Missing annotation for the Overlap column, which is a clear and relevant per-row statistic.; GO term labels are not retained as annotations, although canonical GO IDs are correctly extracted. -## PMC13172311 — **poor** (config sha256: `af8d05069765`) +## PMC13172311 — **poor** (config sha256: `e1d3542f1fda`) - **predicate_appropriateness** (1/3) ⚠️: biolink:actively_involved_in is a plausible gene-to-GO biological-process predicate, but the visible table is only a one-column list of gene symbols from a 'vU1-8 KO v WT' comparison. There is no explicit evidence in the shown table that every listed gene is actively involved in GO:0008380 (RNA splicing). If the table represents genes affected by knockout, differential expression, or another assay, a predicate such as associated_with, affects, or a condition-specific relation would be more appropriate, or the object should be omitted if unsupported. → *Use actively_involved_in only when the table or its caption explicitly asserts gene involvement in GO:0008380. Otherwise map the gene list to the stated experimental condition or use a conservative association predicate supported by the table metadata.* - **encoding_correctness** (1/3) ⚠️: The subject encoding is reasonable: column A contains gene symbols, prioritize Gene is appropriate, and taxon 9606 helps disambiguate human genes. However, the object is a hardcoded GO:0008380 value that does not appear in the displayed 319x1 table. There is no object column or visible table-derived constant justifying that encoding. → *Derive the object from an actual table column, sheet title, caption, or explicit metadata. If no object is present, do not fabricate a constant GO term.* @@ -64,7 +64,7 @@ - **other_mistakes** (0/3) ⚠️: The major mistake is inventing or over-inferring the GO:0008380 object from a table that only shows gene symbols. The regex cleanups are also unnecessary for these gene symbols and suggest generic configuration rather than table-specific evidence. The selected sheet may be valid, but the mapping adds a biological-process annotation not present in the visible table. → *Require that constant objects, especially GO terms, be explicitly present in the table, sheet name, caption, or provided metadata. If only a gene list is available, do not attach a GO term unless the source explicitly defines the list as genes for that GO term.* - **top issues:** Hardcoded GO:0008380 object is not visible in the table and appears hallucinated or over-inferred.; actively_involved_in is not clearly supported by a one-column KO-versus-WT gene list.; Edges lack row-level evidence linking each gene to the assigned GO process. -## PMC12906585 — **poor** (config sha256: `d80ab3fe0ca0`) +## PMC12906585 — **poor** (config sha256: `6d5d4b88c16b`) - **predicate_appropriateness** (0/3) ⚠️: The table is a genome bin/coordinate map (Bin, Chr, start, end, length), but the config uses in_taxon as the primary predicate. This does not capture the main relationship in the table and collapses the data into redundant chromosome-to-taxon assertions. → *Model each row as a genomic bin or interval entity and relate it to its chromosome using a more specific predicate such as biolink:part_of, biolink:located_in, or a genomic-location predicate. If taxon is needed, include it as an annotation or secondary provenance attribute, not as the main predicate.* - **encoding_correctness** (0/3) ⚠️: The subject is derived from the Chr column and transformed into free text like 'chromosome 1', then resolved to MESH terms in the KG sample. This loses the original chr01-style identifiers and produces incorrect generic MeSH chromosome concepts instead of the intended genome/chromosome entities. → *Use a stable row-specific subject such as the Bin column or a generated bin CURIE. If chromosome is used, preserve the original chr value or use an explicit genomic entity identifier. Avoid resolving chromosome labels to MeSH; use GenomicEntity/Chromosome-appropriate identifiers or literals.* @@ -73,7 +73,7 @@ - **other_mistakes** (1/3) ⚠️: The worksheet is correct, but the statement design is poor: every bin row produces a chromosome-to-taxon edge, causing many duplicate edges and failing to represent bin identity, coordinates, or chromosome linkage meaningfully. The regex also discards the original chr identifier format. → *Create one edge or node per bin using Bin as the primary identifier, attach Chr/start/end/length as annotations or properties, and link the bin to the chromosome. Preserve original chromosome labels instead of rewriting them into ambiguous text.* - **top issues:** The primary predicate in_taxon is inappropriate for a genome bin/coordinate table.; Chromosome labels are rewritten and misresolved to generic MESH terms, losing chr01-specific identity and bin-level granularity. -## PMC13172025 — **poor** (config sha256: `86448dd76087`) +## PMC13172025 — **poor** (config sha256: `ac9f8d96c81a`) - **predicate_appropriateness** (2/3): participates_in is reasonable for a gene-to-KEGG-pathway relationship, but the configured object column resolves to mixed disease, UMLS, and GO terms in the sample KG, for which participates_in is not always the most specific predicate. The table is also an enrichment table, so module-to-pathway enrichment would be more semantically precise than generic gene-level participation. → *If mapping gene-to-KEGG pathway membership, keep participates_in but map objects to KEGG pathway identifiers. If mapping disease terms intentionally, use gene_associated_with_condition; if mapping GO biological processes, use actively_involved_in or participates_in consistently. For module-level enrichment, consider a module-to-pathway edge with an enrichment-appropriate predicate if supported.* - **encoding_correctness** (1/3) ⚠️: The subject encoding is correct: column L contains Entrez/NCBI gene IDs and exploding by '/' is appropriate. However, the object encoding is wrong: column F is the pathway Description, while the canonical KEGG pathway identifiers are in column E, such as hsa04610. This causes the KG to resolve objects to UMLS, MONDO, and GO terms instead of KEGG pathways. → *Use object encoding E for the KEGG pathway ID column and assign an appropriate KEGG pathway prefix, for example KEGG.PATHWAY or KEGG. Treat the Description column as a label or annotation, not as the primary object identifier.* @@ -82,7 +82,7 @@ - **other_mistakes** (1/3) ⚠️: The config omits clearly available enrichment metadata, especially qvalue in column K and the Cluster/module column B. Because the table is module-specific enrichment analysis, losing the module context makes the derived gene-pathway edges less faithful to the source. The sheet choice is correct, and the row_slice appears plausible. → *Add annotations for qvalue from column K and Cluster/module from column B. Consider also adding category, subcategory, GeneRatio, and BgRatio if the schema supports them.* - **top issues:** Object is taken from free-text Description column F instead of the KEGG pathway ID column E, causing misresolution to UMLS, MONDO, and GO terms rather than KEGG pathways.; Essential enrichment metadata, especially qvalue and the Cluster/module context, is not captured in the annotations. -## PMC7206184 — **poor** (config sha256: `d6785bc724be`) +## PMC7206184 — **poor** (config sha256: `cedf7b1f7445`) - **predicate_appropriateness** (1/3) ⚠️: The config uses biolink:expressed_in for gene->PANEL tissue, but the sheet is a TWAS association table (v68.lvedv.twas.alltissues) with statistical annotations, not a direct expression table. The primary relation is gene predicted-expression association with LVEDV, not a simple gene expressed_in tissue assertion. → *Model the primary association as gene_associated_with_condition (or associated_with if phenotype term is not available) between gene and LVEDV/phenotype, and capture tissue as a qualifier/annotation. Use expressed_in only when the table explicitly reports expression measurements or expression calls.* - **encoding_correctness** (2/3): Subject column C (ID) as Gene and object column A (PANEL) as AnatomicalEntity are plausible for a gene-tissue edge, and categories/taxon are reasonable. However, the object choice does not capture the TWAS trait, and the annotation encodings S/T are asserted without visible column names, making them hard to verify. → *Keep ID as Gene, but encode the trait/phenotype as the primary object or at least as a required annotation. Specify annotation columns by header names (e.g., TWAS.Z, TWAS.P) and verify that S/T are those columns.* @@ -91,7 +91,7 @@ - **other_mistakes** (1/3) ⚠️: The config reduces a TWAS table to gene-expressed_in-tissue edges and omits the central LVEDV association. It also ignores visible key columns such as BEST.GWAS.ID, BEST.GWAS.Z, EQTL.ID, EQTL.R2, and EQTL.Z, while relying on unseen S/T annotation columns. → *Add the missing statistical and supporting columns as annotations or separate edges, verify annotation column letters against headers, and restructure the KG around the primary gene/trait (or gene/tissue/trait) association.* - **top issues:** Predicate/model mismatch: TWAS association was mapped as gene expressed_in tissue rather than gene-phenotype association with tissue context.; Central phenotype/trait and key GWAS/eQTL statistics are missing from the KG representation.; Annotation columns S/T are not verifiable from the displayed columns and may be hallucinated or mis-specified. -## PMC11947420 — **acceptable** (config sha256: `8a1c74d4efc6`) +## PMC11947420 — **acceptable** (config sha256: `90413a33f8a9`) - **predicate_appropriateness** (2/3): The table is a variant/sample-level table (Chr, Pos, Ref, Alt, CSQ), not an explicit gene-disease association table. gene_associated_with_condition is the correct specific predicate for a gene~disease pair, but here it infers gene-disease association from the presence of variants in a disease cohort. → *If modeling the table faithfully, use a variant-centric relationship such as biolink:has_sequence_variant for gene-to-variant edges, or an appropriate variant-to-condition association predicate. Use gene_associated_with_condition only when the table explicitly supports gene-disease associations or add evidence qualifiers.* - **encoding_correctness** (2/3): Subject prioritization as Gene and taxon 9606 are appropriate, but encoding H selects the Symbol column rather than the Ensembl Gene column G. The disease object is a fixed MONDO value not present in any table column. → *Prefer the Ensembl gene column G where available, or map both Gene and Symbol with human taxon. Ensure the fixed MONDO object is explicitly supported by article metadata or a table column.* @@ -111,4 +111,4 @@ - [PMC12906585] For genome coordinate or bin tables, do not use in_taxon as the main predicate or resolve chromosome labels to MeSH; instead create a distinct entity for each bin using the bin identifier, preserve the original chromosome value, and link the bin to the chromosome with a specific predicate such as part_of or located_in while storing start/end/length as annotations. - [PMC13172025] When a table contains an explicit ontology or pathway identifier column, such as KEGG IDs like hsa04610, always use that identifier column as the primary object with the appropriate prefix and treat the human-readable description column only as a label or annotation. - [PMC7206184] For GWAS/TWAS/eQTL statistical tables, require the agent to identify the primary association from sheet/column metadata and map it explicitly (gene/variant to phenotype/trait) with tissue, z/p, beta/se, SNP/eQTL/GWAS fields as annotations or qualifiers; use expressed_in only when the table directly reports expression values. -- [PMC11947420] When a table contains genomic variant rows (Chr, Pos, Ref, Alt, CSQ), do not default to gene_associated_with_condition unless the table explicitly asserts gene-disease associations; instead extract variant-centric relationships or require an explicit disease column/metadata source for the object. \ No newline at end of file +- [PMC11947420] When a table contains genomic variant rows (Chr, Pos, Ref, Alt, CSQ), do not default to gene_associated_with_condition unless the table explicitly asserts gene-disease associations; instead extract variant-centric relationships or require an explicit disease column/metadata source for the object. diff --git a/src/tablassert/agent.py b/src/tablassert/agent.py index 43744ab6..3623c51c 100644 --- a/src/tablassert/agent.py +++ b/src/tablassert/agent.py @@ -1977,7 +1977,7 @@ def call(prompt: str) -> str: table, each section its own source (different file + url): # (a) tutorial-table — a text/TSV gene~disease association table -source: {kind: text, local: ./tutorial.tsv, delimiter: "\\t"} +source: {kind: text, local: ./tutorial.tsv, url: ["https://example.com/tutorial.tsv"], delimiter: "\\t"} statement: subject: {method: column, encoding: A, prioritize: [Gene]} predicate: associated_with @@ -1990,7 +1990,7 @@ def call(prompt: str) -> str: - {annotation: effect_type, method: value, encoding: odds_ratio} # (b) ALAMV6 — an excel organism~chemical correlation table (fixed chemical object) -source: {kind: excel, local: ./ALAM.XLSX, sheet: "all correlations", row_slice: [2, auto]} +source: {kind: excel, local: ./ALAM.XLSX, url: ["https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx"], sheet: "all correlations", row_slice: [2, auto]} statement: subject: method: column @@ -2006,12 +2006,12 @@ def call(prompt: str) -> str: template: provenance: {repo: PMC, publication: PMC11708054} sections: - - source: {kind: excel, local: ./downloads/PMC11708054/PMC11708054.1/s0006.xlsx, url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC11708054.1/s0006.xlsx", sheet: "all correlations", row_slice: [2, auto]} + - source: {kind: excel, local: ./downloads/PMC11708054/PMC11708054.1/s0006.xlsx, url: ["https://pmc-oa-opendata.s3.amazonaws.com/PMC11708054.1/s0006.xlsx"], sheet: "all correlations", row_slice: [2, auto]} statement: subject: {method: column, encoding: A, prioritize: [OrganismTaxon], avoid: [Gene]} predicate: correlated_with object: {method: value, encoding: "CHEBI:41774"} - - source: {kind: text, local: ./downloads/PMC11708054/PMC11708054.1/s0003.tsv, url: "https://pmc-oa-opendata.s3.amazonaws.com/PMC11708054.1/s0003.tsv", delimiter: "\\t"} + - source: {kind: text, local: ./downloads/PMC11708054/PMC11708054.1/s0003.tsv, url: ["https://pmc-oa-opendata.s3.amazonaws.com/PMC11708054.1/s0003.tsv"], delimiter: "\\t"} statement: subject: {method: column, encoding: A, prioritize: [Gene]} predicate: associated_with @@ -2157,7 +2157,8 @@ def build_agent( # FakeModel-driven agent passes the validate_section final-answer gate and terminates offline. _FAKE_DEFAULT_YAML: str = """\ source: - url: https://example.com/test.tsv + url: + - https://example.com/test.tsv local: ./test.tsv kind: text delimiter: "\\t" @@ -2607,12 +2608,16 @@ def run_supervisor( tables: list[Path] = [path.resolve() for path in candidate_tables(files)] table_list: str if local_dir is not None: - # Local payload: no fabricated S3 link; the agent sets source.url to the original link if known. - table_list = "\n".join(f" - {path} (local payload; set source.url to the original download link if known)" for path in tables) + # Local payload: no fabricated S3 link; source.url is required, so the agent supplies the + # table's original public source URL (a list) rather than inventing one. + table_list = "\n".join( + f" - {path} (local payload; source.url is REQUIRED — supply the table's original public source URL as a list; do not fabricate one)" + for path in tables + ) else: # Present each candidate table as `local -> url` (W3): the agent authors one section per table, # each with its OWN source.local + source.url (the file's public HTTPS link). prefix = parent dir. - table_list = "\n".join(f" - {path} (source.url: {public_url(path.parent.name, path.name)})" for path in tables) + table_list = "\n".join(f" - {path} (source.url: [{public_url(path.parent.name, path.name)}])" for path in tables) article_xml: Path | None = next((path for path in files if path.suffix.lower() in {".xml", ".nxml"}), None) metrics: dict[str, object] = {} diff --git a/src/tablassert/lib.py b/src/tablassert/lib.py index 799210f9..4b284b2f 100644 --- a/src/tablassert/lib.py +++ b/src/tablassert/lib.py @@ -995,7 +995,7 @@ def _provenance_ops(self: Self) -> list[Any]: # Biolink `primary_knowledge_source` is a scalar; `sources` carries the # structured retrieval provenance (roles, upstream ids, record urls). (value, ("primary_knowledge_source", primary_knowledge_source)) if primary_knowledge_source else None, - (retrieval_sources, (primary_knowledge_source, upstream_ids, [str(self.source.url)])) if primary_knowledge_source else None, + (retrieval_sources, (primary_knowledge_source, upstream_ids, [str(u) for u in self.source.url])) if primary_knowledge_source else None, (publications, (publication_values,)) if publication_values else None, # Prune first so class-rejected values are handed to the study rather than lost. (prune_to_class, ()), diff --git a/src/tablassert/models.py b/src/tablassert/models.py index 9390542b..44f4feeb 100644 --- a/src/tablassert/models.py +++ b/src/tablassert/models.py @@ -86,7 +86,11 @@ def comparison_datatypes(self: Self) -> Self: class BaseSource(TablaBase): local: Path = Field(..., description="Local path to read from or download into.") - url: HttpUrl = Field(..., description="Remote source URL fetched before parsing.") + url: list[HttpUrl] = Field( + ..., + min_length=1, + description="One or more remote source URL(s) recorded as provenance; emitted in the edge `sources` list under the primary entry's `source_record_urls` list and in the RIG. Format-validated only; not fetched.", + ) rows: list[NonNegativeInt] | None = Field(None, description="Zero-based row indices kept after any row_slice crop.", examples=[[0, 2, 5]]) row_slice: list[NonNegativeInt | Literal[Tokens.AUTO]] | None = Field( diff --git a/tests/agent_fixtures/GENE_DISEASE/reference_config.yaml b/tests/agent_fixtures/GENE_DISEASE/reference_config.yaml index 81d15ccd..f68bcdbd 100644 --- a/tests/agent_fixtures/GENE_DISEASE/reference_config.yaml +++ b/tests/agent_fixtures/GENE_DISEASE/reference_config.yaml @@ -11,7 +11,8 @@ sections: - source: kind: text local: ./source_table.csv - url: https://example.org/GENE_DISEASE/source_table.csv + url: + - https://example.org/GENE_DISEASE/source_table.csv delimiter: "," statement: subject: diff --git a/tests/agent_fixtures/PMC11708054/ALAMV6.yaml b/tests/agent_fixtures/PMC11708054/ALAMV6.yaml index 01821e1b..80825427 100644 --- a/tests/agent_fixtures/PMC11708054/ALAMV6.yaml +++ b/tests/agent_fixtures/PMC11708054/ALAMV6.yaml @@ -7,7 +7,8 @@ template: source: kind: excel local: ./DATALAKE/ALAM.XLSX - url: https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx + url: + - https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx row_slice: - 2 - auto diff --git a/tests/agent_fixtures/PMC11708054/reference_config.yaml b/tests/agent_fixtures/PMC11708054/reference_config.yaml index 3cd432f2..8df19db4 100644 --- a/tests/agent_fixtures/PMC11708054/reference_config.yaml +++ b/tests/agent_fixtures/PMC11708054/reference_config.yaml @@ -8,7 +8,8 @@ template: source: kind: text local: ./source_table.csv - url: https://example.org/PMC11708054/source_table.csv + url: + - https://example.org/PMC11708054/source_table.csv delimiter: "," statement: subject: diff --git a/tests/fixtures/minimal_section.yaml b/tests/fixtures/minimal_section.yaml index bd34ffb3..9d5c8c78 100644 --- a/tests/fixtures/minimal_section.yaml +++ b/tests/fixtures/minimal_section.yaml @@ -1,5 +1,6 @@ source: - url: https://example.com/test.tsv + url: + - https://example.com/test.tsv local: ./test.tsv kind: text delimiter: "\t" diff --git a/tests/fixtures/minimal_section_with_sections.yaml b/tests/fixtures/minimal_section_with_sections.yaml index cc60366f..013c7e48 100644 --- a/tests/fixtures/minimal_section_with_sections.yaml +++ b/tests/fixtures/minimal_section_with_sections.yaml @@ -1,6 +1,7 @@ template: source: - url: https://example.com/test.xlsx + url: + - https://example.com/test.xlsx local: ./test.xlsx kind: excel sheet: Sheet1 diff --git a/tests/test_agent_branches.py b/tests/test_agent_branches.py index ae2e03ad..388c375f 100644 --- a/tests/test_agent_branches.py +++ b/tests/test_agent_branches.py @@ -42,7 +42,7 @@ def _section_cfg(subject: dict[str, Any], object_: dict[str, Any], predicate: st """A schema-valid Section config YAML with the given subject/object encodings.""" return yaml.safe_dump( { - "source": {"kind": "text", "local": "./t.tsv", "url": "https://e.com/t.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./t.tsv", "url": ["https://e.com/t.tsv"], "delimiter": "\t"}, "statement": {"subject": subject, "predicate": predicate, "object": object_}, "provenance": {"repo": "PMC", "publication": "PMC0000000"}, } @@ -129,7 +129,7 @@ def test_reflexion_with_fullmap_rescores_and_promotes(tmp_path: Path) -> None: table.write_text("g__brca1\tmapk1\ng__brca1\tmapk1\n") cfg = yaml.safe_dump( { - "source": {"kind": "text", "local": str(table), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(table), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_agent_build.py b/tests/test_agent_build.py index 0e9328fd..b2cd4da8 100644 --- a/tests/test_agent_build.py +++ b/tests/test_agent_build.py @@ -63,7 +63,7 @@ def _write_table(tmp_path: Path, text: str) -> Path: def _section_config(data: Path) -> dict[str, Any]: """A bare merged Section config: column A subject, column B object, PMC provenance.""" return { - "source": {"kind": "text", "local": str(data), "url": "https://example.com/data.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(data), "url": ["https://example.com/data.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -217,7 +217,7 @@ def test_build_and_audit_measures_relative_source_with_correct_cwd(tmp_path: Pat workdir.mkdir(parents=True) (workdir / "rel.tsv").write_text("brca1\tmapk1\nbrca1\tmapk1\n") cfg: dict[str, Any] = { - "source": {"kind": "text", "local": "rel.tsv", "url": "https://example.com/rel.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "rel.tsv", "url": ["https://example.com/rel.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -246,7 +246,7 @@ def test_build_and_audit_multi_section_two_files(tmp_path: Path, redb: Path) -> "template": {"provenance": {"repo": "PMC", "publication": "PMC1"}}, "sections": [ { - "source": {"kind": "text", "local": str(t1), "url": "https://example.com/s1.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(t1), "url": ["https://example.com/s1.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -254,7 +254,7 @@ def test_build_and_audit_multi_section_two_files(tmp_path: Path, redb: Path) -> }, }, { - "source": {"kind": "text", "local": str(t2), "url": "https://example.com/s2.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(t2), "url": ["https://example.com/s2.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_agent_coverage.py b/tests/test_agent_coverage.py index 5d399ec4..287645b7 100644 --- a/tests/test_agent_coverage.py +++ b/tests/test_agent_coverage.py @@ -64,7 +64,7 @@ def _write_table(tmp_path: Path, text: str) -> Path: def _section_config(data: Path, *, object_method: str = "column", object_encoding: str = "B") -> dict[str, Any]: """A bare merged Section config: column A subject, configurable object, PMC provenance.""" return { - "source": {"kind": "text", "local": str(data), "url": "https://example.com/data.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(data), "url": ["https://example.com/data.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -230,7 +230,7 @@ def test_coverage_multi_cwd_resolves_relative_source(tmp_path: Path, redb: Path) elsewhere.mkdir(parents=True) (elsewhere / "rel.tsv").write_text("brca1\tmapk1\nbrca1\tmapk1\n") cfg: dict[str, Any] = { - "source": {"kind": "text", "local": "rel.tsv", "url": "https://example.com/rel.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "rel.tsv", "url": ["https://example.com/rel.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_agent_derive.py b/tests/test_agent_derive.py index 2105cd78..839c5bed 100644 --- a/tests/test_agent_derive.py +++ b/tests/test_agent_derive.py @@ -28,7 +28,7 @@ "source": { "kind": "excel", "local": "./DATALAKE/ALAM.XLSX", - "url": "https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx", + "url": ["https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx"], "row_slice": [2, "auto"], "sheet": "all correlations", }, @@ -57,7 +57,7 @@ def _minimal_section() -> dict[str, Any]: """A valid bare merged section dict (mirrors tests/fixtures/minimal_section.yaml).""" return { - "source": {"url": "https://example.com/test.tsv", "local": "./test.tsv", "kind": "text", "delimiter": "\t"}, + "source": {"url": ["https://example.com/test.tsv"], "local": "./test.tsv", "kind": "text", "delimiter": "\t"}, "statement": {"subject": {"method": "value", "encoding": "BRCA1"}, "object": {"method": "value", "encoding": "TP53"}}, "provenance": {"repo": "PMC", "publication": "PMC0000000"}, } diff --git a/tests/test_agent_eval.py b/tests/test_agent_eval.py index cfe409f3..b18a4ada 100644 --- a/tests/test_agent_eval.py +++ b/tests/test_agent_eval.py @@ -48,7 +48,7 @@ # A genuinely valid minimal Section config (used wherever a schema-valid YAML string is needed). VALID_CFG: str = yaml.safe_dump( { - "source": {"kind": "text", "local": "./t.tsv", "url": "https://e.com/t.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./t.tsv", "url": ["https://e.com/t.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "value", "encoding": "BRCA1"}, "predicate": "associated_with", @@ -181,7 +181,7 @@ def test_debias_helpers_pure() -> None: def test_reflexion_improve_offline_schema_valid() -> None: """reflexion_improve returns a schema-valid config + non-empty reflections and never raises.""" cfg = { - "source": {"kind": "text", "local": "./t.tsv", "url": "https://e.com/t.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./t.tsv", "url": ["https://e.com/t.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "correlated_with", @@ -559,7 +559,7 @@ def test_judge_provenance_smarter() -> None: def cfg(provenance: dict[str, Any]) -> str: return yaml.safe_dump( { - "source": {"kind": "text", "local": "./t.tsv", "url": "https://e.com/t.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./t.tsv", "url": ["https://e.com/t.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "value", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_agent_multisection.py b/tests/test_agent_multisection.py index 9f63dd13..1ac17d69 100644 --- a/tests/test_agent_multisection.py +++ b/tests/test_agent_multisection.py @@ -68,7 +68,7 @@ def _write_table(tmp_path: Path, name: str, text: str) -> Path: def _section(local: Path, *, subject: str = "A", obj: str = "B") -> dict[str, Any]: """One section: column subject/object, its OWN source (local + url), associated_with.""" return { - "source": {"kind": "text", "local": str(local), "url": f"https://example.com/{local.name}", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(local), "url": [f"https://example.com/{local.name}"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": subject}, "predicate": "associated_with", diff --git a/tests/test_agent_propose.py b/tests/test_agent_propose.py index 82f56462..dd795e4b 100644 --- a/tests/test_agent_propose.py +++ b/tests/test_agent_propose.py @@ -39,7 +39,7 @@ def _alamv6_section() -> dict[str, Any]: "source": { "kind": "excel", "local": "./DATALAKE/ALAM.XLSX", - "url": "https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx", + "url": ["https://pmc.ncbi.nlm.nih.gov/articles/instance/11708054/bin/mbio.01679-24-s0006.xlsx"], "row_slice": [2, "auto"], "sheet": "all correlations", }, @@ -179,7 +179,7 @@ def test_propose_tool() -> None: def _taxonomic_noise_section() -> dict[str, Any]: """A bare section whose subject has BOTH taxonomic and noise unresolved terms.""" return { - "source": {"kind": "text", "local": "./d.tsv", "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./d.tsv", "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_agent_storage.py b/tests/test_agent_storage.py index 8337a023..060a839e 100644 --- a/tests/test_agent_storage.py +++ b/tests/test_agent_storage.py @@ -189,7 +189,7 @@ def fake_fetch(pmc_id: str, outdir: Path, *, timeout: int = 120) -> list[Path]: good_yaml: str = yaml.safe_dump( { - "source": {"kind": "text", "local": str(expected_outdir / "good.tsv"), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(expected_outdir / "good.tsv"), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -247,7 +247,7 @@ def fake_fetch(pmc_id: str, outdir: Path, *, timeout: int = 120) -> list[Path]: good_yaml: str = yaml.safe_dump( { - "source": {"kind": "text", "local": str(download_dir / "good.tsv"), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(download_dir / "good.tsv"), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -319,7 +319,7 @@ def fake_fetch(pmc_id: str, outdir: Path, *, timeout: int = 120) -> list[Path]: good_yaml: str = yaml.safe_dump( { - "source": {"kind": "text", "local": str(download_dir / "good.tsv"), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(download_dir / "good.tsv"), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -386,7 +386,7 @@ def fake_fetch(pmc_id: str, outdir: Path, *, timeout: int = 120) -> list[Path]: good_yaml: str = yaml.safe_dump( { - "source": {"kind": "text", "local": str(stable_table), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(stable_table), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_agent_supervisor.py b/tests/test_agent_supervisor.py index 52937f3d..34560aa7 100644 --- a/tests/test_agent_supervisor.py +++ b/tests/test_agent_supervisor.py @@ -81,7 +81,7 @@ def _write_table(tmp_path: Path, name: str, text: str) -> Path: def _column_cfg(table: Path) -> dict[str, Any]: """A valid merged Section config: subject=column A, object=column B, PMC provenance.""" return { - "source": {"kind": "text", "local": str(table), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(table), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_cli_validation.py b/tests/test_cli_validation.py index 801de550..76047144 100644 --- a/tests/test_cli_validation.py +++ b/tests/test_cli_validation.py @@ -58,7 +58,7 @@ def _valid_table_config() -> dict[str, Any]: """Minimal valid table config (value-encoded; validation never reads the source file).""" return { "template": { - "source": {"kind": "text", "local": "./test.tsv", "url": "https://example.com/test.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./test.tsv", "url": ["https://example.com/test.tsv"], "delimiter": "\t"}, "statement": {"subject": {"method": "value", "encoding": "BRCA1"}, "object": {"method": "value", "encoding": "TP53"}}, "provenance": {"repo": "PMC", "publication": "PMC0000000"}, } diff --git a/tests/test_cover_agent_core.py b/tests/test_cover_agent_core.py index f72a30e4..d3eb77a3 100644 --- a/tests/test_cover_agent_core.py +++ b/tests/test_cover_agent_core.py @@ -215,7 +215,7 @@ def _write_table(tmp_path: Path, text: str) -> Path: def _section_config(data: Path) -> dict[str, Any]: """A bare merged Section config: column A subject, column B object, PMC provenance.""" return { - "source": {"kind": "text", "local": str(data), "url": "https://example.com/data.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(data), "url": ["https://example.com/data.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_cover_agent_eval.py b/tests/test_cover_agent_eval.py index 5a670984..2f32b6a9 100644 --- a/tests/test_cover_agent_eval.py +++ b/tests/test_cover_agent_eval.py @@ -34,7 +34,7 @@ # A genuinely valid minimal Section config (schema-valid wherever a valid YAML string is needed). VALID_CFG: str = yaml.safe_dump( { - "source": {"kind": "text", "local": "./t.tsv", "url": "https://e.com/t.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./t.tsv", "url": ["https://e.com/t.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "value", "encoding": "BRCA1"}, "predicate": "associated_with", diff --git a/tests/test_cover_agent_propose.py b/tests/test_cover_agent_propose.py index 2fd9e74b..53a6f86c 100644 --- a/tests/test_cover_agent_propose.py +++ b/tests/test_cover_agent_propose.py @@ -94,7 +94,7 @@ def _write_table(tmp_path: Path, name: str, text: str) -> Path: def _column_cfg(table: Path) -> dict[str, Any]: """A valid merged Section config: subject=column A, object=column B, PMC provenance.""" return { - "source": {"kind": "text", "local": str(table), "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(table), "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -192,7 +192,7 @@ def test_propose_edit_fails_validation(monkeypatch: pytest.MonkeyPatch) -> None: """ monkeypatch.setattr("tablassert.agent.validate_section", lambda *args, **kwargs: False) config: dict[str, Any] = { - "source": {"kind": "text", "local": "./d.tsv", "url": "https://e.com/d.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./d.tsv", "url": ["https://e.com/d.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", diff --git a/tests/test_e2e_smoke.py b/tests/test_e2e_smoke.py index 299f4e41..7edff84c 100644 --- a/tests/test_e2e_smoke.py +++ b/tests/test_e2e_smoke.py @@ -72,7 +72,7 @@ def test_build_pipeline_against_real_redb(tmp_path: Path, monkeypatch: pytest.Mo table: Path = tmp_path / "table.yaml" table_config: dict[str, Any] = { "template": { - "source": {"kind": "text", "local": str(data), "url": "https://example.com/data.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": str(data), "url": ["https://example.com/data.tsv"], "delimiter": "\t"}, "statement": { "subject": {"method": "column", "encoding": "A"}, "predicate": "associated_with", @@ -122,7 +122,7 @@ def test_validate_command_happy_path(tmp_path: Path) -> None: config: Path = tmp_path / "table.yaml" table_config: dict[str, Any] = { "template": { - "source": {"kind": "text", "local": "./test.tsv", "url": "https://example.com/test.tsv", "delimiter": "\t"}, + "source": {"kind": "text", "local": "./test.tsv", "url": ["https://example.com/test.tsv"], "delimiter": "\t"}, "statement": {"subject": {"method": "value", "encoding": "BRCA1"}, "object": {"method": "value", "encoding": "TP53"}}, "provenance": {"repo": "PMC", "publication": "PMC0000000"}, } diff --git a/tests/test_lib.py b/tests/test_lib.py index 0e43c532..d9ccb06e 100644 --- a/tests/test_lib.py +++ b/tests/test_lib.py @@ -79,7 +79,7 @@ def write_text_section(tmp_path: Path, name: str, section: dict[str, object], ro source_path.write_text("\n".join(rows) + "\n") source_config: object = section.get("source", {}) source_overrides: dict[str, object] = source_config if isinstance(source_config, dict) else {} - section["source"] = {"url": f"https://example.com/{name}.tsv", "local": str(source_path), "kind": "text", "delimiter": "\t", **source_overrides} + section["source"] = {"url": [f"https://example.com/{name}.tsv"], "local": str(source_path), "kind": "text", "delimiter": "\t", **source_overrides} to_yaml(table_path, section) return table_path, source_path diff --git a/tests/test_models.py b/tests/test_models.py index 6ab1e3b9..6824ad40 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -83,14 +83,14 @@ def test_graph_rejects_qc_and_log_keys() -> None: def test_excel_source_valid() -> None: """valid minimal excel section.""" - source: Excel = Excel(local=Path("./test.xlsx"), url="https://example.com/test.xlsx", kind="excel", sheet="Sheet1") # pyright: ignore + source: Excel = Excel(local=Path("./test.xlsx"), url=["https://example.com/test.xlsx"], kind="excel", sheet="Sheet1") # pyright: ignore assert source.kind == "excel" assert source.sheet == "Sheet1" def test_text_source_valid() -> None: """valid text source.""" - source: Text = Text(local=Path("./test.tsv"), url="https://example.com/test.tsv", kind="text", delimiter="\t") # pyright: ignore + source: Text = Text(local=Path("./test.tsv"), url=["https://example.com/test.tsv"], kind="text", delimiter="\t") # pyright: ignore assert source.kind == "text" assert source.delimiter == "\t" @@ -364,7 +364,7 @@ def test_section_rejects_extra_fields() -> None: """section rejects extra fields.""" with pytest.raises(ValidationError): Section( - source={"local": "./t.tsv", "url": "https://example.com/t.tsv", "kind": "text"}, + source={"local": "./t.tsv", "url": ["https://example.com/t.tsv"], "kind": "text"}, statement={"subject": {"method": "value", "encoding": "A"}, "object": {"method": "value", "encoding": "B"}}, provenance={"repo": "PMC", "publication": "PMC000"}, unknown_field="bad", # pyright: ignore @@ -374,17 +374,35 @@ def test_section_rejects_extra_fields() -> None: def test_section_with_row_slice() -> None: """section with row slice.""" source: Text = Text( # pyright: ignore - local=Path("./test.tsv"), url="https://example.com/test.tsv", kind="text", row_slice=[2, "auto"] + local=Path("./test.tsv"), url=["https://example.com/test.tsv"], kind="text", row_slice=[2, "auto"] ) assert source.row_slice == [2, "auto"] def test_section_with_rows() -> None: """section with rows.""" - source: Text = Text(local=Path("./test.tsv"), url="https://example.com/test.tsv", kind="text", rows=[1, 2, 5]) # pyright: ignore + source: Text = Text(local=Path("./test.tsv"), url=["https://example.com/test.tsv"], kind="text", rows=[1, 2, 5]) # pyright: ignore assert source.rows == [1, 2, 5] +def test_source_url_accepts_multiple() -> None: + """a source records every URL in order (multiple URLs per section).""" + source: Text = Text(local=Path("./t.tsv"), url=["https://a.example.com/x", "https://b.example.com/y"], kind="text") # pyright: ignore + assert [str(u).rstrip("/") for u in source.url] == ["https://a.example.com/x", "https://b.example.com/y"] + + +def test_source_url_rejects_scalar() -> None: + """`url` is a list; a legacy scalar URL is rejected.""" + with pytest.raises(ValidationError): + Text.model_validate({"local": "./t.tsv", "url": "https://example.com/x.tsv", "kind": "text"}) # pyright: ignore + + +def test_source_url_rejects_empty() -> None: + """an empty `url` list is rejected (at least one URL is required).""" + with pytest.raises(ValidationError): + Text(local=Path("./t.tsv"), url=[], kind="text") # pyright: ignore + + def test_section_rows_and_row_slice_accept_zero() -> None: """Guard: `rows` and `row_slice` are zero-based, so 0 is a valid index. @@ -394,23 +412,23 @@ def test_section_rows_and_row_slice_accept_zero() -> None: ``rows``/``row_slice`` starting at the first row (index 0) must validate, while a genuinely negative index is still rejected. """ - rows_source: Text = Text(local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", rows=[0, 2, 5]) # pyright: ignore + rows_source: Text = Text(local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", rows=[0, 2, 5]) # pyright: ignore assert rows_source.rows == [0, 2, 5] - slice_source: Text = Text(local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", row_slice=[0, 50]) # pyright: ignore + slice_source: Text = Text(local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", row_slice=[0, 50]) # pyright: ignore assert slice_source.row_slice == [0, 50] with pytest.raises(ValidationError): - Text(local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", rows=[-1, 2]) # pyright: ignore + Text(local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", rows=[-1, 2]) # pyright: ignore with pytest.raises(ValidationError): - Text(local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", row_slice=[-1, 50]) # pyright: ignore + Text(local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", row_slice=[-1, 50]) # pyright: ignore def test_section_with_reindex() -> None: """section with reindex.""" source: Text = Text( # pyright: ignore - local=Path("./test.tsv"), url="https://example.com/test.tsv", kind="text", reindex=[{"column": "A", "comparison": "ne", "comparator": ""}] + local=Path("./test.tsv"), url=["https://example.com/test.tsv"], kind="text", reindex=[{"column": "A", "comparison": "ne", "comparator": ""}] ) assert len(source.reindex) == 1 # pyright: ignore assert source.reindex[0].column == "A" # pyright: ignore @@ -419,7 +437,7 @@ def test_section_with_reindex() -> None: def test_section_with_qualifiers() -> None: """section with qualifiers.""" section: Section = Section( # pyright: ignore - source={"local": "./t.tsv", "url": "https://example.com/t.tsv", "kind": "text"}, + source={"local": "./t.tsv", "url": ["https://example.com/t.tsv"], "kind": "text"}, statement={ "subject": {"method": "value", "encoding": "A"}, "object": {"method": "value", "encoding": "B"}, @@ -433,7 +451,7 @@ def test_section_with_qualifiers() -> None: def test_section_with_annotations() -> None: """section with annotations.""" data: dict[str, Any] = { - "source": {"local": "./t.tsv", "url": "https://example.com/t.tsv", "kind": "text"}, + "source": {"local": "./t.tsv", "url": ["https://example.com/t.tsv"], "kind": "text"}, "statement": {"subject": {"method": "value", "encoding": "A"}, "object": {"method": "value", "encoding": "B"}}, "provenance": {"repo": "PMC", "publication": "PMC000"}, "annotations": [ @@ -471,7 +489,7 @@ def test_source_with_both_rows_and_row_slice_rejected() -> None: Catches an ambiguous row-selection config at config time instead of deep inside a multi-hour build. """ with pytest.raises(ValidationError) as exc_info: - Text(local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", rows=[1], row_slice=[1, 5]) # pyright: ignore + Text(local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", rows=[1], row_slice=[1, 5]) # pyright: ignore assert "config-rows-and-row-slice-conflict" in str(exc_info.value) @@ -560,7 +578,7 @@ def test_deprecated_key_in_registry_warns_but_still_validates(monkeypatch: pytes """ monkeypatch.setitem(models.DEPRECATED_KEYS, "delimiter", "delimiter is deprecated; use 'sep' instead") with pytest.warns(UserWarning, match="sep"): - source: Text = Text(local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", delimiter="\t") # pyright: ignore + source: Text = Text(local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", delimiter="\t") # pyright: ignore assert source.delimiter == "\t" diff --git a/tests/test_progress.py b/tests/test_progress.py index a7d8b358..7b099e81 100644 --- a/tests/test_progress.py +++ b/tests/test_progress.py @@ -125,7 +125,7 @@ def test_dynamic_loop_never_overflows_when_a_phase_total_resets_to_zero() -> Non "comparison-bad-comparator-type": lambda: Reindex(column="A", comparison=Comparisons.EQ, comparator=5), "comparison-nonnumeric-comparator": lambda: Reindex(column="A", comparison=Comparisons.GT, comparator="x"), "config-rows-and-row-slice-conflict": lambda: Text( - local=Path("./t.tsv"), url="https://example.com/t.tsv", kind="text", rows=[1], row_slice=[1, 5] + local=Path("./t.tsv"), url=["https://example.com/t.tsv"], kind="text", rows=[1], row_slice=[1, 5] ), # pyright: ignore "regex-bad-pattern": lambda: Regex(pattern="(", replacement=" "), "regex-bad-replacement": lambda: Regex(pattern="ok", replacement="("),