Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ jobs:
fail-fast: false
matrix:
python:
- '3.10'
#- 3.11
- '3.11'
steps:
- name: Check out repository
uses: actions/checkout@v7
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.11'

- name: Install build & twine
run: python -m pip install build twine
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ jobs:
- macos-latest # M1 architecture
- macos-15-intel # non-M1 architecture
python:
- '3.10' # Needs quotes so YAML doesn't think it's 3.1
- '3.11'
- '3.11' # Needs quotes so YAML doesn't think it's 3.1
- '3.12'
- '3.13'
- '3.14'
Expand All @@ -48,10 +47,10 @@ jobs:
- default
include:
- os: ubuntu-latest
python: '3.10'
python: '3.11'
mode: dandi-api
- os: ubuntu-latest
python: '3.10'
python: '3.11'
mode: dandi-api
vendored_dandiapi: ember-dandi
instance_name: EMBER-DANDI
Expand All @@ -61,13 +60,13 @@ jobs:
python: 3.14
mode: obolibrary-only
- os: ubuntu-latest
python: '3.10'
python: '3.11'
mode: dev-deps
- os: ubuntu-latest
python: 3.14
mode: dev-deps
- os: ubuntu-latest
python: '3.10'
python: '3.11'
mode: nfs

steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/typing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.10'
python-version: '3.11'

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3.10"
python: "3.11"
python:
install:
- requirements: docs/requirements.txt
Expand Down
20 changes: 8 additions & 12 deletions dandi/cli/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,17 @@ def get_metavar(self, param, ctx=None):


class EnumChoice(click.Choice):
"""A ``click.Choice`` over a ``str``-valued ``Enum``, matched on member values.
"""A ``click.Choice`` over an ``Enum``, matched on member values.

The available choices presented on the command line are the enum member
values (e.g. ``error``, ``skip``), and ``convert`` returns the corresponding
enum member. A string default is converted to its member as well.
``click >= 8.2`` matches ``Enum`` members on ``.name`` by default; this
subclass overrides :meth:`click.Choice.normalize_choice` to match on
``.value`` instead, so command-line tokens stay the lowercase enum values
(``error``, ``skip``, ...). ``click.Choice.convert`` then returns the
matched enum member.
"""

def __init__(self, enum_cls: type[Enum], case_sensitive: bool = True) -> None:
self.enum_cls = enum_cls
super().__init__([e.value for e in enum_cls], case_sensitive=case_sensitive)

def convert(self, value, param, ctx):
if value is None or isinstance(value, self.enum_cls):
return value
return self.enum_cls(super().convert(value, param, ctx))
def normalize_choice(self, choice, ctx=None):
return choice.value if isinstance(choice, Enum) else str(choice)


class ChoiceList(click.ParamType):
Expand Down
7 changes: 2 additions & 5 deletions dandi/cli/tests/test_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from enum import Enum
from enum import StrEnum

import click
from click.testing import CliRunner
Expand All @@ -7,14 +7,11 @@
from ..base import EnumChoice


class _Existing(str, Enum):
class _Existing(StrEnum):
ERROR = "error"
SKIP = "skip"
OVERWRITE = "overwrite-different"

def __str__(self) -> str:
return self.value


def _make_command(**option_kwargs):
@click.command()
Expand Down
7 changes: 2 additions & 5 deletions dandi/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from collections.abc import Iterator
from dataclasses import dataclass
from enum import Enum
from enum import Enum, StrEnum
import os

#: A list of metadata fields which dandi extracts from .nwb files.
Expand Down Expand Up @@ -101,13 +101,10 @@ class EmbargoStatus(Enum):
EMBARGOED = "EMBARGOED"


class SyncMode(str, Enum):
class SyncMode(StrEnum):
ASK = "ask"
DO = "do"

def __str__(self) -> str:
return self.value


dandiset_metadata_file = "dandiset.yaml"
dandiset_identifier_regex = f"^{DANDISET_ID_REGEX}$"
Expand Down
17 changes: 4 additions & 13 deletions dandi/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from collections.abc import Callable, Iterable, Iterator, Sequence
from dataclasses import InitVar, dataclass, field
from datetime import datetime
from enum import Enum
from enum import Enum, StrEnum
from functools import partial
import hashlib
import inspect
Expand Down Expand Up @@ -69,32 +69,23 @@
lgr = get_logger()


class DownloadExisting(str, Enum):
class DownloadExisting(StrEnum):
ERROR = "error"
SKIP = "skip"
OVERWRITE = "overwrite"
OVERWRITE_DIFFERENT = "overwrite-different"
REFRESH = "refresh"

def __str__(self) -> str:
return self.value


class DownloadFormat(str, Enum):
class DownloadFormat(StrEnum):
PYOUT = "pyout"
DEBUG = "debug"

def __str__(self) -> str:
return self.value


class PathType(str, Enum):
class PathType(StrEnum):
EXACT = "exact"
GLOB = "glob"

def __str__(self) -> str:
return self.value


def download(
urls: str | Sequence[str],
Expand Down
12 changes: 3 additions & 9 deletions dandi/move.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from collections.abc import Iterator
from contextlib import ExitStack
from dataclasses import dataclass, field
from enum import Enum
from enum import StrEnum
from itertools import zip_longest
import os.path
from pathlib import Path, PurePosixPath
Expand All @@ -35,24 +35,18 @@
lgr = get_logger()


class MoveExisting(str, Enum):
class MoveExisting(StrEnum):
ERROR = "error"
SKIP = "skip"
OVERWRITE = "overwrite"

def __str__(self) -> str:
return self.value


class MoveWorkOn(str, Enum):
class MoveWorkOn(StrEnum):
AUTO = "auto"
BOTH = "both"
LOCAL = "local"
REMOTE = "remote"

def __str__(self) -> str:
return self.value


#: A /-separated path to an asset, relative to the root of the Dandiset
AssetPath = NewType("AssetPath", str)
Expand Down
19 changes: 5 additions & 14 deletions dandi/organize.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
from collections import Counter
from collections.abc import Sequence
from copy import deepcopy
from enum import Enum
from enum import StrEnum
import os
import os.path as op
import posixpath
from pathlib import Path, PurePosixPath
import posixpath
import re
import traceback
import uuid
Expand Down Expand Up @@ -54,7 +54,7 @@
lgr = get_logger()


class FileOperationMode(str, Enum):
class FileOperationMode(StrEnum):
DRY = "dry"
SIMULATE = "simulate"
COPY = "copy"
Expand All @@ -63,23 +63,17 @@ class FileOperationMode(str, Enum):
SYMLINK = "symlink"
AUTO = "auto"

def __str__(self) -> str:
return self.value

def as_copy_mode(self) -> CopyMode:
# Raises ValueError if the mode can't be mapped to a CopyMode
return CopyMode(self.value)


class CopyMode(str, Enum):
class CopyMode(StrEnum):
SYMLINK = "symlink"
COPY = "copy"
MOVE = "move"
HARDLINK = "hardlink"

def __str__(self) -> str:
return self.value

def copy(self, old_path: AnyPath, new_path: AnyPath) -> None:
if self is CopyMode.SYMLINK:
os.symlink(old_path, new_path)
Expand All @@ -93,13 +87,10 @@ def copy(self, old_path: AnyPath, new_path: AnyPath) -> None:
raise AssertionError(f"Unhandled CopyMode member: {self!r}")


class OrganizeInvalid(str, Enum):
class OrganizeInvalid(StrEnum):
FAIL = "fail"
WARN = "warn"

def __str__(self) -> str:
return self.value


dandi_path = op.join("sub-{subject_id}", "{dandi_filename}")

Expand Down
12 changes: 3 additions & 9 deletions dandi/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from collections import defaultdict
from collections.abc import Iterator, Sequence
from contextlib import ExitStack
from enum import Enum
from enum import StrEnum
import io
import os.path
from pathlib import Path
Expand Down Expand Up @@ -83,25 +83,19 @@ class Uploaded(TypedDict):
errors: list[str]


class UploadExisting(str, Enum):
class UploadExisting(StrEnum):
ERROR = "error"
SKIP = "skip"
FORCE = "force"
OVERWRITE = "overwrite"
REFRESH = "refresh"

def __str__(self) -> str:
return self.value


class UploadValidation(str, Enum):
class UploadValidation(StrEnum):
REQUIRE = "require"
SKIP = "skip"
IGNORE = "ignore"

def __str__(self) -> str:
return self.value


def upload(
paths: Sequence[str | Path] | None = None,
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-backend = 'setuptools.build_meta'
name = "dandi"
description = "Command line client for interaction with DANDI instances"
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.11"
license = {file = "LICENSE"}
authors = [
{name = "DANDI developers", email = "team@dandiarchive.org"},
Expand Down Expand Up @@ -36,7 +36,6 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
Expand All @@ -46,7 +45,7 @@ classifiers = [
dependencies = [
"bidsschematools ~= 1.0",
"bids-validator-deno >= 2.0.5",
"click >= 7.1",
"click >= 8.2",
"click-didyoumean",
"dandischema ~= 0.12.0",
"etelemetry >= 0.2.2",
Expand Down Expand Up @@ -237,7 +236,7 @@ init_forbid_extra = true

[tool.hatch.envs.default]
installer = "uv"
python = "3.10"
python = "3.11"

[tool.hatch.envs.test]
features = ["test"]
Expand Down
1 change: 0 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ skip_install = true
deps =
codespell~=2.4.1
flake8
tomli; python_version < '3.11'
commands =
codespell dandi docs tools setup.py
flake8 --config=setup.cfg {posargs} dandi setup.py
Expand Down
Loading