Skip to content

feat: spec 004, the source toolkit (benchcommons-tools-dataset 0.1.0) - #7

Merged
matbrgz merged 17 commits into
mainfrom
feat/004-source-toolkit
Oct 12, 2026
Merged

matbrgz merged 17 commits into
mainfrom
feat/004-source-toolkit

Conversation

@matbrgz

@matbrgz matbrgz commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What changed

Spec 004, the source toolkit: the generic parts of dataset-names that download, read and register upstream sources move here as the installable package benchcommons-tools-dataset (import datasettool), per the owner decision of 2026-10-12.

  • Spec, plan and tasks: docs/specs/004-source-toolkit/; roadmap row 4 (no dependency on 001).
  • ADRs: 0003 package name, 0004 dataset repos depend on a uv git source pinned to a tag, 0005 runtime dependencies (pyyaml==6.0.3, xlrd==2.0.2), 0006 accepted licences per dataset (links .product ADR 0003), 0007 the toolkit lives in tools-dataset rather than tools-commons. 0001 and 0002 stay reserved for spec 001's T1.
  • datasettool.sources.cache, .spreadsheet, .adapters, .registry and datasettool.files, each ported with its tests (origin in each commit message: ported from BenchCommons/dataset-names@e7e8405 ...; the spreadsheet DTD and encoding guards from dataset-names@eaaa4d1, PR #39).
  • Apache-2.0 LICENSE, pyproject.toml, uv.lock, AGENTS.md and README.md with stack and commands.
  • CI keeps the org docs job and adds tests (uv sync --locked, uv run pytest -q) and install-from-git (fresh uv project installs the commit under test from this repo's git URL and imports every module).
  • Spec 001 T2 (skeleton) now points to spec 004; repo URLs follow the rename to tools-dataset.

Acceptance criteria and evidence

  1. Ported tests pass with unchanged vectors: 360 tests pass locally (uv run pytest -q). Port map in plan section 3; not ported (names-specific) is listed there: the INE sample test, RawRecord parse, the names default adapter package, pipeline tests.
  2. No network: an autouse fixture in tests/conftest.py refuses every non-loopback connect and name lookup in every test (tests/test_no_network.py proves it).
  3. A registry schema built only from the public API (tests/names_registry.py) passes every test of dataset-names' test_sources.py and test_sources_extensoes.py.
  4. Installable from git: in an empty uv init --bare project, uv add "benchcommons-tools-dataset @ git+https://github.com/BenchCommons/tools-dataset@41e563c" installs and every module imports; the ADR 0004 form ([tool.uv.sources] git source) also syncs (checked with the branch, since the tag comes after merge). The CI job repeats this on the PR head.
  5. CI: see the checks on this PR.
  6. Adoption by dataset-names is tracked in dataset-names adopts the source toolkit (spec 004) #5.

Review fix round

  • Path traversal: cached_path/fetch_resource now refuse a source id outside the registry id format, an unsafe file name, and any path resolving outside the cache (ValueError), with tests (.., /etc/passwd, symlinked source directory).
  • A str given for accepted_licenses, fields, checks or optional is refused (TypeError); collections are coerced to frozenset/tuples (a str licence list made "BY" pass for "CC-BY-4.0").
  • Damaged ZIP members (bad CRC, bad deflate data, truncation) become FormatError, with tests.
  • The default Source is hashable (extra left out of the hash).
  • hatchling==1.32.4 pinned; CI runs on v* tags and fails when the tag differs from the package version.
  • Plan 2.4 states the real registry error order and its drift from dataset-names (noted in dataset-names adopts the source toolkit (spec 004) #5); placement decision split into ADR 0007; spec 001's plan uses a placeholder tag (v0.1.0 has no CLI); the test-only example adapter is in English; the spreadsheet docstring names both origins (e7e8405 and eaaa4d1).
  • Left open as issues: cache hardening Cache hardening: size limit, total timeout, redirect rules, concurrent .part files #8; the CONTRACTS.md row in CONTRACTS.md: add the source toolkit contract (tools-dataset spec 004) .product#4. Comment on tools-commons#1 points to the existing cache/retry code.

Release step (maintainer, after merge)

git tag -s v0.1.0 <merge sha> -m "benchcommons-tools-dataset 0.1.0 (spec 004)"
git push origin v0.1.0
uv init --bare --python 3.12 && uv add "benchcommons-tools-dataset @ git+https://github.com/BenchCommons/tools-dataset@v0.1.0"
uv run python -c "import datasettool.sources.cache, datasettool.sources.spreadsheet, datasettool.sources.adapters, datasettool.sources.registry, datasettool.files"

Decisions taken alone (cost if wrong)

  • Distribution benchcommons-tools-dataset after the repo rename, import datasettool kept from spec 001's plan. Cost: renaming after v0.1.0 breaks every consumer's imports and lock file; before the tag it is a find-and-replace.
  • The toolkit stays in tools-dataset, not tools-commons (ADR 0007): its consumers are the dataset repos. tools-commons#1 lists "retry wrappers for flaky downloads" as a candidate, which overlaps with the cache. Cost: if tools-benchmark needs the cache, it moves to tools-commons by a spec there and this package depends on it.
  • Accepted licences are per dataset, no implicit default; ATTRIBUTION_ONLY_LICENSES is a shared starting set without statcan-open-licence (dataset-names adds it). Cost: if .product ADR 0003 sets an org-wide list, it becomes one more constant.
  • Messages and identifiers translated to English (repo convention); vectors unchanged. Cost: dataset-names' assertions on Portuguese messages change on adoption (dataset-names adopts the source toolkit (spec 004) #5).
  • Registry extension by Field list, cross-entry checks and a factory rather than subclassing or JSON Schema. Cost: an API change in a 0.x minor release.
  • describe_files refuses files with no registered row counter instead of counting them as CSV (dataset-names' fallback). Cost: none for dataset-names, which only has CSV and Parquet.
  • INE spreadsheet samples (CC BY 4.0 data) stay in dataset-names; only the synthetic types.xls moved. Cost: one real-file test less here.
  • Apache-2.0 LICENSE added; the owner confirmed tools-dataset keeps Apache-2.0.
  • ADR numbers 0003 to 0006 leave 0001 and 0002 to spec 001's T1.

Closes #4

🤖 Generated with Claude Code

matbrgz and others added 17 commits October 12, 2026 15:16
Spec 004 moves the generic source code of dataset-names (download cache,
spreadsheet reader, adapter base, source registry, file helpers) into an
installable package. ADRs record the package name, how dataset repos
depend on it (uv git source pinned to a tag), the runtime dependencies
and the per-dataset list of accepted licences.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The plan fixes the public API of v0.1.0, how dataset-names maps onto the
registry schema, the port map of dataset-names tests, the CI jobs and the
release tag step. Spec 001's skeleton task now points to spec 004.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
pyproject (Python 3.12, hatchling, pinned pyyaml and xlrd), uv.lock,
Apache-2.0 LICENSE, the datasettool package, the local HTTP test server
and CI jobs that run the tests and install the package from a git URL.
The docs CI job is kept.

Local test server ported from BenchCommons/dataset-names@e7e8405 tests/conftest.py

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Resource, SourceFiles, FormatError, AdapterError, the URL and file name
checks, check_adapter and load_adapter (absent versus broken), and
parse_count/parse_code. The adapter package is chosen by the dataset and
the record type is the dataset's own; optional protocol functions such as
dataset-names' links are checked when declared. Messages are in English;
test vectors are unchanged.

ported from BenchCommons/dataset-names@e7e8405 src/namescommons/adapters/__init__.py
tests ported from BenchCommons/dataset-names@e7e8405 tests/test_adapters.py, tests/test_contrato_adaptador.py, tests/adaptadores/exemplo.py

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cache/<source_id>/<file> with .meta.json, User-Agent, retries with
doubling backoff, no retry on 4xx except 408 and 429, atomic writes,
Content-Length check, BlockedError for text/html, the 403 manual
download hint and the offline/refresh rules. The User-Agent defaults to
dataset-tool's and a dataset passes its own; parameters are renamed to
attempts, backoff and sleep. Test vectors unchanged.

ported from BenchCommons/dataset-names@e7e8405 src/namescommons/cache.py
tests ported from BenchCommons/dataset-names@e7e8405 tests/test_cache.py

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Streaming XLSX reader on the standard library with DTD, ZIP bomb, column
and row guards; XLS through xlrd 2.0.2; same value types in both
formats; cell_count. The synthetic types.xls fixture is a byte-identical
copy of dataset-names' tipos.xls. The INE sample test stays in
dataset-names. Messages in English; test vectors unchanged.

ported from BenchCommons/dataset-names@e7e8405 src/namescommons/spreadsheet.py
tests ported from BenchCommons/dataset-names@e7e8405 tests/test_spreadsheet.py and tests/fixtures/planilhas/tipos.xls

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The repo is now BenchCommons/tools-dataset (.product ADR 0005). URLs,
the distribution name (benchcommons-tools-dataset) and the default
User-Agent follow it; the import package datasettool is unchanged. ADR
0003 and the plan record why the toolkit stays here rather than in
tools-commons.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Generic fields (id, name, url, license, attribution, retrieved, adapter)
with the id, url, licence and date rules; datasets add their own fields,
accepted licences, policy text, cross-entry checks and source factory.
ATTRIBUTION_ONLY_LICENSES is the shared starting set (ADR 0006). The
dataset-names registry tests run through a test schema that rebuilds its
rules (country, count_type, primary, suppression, uniform_case, one
primary) from the public API.

ported from BenchCommons/dataset-names@e7e8405 src/namescommons/sources.py
tests ported from BenchCommons/dataset-names@e7e8405 tests/test_sources.py and tests/test_sources_extensoes.py

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… expat level

The DTD pre-scan read members as UTF-8 or UTF-16, while expat honours the
encoding named in the XML declaration. Members must now be valid UTF-8 or
UTF-16 and declare no other encoding, and expat is used directly with
ElementTree.TreeBuilder so DOCTYPE and ENTITY declarations are refused by
the parser itself, independently of the pre-scan, while streaming.
Spec R3.4 and ADR 0005 list the new guards.

ported from BenchCommons/dataset-names@eaaa4d1 (PR #39) src/namescommons/spreadsheet.py
tests ported from BenchCommons/dataset-names@eaaa4d1 tests/test_spreadsheet.py

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
sha256_file, count_csv_records (CSV reader, not physical lines) and
describe_files, the file loop of dataset-names' manifest with a row
counter per suffix registered by the dataset; files without a counter
are refused. The names manifest itself stays in dataset-names. The CI
install job now imports every module.

ported from BenchCommons/dataset-names@e7e8405 src/namescommons/manifest.py
tests ported from BenchCommons/dataset-names@e7e8405 tests/test_manifest.py and tests/test_robustez.py

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tasks T1 to T8 done; the plan and spec name dataset-names PR #39 as the
origin of the spreadsheet reader's encoding and expat-level DTD guards.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
cached_path, and so fetch_resource, now validate the source id against
the registry id format and the file name against the adapter rule, and
check that the resolved path stays under the cache directory. Before,
fetch_resource(cache, '..', ...) read outside the cache and a file name
'/etc/passwd' escaped it. ID_PATTERN moves to the adapters module and the
registry re-exports it.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A str licence list turned the licence check into a substring test ('BY'
accepted for 'CC-BY-4.0'), and optional='links' in check_adapter was
iterated character by character. RegistrySchema now refuses str for
accepted_licenses, fields and checks, coerces them to frozenset and
tuples and checks their item types; check_adapter and load_adapter refuse
a str optional.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A bad CRC, bad deflate data or a truncated member raised BadZipFile,
zlib.error or EOFError from inside the reader. Opening and reading a
member now turns them into FormatError naming the file and member. The
module docstring names both dataset-names origins (e7e8405 and eaaa4d1).

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An autouse fixture patches socket connect, connect_ex and getaddrinfo so
any test that reaches a host other than 127.0.0.1, ::1 or localhost
fails, enforcing spec R7.3 instead of relying on review. Also makes the
default Source hashable: extra, a read-only MappingProxyType, is left
out of the hash but kept in equality.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The build backend is pinned to hatchling 1.32.4. CI now also runs on v*
tag pushes and fails when the tag differs from v<pyproject version>, as
ADR 0004 requires.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…est adapter

Plan 2.4 now states the real validation order and how it differs from
dataset-names (only the order of lines in a multi-error message), for the
adoption in #5. The decision to keep the toolkit in tools-dataset rather
than tools-commons moves to its own ADR 0007. Spec 001's plan uses a
placeholder tag, since v0.1.0 ships no CLI. The test-only example adapter
is renamed to English.

Refs #4

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Spec 004: shared source toolkit (cache, spreadsheet reader, adapter base, source registry)

1 participant