Skip to content

Drop python 3.10 and simplify for click 8.2 enum choice #1890

Merged
CodyCBakerPhD merged 2 commits into
masterfrom
enh-click-enum-choice-simplify
Jul 9, 2026
Merged

Drop python 3.10 and simplify for click 8.2 enum choice #1890
CodyCBakerPhD merged 2 commits into
masterfrom
enh-click-enum-choice-simplify

Conversation

@yarikoptic

@yarikoptic yarikoptic commented Jul 9, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #1883 that collapses the click-8.2 compatibility shim
and rebases the CLI enums on the standard-library enum.StrEnum
(Python 3.11+). No behavior change — same command-line surface,
same --help output, same accept/reject semantics.

Two independent commits:

  1. click 8.2 exposes Choice.normalize_choice as the explicit hook
    for choice-string normalization. Overriding it to return .value
    for Enum members (instead of the default .name) is all that
    is needed to preserve the pre-8.2 CLI surface, because
    Choice.convert then matches by normalized string and returns the
    matched enum member automatically. The custom __init__ +
    convert + self.enum_cls bookkeeping in Support click >= 8.2.0 #1883's EnumChoice
    become unnecessary. Bumps the click floor from >= 7.1 to
    >= 8.2 — the earlier bound predates normalize_choice.

  2. Migrate the 11 CLI-facing (str, Enum) classes to
    enum.StrEnum
    and drop their hand-written
    __str__ -> self.value overrides. StrEnum was introduced in
    Python 3.11 and provides exactly these semantics natively. This
    requires bumping the minimum Python from 3.10 (EOL 2026-10-04)
    to 3.11: pyproject.toml, tox.ini, and 5 GitHub Actions
    workflows are updated accordingly (matrix drops the 3.10 leg;
    pinned single-Python include: entries move to 3.11).

Net change vs merged #1883: +39 / −83 across 14 files.

Commits

  • b2d3c64 Simplify EnumChoice via click 8.2 normalize_choice hook
  • 5d2cec5 Adopt enum.StrEnum for CLI choice enums; drop Python 3.10
Per-commit details

b2d3c64 Simplify EnumChoice via click 8.2 normalize_choice hook

EnumChoice shrinks from ~15 lines (custom __init__, convert,
self.enum_cls, defensive None/isinstance guards) to a single
3-line normalize_choice override. Callsites are unchanged —
iterating an Enum class yields members, and click.Choice.__init__
already does tuple(choices) on any iterable, so
EnumChoice(DownloadExisting) still works verbatim.

pyproject.toml: click >= 7.1click >= 8.2. The
normalize_choice hook is documented "added in 8.2.0" upstream;
retaining a lower bound would just be dead compatibility.

5d2cec5 Adopt enum.StrEnum for CLI choice enums; drop Python 3.10

Migrated: SyncMode (consts.py), DownloadExisting /
DownloadFormat / PathType (download.py), UploadExisting /
UploadValidation (upload.py), MoveExisting / MoveWorkOn
(move.py), FileOperationMode / CopyMode / OrganizeInvalid
(organize.py). Each gets its (str, Enum) base changed to
StrEnum and its __str__ -> self.value override deleted.

_Existing in dandi/cli/tests/test_base.py also migrated for
consistency.

EnumChoice itself is unchanged — StrEnum still exposes distinct
.name and .value, and click 8.2 still defaults to matching on
.name, so the normalize_choice override remains necessary.

Python floor moves from 3.10 to 3.11 to unlock enum.StrEnum:

  • pyproject.toml: requires-python = ">=3.11", drop the 3.10
    classifier, [tool.hatch.envs.default] python = "3.11".
  • tox.ini: drop the tomli; python_version < '3.11' conditional
    from the lint env (unreachable under the new floor).
  • .github/workflows/run-tests.yml: matrix drops 3.10; the five
    include: entries pinned to '3.10' (dandi-api, dandi-api +
    ember-dandi, dev-deps, nfs, and the ember-dandi sandbox
    vendored config) move to '3.11'.
  • .github/workflows/{lint,docs,release,typing}.yml: single
    python-version bumps to '3.11'.

Intentionally not migrated: dandi/utils.py:StrEnum (a
(str, Enum) shim used by dandi/validate/_types.py and
dandi/bids_validator_deno/_models.py). Its
_generate_next_value_ returns name (case-preserving), while
enum.StrEnum._generate_next_value_ returns name.lower().
Migrating it would change every auto()-derived value that appears
in serialized ValidationResult output (Standard.BIDS"BIDS"
vs "bids", etc.) — a separate audit worth its own PR.

Test plan

  • pytest dandi/cli/tests/{test_base,test_command,test_move,test_download}.py — all pass on click 8.4.2 + Python 3.13
  • Broader dandi/cli/tests/ + dandi/tests/test_organize.py — 154 pass (only pre-existing bids-validator-deno-binary-dependent tests skipped in local env)
  • dandi organize --files-mode SIMULATE still rejected; lowercase simulate still accepted
  • dandi {download,upload,organize,move} --help render lowercase choice lists unchanged
  • Pre-commit hooks (black, isort, codespell, flake8, reuse) pass on both commits
  • Test locally interactively
  • CI green on the PR (Ubuntu / macOS / Windows × Python 3.11–3.14)

click 8.2 exposes `Choice.normalize_choice` as the extension point for
mapping choices to CLI tokens; overriding it to return `.value` for Enum
members (instead of the default `.name`) is all that's needed to preserve
the lowercase value tokens on the command line. `Choice.convert` then
matches by normalized string and returns the enum member automatically,
so the custom `__init__` and `convert` become unnecessary.

Bump the `click` dependency floor to `>= 8.2` accordingly; the earlier
lower bound (`>= 7.1`) predates the `normalize_choice` API.

Co-Authored-By: Claude Code 2.1.197 / Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CodyCBakerPhD CodyCBakerPhD added patch Increment the patch version when merged dependencies Update one or more dependencies version internal Changes only affect the internal API labels Jul 9, 2026
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 47.61905% with 11 lines in your changes missing coverage. Please review.
✅ Project coverage is 76.93%. Comparing base (3f2b6d1) to head (b53ceba).

Files with missing lines Patch % Lines
dandi/organize.py 0.00% 5 Missing ⚠️
dandi/upload.py 0.00% 3 Missing ⚠️
dandi/consts.py 0.00% 2 Missing ⚠️
dandi/cli/base.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1890      +/-   ##
==========================================
+ Coverage   76.88%   76.93%   +0.04%     
==========================================
  Files          88       88              
  Lines       12901    12872      -29     
==========================================
- Hits         9919     9903      -16     
+ Misses       2982     2969      -13     
Flag Coverage Δ
unittests 76.93% <47.61%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@CodyCBakerPhD

Copy link
Copy Markdown
Contributor

hint: if you tell your agent to 'watch the pr' it will automatically fix any CI failures, such as the docs build error

@CodyCBakerPhD CodyCBakerPhD marked this pull request as ready for review July 9, 2026 20:34
@yarikoptic

Copy link
Copy Markdown
Member Author

thanks! I typically do not permit the beasts to push without my supervision, so would need another manual step in the loop

Python 3.11 introduced `enum.StrEnum` — a string-mixed `Enum` with a
`__str__` that returns the value. That is exactly the semantics the 11
`(str, Enum)` classes used for click `--` choice options implemented by
hand via a `__str__ -> self.value` override. Migrate them to `StrEnum`
and drop the overrides.

Bump the minimum Python to 3.11 (from 3.10, EOL 2026-10-04): update the
`requires-python`, classifiers, hatch default env, GitHub Actions
matrix (removing the 3.10 leg and repointing the pinned single-Python
`include:` entries to 3.11), the lone lint workflow, and remove the
`tomli; python_version < '3.11'` conditional from the lint tox env
(unreachable under the new floor).

`EnumChoice` is unchanged — `StrEnum` members still expose distinct
`.name` and `.value`, and click 8.2 still defaults to matching on
`.name`, so the `normalize_choice` override that selects `.value`
remains necessary.

`dandi/utils.py:StrEnum` (a `(str, Enum)` shim used by
`dandi/validate/_types.py` and `dandi/bids_validator_deno/_models.py`)
is intentionally left untouched: its `_generate_next_value_` returns
`name` (case-preserving), while `enum.StrEnum` returns `name.lower()`.
Migrating those enums would change every `auto()`-derived value that
appears in serialized validation output.

Co-Authored-By: Claude Code 2.1.197 / Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@yarikoptic yarikoptic force-pushed the enh-click-enum-choice-simplify branch from 5d2cec5 to b53ceba Compare July 9, 2026 20:53
@CodyCBakerPhD CodyCBakerPhD merged commit 1c62704 into master Jul 9, 2026
89 of 92 checks passed
@CodyCBakerPhD CodyCBakerPhD deleted the enh-click-enum-choice-simplify branch July 9, 2026 21:49
@github-actions

Copy link
Copy Markdown

🚀 PR was released in 0.76.7 🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Update one or more dependencies version internal Changes only affect the internal API patch Increment the patch version when merged released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants