From 3cd038a6d80c84a0b862bb8401cf6b070385c50b Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:07:56 -0400 Subject: [PATCH 01/10] add ruff --- pyproject.toml | 1 + ruff.toml | 76 ++++ src/bids_validator/_version.py | 462 ++++++++++++---------- src/bids_validator/test_bids_validator.py | 4 +- 4 files changed, 324 insertions(+), 219 deletions(-) create mode 100644 ruff.toml diff --git a/pyproject.toml b/pyproject.toml index 26eb474..4289ced 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -154,6 +154,7 @@ quote-style = "single" [dependency-groups] dev = [ "ipython>=8.37.0", + "ruff>=0.15.5", ] test = [ "pytest >=8", diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..702a998 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,76 @@ +# Exclude a variety of commonly ignored directories. +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", + "tests/data", + "_version.py", +] + +line-length = 100 +indent-width = 4 + +[lint] +# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. +# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or +# McCabe complexity (`C901`) by default. +select = ["E4", "E7", "E9", "F"] +ignore = [] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[format] +# Like Black, use double quotes for strings. +quote-style = "single" + +# Like Black, indent with spaces, rather than tabs. +indent-style = "space" + +# Like Black, respect magic trailing commas. +skip-magic-trailing-comma = false + +# Like Black, automatically detect the appropriate line ending. +line-ending = "auto" + +# Enable auto-formatting of code examples in docstrings. Markdown, +# reStructuredText code/literal blocks and doctests are all supported. +# +# This is currently disabled by default, but it is planned for this +# to be opt-out in the future. +docstring-code-format = false + +# Set the line length limit used when formatting code snippets in +# docstrings. +# +# This only has an effect when the `docstring-code-format` setting is +# enabled. +docstring-code-line-length = "dynamic" + diff --git a/src/bids_validator/_version.py b/src/bids_validator/_version.py index af0aa8b..a2b546f 100644 --- a/src/bids_validator/_version.py +++ b/src/bids_validator/_version.py @@ -1,4 +1,3 @@ - # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -26,10 +25,10 @@ def get_keywords() -> Dict[str, str]: # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). - git_refnames = "$Format:%d$" - git_full = "$Format:%H$" - git_date = "$Format:%ci$" - keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} + git_refnames = '$Format:%d$' + git_full = '$Format:%H$' + git_date = '$Format:%ci$' + keywords = {'refnames': git_refnames, 'full': git_full, 'date': git_date} return keywords @@ -49,11 +48,11 @@ def get_config() -> VersioneerConfig: # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() - cfg.VCS = "git" - cfg.style = "pep440" - cfg.tag_prefix = "" - cfg.parentdir_prefix = "" - cfg.versionfile_source = "src/bids_validator/_version.py" + cfg.VCS = 'git' + cfg.style = 'pep440' + cfg.tag_prefix = '' + cfg.parentdir_prefix = '' + cfg.versionfile_source = 'src/bids_validator/_version.py' cfg.verbose = False return cfg @@ -68,12 +67,14 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator """Create decorator to mark a method as the handler of a VCS.""" + def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f + return decorate @@ -90,37 +91,41 @@ def run_command( process = None popen_kwargs: Dict[str, Any] = {} - if sys.platform == "win32": + if sys.platform == 'win32': # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - popen_kwargs["startupinfo"] = startupinfo + popen_kwargs['startupinfo'] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen([command] + args, cwd=cwd, env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr - else None), **popen_kwargs) + process = subprocess.Popen( + [command] + args, + cwd=cwd, + env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr else None), + **popen_kwargs, + ) break except OSError as e: if e.errno == errno.ENOENT: continue if verbose: - print("unable to run %s" % dispcmd) + print('unable to run %s' % dispcmd) print(e) return None, None else: if verbose: - print("unable to find command, tried %s" % (commands,)) + print('unable to find command, tried %s' % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: - print("unable to run %s (error)" % dispcmd) - print("stdout was %s" % stdout) + print('unable to run %s (error)' % dispcmd) + print('stdout was %s' % stdout) return None, process.returncode return stdout, process.returncode @@ -141,19 +146,25 @@ def versions_from_parentdir( for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return {"version": dirname[len(parentdir_prefix):], - "full-revisionid": None, - "dirty": False, "error": None, "date": None} + return { + 'version': dirname[len(parentdir_prefix) :], + 'full-revisionid': None, + 'dirty': False, + 'error': None, + 'date': None, + } rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print("Tried directories %s but none started with prefix %s" % - (str(rootdirs), parentdir_prefix)) + print( + 'Tried directories %s but none started with prefix %s' + % (str(rootdirs), parentdir_prefix) + ) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") -@register_vcs_handler("git", "get_keywords") +@register_vcs_handler('git', 'get_keywords') def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these @@ -162,35 +173,35 @@ def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: # _version.py. keywords: Dict[str, str] = {} try: - with open(versionfile_abs, "r") as fobj: + with open(versionfile_abs, 'r') as fobj: for line in fobj: - if line.strip().startswith("git_refnames ="): + if line.strip().startswith('git_refnames ='): mo = re.search(r'=\s*"(.*)"', line) if mo: - keywords["refnames"] = mo.group(1) - if line.strip().startswith("git_full ="): + keywords['refnames'] = mo.group(1) + if line.strip().startswith('git_full ='): mo = re.search(r'=\s*"(.*)"', line) if mo: - keywords["full"] = mo.group(1) - if line.strip().startswith("git_date ="): + keywords['full'] = mo.group(1) + if line.strip().startswith('git_date ='): mo = re.search(r'=\s*"(.*)"', line) if mo: - keywords["date"] = mo.group(1) + keywords['date'] = mo.group(1) except OSError: pass return keywords -@register_vcs_handler("git", "keywords") +@register_vcs_handler('git', 'keywords') def git_versions_from_keywords( keywords: Dict[str, str], tag_prefix: str, verbose: bool, ) -> Dict[str, Any]: """Get version information from git keywords.""" - if "refnames" not in keywords: - raise NotThisMethod("Short version file found") - date = keywords.get("date") + if 'refnames' not in keywords: + raise NotThisMethod('Short version file found') + date = keywords.get('date') if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. @@ -202,17 +213,17 @@ def git_versions_from_keywords( # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. - date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) - refnames = keywords["refnames"].strip() - if refnames.startswith("$Format"): + date = date.strip().replace(' ', 'T', 1).replace(' ', '', 1) + refnames = keywords['refnames'].strip() + if refnames.startswith('$Format'): if verbose: - print("keywords are unexpanded, not using") - raise NotThisMethod("unexpanded keywords, not a git-archive tarball") - refs = {r.strip() for r in refnames.strip("()").split(",")} + print('keywords are unexpanded, not using') + raise NotThisMethod('unexpanded keywords, not a git-archive tarball') + refs = {r.strip() for r in refnames.strip('()').split(',')} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = "tag: " - tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} + TAG = 'tag: ' + tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -223,38 +234,42 @@ def git_versions_from_keywords( # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: - print("discarding '%s', no digits" % ",".join(refs - tags)) + print("discarding '%s', no digits" % ','.join(refs - tags)) if verbose: - print("likely tags: %s" % ",".join(sorted(tags))) + print('likely tags: %s' % ','.join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix):] + r = ref[len(tag_prefix) :] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: - print("picking %s" % r) - return {"version": r, - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": None, - "date": date} + print('picking %s' % r) + return { + 'version': r, + 'full-revisionid': keywords['full'].strip(), + 'dirty': False, + 'error': None, + 'date': date, + } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print("no suitable tags, using unknown + full revision id") - return {"version": "0+unknown", - "full-revisionid": keywords["full"].strip(), - "dirty": False, "error": "no suitable tags", "date": None} + print('no suitable tags, using unknown + full revision id') + return { + 'version': '0+unknown', + 'full-revisionid': keywords['full'].strip(), + 'dirty': False, + 'error': 'no suitable tags', + 'date': None, + } -@register_vcs_handler("git", "pieces_from_vcs") +@register_vcs_handler('git', 'pieces_from_vcs') def git_pieces_from_vcs( - tag_prefix: str, - root: str, - verbose: bool, - runner: Callable = run_command + tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command ) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. @@ -262,96 +277,102 @@ def git_pieces_from_vcs( expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ - GITS = ["git"] - if sys.platform == "win32": - GITS = ["git.cmd", "git.exe"] + GITS = ['git'] + if sys.platform == 'win32': + GITS = ['git.cmd', 'git.exe'] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() - env.pop("GIT_DIR", None) + env.pop('GIT_DIR', None) runner = functools.partial(runner, env=env) - _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, - hide_stderr=not verbose) + _, rc = runner(GITS, ['rev-parse', '--git-dir'], cwd=root, hide_stderr=not verbose) if rc != 0: if verbose: - print("Directory %s not under git control" % root) + print('Directory %s not under git control' % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner(GITS, [ - "describe", "--tags", "--dirty", "--always", "--long", - "--match", f"{tag_prefix}[[:digit:]]*" - ], cwd=root) + describe_out, rc = runner( + GITS, + [ + 'describe', + '--tags', + '--dirty', + '--always', + '--long', + '--match', + f'{tag_prefix}[[:digit:]]*', + ], + cwd=root, + ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) + full_out, rc = runner(GITS, ['rev-parse', 'HEAD'], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces: Dict[str, Any] = {} - pieces["long"] = full_out - pieces["short"] = full_out[:7] # maybe improved later - pieces["error"] = None + pieces['long'] = full_out + pieces['short'] = full_out[:7] # maybe improved later + pieces['error'] = None - branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], - cwd=root) + branch_name, rc = runner(GITS, ['rev-parse', '--abbrev-ref', 'HEAD'], cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() - if branch_name == "HEAD": + if branch_name == 'HEAD': # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. - branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) + branches, rc = runner(GITS, ['branch', '--contains'], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split("\n") + branches = branches.split('\n') # Remove the first line if we're running detached - if "(" in branches[0]: + if '(' in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] - if "master" in branches: - branch_name = "master" + if 'master' in branches: + branch_name = 'master' elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] - pieces["branch"] = branch_name + pieces['branch'] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix - dirty = git_describe.endswith("-dirty") - pieces["dirty"] = dirty + dirty = git_describe.endswith('-dirty') + pieces['dirty'] = dirty if dirty: - git_describe = git_describe[:git_describe.rindex("-dirty")] + git_describe = git_describe[: git_describe.rindex('-dirty')] # now we have TAG-NUM-gHEX or HEX - if "-" in git_describe: + if '-' in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? - pieces["error"] = ("unable to parse git-describe output: '%s'" - % describe_out) + pieces['error'] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag @@ -360,38 +381,37 @@ def git_pieces_from_vcs( if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" - % (full_tag, tag_prefix)) + pieces['error'] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix) return pieces - pieces["closest-tag"] = full_tag[len(tag_prefix):] + pieces['closest-tag'] = full_tag[len(tag_prefix) :] # distance: number of commits since tag - pieces["distance"] = int(mo.group(2)) + pieces['distance'] = int(mo.group(2)) # commit: short hex revision ID - pieces["short"] = mo.group(3) + pieces['short'] = mo.group(3) else: # HEX: no tags - pieces["closest-tag"] = None - out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) - pieces["distance"] = len(out.split()) # total number of commits + pieces['closest-tag'] = None + out, rc = runner(GITS, ['rev-list', 'HEAD', '--left-right'], cwd=root) + pieces['distance'] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() + date = runner(GITS, ['show', '-s', '--format=%ci', 'HEAD'], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] - pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + pieces['date'] = date.strip().replace(' ', 'T', 1).replace(' ', '', 1) return pieces def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" - if "+" in pieces.get("closest-tag", ""): - return "." - return "+" + if '+' in pieces.get('closest-tag', ''): + return '.' + return '+' def render_pep440(pieces: Dict[str, Any]) -> str: @@ -403,19 +423,18 @@ def render_pep440(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + if pieces['distance'] or pieces['dirty']: rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" + rendered += '%d.g%s' % (pieces['distance'], pieces['short']) + if pieces['dirty']: + rendered += '.dirty' else: # exception #1 - rendered = "0+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" + rendered = '0+untagged.%d.g%s' % (pieces['distance'], pieces['short']) + if pieces['dirty']: + rendered += '.dirty' return rendered @@ -428,24 +447,23 @@ def render_pep440_branch(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - if pieces["branch"] != "master": - rendered += ".dev0" + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + if pieces['distance'] or pieces['dirty']: + if pieces['branch'] != 'master': + rendered += '.dev0' rendered += plus_or_dot(pieces) - rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" + rendered += '%d.g%s' % (pieces['distance'], pieces['short']) + if pieces['dirty']: + rendered += '.dirty' else: # exception #1 - rendered = "0" - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+untagged.%d.g%s" % (pieces["distance"], - pieces["short"]) - if pieces["dirty"]: - rendered += ".dirty" + rendered = '0' + if pieces['branch'] != 'master': + rendered += '.dev0' + rendered += '+untagged.%d.g%s' % (pieces['distance'], pieces['short']) + if pieces['dirty']: + rendered += '.dirty' return rendered @@ -455,7 +473,7 @@ def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ - vc = str.split(ver, ".post") + vc = str.split(ver, '.post') return vc[0], int(vc[1] or 0) if len(vc) == 2 else None @@ -465,21 +483,21 @@ def render_pep440_pre(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.post0.devDISTANCE """ - if pieces["closest-tag"]: - if pieces["distance"]: + if pieces['closest-tag']: + if pieces['distance']: # update the post release segment - tag_version, post_version = pep440_split_post(pieces["closest-tag"]) + tag_version, post_version = pep440_split_post(pieces['closest-tag']) rendered = tag_version if post_version is not None: - rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) + rendered += '.post%d.dev%d' % (post_version + 1, pieces['distance']) else: - rendered += ".post0.dev%d" % (pieces["distance"]) + rendered += '.post0.dev%d' % (pieces['distance']) else: # no commits, use the tag as the version - rendered = pieces["closest-tag"] + rendered = pieces['closest-tag'] else: # exception #1 - rendered = "0.post0.dev%d" % pieces["distance"] + rendered = '0.post0.dev%d' % pieces['distance'] return rendered @@ -493,20 +511,20 @@ def render_pep440_post(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + if pieces['distance'] or pieces['dirty']: + rendered += '.post%d' % pieces['distance'] + if pieces['dirty']: + rendered += '.dev0' rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] + rendered += 'g%s' % pieces['short'] else: # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] + rendered = '0.post%d' % pieces['distance'] + if pieces['dirty']: + rendered += '.dev0' + rendered += '+g%s' % pieces['short'] return rendered @@ -518,24 +536,24 @@ def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + if pieces['distance'] or pieces['dirty']: + rendered += '.post%d' % pieces['distance'] + if pieces['branch'] != 'master': + rendered += '.dev0' rendered += plus_or_dot(pieces) - rendered += "g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" + rendered += 'g%s' % pieces['short'] + if pieces['dirty']: + rendered += '.dirty' else: # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["branch"] != "master": - rendered += ".dev0" - rendered += "+g%s" % pieces["short"] - if pieces["dirty"]: - rendered += ".dirty" + rendered = '0.post%d' % pieces['distance'] + if pieces['branch'] != 'master': + rendered += '.dev0' + rendered += '+g%s' % pieces['short'] + if pieces['dirty']: + rendered += '.dirty' return rendered @@ -547,17 +565,17 @@ def render_pep440_old(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"] or pieces["dirty"]: - rendered += ".post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + if pieces['distance'] or pieces['dirty']: + rendered += '.post%d' % pieces['distance'] + if pieces['dirty']: + rendered += '.dev0' else: # exception #1 - rendered = "0.post%d" % pieces["distance"] - if pieces["dirty"]: - rendered += ".dev0" + rendered = '0.post%d' % pieces['distance'] + if pieces['dirty']: + rendered += '.dev0' return rendered @@ -569,15 +587,15 @@ def render_git_describe(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - if pieces["distance"]: - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + if pieces['distance']: + rendered += '-%d-g%s' % (pieces['distance'], pieces['short']) else: # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" + rendered = pieces['short'] + if pieces['dirty']: + rendered += '-dirty' return rendered @@ -590,51 +608,57 @@ def render_git_describe_long(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ - if pieces["closest-tag"]: - rendered = pieces["closest-tag"] - rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) + if pieces['closest-tag']: + rendered = pieces['closest-tag'] + rendered += '-%d-g%s' % (pieces['distance'], pieces['short']) else: # exception #1 - rendered = pieces["short"] - if pieces["dirty"]: - rendered += "-dirty" + rendered = pieces['short'] + if pieces['dirty']: + rendered += '-dirty' return rendered def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" - if pieces["error"]: - return {"version": "unknown", - "full-revisionid": pieces.get("long"), - "dirty": None, - "error": pieces["error"], - "date": None} - - if not style or style == "default": - style = "pep440" # the default - - if style == "pep440": + if pieces['error']: + return { + 'version': 'unknown', + 'full-revisionid': pieces.get('long'), + 'dirty': None, + 'error': pieces['error'], + 'date': None, + } + + if not style or style == 'default': + style = 'pep440' # the default + + if style == 'pep440': rendered = render_pep440(pieces) - elif style == "pep440-branch": + elif style == 'pep440-branch': rendered = render_pep440_branch(pieces) - elif style == "pep440-pre": + elif style == 'pep440-pre': rendered = render_pep440_pre(pieces) - elif style == "pep440-post": + elif style == 'pep440-post': rendered = render_pep440_post(pieces) - elif style == "pep440-post-branch": + elif style == 'pep440-post-branch': rendered = render_pep440_post_branch(pieces) - elif style == "pep440-old": + elif style == 'pep440-old': rendered = render_pep440_old(pieces) - elif style == "git-describe": + elif style == 'git-describe': rendered = render_git_describe(pieces) - elif style == "git-describe-long": + elif style == 'git-describe-long': rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) - return {"version": rendered, "full-revisionid": pieces["long"], - "dirty": pieces["dirty"], "error": None, - "date": pieces.get("date")} + return { + 'version': rendered, + 'full-revisionid': pieces['long'], + 'dirty': pieces['dirty'], + 'error': None, + 'date': pieces.get('date'), + } def get_versions() -> Dict[str, Any]: @@ -648,8 +672,7 @@ def get_versions() -> Dict[str, Any]: verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, - verbose) + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass @@ -661,10 +684,13 @@ def get_versions() -> Dict[str, Any]: for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to find root of source tree", - "date": None} + return { + 'version': '0+unknown', + 'full-revisionid': None, + 'dirty': None, + 'error': 'unable to find root of source tree', + 'date': None, + } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -678,6 +704,10 @@ def get_versions() -> Dict[str, Any]: except NotThisMethod: pass - return {"version": "0+unknown", "full-revisionid": None, - "dirty": None, - "error": "unable to compute version", "date": None} + return { + 'version': '0+unknown', + 'full-revisionid': None, + 'dirty': None, + 'error': 'unable to compute version', + 'date': None, + } diff --git a/src/bids_validator/test_bids_validator.py b/src/bids_validator/test_bids_validator.py index de3bc8f..eaa3384 100644 --- a/src/bids_validator/test_bids_validator.py +++ b/src/bids_validator/test_bids_validator.py @@ -204,9 +204,7 @@ def test_is_session_level(validator: BIDSValidator, fname: str) -> None: '/sub-01/sub-01_acq_dwi.bval', # missed suffix value '/sub-01/sub-01_acq-23-singleband_dwi.bvec', # redundant -23- '/sub-01/anat/sub-01_acq-singleband_dwi.json', # redundant /anat/ - ( - '/sub-01/sub-01_recrod-record_acq-singleband_run-01_dwi.bval' - ), # redundant record-record_ + ('/sub-01/sub-01_recrod-record_acq-singleband_run-01_dwi.bval'), # redundant record-record_ '/sub_01/sub-01_acq-singleband_run-01_dwi.bvec', # wrong /sub_01/ '/sub-01/sub-01_acq-singleband__run-01_dwi.json', # wrong __ '/sub-01/ses-test/sub-01_ses_test_dwi.bval', # wrong ses_test From a3efa34fccd99d6adc43a645129d2299bcd4e4cd Mon Sep 17 00:00:00 2001 From: Anthony Galassi <28850131+bendhouseart@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:17:48 -0400 Subject: [PATCH 02/10] ran precommit --- .gitignore | 2 +- ruff.toml | 1 - src/bids_validator/_version.py | 462 +++++++++++++++------------------ 3 files changed, 217 insertions(+), 248 deletions(-) diff --git a/.gitignore b/.gitignore index 8bd6218..1a2c03f 100644 --- a/.gitignore +++ b/.gitignore @@ -297,7 +297,7 @@ $RECYCLE.BIN/ .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* diff --git a/ruff.toml b/ruff.toml index 702a998..9c3ac57 100644 --- a/ruff.toml +++ b/ruff.toml @@ -73,4 +73,3 @@ docstring-code-format = false # This only has an effect when the `docstring-code-format` setting is # enabled. docstring-code-line-length = "dynamic" - diff --git a/src/bids_validator/_version.py b/src/bids_validator/_version.py index a2b546f..af0aa8b 100644 --- a/src/bids_validator/_version.py +++ b/src/bids_validator/_version.py @@ -1,3 +1,4 @@ + # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build @@ -25,10 +26,10 @@ def get_keywords() -> Dict[str, str]: # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). - git_refnames = '$Format:%d$' - git_full = '$Format:%H$' - git_date = '$Format:%ci$' - keywords = {'refnames': git_refnames, 'full': git_full, 'date': git_date} + git_refnames = "$Format:%d$" + git_full = "$Format:%H$" + git_date = "$Format:%ci$" + keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords @@ -48,11 +49,11 @@ def get_config() -> VersioneerConfig: # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() - cfg.VCS = 'git' - cfg.style = 'pep440' - cfg.tag_prefix = '' - cfg.parentdir_prefix = '' - cfg.versionfile_source = 'src/bids_validator/_version.py' + cfg.VCS = "git" + cfg.style = "pep440" + cfg.tag_prefix = "" + cfg.parentdir_prefix = "" + cfg.versionfile_source = "src/bids_validator/_version.py" cfg.verbose = False return cfg @@ -67,14 +68,12 @@ class NotThisMethod(Exception): def register_vcs_handler(vcs: str, method: str) -> Callable: # decorator """Create decorator to mark a method as the handler of a VCS.""" - def decorate(f: Callable) -> Callable: """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f - return decorate @@ -91,41 +90,37 @@ def run_command( process = None popen_kwargs: Dict[str, Any] = {} - if sys.platform == 'win32': + if sys.platform == "win32": # This hides the console window if pythonw.exe is used startupinfo = subprocess.STARTUPINFO() startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW - popen_kwargs['startupinfo'] = startupinfo + popen_kwargs["startupinfo"] = startupinfo for command in commands: try: dispcmd = str([command] + args) # remember shell=False, so use git.cmd on windows, not just git - process = subprocess.Popen( - [command] + args, - cwd=cwd, - env=env, - stdout=subprocess.PIPE, - stderr=(subprocess.PIPE if hide_stderr else None), - **popen_kwargs, - ) + process = subprocess.Popen([command] + args, cwd=cwd, env=env, + stdout=subprocess.PIPE, + stderr=(subprocess.PIPE if hide_stderr + else None), **popen_kwargs) break except OSError as e: if e.errno == errno.ENOENT: continue if verbose: - print('unable to run %s' % dispcmd) + print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: - print('unable to find command, tried %s' % (commands,)) + print("unable to find command, tried %s" % (commands,)) return None, None stdout = process.communicate()[0].strip().decode() if process.returncode != 0: if verbose: - print('unable to run %s (error)' % dispcmd) - print('stdout was %s' % stdout) + print("unable to run %s (error)" % dispcmd) + print("stdout was %s" % stdout) return None, process.returncode return stdout, process.returncode @@ -146,25 +141,19 @@ def versions_from_parentdir( for _ in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): - return { - 'version': dirname[len(parentdir_prefix) :], - 'full-revisionid': None, - 'dirty': False, - 'error': None, - 'date': None, - } + return {"version": dirname[len(parentdir_prefix):], + "full-revisionid": None, + "dirty": False, "error": None, "date": None} rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: - print( - 'Tried directories %s but none started with prefix %s' - % (str(rootdirs), parentdir_prefix) - ) + print("Tried directories %s but none started with prefix %s" % + (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") -@register_vcs_handler('git', 'get_keywords') +@register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these @@ -173,35 +162,35 @@ def git_get_keywords(versionfile_abs: str) -> Dict[str, str]: # _version.py. keywords: Dict[str, str] = {} try: - with open(versionfile_abs, 'r') as fobj: + with open(versionfile_abs, "r") as fobj: for line in fobj: - if line.strip().startswith('git_refnames ='): + if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - keywords['refnames'] = mo.group(1) - if line.strip().startswith('git_full ='): + keywords["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - keywords['full'] = mo.group(1) - if line.strip().startswith('git_date ='): + keywords["full"] = mo.group(1) + if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: - keywords['date'] = mo.group(1) + keywords["date"] = mo.group(1) except OSError: pass return keywords -@register_vcs_handler('git', 'keywords') +@register_vcs_handler("git", "keywords") def git_versions_from_keywords( keywords: Dict[str, str], tag_prefix: str, verbose: bool, ) -> Dict[str, Any]: """Get version information from git keywords.""" - if 'refnames' not in keywords: - raise NotThisMethod('Short version file found') - date = keywords.get('date') + if "refnames" not in keywords: + raise NotThisMethod("Short version file found") + date = keywords.get("date") if date is not None: # Use only the last line. Previous lines may contain GPG signature # information. @@ -213,17 +202,17 @@ def git_versions_from_keywords( # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. - date = date.strip().replace(' ', 'T', 1).replace(' ', '', 1) - refnames = keywords['refnames'].strip() - if refnames.startswith('$Format'): + date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) + refnames = keywords["refnames"].strip() + if refnames.startswith("$Format"): if verbose: - print('keywords are unexpanded, not using') - raise NotThisMethod('unexpanded keywords, not a git-archive tarball') - refs = {r.strip() for r in refnames.strip('()').split(',')} + print("keywords are unexpanded, not using") + raise NotThisMethod("unexpanded keywords, not a git-archive tarball") + refs = {r.strip() for r in refnames.strip("()").split(",")} # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. - TAG = 'tag: ' - tags = {r[len(TAG) :] for r in refs if r.startswith(TAG)} + TAG = "tag: " + tags = {r[len(TAG):] for r in refs if r.startswith(TAG)} if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d @@ -234,42 +223,38 @@ def git_versions_from_keywords( # "stabilization", as well as "HEAD" and "master". tags = {r for r in refs if re.search(r'\d', r)} if verbose: - print("discarding '%s', no digits" % ','.join(refs - tags)) + print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: - print('likely tags: %s' % ','.join(sorted(tags))) + print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): - r = ref[len(tag_prefix) :] + r = ref[len(tag_prefix):] # Filter out refs that exactly match prefix or that don't start # with a number once the prefix is stripped (mostly a concern # when prefix is '') if not re.match(r'\d', r): continue if verbose: - print('picking %s' % r) - return { - 'version': r, - 'full-revisionid': keywords['full'].strip(), - 'dirty': False, - 'error': None, - 'date': date, - } + print("picking %s" % r) + return {"version": r, + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": None, + "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: - print('no suitable tags, using unknown + full revision id') - return { - 'version': '0+unknown', - 'full-revisionid': keywords['full'].strip(), - 'dirty': False, - 'error': 'no suitable tags', - 'date': None, - } + print("no suitable tags, using unknown + full revision id") + return {"version": "0+unknown", + "full-revisionid": keywords["full"].strip(), + "dirty": False, "error": "no suitable tags", "date": None} -@register_vcs_handler('git', 'pieces_from_vcs') +@register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs( - tag_prefix: str, root: str, verbose: bool, runner: Callable = run_command + tag_prefix: str, + root: str, + verbose: bool, + runner: Callable = run_command ) -> Dict[str, Any]: """Get version from 'git describe' in the root of the source tree. @@ -277,102 +262,96 @@ def git_pieces_from_vcs( expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ - GITS = ['git'] - if sys.platform == 'win32': - GITS = ['git.cmd', 'git.exe'] + GITS = ["git"] + if sys.platform == "win32": + GITS = ["git.cmd", "git.exe"] # GIT_DIR can interfere with correct operation of Versioneer. # It may be intended to be passed to the Versioneer-versioned project, # but that should not change where we get our version from. env = os.environ.copy() - env.pop('GIT_DIR', None) + env.pop("GIT_DIR", None) runner = functools.partial(runner, env=env) - _, rc = runner(GITS, ['rev-parse', '--git-dir'], cwd=root, hide_stderr=not verbose) + _, rc = runner(GITS, ["rev-parse", "--git-dir"], cwd=root, + hide_stderr=not verbose) if rc != 0: if verbose: - print('Directory %s not under git control' % root) + print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) - describe_out, rc = runner( - GITS, - [ - 'describe', - '--tags', - '--dirty', - '--always', - '--long', - '--match', - f'{tag_prefix}[[:digit:]]*', - ], - cwd=root, - ) + describe_out, rc = runner(GITS, [ + "describe", "--tags", "--dirty", "--always", "--long", + "--match", f"{tag_prefix}[[:digit:]]*" + ], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() - full_out, rc = runner(GITS, ['rev-parse', 'HEAD'], cwd=root) + full_out, rc = runner(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces: Dict[str, Any] = {} - pieces['long'] = full_out - pieces['short'] = full_out[:7] # maybe improved later - pieces['error'] = None + pieces["long"] = full_out + pieces["short"] = full_out[:7] # maybe improved later + pieces["error"] = None - branch_name, rc = runner(GITS, ['rev-parse', '--abbrev-ref', 'HEAD'], cwd=root) + branch_name, rc = runner(GITS, ["rev-parse", "--abbrev-ref", "HEAD"], + cwd=root) # --abbrev-ref was added in git-1.6.3 if rc != 0 or branch_name is None: raise NotThisMethod("'git rev-parse --abbrev-ref' returned error") branch_name = branch_name.strip() - if branch_name == 'HEAD': + if branch_name == "HEAD": # If we aren't exactly on a branch, pick a branch which represents # the current commit. If all else fails, we are on a branchless # commit. - branches, rc = runner(GITS, ['branch', '--contains'], cwd=root) + branches, rc = runner(GITS, ["branch", "--contains"], cwd=root) # --contains was added in git-1.5.4 if rc != 0 or branches is None: raise NotThisMethod("'git branch --contains' returned error") - branches = branches.split('\n') + branches = branches.split("\n") # Remove the first line if we're running detached - if '(' in branches[0]: + if "(" in branches[0]: branches.pop(0) # Strip off the leading "* " from the list of branches. branches = [branch[2:] for branch in branches] - if 'master' in branches: - branch_name = 'master' + if "master" in branches: + branch_name = "master" elif not branches: branch_name = None else: # Pick the first branch that is returned. Good or bad. branch_name = branches[0] - pieces['branch'] = branch_name + pieces["branch"] = branch_name # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix - dirty = git_describe.endswith('-dirty') - pieces['dirty'] = dirty + dirty = git_describe.endswith("-dirty") + pieces["dirty"] = dirty if dirty: - git_describe = git_describe[: git_describe.rindex('-dirty')] + git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX - if '-' in git_describe: + if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparsable. Maybe git-describe is misbehaving? - pieces['error'] = "unable to parse git-describe output: '%s'" % describe_out + pieces["error"] = ("unable to parse git-describe output: '%s'" + % describe_out) return pieces # tag @@ -381,37 +360,38 @@ def git_pieces_from_vcs( if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) - pieces['error'] = "tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix) + pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" + % (full_tag, tag_prefix)) return pieces - pieces['closest-tag'] = full_tag[len(tag_prefix) :] + pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag - pieces['distance'] = int(mo.group(2)) + pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID - pieces['short'] = mo.group(3) + pieces["short"] = mo.group(3) else: # HEX: no tags - pieces['closest-tag'] = None - out, rc = runner(GITS, ['rev-list', 'HEAD', '--left-right'], cwd=root) - pieces['distance'] = len(out.split()) # total number of commits + pieces["closest-tag"] = None + out, rc = runner(GITS, ["rev-list", "HEAD", "--left-right"], cwd=root) + pieces["distance"] = len(out.split()) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() - date = runner(GITS, ['show', '-s', '--format=%ci', 'HEAD'], cwd=root)[0].strip() + date = runner(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() # Use only the last line. Previous lines may contain GPG signature # information. date = date.splitlines()[-1] - pieces['date'] = date.strip().replace(' ', 'T', 1).replace(' ', '', 1) + pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces: Dict[str, Any]) -> str: """Return a + if we don't already have one, else return a .""" - if '+' in pieces.get('closest-tag', ''): - return '.' - return '+' + if "+" in pieces.get("closest-tag", ""): + return "." + return "+" def render_pep440(pieces: Dict[str, Any]) -> str: @@ -423,18 +403,19 @@ def render_pep440(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - if pieces['distance'] or pieces['dirty']: + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) - rendered += '%d.g%s' % (pieces['distance'], pieces['short']) - if pieces['dirty']: - rendered += '.dirty' + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" else: # exception #1 - rendered = '0+untagged.%d.g%s' % (pieces['distance'], pieces['short']) - if pieces['dirty']: - rendered += '.dirty' + rendered = "0+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" return rendered @@ -447,23 +428,24 @@ def render_pep440_branch(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0[.dev0]+untagged.DISTANCE.gHEX[.dirty] """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - if pieces['distance'] or pieces['dirty']: - if pieces['branch'] != 'master': - rendered += '.dev0' + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + if pieces["branch"] != "master": + rendered += ".dev0" rendered += plus_or_dot(pieces) - rendered += '%d.g%s' % (pieces['distance'], pieces['short']) - if pieces['dirty']: - rendered += '.dirty' + rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" else: # exception #1 - rendered = '0' - if pieces['branch'] != 'master': - rendered += '.dev0' - rendered += '+untagged.%d.g%s' % (pieces['distance'], pieces['short']) - if pieces['dirty']: - rendered += '.dirty' + rendered = "0" + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+untagged.%d.g%s" % (pieces["distance"], + pieces["short"]) + if pieces["dirty"]: + rendered += ".dirty" return rendered @@ -473,7 +455,7 @@ def pep440_split_post(ver: str) -> Tuple[str, Optional[int]]: Returns the release segments before the post-release and the post-release version number (or -1 if no post-release segment is present). """ - vc = str.split(ver, '.post') + vc = str.split(ver, ".post") return vc[0], int(vc[1] or 0) if len(vc) == 2 else None @@ -483,21 +465,21 @@ def render_pep440_pre(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.post0.devDISTANCE """ - if pieces['closest-tag']: - if pieces['distance']: + if pieces["closest-tag"]: + if pieces["distance"]: # update the post release segment - tag_version, post_version = pep440_split_post(pieces['closest-tag']) + tag_version, post_version = pep440_split_post(pieces["closest-tag"]) rendered = tag_version if post_version is not None: - rendered += '.post%d.dev%d' % (post_version + 1, pieces['distance']) + rendered += ".post%d.dev%d" % (post_version + 1, pieces["distance"]) else: - rendered += '.post0.dev%d' % (pieces['distance']) + rendered += ".post0.dev%d" % (pieces["distance"]) else: # no commits, use the tag as the version - rendered = pieces['closest-tag'] + rendered = pieces["closest-tag"] else: # exception #1 - rendered = '0.post0.dev%d' % pieces['distance'] + rendered = "0.post0.dev%d" % pieces["distance"] return rendered @@ -511,20 +493,20 @@ def render_pep440_post(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - if pieces['distance'] or pieces['dirty']: - rendered += '.post%d' % pieces['distance'] - if pieces['dirty']: - rendered += '.dev0' + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" rendered += plus_or_dot(pieces) - rendered += 'g%s' % pieces['short'] + rendered += "g%s" % pieces["short"] else: # exception #1 - rendered = '0.post%d' % pieces['distance'] - if pieces['dirty']: - rendered += '.dev0' - rendered += '+g%s' % pieces['short'] + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] return rendered @@ -536,24 +518,24 @@ def render_pep440_post_branch(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.postDISTANCE[.dev0]+gHEX[.dirty] """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - if pieces['distance'] or pieces['dirty']: - rendered += '.post%d' % pieces['distance'] - if pieces['branch'] != 'master': - rendered += '.dev0' + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" rendered += plus_or_dot(pieces) - rendered += 'g%s' % pieces['short'] - if pieces['dirty']: - rendered += '.dirty' + rendered += "g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" else: # exception #1 - rendered = '0.post%d' % pieces['distance'] - if pieces['branch'] != 'master': - rendered += '.dev0' - rendered += '+g%s' % pieces['short'] - if pieces['dirty']: - rendered += '.dirty' + rendered = "0.post%d" % pieces["distance"] + if pieces["branch"] != "master": + rendered += ".dev0" + rendered += "+g%s" % pieces["short"] + if pieces["dirty"]: + rendered += ".dirty" return rendered @@ -565,17 +547,17 @@ def render_pep440_old(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - if pieces['distance'] or pieces['dirty']: - rendered += '.post%d' % pieces['distance'] - if pieces['dirty']: - rendered += '.dev0' + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"] or pieces["dirty"]: + rendered += ".post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" else: # exception #1 - rendered = '0.post%d' % pieces['distance'] - if pieces['dirty']: - rendered += '.dev0' + rendered = "0.post%d" % pieces["distance"] + if pieces["dirty"]: + rendered += ".dev0" return rendered @@ -587,15 +569,15 @@ def render_git_describe(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - if pieces['distance']: - rendered += '-%d-g%s' % (pieces['distance'], pieces['short']) + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + if pieces["distance"]: + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 - rendered = pieces['short'] - if pieces['dirty']: - rendered += '-dirty' + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" return rendered @@ -608,57 +590,51 @@ def render_git_describe_long(pieces: Dict[str, Any]) -> str: Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ - if pieces['closest-tag']: - rendered = pieces['closest-tag'] - rendered += '-%d-g%s' % (pieces['distance'], pieces['short']) + if pieces["closest-tag"]: + rendered = pieces["closest-tag"] + rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 - rendered = pieces['short'] - if pieces['dirty']: - rendered += '-dirty' + rendered = pieces["short"] + if pieces["dirty"]: + rendered += "-dirty" return rendered def render(pieces: Dict[str, Any], style: str) -> Dict[str, Any]: """Render the given version pieces into the requested style.""" - if pieces['error']: - return { - 'version': 'unknown', - 'full-revisionid': pieces.get('long'), - 'dirty': None, - 'error': pieces['error'], - 'date': None, - } - - if not style or style == 'default': - style = 'pep440' # the default - - if style == 'pep440': + if pieces["error"]: + return {"version": "unknown", + "full-revisionid": pieces.get("long"), + "dirty": None, + "error": pieces["error"], + "date": None} + + if not style or style == "default": + style = "pep440" # the default + + if style == "pep440": rendered = render_pep440(pieces) - elif style == 'pep440-branch': + elif style == "pep440-branch": rendered = render_pep440_branch(pieces) - elif style == 'pep440-pre': + elif style == "pep440-pre": rendered = render_pep440_pre(pieces) - elif style == 'pep440-post': + elif style == "pep440-post": rendered = render_pep440_post(pieces) - elif style == 'pep440-post-branch': + elif style == "pep440-post-branch": rendered = render_pep440_post_branch(pieces) - elif style == 'pep440-old': + elif style == "pep440-old": rendered = render_pep440_old(pieces) - elif style == 'git-describe': + elif style == "git-describe": rendered = render_git_describe(pieces) - elif style == 'git-describe-long': + elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) - return { - 'version': rendered, - 'full-revisionid': pieces['long'], - 'dirty': pieces['dirty'], - 'error': None, - 'date': pieces.get('date'), - } + return {"version": rendered, "full-revisionid": pieces["long"], + "dirty": pieces["dirty"], "error": None, + "date": pieces.get("date")} def get_versions() -> Dict[str, Any]: @@ -672,7 +648,8 @@ def get_versions() -> Dict[str, Any]: verbose = cfg.verbose try: - return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) + return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, + verbose) except NotThisMethod: pass @@ -684,13 +661,10 @@ def get_versions() -> Dict[str, Any]: for _ in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: - return { - 'version': '0+unknown', - 'full-revisionid': None, - 'dirty': None, - 'error': 'unable to find root of source tree', - 'date': None, - } + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to find root of source tree", + "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) @@ -704,10 +678,6 @@ def get_versions() -> Dict[str, Any]: except NotThisMethod: pass - return { - 'version': '0+unknown', - 'full-revisionid': None, - 'dirty': None, - 'error': 'unable to compute version', - 'date': None, - } + return {"version": "0+unknown", "full-revisionid": None, + "dirty": None, + "error": "unable to compute version", "date": None} From a6297e5c5095bd4a32e2fa92c3c34f9ebefa61e6 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 12 Mar 2026 23:35:16 -0400 Subject: [PATCH 03/10] chore: Remove default config, only include overrides --- ruff.toml | 73 ++++++------------------------------------------------- 1 file changed, 7 insertions(+), 66 deletions(-) diff --git a/ruff.toml b/ruff.toml index 9c3ac57..817702f 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,75 +1,16 @@ -# Exclude a variety of commonly ignored directories. -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".ipynb_checkpoints", - ".mypy_cache", - ".nox", - ".pants.d", - ".pyenv", - ".pytest_cache", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - ".vscode", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "site-packages", - "venv", +extend-exclude = [ "tests/data", "_version.py", ] - line-length = 100 -indent-width = 4 [lint] -# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or -# McCabe complexity (`C901`) by default. -select = ["E4", "E7", "E9", "F"] -ignore = [] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +extend-select = [ +] +ignore = [ +] [format] -# Like Black, use double quotes for strings. quote-style = "single" - -# Like Black, indent with spaces, rather than tabs. -indent-style = "space" - -# Like Black, respect magic trailing commas. -skip-magic-trailing-comma = false - -# Like Black, automatically detect the appropriate line ending. -line-ending = "auto" - -# Enable auto-formatting of code examples in docstrings. Markdown, -# reStructuredText code/literal blocks and doctests are all supported. -# -# This is currently disabled by default, but it is planned for this -# to be opt-out in the future. -docstring-code-format = false - -# Set the line length limit used when formatting code snippets in -# docstrings. -# -# This only has an effect when the `docstring-code-format` setting is -# enabled. -docstring-code-line-length = "dynamic" +line-ending = "lf" +docstring-code-format = true From 15f8602f2d35941c18400b896c46a66082d47661 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 12 Mar 2026 23:29:15 -0400 Subject: [PATCH 04/10] [DATALAD RUNCMD] uv lock === Do not change lines below === { "chain": [], "cmd": "uv lock", "exit": 0, "extra_inputs": [], "inputs": [ "pyproject.toml", "uv.lock" ], "outputs": [ "uv.lock" ], "pwd": "." } ^^^ Do not change lines above ^^^ --- uv.lock | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 49c180e..d815a5f 100644 --- a/uv.lock +++ b/uv.lock @@ -80,6 +80,7 @@ cli = [ [package.dev-dependencies] dev = [ { name = "ipython" }, + { name = "ruff" }, ] test = [ { name = "cattrs" }, @@ -105,7 +106,10 @@ requires-dist = [ provides-extras = ["cli"] [package.metadata.requires-dev] -dev = [{ name = "ipython", specifier = ">=8.37.0" }] +dev = [ + { name = "ipython", specifier = ">=8.37.0" }, + { name = "ruff", specifier = ">=0.15.5" }, +] test = [ { name = "cattrs", specifier = ">=24.1.3" }, { name = "coverage", extras = ["toml"], specifier = ">=7.2" }, @@ -1563,6 +1567,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, ] +[[package]] +name = "ruff" +version = "0.15.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/df/f8629c19c5318601d3121e230f74cbee7a3732339c52b21daa2b82ef9c7d/ruff-0.15.6.tar.gz", hash = "sha256:8394c7bb153a4e3811a4ecdacd4a8e6a4fa8097028119160dffecdcdf9b56ae4", size = 4597916, upload-time = "2026-03-12T23:05:47.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/2f/4e03a7e5ce99b517e98d3b4951f411de2b0fa8348d39cf446671adcce9a2/ruff-0.15.6-py3-none-linux_armv6l.whl", hash = "sha256:7c98c3b16407b2cf3d0f2b80c80187384bc92c6774d85fefa913ecd941256fff", size = 10508953, upload-time = "2026-03-12T23:05:17.246Z" }, + { url = "https://files.pythonhosted.org/packages/70/60/55bcdc3e9f80bcf39edf0cd272da6fa511a3d94d5a0dd9e0adf76ceebdb4/ruff-0.15.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee7dcfaad8b282a284df4aa6ddc2741b3f4a18b0555d626805555a820ea181c3", size = 10942257, upload-time = "2026-03-12T23:05:23.076Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/005c29bd1726c0f492bfa215e95154cf480574140cb5f867c797c18c790b/ruff-0.15.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3bd9967851a25f038fc8b9ae88a7fbd1b609f30349231dffaa37b6804923c4bb", size = 10322683, upload-time = "2026-03-12T23:05:33.738Z" }, + { url = "https://files.pythonhosted.org/packages/5f/74/2f861f5fd7cbb2146bddb5501450300ce41562da36d21868c69b7a828169/ruff-0.15.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13f4594b04e42cd24a41da653886b04d2ff87adbf57497ed4f728b0e8a4866f8", size = 10660986, upload-time = "2026-03-12T23:05:53.245Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a1/309f2364a424eccb763cdafc49df843c282609f47fe53aa83f38272389e0/ruff-0.15.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e2ed8aea2f3fe57886d3f00ea5b8aae5bf68d5e195f487f037a955ff9fbaac9e", size = 10332177, upload-time = "2026-03-12T23:05:56.145Z" }, + { url = "https://files.pythonhosted.org/packages/30/41/7ebf1d32658b4bab20f8ac80972fb19cd4e2c6b78552be263a680edc55ac/ruff-0.15.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70789d3e7830b848b548aae96766431c0dc01a6c78c13381f423bf7076c66d15", size = 11170783, upload-time = "2026-03-12T23:06:01.742Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/6d488f6adca047df82cd62c304638bcb00821c36bd4881cfca221561fdfc/ruff-0.15.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:542aaf1de3154cea088ced5a819ce872611256ffe2498e750bbae5247a8114e9", size = 12044201, upload-time = "2026-03-12T23:05:28.697Z" }, + { url = "https://files.pythonhosted.org/packages/71/68/e6f125df4af7e6d0b498f8d373274794bc5156b324e8ab4bf5c1b4fc0ec7/ruff-0.15.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c22e6f02c16cfac3888aa636e9eba857254d15bbacc9906c9689fdecb1953ab", size = 11421561, upload-time = "2026-03-12T23:05:31.236Z" }, + { url = "https://files.pythonhosted.org/packages/f1/9f/f85ef5fd01a52e0b472b26dc1b4bd228b8f6f0435975442ffa4741278703/ruff-0.15.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98893c4c0aadc8e448cfa315bd0cc343a5323d740fe5f28ef8a3f9e21b381f7e", size = 11310928, upload-time = "2026-03-12T23:05:45.288Z" }, + { url = "https://files.pythonhosted.org/packages/8c/26/b75f8c421f5654304b89471ed384ae8c7f42b4dff58fa6ce1626d7f2b59a/ruff-0.15.6-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:70d263770d234912374493e8cc1e7385c5d49376e41dfa51c5c3453169dc581c", size = 11235186, upload-time = "2026-03-12T23:05:50.677Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d4/d5a6d065962ff7a68a86c9b4f5500f7d101a0792078de636526c0edd40da/ruff-0.15.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:55a1ad63c5a6e54b1f21b7514dfadc0c7fb40093fa22e95143cf3f64ebdcd512", size = 10635231, upload-time = "2026-03-12T23:05:37.044Z" }, + { url = "https://files.pythonhosted.org/packages/d6/56/7c3acf3d50910375349016cf33de24be021532042afbed87942858992491/ruff-0.15.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8dc473ba093c5ec238bb1e7429ee676dca24643c471e11fbaa8a857925b061c0", size = 10340357, upload-time = "2026-03-12T23:06:04.748Z" }, + { url = "https://files.pythonhosted.org/packages/06/54/6faa39e9c1033ff6a3b6e76b5df536931cd30caf64988e112bbf91ef5ce5/ruff-0.15.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:85b042377c2a5561131767974617006f99f7e13c63c111b998f29fc1e58a4cfb", size = 10860583, upload-time = "2026-03-12T23:05:58.978Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/509a201b843b4dfb0b32acdedf68d951d3377988cae43949ba4c4133a96a/ruff-0.15.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cef49e30bc5a86a6a92098a7fbf6e467a234d90b63305d6f3ec01225a9d092e0", size = 11410976, upload-time = "2026-03-12T23:05:39.955Z" }, + { url = "https://files.pythonhosted.org/packages/6c/25/3fc9114abf979a41673ce877c08016f8e660ad6cf508c3957f537d2e9fa9/ruff-0.15.6-py3-none-win32.whl", hash = "sha256:bbf67d39832404812a2d23020dda68fee7f18ce15654e96fb1d3ad21a5fe436c", size = 10616872, upload-time = "2026-03-12T23:05:42.451Z" }, + { url = "https://files.pythonhosted.org/packages/89/7a/09ece68445ceac348df06e08bf75db72d0e8427765b96c9c0ffabc1be1d9/ruff-0.15.6-py3-none-win_amd64.whl", hash = "sha256:aee25bc84c2f1007ecb5037dff75cef00414fdf17c23f07dc13e577883dca406", size = 11787271, upload-time = "2026-03-12T23:05:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d0/578c47dd68152ddddddf31cd7fc67dc30b7cdf639a86275fda821b0d9d98/ruff-0.15.6-py3-none-win_arm64.whl", hash = "sha256:c34de3dd0b0ba203be50ae70f5910b17188556630e2178fd7d79fc030eb0d837", size = 11060497, upload-time = "2026-03-12T23:05:25.968Z" }, +] + [[package]] name = "s3transfer" version = "0.14.0" From a507fa9acb0d417aac52334760c47dfdcab6c7c6 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 12 Mar 2026 23:37:44 -0400 Subject: [PATCH 05/10] [DATALAD RUNCMD] bash -c 'ruff check --fix && ruff format... === Do not change lines below === { "chain": [], "cmd": "bash -c 'ruff check --fix && ruff format'", "exit": 0, "extra_inputs": [], "inputs": [], "outputs": [], "pwd": "." } ^^^ Do not change lines above ^^^ --- src/bids_validator/bids_validator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/bids_validator/bids_validator.py b/src/bids_validator/bids_validator.py index 6c25ab5..2c81741 100644 --- a/src/bids_validator/bids_validator.py +++ b/src/bids_validator/bids_validator.py @@ -136,16 +136,16 @@ def parse(cls, path: str) -> dict[str, str]: -------- >>> from bids_validator import BIDSValidator >>> validator = BIDSValidator() - >>> validator.parse("/sub-01/anat/sub-01_rec-CSD_T1w.nii.gz") + >>> validator.parse('/sub-01/anat/sub-01_rec-CSD_T1w.nii.gz') {'subject': '01', 'datatype': 'anat', 'reconstruction': 'CSD', 'suffix': 'T1w', 'extension': '.nii.gz'} - >>> validator.parse("/sub-01/anat/sub-01_acq-23_rec-CSD_T1w.exe") + >>> validator.parse('/sub-01/anat/sub-01_acq-23_rec-CSD_T1w.exe') {} - >>> validator.parse("home/username/my_dataset/participants.tsv") + >>> validator.parse('home/username/my_dataset/participants.tsv') Traceback (most recent call last): ... ValueError: Path must be relative to root of a BIDS dataset, ... - >>> validator.parse("/participants.tsv") + >>> validator.parse('/participants.tsv') {'stem': 'participants', 'extension': '.tsv'} """ @@ -196,10 +196,10 @@ def is_bids(cls, path: str) -> bool: >>> from bids_validator import BIDSValidator >>> validator = BIDSValidator() >>> filepaths = [ - ... "/sub-01/anat/sub-01_rec-CSD_T1w.nii.gz", - ... "/sub-01/anat/sub-01_acq-23_rec-CSD_T1w.exe", # wrong extension - ... "home/username/my_dataset/participants.tsv", # not relative to root - ... "/participants.tsv", + ... '/sub-01/anat/sub-01_rec-CSD_T1w.nii.gz', + ... '/sub-01/anat/sub-01_acq-23_rec-CSD_T1w.exe', # wrong extension + ... 'home/username/my_dataset/participants.tsv', # not relative to root + ... '/participants.tsv', ... ] >>> for filepath in filepaths: ... print(validator.is_bids(filepath)) From 28d7f7c92e2561475171d9f6cb57cd41d5a96838 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 12 Mar 2026 23:41:33 -0400 Subject: [PATCH 06/10] Do not reformat .gitignore --- .gitignore | 2 +- .pre-commit-config.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1a2c03f..8bd6218 100644 --- a/.gitignore +++ b/.gitignore @@ -297,7 +297,7 @@ $RECYCLE.BIN/ .LSOverride # Icon must end with two \r -Icon +Icon # Thumbnails ._* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6b200e8..f9feb43 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,7 @@ repos: rev: v6.0.0 hooks: - id: trailing-whitespace + exclude: .gitignore - id: end-of-file-fixer - id: check-yaml - id: check-json From bbe0151ad04079ba4790330fa4f6003d93ac2092 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 12 Mar 2026 23:42:45 -0400 Subject: [PATCH 07/10] chore: Remove unnecessary parens --- src/bids_validator/test_bids_validator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bids_validator/test_bids_validator.py b/src/bids_validator/test_bids_validator.py index eaa3384..ba8f218 100644 --- a/src/bids_validator/test_bids_validator.py +++ b/src/bids_validator/test_bids_validator.py @@ -204,7 +204,7 @@ def test_is_session_level(validator: BIDSValidator, fname: str) -> None: '/sub-01/sub-01_acq_dwi.bval', # missed suffix value '/sub-01/sub-01_acq-23-singleband_dwi.bvec', # redundant -23- '/sub-01/anat/sub-01_acq-singleband_dwi.json', # redundant /anat/ - ('/sub-01/sub-01_recrod-record_acq-singleband_run-01_dwi.bval'), # redundant record-record_ + '/sub-01/sub-01_recrod-record_acq-singleband_run-01_dwi.bval', # redundant record-record_ '/sub_01/sub-01_acq-singleband_run-01_dwi.bvec', # wrong /sub_01/ '/sub-01/sub-01_acq-singleband__run-01_dwi.json', # wrong __ '/sub-01/ses-test/sub-01_ses_test_dwi.bval', # wrong ses_test From 425aa628f91743c53d75a931b6938b9e847b5836 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Thu, 12 Mar 2026 23:43:25 -0400 Subject: [PATCH 08/10] chore: Enable pyupgrade rules --- ruff.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/ruff.toml b/ruff.toml index 817702f..2285690 100644 --- a/ruff.toml +++ b/ruff.toml @@ -6,6 +6,7 @@ line-length = 100 [lint] extend-select = [ + 'UP', ] ignore = [ ] From 19a6872ec47fb9ba25de37b65619741337af50cd Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 13 Mar 2026 08:01:37 -0400 Subject: [PATCH 09/10] chore: Transfer config from pyproject.toml to ruff.toml --- pyproject.toml | 52 +------------------------------------------------- ruff.toml | 46 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 39 insertions(+), 59 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4289ced..562176f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -99,57 +99,7 @@ exclude_lines = [ [tool.black] exclude = ".*" -[tool.ruff] -line-length = 99 -extend-exclude = [ - "_version.py", - "tests/data", -] - -[tool.ruff.lint] -extend-select = [ - "F", - "E", - "W", - "I", - "D", - "UP", - "YTT", - "S", - "BLE", - "B", - "A", - # "CPY", - "C4", - "DTZ", - "T10", - # "EM", - "EXE", - "ISC", - "ICN", - "PT", - "Q", -] -ignore = [ - "ISC001", - "D105", - "D107", - "D203", - "D213", -] - -[tool.ruff.lint.flake8-quotes] -inline-quotes = "single" - -[tool.ruff.lint.extend-per-file-ignores] -"setup.py" = ["D"] -"*/test_*.py" = [ - "S101", - "D", -] - -[tool.ruff.format] -quote-style = "single" +# Ruff configured in ruff.toml [dependency-groups] dev = [ diff --git a/ruff.toml b/ruff.toml index 2285690..cc12b9b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,17 +1,47 @@ +line-length = 99 extend-exclude = [ - "tests/data", - "_version.py", + "_version.py", # Auto-generated + "tests/data", # Submodules ] -line-length = 100 + +[format] +quote-style = "single" +line-ending = "lf" +docstring-code-format = true [lint] extend-select = [ - 'UP', + "F", # pyflakes + "E", # pycodestyle errors + "W", # pycodestyle warnings + "I", # isort + "D", # pydocstyle + "UP", # pyupgrade + "YTT", # flake8-2020 + "S", # bandit + "BLE", # blind-except + "B", # bugbear + "A", # builtins + "C4", # comprehensions + "DTZ", # datetimez + "T10", # debugger + # "EM", # errmsg + "EXE", # executable + "ISC", # implicit-str-concat + "ICN", # import-conventions + "PT", # pytest-style + "Q", # quotes ] ignore = [ + "D105", # undocumented-magic-method + "D107", # undocumented __init__ + "D203", # blank line before class docstring + "D213", # multi-line-summary-first-line ] -[format] -quote-style = "single" -line-ending = "lf" -docstring-code-format = true +[lint.extend-per-file-ignores] +"setup.py" = ["D"] +"*/test_*.py" = [ + "S101", # assert + "D", +] From f268033e638492cab54287357894bb88bacbe504 Mon Sep 17 00:00:00 2001 From: "Christopher J. Markiewicz" Date: Fri, 13 Mar 2026 08:04:46 -0400 Subject: [PATCH 10/10] chore: Prefer formatter to Q000/Q003 --- ruff.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ruff.toml b/ruff.toml index cc12b9b..f3f462b 100644 --- a/ruff.toml +++ b/ruff.toml @@ -37,6 +37,8 @@ ignore = [ "D107", # undocumented __init__ "D203", # blank line before class docstring "D213", # multi-line-summary-first-line + "Q000", # double quotes + "Q003", # avoidable-escaped-quote ] [lint.extend-per-file-ignores]