Types (1/4): fix invalid type hint syntax (X or Y → X | Y)#306
Open
jeandet wants to merge 155 commits into
Open
Types (1/4): fix invalid type hint syntax (X or Y → X | Y)#306jeandet wants to merge 155 commits into
jeandet wants to merge 155 commits into
Conversation
Captures decisions on UV adoption, hatchling build backend, ruff/basedpyright tooling, and three-tier test strategy (unit/contract/e2e). Sequences the work as 17 small PRs ending with a mass reformat. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Per-task implementation plan for the first PR of the modernisation effort. Covers pyproject.toml updates, uv.lock generation, CI/RTD switch to uv, deletion of requirements*.txt / tox.ini / setup.cfg, and developer-doc updates to drop the PYTHONPATH=. pattern. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add SPEASY_CORE_HTTP_REWRITE_RULES env to PRs.yml non-3.10 pytest step (previously only on push/scheduled tests.yml — would have hit a non-existent server on PR builds for non-3.10 matrix entries). - Add --with wheel to PRs.yml build step for parity with tests.yml. - Scope flake8 to 'speasy tests' in both workflows (matches Makefile lint target). Avoids silently broadening lint to docs/conf.py and removes the .venv exclusion workaround that was needed when flake8 ran from repo root.
Without UV_PROJECT_ENVIRONMENT, uv creates .venv/ inside the project and RTD's sphinx step (which calls $READTHEDOCS_VIRTUALENV_PATH/bin/python directly) fails with 'python: not found'. Point uv at RTD's venv so the install lands where the runner looks for it.
Classified via devtools/apply_test_markers.py: - 12 files marked unit (pure-logic, no network) - 19 files marked contract (real-server, will be migrated to cassettes in PRs 4-9) Reclassifications during manual review: - test_cache.py: contract -> unit (pure cache-logic, no network or speasy provider use) - test_file_access.py: unit -> contract (uses HTTP via any_loc_open against live servers) test_wasm.py was manually adjusted to place pytestmark at module level (the file's body lives inside a try/except ImportError block, so the script's naive insertion landed at wrong indentation).
…le path and sample across the inventory The flat_inventories.generic_archive lookup uses module attribute access on an instance, not a submodule import. Also, the first N parameters in the flat inventory are clustered by mission, so a fixed time range can miss all of them; sample across the full list instead.
- test_e2e_smoke.test_generic_archive: fail loudly if every candidate raises (was silently skipping, defeating the e2e tier's purpose). - pyproject.toml: drop dead --ignore=setup.py from addopts and document the -m unit override semantics so future contributors don't trip on 'pytest tests/test_amda.py' silently collecting nothing. - contract.yml / e2e.yml: add concurrency groups so a manual run can't overlap with a cron run hammering the same upstream servers. - CONTRIBUTING.rst: add a short note explaining the three test tiers and how to invoke each from local dev.
CI failure (blocking): - unit.yml: 'make doctest' was using system Python (no sphinx in scope). Prefix with 'uv run' so make uses the project venv. Reviewer findings: - wasm_tests.yml: pytest tests/test_wasm.py without -m collected 0 tests under the new addopts default (test_wasm.py is contract-marked). Add -m '' to override. - CLAUDE.md: examples like 'uv run pytest tests/test_amda.py' silently collected 0 tests under -m unit default. Replaced with tier-aware examples and added -m '' for the all-tests case. - unit.yml: only sync --group docs on the coverage runner that needs it, not on every matrix entry.
nbsphinx requires the system pandoc binary (not the Python pandoc wrapper that's in the docs dependency group). PR 1's tests.yml had 'sudo apt install -y texlive pandoc' before make doctest; my unit.yml rewrite in PR 2 dropped that line, so the doctest job failed with 'nbsphinx.NotebookError: PandocMissing in examples/AMDA.ipynb'. Restored as a separate apt step on the coverage runner.
The doctest step's examples reference all data providers (cdpp3dview included) and live inventories. The job-level SPEASY_CORE_DISABLED_PROVIDERS='cdpp3dview' makes the inventory tree's cdpp3dview attribute missing during doctest, surfacing as 'types.SimpleNamespace object has no attribute cdpp3dview' and a chain of NameErrors for variables defined in earlier doctest blocks. Original tests.yml overrode SPEASY_CORE_DISABLED_PROVIDERS="" on the combined pytest+doctest step, plus set HTTP_REWRITE_RULES (re-routes the placeholder URL used in some examples to LPP's mirror) and USER_AGENT. My PR 2 rewrite dropped the env block; restoring it on the doctest step.
Pandas now prints its public name in type() repr ('pandas.DataFrame')
rather than the internal module path ('pandas.core.frame.DataFrame').
The user/numpy.rst doctest was written against the old form.
Surfaced now that uv.lock pins a recent pandas; pip-installed envs
were getting older pandas where the old form still applied.
Automatic fixes from codespell -w plus one manual fix for 'Cant'' → 'Can't' in speasy/products/variable.py (codespell's auto-write skips apostrophe-adjacent corrections). Affects 9 files: HISTORY.rst, docstrings in speasy/core/proxy, speasy/core/impex, speasy/core/hapi, speasy/data_providers/amda, speasy/products/catalog, speasy/products/variable, and a test assertion message in tests/test_hapi_codecs.
basedpyright doesn't publish a 'pydantic' extra — uv lock emits a non-fatal warning. Cleanup nit; behavior unchanged.
These 9 `X or Y` annotations are runtime-truthy: `X or Y` evaluates to X at runtime, so callers see only `X` as the type. Replacing with `X | Y` correctly widens the annotation, but cascades None-propagation and type- narrowing errors that are out of scope for PR 14a (pure annotation syntax, no behavior changes). Resolving these will be tackled in PR 14c.
|
| if any("import pytest" in ln for ln in lines): | ||
| block = [f"\n{MARKER_LINE_PREFIX}{tier}\n", "\n"] | ||
| lines[insert_at:insert_at] = block | ||
| path.write_text("".join(lines)) |
|
|
||
|
|
||
| def version_to_str(v: Union[Version, datetime.datetime]) -> str: | ||
| def version_to_str(v: Version | datetime.datetime) -> str: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.




Summary
First of four sub-PRs that drive PR 13's basedpyright baseline (1169 errors) toward zero. This one is pure annotation syntax:
def f(x: int or None)→def f(x: int | None). PEP 604 union syntax (valid since Python 3.10, Speasy's floor since PR 1). No runtime behavior changes.Plan:
docs/superpowers/plans/2026-05-27-pr14a-typing-annotation-syntax.md.Stacked on PR #305 (basedpyright non-blocking).
What this PR does
reportInvalidTypeForm. 17 small commits, one per file (or small group).orinstead of|/Optional: rewrites only the annotation. Runtime expressions (default values, real boolean ops) untouched.Hard gates applied at every commit
pytest -m unitshowed baseline pass/skip counts (623 passed, 7 skipped) after every commit.make doctestclean at HEAD: 463 tests, 0 failures.ruffclean throughout.# type: ignoreper the plan's STOP-and-defer rule).Error count delta
reportInvalidTypeFormNet drop: -119 errors. All 142 direct
reportInvalidTypeFormresolved (134 by annotation fix, 8 by# type: ignore).reportArgumentTypeessentially flat (207 → 210),reportAttributeAccessIssue+4 — annotation widening exposed real None-propagation issues PR 14c will address.# type: ignore[invalidTypeForm]additionsSeven sites in
speasy/products/variable.py. Each has a one-line reason comment pointing at PR 14c:def _check_axes(axes: list[VariableAxis or VariableTimeAxis], ...):axes: list[VariableAxis or VariableTimeAxis],(SpeasyVariable.init)out: 'SpeasyVariable' or None = None(array_ufunc)def axes(self) -> list[VariableTimeAxis or VariableAxis]:def unit_applied(self, unit: str or None = None, ...) -> "SpeasyVariable":from_dictionary(dictionary: dict[str, object] or None) -> "SpeasyVariable" or None:Why deferred: mechanically replacing
orwith|here widens the type to be technically correct, but the original forward-ref-string-as-truthy semantics meant the type-checker was seeing a non-Optional type and missing all downstream None-propagation. Applying the literal fix here cascaded into ~20 newreportOptionalMemberAccess/reportArgumentTypeerrors — out of scope for "pure annotation syntax." Proper None handling lands in PR 14c.What this PR does NOT do
reportAttributeAccessIssue(515 errors): PR 14b.Note on pre-existing test failures
3 AMDA cassette tests (
test_get_timetable_from_Index,test_returns_product_type_from_either_id_or_index_03/04) fail with VCR cassette key mismatch (sharedtimeTable_206expected, cassette has_205). Verified pre-existing on PR 13 baseline — unrelated to PR 14a. Worth fixing in a separate cassette re-record.Test plan
lint.ymlgreen (basedpyright still non-blocking; error count visibly dropped)unit.ymlgreenmake doctestclean (463 tests, 0 failures)