diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..34fb486b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,42 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- +version: 2 +updates: + - package-ecosystem: uv + directory: / + schedule: + interval: daily + cooldown: + default-days: 7 + allow: + - dependency-type: all + groups: + python-version-updates: + applies-to: version-updates + patterns: ["*"] + exclude-patterns: ["setuptools"] + + - package-ecosystem: pre-commit + directory: / + schedule: + interval: daily + cooldown: + default-days: 7 + groups: + hook-version-updates: + applies-to: version-updates + patterns: ["*"] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: daily + cooldown: + default-days: 7 + groups: + action-version-updates: + applies-to: version-updates + patterns: ["*"] diff --git a/.github/linters/.perlcriticrc b/.github/linters/.perlcriticrc deleted file mode 100644 index 56c7874d..00000000 --- a/.github/linters/.perlcriticrc +++ /dev/null @@ -1,5 +0,0 @@ -# Report slightly less severe violations (severity >= 4) -severity = 4 -# Verbosity format -# filename:line:column [severity policy] message near 'string of source code that caused the violation' -verbose = %f:%l:%c [%s %p] %m near '%r'\n diff --git a/.github/linters/.python-black b/.github/linters/.python-black deleted file mode 100644 index e69de29b..00000000 diff --git a/.github/linters/.python-lint b/.github/linters/.python-lint deleted file mode 100644 index b49d38ea..00000000 --- a/.github/linters/.python-lint +++ /dev/null @@ -1,20 +0,0 @@ -[MASTER] -# Use multiple processes to speed up Pylint. -jobs=2 - -[MESSAGES CONTROL] -# C0114: Missing module docstring (missing-module-docstring) -# C0116: Missing function or method docstring (missing-function-docstring) -# C0103: Constant name doesn't conform to UPPER_CASE naming style (invalid-name) -# C0209: Formatting a regular string which could be an f-string (consider-using-f-string) -# E0401: Unable to import module (import-error) -# W0611: Unused import sphinx_rtd_theme (unused-import) -# W0622: Redefining built-in 'copyright' (redefined-builtin) -# R0801: Similar lines in 2 files -disable=C0114,C0116,C0103,C0209,E0401,W0611,W0622,R0801, - -[SIMILARITIES] -# Minimum lines number of a similarity. -min-similarity-lines=4 -ignore-comments=yes -ignore-docstrings=yes diff --git a/.github/linters/.ruff.toml b/.github/linters/.ruff.toml deleted file mode 100644 index 02338e42..00000000 --- a/.github/linters/.ruff.toml +++ /dev/null @@ -1,17 +0,0 @@ -cache-dir = "/tmp/.ruff_cache" -line-length = 88 -output-format = "grouped" -exclude = ["*.F90"] - -[lint] -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -# undefined-local-with-import-star-usage -ignore = ["F405"] - -[format] -# Like Black, indent with spaces, rather than tabs. -indent-style = "space" -quote-style = "double" -# Like Black, respect magic trailing commas. -skip-magic-trailing-comma = false diff --git a/.github/linters/.shellcheckrc b/.github/linters/.shellcheckrc deleted file mode 100644 index 472bfba3..00000000 --- a/.github/linters/.shellcheckrc +++ /dev/null @@ -1 +0,0 @@ -source-path=SCRIPTDIR diff --git a/.github/linters/.yaml-lint.yml b/.github/linters/.yaml-lint.yml deleted file mode 100644 index c8188849..00000000 --- a/.github/linters/.yaml-lint.yml +++ /dev/null @@ -1,15 +0,0 @@ -extends: default -ignore: | - .venv/ - .svn/ -rules: - comments: - level: warning - require-starting-space: true - min-spaces-from-content: 1 - document-start: disable - indentation: - spaces: 2 - indent-sequences: true - line-length: disable - truthy: disable diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 09f0aa1f..f1da6b39 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -5,7 +5,7 @@ # under which the code may be used. # ----------------------------------------------------------------------------- -# Lint/analyse code with Super-Linter. +# Run the same pre-commit checks used locally. name: Checks on: @@ -16,7 +16,7 @@ on: workflow_dispatch: concurrency: - group: ${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} permissions: @@ -27,42 +27,35 @@ jobs: name: Lint runs-on: ubuntu-latest timeout-minutes: 10 - strategy: - max-parallel: 4 - fail-fast: false - matrix: - python-version: ["3.11", "3.12", "3.14"] - - permissions: - contents: read - statuses: write - steps: - name: Checkout current - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false fetch-depth: 0 - - name: Super-Lint - uses: super-linter/super-linter/slim@d5b0a2ab116623730dd094f15ddc1b6b25bf7b99 # v8.3.2 + - name: Set up uv with Python + uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 + with: + python-version: '3.14' + enable-cache: false + + - name: Install quality tools + run: uv sync --frozen --extra dev + + - name: Check changed files + if: github.event_name != 'workflow_dispatch' env: - # Once the codebase is perfectly linted/formatted, switch to validating only changed files - # VALIDATE_ALL_CODEBASE: false # lint and format new or changed files only - FILTER_REGEX_EXCLUDE: (.*[.]conf.py|pull_request_template.md) - IGNORE_GITIGNORED_FILES: true - VALIDATE_BASH: true - VALIDATE_CHECKOV: true - VALIDATE_GITHUB_ACTIONS: true - VALIDATE_GIT_LEAKS: true - VALIDATE_GIT_MERGE_CONFLICT_MARKERS: true - VALIDATE_JSON: true - VALIDATE_MARKDOWN: true - VALIDATE_NATURAL_LANGUAGE: true - VALIDATE_PERL: true - VALIDATE_PYTHON_RUFF_FORMAT: true - VALIDATE_PYTHON_RUFF: true - VALIDATE_YAML: true - # To report GitHub Actions status checks - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FROM_REF: ${{ github.event.pull_request.base.sha || github.event.before }} + TO_REF: ${{ github.sha }} + run: | + if [[ "$FROM_REF" =~ ^0+$ ]]; then + uv run --frozen --extra dev pre-commit run --hook-stage manual --all-files --show-diff-on-failure + else + uv run --frozen --extra dev pre-commit run --hook-stage manual --from-ref "$FROM_REF" --to-ref "$TO_REF" --show-diff-on-failure + fi + + - name: Audit all files + if: github.event_name == 'workflow_dispatch' + run: uv run --frozen --extra dev pre-commit run --hook-stage manual --all-files --show-diff-on-failure ... diff --git a/.github/workflows/python_unit_tests.yaml b/.github/workflows/python_unit_tests.yaml index 7cfaf118..3ae59a59 100644 --- a/.github/workflows/python_unit_tests.yaml +++ b/.github/workflows/python_unit_tests.yaml @@ -10,10 +10,14 @@ on: pull_request: paths: - '**.py' + - 'pyproject.toml' + - 'uv.lock' - '.github/workflows/python_unit_tests.yaml' push: paths: - '**.py' + - 'pyproject.toml' + - 'uv.lock' - '.github/workflows/python_unit_tests.yaml' workflow_dispatch: @@ -32,22 +36,22 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Set up uv with Python - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + uses: astral-sh/setup-uv@68559e99cc807c1eb7e9238fd43a15bba460eec2 # v10.3.3 with: python-version: '3.14' enable-cache: true - name: Install dependencies - run: uv sync --extra test + run: uv sync --frozen --extra test - name: Test with pytest run: | # Setup git for git_bdiff and get_git_sources testing git config --global user.name 'Testing' git config --global user.email 'Testing' - uv run pytest -vv + uv run --frozen --extra test pytest -vv diff --git a/.github/zizmor.yml b/.github/zizmor.yml index e66c5e79..f3b335ad 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,8 +1,8 @@ -# ------------------------------------------------------------------------------ -# (c) Crown copyright Met Office. All rights reserved. +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -# ------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # Rules for Zizmor GitHub workflow linter. This file is used to configure which # rules to ignore for specific workflow files, allowing for exceptions to be diff --git a/.gitignore b/.gitignore index 207b57e7..8b5ef930 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- *.sublime-workspace *.code-workspace .vscode @@ -7,4 +12,8 @@ __pycache__/ *.swp .conda .idea -uv.lock +.venv/ +.ruff_cache/ +*.egg-info/ +.pytest_cache/ +.rumdl_cache/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..9a5205f2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,71 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- +minimum_pre_commit_version: '4.7.0' +exclude: | + (?x)( + ^script_copyright_checker/file/ + | ^script_umdp3_checker/tests/example_fortran_code\.F90$ + | (^|/)([^/]+\.conf\.py|pull_request_template\.md)$ + ) + +repos: + - repo: local + hooks: + - id: ruff-check + name: Ruff lint + entry: ruff check --fix + language: system + types_or: [python, pyi] + - id: ruff-format + name: Ruff format + entry: ruff format + language: system + types_or: [python, pyi] + - id: rumdl + name: Markdown lint + entry: rumdl check --fix + language: system + types: [markdown] + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0 + hooks: + - id: check-merge-conflict + - id: check-json + - id: check-yaml + - id: trailing-whitespace + - id: end-of-file-fixer + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: a6564f5d57444e123433a5c9af733e3975c77229 # frozen: v0.11.0.1-1 + hooks: + - id: shellcheck + types: [shell] + args: ["--source-path=SCRIPTDIR"] + + - repo: https://github.com/rhysd/actionlint + rev: 914e7df21a07ef503a81201c76d2b11c789d3fca # frozen: v1.7.12 + hooks: + - id: actionlint + + - repo: https://github.com/zizmorcore/zizmor-pre-commit + rev: 06a39750ec31860bb44f2acf67182c40016cd1e7 # frozen: v1.32.0 + hooks: + - id: zizmor + files: '(^|/)(\.github/(workflows/[^/]+|zizmor|dependabot)\.ya?ml|action\.ya?ml|\.pre-commit-(config|hooks)\.ya?ml)$' + pass_filenames: false + args: ["--no-progress", "--offline", "--config", ".github/zizmor.yml", "."] + + - repo: https://github.com/gitleaks/gitleaks + rev: 83d9cd684c87d95d656c1458ef04895a7f1cbd8e # frozen: v8.30.1 + hooks: + - id: gitleaks + stages: [pre-commit] + - id: gitleaks + alias: gitleaks-history + name: Detect hardcoded secrets in Git history + entry: gitleaks git --redact --verbose + stages: [manual] diff --git a/README.md b/README.md index ed3cb1fc..b8f4faa1 100644 --- a/README.md +++ b/README.md @@ -10,3 +10,134 @@ owned and maintained by the Simulation Systems and Deployment (SSD) team. Also contains a copy of `script_updater.sh` which is intended to live in the fcm repositories to pull from this repository. + +## Local Development + +Use Python 3.12 or newer. Run the following commands from the repository root. +Choose either `uv` (recommended) or `pip` to create your local environment. + +### With uv + +Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then run: + +```sh +uv sync --locked --extra dev --extra test +uv run --frozen --extra dev pre-commit install +uv run --frozen --extra test pytest -vv +``` + +`uv sync` creates `.venv`, installs the versions recorded in `uv.lock`, and +installs this project in editable mode. Code edits do not require reinstalling; +scripts continue to run from the checkout. Rerun `uv sync` after changing +dependencies. +Activate it before committing so the local hooks can find Ruff and rumdl: + +```sh +source .venv/bin/activate +``` + +Alternatively, commit with `uv run --frozen --extra dev git commit`. + +### With pip + +Use a Python 3.12 or newer executable to create a virtual environment: + +```sh +python3.12 -m venv .venv +source .venv/bin/activate +python -m pip install --upgrade pip +python -m pip install -e '.[dev,test]' +pre-commit install +pytest -vv +``` + +Keep the environment activated when committing or running quality tools. +The `-e` option installs the project in editable mode for continuous development. +The `pip` setup uses the same pinned Ruff, rumdl, and pre-commit versions as +`uv`, but resolves transitive dependencies independently of `uv.lock`. + +### Quality Checks + +Ruff handles Python linting, import sorting, and formatting. rumdl handles +Markdown linting and fixes. Their configuration is in `pyproject.toml`, and +the shared hooks are defined in `.pre-commit-config.yaml`. + +With `uv`, run all hooks explicitly: + +```sh +uv run --frozen --extra dev pre-commit run --hook-stage manual --all-files +``` + +With an activated `pip` environment, use: + +```sh +pre-commit run --hook-stage manual --all-files +``` + +Hooks run automatically on staged files at commit time. If a hook changes a +file, review and stage its fixes, then commit again. The first run installs +the hook environments; subsequent runs reuse them. External hooks require +Git, and the actionlint and gitleaks hooks also require Go on `PATH`. + +CI uses `uv.lock` and the same hooks to check changed files on pull requests +and pushes. Manually dispatching the Checks workflow audits all files; +existing files may still need import sorting or other cleanup. +Secret checks scan staged changes at commit time and Git history in CI and +manual audits. + +The lightweight hooks also check shell scripts, GitHub Actions workflows, +JSON and YAML syntax, merge conflicts, whitespace, and hardcoded secrets. +Zizmor checks GitHub Actions security using `.github/zizmor.yml`. Changes to +workflows, action definitions, Dependabot, or pre-commit/zizmor configuration +trigger a repository-wide audit locally and through the same hook in the +Checks workflow. It runs offline without GitHub credentials; network-dependent +audits are not available in this mode. + +Super-Linter is no longer used. Checkov, natural-language, and Perl checks +are not part of the replacement; YAML checking validates syntax rather than +the former style rules. + +For dependency updates, edit `pyproject.toml` and regenerate the lockfile with +`uv lock --upgrade`. Commit both files together. The Python Unit Tests workflow +uses the same `test` extra and runs `pytest -vv`. + +Remote pre-commit hooks are pinned to full Git commit SHAs. To update them +while retaining immutable pins, run: + +```sh +uv run --frozen --extra dev pre-commit autoupdate --freeze +``` + +Review and commit the updated hook configuration. Python quality tools and +the build backend use exact package versions in `pyproject.toml`; `uv.lock` +records resolved dependencies and SHA-256 hashes for their distribution +artifacts. Keeping registry packages avoids Rust source builds for Ruff and +rumdl. Use the locked `uv` setup for reproducible, hash-verified dependencies; +the plain `pip` setup does not enforce the hashes in `uv.lock`. + +### Dependency Security + +Dependabot checks Python dependencies, pre-commit hooks, and GitHub Actions +for version updates every weekday. Routine updates are grouped by ecosystem; +security updates are left ungrouped for independent review. Hook updates retain +full commit SHA pins and their `# frozen:` version labels. + +Routine version updates have a seven-day cooldown to reduce exposure to newly +published compromised releases. Security updates bypass this cooldown. +Setuptools remains at `80.9.0` for downstream compatibility, and future +setuptools updates are kept separate from grouped Python updates for review. + +### Note for repository admins + +A repository administrator must enable the dependency graph, Dependabot alerts, +and Dependabot security updates in GitHub's repository security settings after +this configuration reaches the default branch. The YAML configuration does not +enable alerts or email notifications by itself. Maintainers should also enable +Dependabot alert notifications in their personal GitHub notification settings. + +Treat critical- and high-severity CVE alerts as emergency triage items. In the +repository's Security tab, filter Dependabot alerts using `severity:critical` +or `severity:high`, assess exposure, and prioritize the corresponding security +update pull request. Alerts are driven by GitHub's advisory database and are +not delayed until the scheduled version-update check. Do not wait for a grouped +routine update or auto-merge an unreviewed security fix. diff --git a/fortitude_linter/fortitude_launcher.py b/fortitude_linter/fortitude_launcher.py index ecb9adef..a37ffc0e 100755 --- a/fortitude_linter/fortitude_launcher.py +++ b/fortitude_linter/fortitude_launcher.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 -############################################################################## -# (c) Crown copyright Met Office. All rights reserved. +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -############################################################################## +# ----------------------------------------------------------------------------- """ Launch fortitude on list of directories. Run on all and print outputs. Fail if any style changes required. """ -import sys -import subprocess import argparse +import subprocess +import sys from pathlib import Path diff --git a/gh_review_project/cr_deadline.py b/gh_review_project/cr_deadline.py index 8f84297e..85c53a85 100755 --- a/gh_review_project/cr_deadline.py +++ b/gh_review_project/cr_deadline.py @@ -12,12 +12,13 @@ import argparse from pathlib import Path -from review_project import ProjectData, ISSUE_ID + +from review_project import ISSUE_ID, ProjectData def remove_milestone( issue_data: ProjectData, milestone: str, dry_run: bool = False -) -> int: +) -> None: """ Remove the milestone from all open issues that do not have any linked PRs attached to them. Leave a comment explaining why. diff --git a/gh_review_project/finish_milestone.py b/gh_review_project/finish_milestone.py index ba2281b0..64e31b17 100755 --- a/gh_review_project/finish_milestone.py +++ b/gh_review_project/finish_milestone.py @@ -15,9 +15,10 @@ * Closed PRs against this milestone """ -from pathlib import Path import argparse -from review_project import ProjectData, REVIEW_ID, ISSUE_ID +from pathlib import Path + +from review_project import ISSUE_ID, REVIEW_ID, ProjectData from set_milestone import add_milestone diff --git a/gh_review_project/review_project.py b/gh_review_project/review_project.py index d7609742..a8ca5550 100644 --- a/gh_review_project/review_project.py +++ b/gh_review_project/review_project.py @@ -12,10 +12,10 @@ from __future__ import annotations import json -import subprocess -from pathlib import Path import shlex +import subprocess from collections import defaultdict +from pathlib import Path REVIEW_ID = 376 ISSUE_ID = 418 @@ -35,10 +35,10 @@ class ProjectData: """ A class to hold GitHub project data - pull_requsts: list raw_data turned into a list of PullRequest objects. + pull_requests: list raw_data turned into a list of PullRequest objects. test: bool Run using test data and extra logging. milestones: list All milestones currently used in the project - repos: list All repositories currectly represented in the project + repos: list All repositories correctly represented in the project """ def __init__(self, project: int, items: list, test: bool = False): @@ -275,7 +275,7 @@ def count_items( milestone: str, repository: str = "all", status: str = "all", - message: str = None, + message: str | None = None, ) -> int: """ Count items filtered using the provided information. Print details of items found. diff --git a/gh_review_project/set_milestone.py b/gh_review_project/set_milestone.py index 01a2c710..df46f596 100755 --- a/gh_review_project/set_milestone.py +++ b/gh_review_project/set_milestone.py @@ -12,7 +12,8 @@ import argparse from pathlib import Path -from review_project import ProjectData, REVIEW_ID + +from review_project import REVIEW_ID, ProjectData def print_banner(message: str) -> None: diff --git a/gh_review_project/workload.py b/gh_review_project/workload.py index 0eb4df9c..78cf7853 100755 --- a/gh_review_project/workload.py +++ b/gh_review_project/workload.py @@ -15,9 +15,9 @@ import json import subprocess from pathlib import Path -from prettytable import PrettyTable -from review_project import ProjectData, REVIEW_ID +from prettytable import PrettyTable +from review_project import REVIEW_ID, ProjectData lfric_repositories = [ "lfric_apps", @@ -232,7 +232,7 @@ def main(total: bool, test: bool, capture_project: bool, file: Path): "Other": Team("SimSysCodeReviewers", test), } - # Create tables for each combination of reviewers and reposotories + # Create tables for each combination of reviewers and repositories tables = {} ## Table for non-LFRic repositories diff --git a/git-migration/ssd_svn2git.sh b/git-migration/ssd_svn2git.sh index 81aa8789..f1d5a7bc 100755 --- a/git-migration/ssd_svn2git.sh +++ b/git-migration/ssd_svn2git.sh @@ -1,4 +1,9 @@ #!/bin/bash --login +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- set -euo pipefail # ---------------------------------------------------------------------------- diff --git a/github_scripts/get_git_sources.py b/github_scripts/get_git_sources.py index e61bb9d2..dcd84a99 100644 --- a/github_scripts/get_git_sources.py +++ b/github_scripts/get_git_sources.py @@ -11,15 +11,15 @@ top-level of the repo. If not set, then a default list is defined below. """ +import logging import os import re +import shlex import subprocess from datetime import datetime -from typing import Optional, Union from pathlib import Path from shutil import rmtree -import shlex -import logging +from typing import Optional, Union logger = logging.getLogger(__name__) diff --git a/github_scripts/git_bdiff.py b/github_scripts/git_bdiff.py index 2db51ddf..cae9c49a 100644 --- a/github_scripts/git_bdiff.py +++ b/github_scripts/git_bdiff.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *********************************COPYRIGHT************************************ +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *********************************COPYRIGHT************************************ +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Module to obtain a list of all altered files on a git branch from point where it diverged from the parent branch to the most recent diff --git a/github_scripts/merge_sources.py b/github_scripts/merge_sources.py index 9f884911..ad82693b 100755 --- a/github_scripts/merge_sources.py +++ b/github_scripts/merge_sources.py @@ -9,11 +9,12 @@ """ import argparse +import logging import os -import yaml from pathlib import Path + +import yaml from get_git_sources import clone_and_merge, set_https, validate_dependencies -import logging def parse_args(): diff --git a/github_scripts/rose_stem_extract_source.py b/github_scripts/rose_stem_extract_source.py index 26033b93..4914ca99 100755 --- a/github_scripts/rose_stem_extract_source.py +++ b/github_scripts/rose_stem_extract_source.py @@ -11,11 +11,12 @@ environment variables """ +import logging import os -from pathlib import Path from ast import literal_eval +from pathlib import Path + from get_git_sources import clone_and_merge, set_https, validate_dependencies -import logging def main() -> None: diff --git a/github_scripts/suite_data.py b/github_scripts/suite_data.py index 3d23a27d..86b59094 100644 --- a/github_scripts/suite_data.py +++ b/github_scripts/suite_data.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Class containing helper methods for gathering data needed for a SuiteReport object """ @@ -18,7 +18,6 @@ from typing import Optional, Union import yaml - from git_bdiff import GitBDiff, GitInfo diff --git a/github_scripts/suite_report_git.py b/github_scripts/suite_report_git.py index a9acafa8..4aff0e84 100755 --- a/github_scripts/suite_report_git.py +++ b/github_scripts/suite_report_git.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Script to produce report on testing completed by a simulation-systems test suite. Intended to be run at the end of a rose-stem run. diff --git a/github_scripts/tests/test_get_git_sources.py b/github_scripts/tests/test_get_git_sources.py index 66112be3..69eadc0d 100644 --- a/github_scripts/tests/test_get_git_sources.py +++ b/github_scripts/tests/test_get_git_sources.py @@ -9,19 +9,20 @@ import os import subprocess -from shlex import split from pathlib import Path +from shlex import split + import pytest from ..get_git_sources import ( - validate_dependencies, - determine_mirror_fetch, - set_https, + check_existing, clone_repo, clone_repo_mirror, - sync_repo, - check_existing, + determine_mirror_fetch, merge_source, + set_https, + sync_repo, + validate_dependencies, ) diff --git a/github_scripts/tests/test_git_bdiff.py b/github_scripts/tests/test_git_bdiff.py index c261cea6..70e4b424 100644 --- a/github_scripts/tests/test_git_bdiff.py +++ b/github_scripts/tests/test_git_bdiff.py @@ -1,19 +1,19 @@ #!/usr/bin/env python3 -# *********************************COPYRIGHT************************************ +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *********************************COPYRIGHT************************************ +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Test suite for git_bdiff module. """ import os import subprocess -import pytest -from ..git_bdiff import GitBDiff, GitBDiffError, GitBDiffNotGit, GitInfo, GitBase +import pytest +from ..git_bdiff import GitBase, GitBDiff, GitBDiffError, GitBDiffNotGit, GitInfo # Disable warnings caused by the use of pytest fixtures # pylint: disable=redefined-outer-name @@ -244,25 +244,28 @@ def test_is_main(git_repo): assert info.is_main() -def find_previous_hash(): - """ - Loop over a git log output and extract a hash that isn't the current head - """ - - result = subprocess.run(["git", "log"], check=True, capture_output=True, text=True) - for line in result.stdout.split("\n"): - if line.startswith("commit") and "HEAD" not in line: - return line.split()[1] - - -def test_detached_head(git_repo): +@pytest.mark.parametrize("log_format", ["medium", "format:%h %s"]) +def test_detached_head(git_repo, monkeypatch, log_format): """Test Detached Head State""" + monkeypatch.setenv("GIT_CONFIG_COUNT", "1") + monkeypatch.setenv("GIT_CONFIG_KEY_0", "format.pretty") + monkeypatch.setenv("GIT_CONFIG_VALUE_0", log_format) + os.chdir(git_repo) subprocess.run(["git", "checkout", "main"], check=True) - commit_hash = find_previous_hash() - subprocess.run(["git", "checkout", commit_hash], check=True) + result = subprocess.run( + ["git", "rev-parse", "--verify", "HEAD"], + check=True, + capture_output=True, + text=True, + ) + commit_hash = result.stdout.strip() + subprocess.run(["git", "checkout", "--detach", commit_hash], check=True) - git_base = GitBase() - assert git_base.get_branch_name() == git_base.detached_head_reference + try: + git_base = GitBase() + assert git_base.get_branch_name() == git_base.detached_head_reference + finally: + subprocess.run(["git", "checkout", "main"], check=True) diff --git a/kgo_updates/kgo_update/kgo_update.py b/kgo_updates/kgo_update/kgo_update.py index bc6ce86e..a300aec7 100755 --- a/kgo_updates/kgo_update/kgo_update.py +++ b/kgo_updates/kgo_update/kgo_update.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ This script will parse the rose-ana-comparisons.db produced by a valid rose-stem suite, writing and executing a sub-script which performs a @@ -19,7 +19,7 @@ Optional Arguments: => --new-kgo-dir -N Give the name of the new kgo dir. If not specified then the - suite will install kgo to the directory kgois currently + suite will install kgo to the directory kgo is currently found in. => --non-interactive Run in non-interactive mode. The suite will not ask any questions. This is not a recommended option diff --git a/kgo_updates/kgo_update/meto_run_kgo_script.sh b/kgo_updates/kgo_update/meto_run_kgo_script.sh index e7b54219..c7ed1cb6 100755 --- a/kgo_updates/kgo_update/meto_run_kgo_script.sh +++ b/kgo_updates/kgo_update/meto_run_kgo_script.sh @@ -1,11 +1,11 @@ #!/usr/bin/env bash -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- -# This script is intended to be run by frum having been called by +# This script is intended to be run by umadmin having been called by # `../meto_update_kgo.sh` . It runs the `kgo_update.py` script which it # assumes is in the same directory. It takes suite name, suite user, version # number, ticket number and required platforms as inputs. It runs the script on @@ -85,7 +85,7 @@ if [[ $platforms == *"ex1a"* ]]; then # SSH to the ex1a with the run command if ssh -Y "$host_ex" "$command"; then - # rsync the generated variables file and script back to frum on linux + # rsync the generated variables file and script back to umadmin on linux # This cleans up the original files printf "${GREEN}\n\nSuccessfully installed on ex1a.\n${NC}" printf "${GREEN}Rsyncing the generated files into azspice ${variables_dir}.\n${NC}" diff --git a/kgo_updates/meto_update_kgo.sh b/kgo_updates/meto_update_kgo.sh index 9605736b..995eb101 100755 --- a/kgo_updates/meto_update_kgo.sh +++ b/kgo_updates/meto_update_kgo.sh @@ -1,9 +1,9 @@ #!/bin/bash -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- # This script will ask for kgo update information then moves the directory kgo_update # to ~${root_user}. It then calls `run_kgo_script.sh` as ${root_user} which itself calls the update diff --git a/lfric_macros/apply_macros.py b/lfric_macros/apply_macros.py index 3920df0e..a2419d6e 100755 --- a/lfric_macros/apply_macros.py +++ b/lfric_macros/apply_macros.py @@ -1,9 +1,9 @@ #!/usr/bin/env python -############################################################################## -# (c) Crown copyright 2024 Met Office. All rights reserved. +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -############################################################################## +# ----------------------------------------------------------------------------- """ Script for lfric_apps upgrade macros. Pre-processes the macros and then applies them to rose-stem apps. @@ -18,12 +18,13 @@ import shutil import subprocess import tempfile -import yaml -import networkx as nx from collections import defaultdict -from concurrent.futures import as_completed, ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed from pathlib import Path +import networkx as nx +import yaml + BLACK_COMMAND = "black --line-length=80" CLASS_NAME_REGEX = r"vn\d+(_t\d+\w*)?" TAG_REGEX = r"\s*=\s*[\"']\s*(\S+)\s*[\"']" @@ -725,7 +726,7 @@ def combine_macros(self, import_order: list[str]) -> str: import_order. Inputs: - import_order, the metadata import order to match the order of - marcro commands. + macro commands. Returns: - combined macro commands in a single formatted string """ diff --git a/lfric_macros/check_macro_chains.py b/lfric_macros/check_macro_chains.py index ff991cea..16ab17e4 100755 --- a/lfric_macros/check_macro_chains.py +++ b/lfric_macros/check_macro_chains.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -############################################################################## -# (c) Crown copyright 2025 Met Office. All rights reserved. +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -############################################################################## +# ----------------------------------------------------------------------------- """ Script to check that all version.py macro chains appear valid and that the final after tag of the chain matches metadata import statement for each rose app. diff --git a/lfric_macros/files/template_versions.py b/lfric_macros/files/template_versions.py index 63fe7648..55588550 100644 --- a/lfric_macros/files/template_versions.py +++ b/lfric_macros/files/template_versions.py @@ -1,6 +1,10 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- import sys - from metomi.rose.upgrade import MacroUpgrade # noqa: F401 diff --git a/lfric_macros/release_lfric.py b/lfric_macros/release_lfric.py index 85d86b0e..e4f106b1 100755 --- a/lfric_macros/release_lfric.py +++ b/lfric_macros/release_lfric.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -############################################################################## -# (c) Crown copyright 2025 Met Office. All rights reserved. +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -############################################################################## +# ----------------------------------------------------------------------------- """ Release a new version of LFRic by: @@ -19,9 +19,9 @@ import argparse import getpass import re -import subprocess -import shutil import shlex +import shutil +import subprocess from pathlib import Path from apply_macros import ( @@ -79,7 +79,7 @@ def raise_exception(result: subprocess.CompletedProcess, command: str) -> None: def find_meta_dirs(paths: list[Path], exclude_dirs: tuple[str] = ()) -> set[Path]: """ Return a set of rose-metadata directories that can be found in the apps and - core sources. Done by seaching for rose-meta.conf files. Records the parent + core sources. Done by searching for rose-meta.conf files. Records the parent directory of the current one, as rose-meta.conf files end up in 'HEAD' or 'vnX.Y' directories. """ @@ -439,7 +439,7 @@ def main() -> None: args.core, ) - # Find all metadata directories, excluing lfric-inputs as this has metadata but no + # Find all metadata directories, excluding lfric-inputs as this has metadata but no # macros. exclude_dirs = ( ".git", diff --git a/lfric_macros/tests/test_apply_macros.py b/lfric_macros/tests/test_apply_macros.py index 138fd2c2..d4a85c15 100644 --- a/lfric_macros/tests/test_apply_macros.py +++ b/lfric_macros/tests/test_apply_macros.py @@ -8,15 +8,16 @@ """ from pathlib import Path + import pytest from ..apply_macros import ( ApplyMacros, - split_macros, - match_python_import, deduplicate_list, - read_versions_file, + match_python_import, read_python_imports, + read_versions_file, + split_macros, ) # Commonly used paths diff --git a/lfric_macros/tests/test_lfric_apps_dir/test_read_python_imports.py b/lfric_macros/tests/test_lfric_apps_dir/test_read_python_imports.py index 77fb6869..9b24898b 100644 --- a/lfric_macros/tests/test_lfric_apps_dir/test_read_python_imports.py +++ b/lfric_macros/tests/test_lfric_apps_dir/test_read_python_imports.py @@ -1,4 +1,5 @@ import os # noqa: F401 -from shlex import split # noqa: F401 from pathlib import Path, PurePath # noqa: F401 +from shlex import split # noqa: F401 + import networkx as nx # noqa: F401 diff --git a/lfric_macros/validate_rose_meta.py b/lfric_macros/validate_rose_meta.py index 031a52c0..13e7b1d2 100755 --- a/lfric_macros/validate_rose_meta.py +++ b/lfric_macros/validate_rose_meta.py @@ -1,17 +1,17 @@ #!/usr/bin/env python3 -############################################################################## -# (c) Crown copyright 2024 Met Office. All rights reserved. +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -############################################################################## +# ----------------------------------------------------------------------------- """ Validate the rose metadata in lfric_apps """ +import argparse import os -import sys import subprocess -import argparse +import sys from pathlib import Path # A list of invalid metadata sections. Most are invalid as they are imported by diff --git a/lfric_styling/lfric_styling.py b/lfric_styling/lfric_styling.py index 7ad51182..22b0a257 100755 --- a/lfric_styling/lfric_styling.py +++ b/lfric_styling/lfric_styling.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file LICENCE -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Python script to apply LFRic styling to .F90/.f90 files, given a path/directory. @@ -15,10 +15,11 @@ -lowercasing keywords """ -import re import argparse import os +import re from pathlib import Path + from styling_keywords import NEW_KEYWORDS diff --git a/lfric_styling/styling_keywords.py b/lfric_styling/styling_keywords.py index 8427b3f1..0c9977b2 100644 --- a/lfric_styling/styling_keywords.py +++ b/lfric_styling/styling_keywords.py @@ -1,10 +1,10 @@ -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file LICENSE -# which you should have received as part of this distribution. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- # Created date: 10/03/2025 # Modified date: 30/09/2025 -# *****************************COPYRIGHT******************************* """ Newer keywords used during physics forking. diff --git a/nightly_testing/example_meto_configs.yaml b/nightly_testing/example_meto_configs.yaml index 1c278933..6e9be1c5 100644 --- a/nightly_testing/example_meto_configs.yaml +++ b/nightly_testing/example_meto_configs.yaml @@ -1,3 +1,8 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- base: cylc_run_path: /path/to/nightly/user/cylc-run rose_bush_base_url: https://cylchub/services/cylc-review/taskjobs/umtest/?suite= diff --git a/nightly_testing/generate_test_suite_cron.py b/nightly_testing/generate_test_suite_cron.py index 25fca01c..b1ba179c 100755 --- a/nightly_testing/generate_test_suite_cron.py +++ b/nightly_testing/generate_test_suite_cron.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Script to generate a cron file, from which nightly testing is run. @@ -35,6 +35,7 @@ import os import subprocess import sys + import yaml DEPENDENCIES = { diff --git a/nightly_testing/retrigger_nightlies.py b/nightly_testing/retrigger_nightlies.py index 9476313e..61924c88 100755 --- a/nightly_testing/retrigger_nightlies.py +++ b/nightly_testing/retrigger_nightlies.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Scan a users (inteded as frzz for nightly testing) cylc-run directory for diff --git a/nightly_testing/tests/test_generate_test_suite_cron.py b/nightly_testing/tests/test_generate_test_suite_cron.py index 3b839a39..4bd986b2 100644 --- a/nightly_testing/tests/test_generate_test_suite_cron.py +++ b/nightly_testing/tests/test_generate_test_suite_cron.py @@ -1,15 +1,20 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- import pytest + from ..generate_test_suite_cron import ( - MIRROR_PATH, CLONE_DIR, - generate_cron_timing_str, - generate_clean_commands, - populate_cl_variables, + MIRROR_PATH, create_git_clone_cron, + generate_clean_commands, + generate_cron_timing_str, generate_cylc_command, + populate_cl_variables, ) - # Test generate_cron_timing_str data_generate_cron_timing_str = [ ({"period": "weekly", "cron_launch": "30 00"}, "main", "30 00 * * 1 "), diff --git a/pyproject.toml b/pyproject.toml index 827961a9..b8b8357e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,15 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- +[build-system] +requires = ["setuptools==80.9.0"] +build-backend = "setuptools.build_meta" + [project] name = "simsys_scripts" -version = "2026.07.1" +version = "2026.11.1" description = "Development support scripts for Simulation Systems deployment" readme = "README.md" license = { file = "LICENCE" } @@ -9,15 +18,36 @@ requires-python = ">=3.12" dependencies = [] [project.optional-dependencies] +dev = [ + "pre-commit==4.7.0", + "ruff==0.17.4", + "rumdl==0.3.2" +] test = [ - "pytest", - "networkx", - "PyYAML" + "pytest==9.2.0", + "networkx==3.7", + "PyYAML==6.0.3" ] [project.urls] repository = "https://github.com/MetOffice/SimSys_Scripts" -discussion = "https://github.com/MetOffice/simulation-systems/discussions/categories/lfric" +discussion = "https://github.com/MetOffice/simulation-systems/discussions" [tool.setuptools] packages = [] + +[tool.ruff] +target-version = "py312" +line-length = 88 +extend-exclude = ["*.conf.py", ".svn"] + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F", "I"] +ignore = ["F405"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" + +[tool.rumdl] +respect-gitignore = true diff --git a/sbin/gh_add_user b/sbin/gh_add_user index d24d66a1..326cb26c 100755 --- a/sbin/gh_add_user +++ b/sbin/gh_add_user @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# ---------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -# ---------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Add collaborator to a repository # gh api --method PUT /repos/:owner/:repo/collaborators/:username diff --git a/sbin/gh_manage_labels b/sbin/gh_manage_labels index c5229944..c8aa3bb9 100755 --- a/sbin/gh_manage_labels +++ b/sbin/gh_manage_labels @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# ---------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -# ---------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Script to create standard labels in multiple GitHub repositories in parallel # Requires: diff --git a/sbin/gh_manage_milestones b/sbin/gh_manage_milestones index f2fd3b6b..433840a2 100755 --- a/sbin/gh_manage_milestones +++ b/sbin/gh_manage_milestones @@ -1,10 +1,10 @@ #!/usr/bin/env bash -# ---------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. # The file LICENCE, distributed with this code, contains details of the terms # under which the code may be used. -# ---------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Script to create, update and close milestones in multiple GitHub repositories # Requires GitHub CLI: https://cli.github.com/ and Admin privileges to the repos diff --git a/script_copyright_checker/bin/copyright_checker.py b/script_copyright_checker/bin/copyright_checker.py index 89e35d83..67f7b3dc 100755 --- a/script_copyright_checker/bin/copyright_checker.py +++ b/script_copyright_checker/bin/copyright_checker.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *********************************COPYRIGHT************************************ +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *********************************COPYRIGHT************************************ +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Script which tests code files within the UM repository to ensure they contain a recognised copyright notice. diff --git a/script_umdp3_checker/checker_dispatch_tables.py b/script_umdp3_checker/checker_dispatch_tables.py index e325f884..885d77e7 100644 --- a/script_umdp3_checker/checker_dispatch_tables.py +++ b/script_umdp3_checker/checker_dispatch_tables.py @@ -10,6 +10,7 @@ """ from typing import Callable + from umdp3_checker_rules import UMDP3Checker """ diff --git a/script_umdp3_checker/fortran_keywords.py b/script_umdp3_checker/fortran_keywords.py index a9d5e4b4..1b5735c8 100644 --- a/script_umdp3_checker/fortran_keywords.py +++ b/script_umdp3_checker/fortran_keywords.py @@ -1,8 +1,8 @@ -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file LICENSE -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ List of the Fortran Keywords for the UMDP3 checker to check against. diff --git a/script_umdp3_checker/tests/conftest.py b/script_umdp3_checker/tests/conftest.py index ff03af93..5d3a5031 100644 --- a/script_umdp3_checker/tests/conftest.py +++ b/script_umdp3_checker/tests/conftest.py @@ -1,4 +1,10 @@ +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- from pathlib import Path + import pytest diff --git a/script_umdp3_checker/tests/test_fortran_checks.py b/script_umdp3_checker/tests/test_fortran_checks.py index 721f91cc..ec805907 100644 --- a/script_umdp3_checker/tests/test_fortran_checks.py +++ b/script_umdp3_checker/tests/test_fortran_checks.py @@ -4,14 +4,14 @@ # under which the code may be used. # ----------------------------------------------------------------------------- -import pytest import sys from pathlib import Path +import pytest + # Add the current directory to Python path sys.path.insert(0, str(Path(__file__).parent.parent)) -from umdp3_checker_rules import UMDP3Checker, TestResult - +from umdp3_checker_rules import TestResult, UMDP3Checker # Prevent pytest from trying to collect TestResult as more tests: TestResult.__test__ = False # type: ignore diff --git a/script_umdp3_checker/tests/test_umdp3.py b/script_umdp3_checker/tests/test_umdp3.py index bec81679..4697b457 100644 --- a/script_umdp3_checker/tests/test_umdp3.py +++ b/script_umdp3_checker/tests/test_umdp3.py @@ -16,8 +16,8 @@ # Add the current directory to Python path sys.path.insert(0, str(Path(__file__).parent.parent)) -from umdp3_checker_rules import UMDP3Checker, TestResult from checker_dispatch_tables import CheckerDispatchTables +from umdp3_checker_rules import TestResult, UMDP3Checker # Prevent pytest from trying to collect TestResult as more tests: TestResult.__test__ = False # type: ignore diff --git a/script_umdp3_checker/tests/test_umdp3_conformance.py b/script_umdp3_checker/tests/test_umdp3_conformance.py index f502502b..eb81e898 100644 --- a/script_umdp3_checker/tests/test_umdp3_conformance.py +++ b/script_umdp3_checker/tests/test_umdp3_conformance.py @@ -4,28 +4,29 @@ # under which the code may be used. # ----------------------------------------------------------------------------- -import sys import subprocess +import sys from pathlib import Path from types import SimpleNamespace + import pytest # Add the current directory to Python path sys.path.insert(0, str(Path(__file__).parent.parent)) from umdp3_conformance import ( - StyleChecker, - detangle_file_types, ALLOWABLE_FILE_TYPES, GROUP_FILE_TYPES, Check_Runner, + CheckResult, + ConformanceChecker, + StyleChecker, + detangle_file_types, get_files_to_check, line_1, line_2, - which_cms_is_it, - ConformanceChecker, - CheckResult, process_arguments, + which_cms_is_it, ) diff --git a/script_umdp3_checker/tests/test_umdp3_rules_S3.py b/script_umdp3_checker/tests/test_umdp3_rules_S3.py index ab1df3e4..df058748 100644 --- a/script_umdp3_checker/tests/test_umdp3_rules_S3.py +++ b/script_umdp3_checker/tests/test_umdp3_rules_S3.py @@ -6,19 +6,19 @@ # from pyparsing import remove_quotes import pytest + # import sys # from pathlib import Path - # Add the current directory to Python path from ..umdp3_rules_S3 import ( - remove_comments, - remove_quoted, concatenate_lines, r3_1_1_there_can_be_only_one, r3_2_1_check_crown_copyright, r3_3_2_line_too_long, r3_4_1_capitalised_keywords, r3_4_2_no_full_uppercase_variable_names, + remove_comments, + remove_quoted, ) diff --git a/script_umdp3_checker/umdp3_checker_rules.py b/script_umdp3_checker/umdp3_checker_rules.py index eddc9140..05bdc34b 100644 --- a/script_umdp3_checker/umdp3_checker_rules.py +++ b/script_umdp3_checker/umdp3_checker_rules.py @@ -1,8 +1,8 @@ -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file LICENSE -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Package to contain functions which test for UMDP3 compliance. @@ -10,14 +10,15 @@ """ import re -from typing import List, Dict +from dataclasses import dataclass, field +from typing import Dict, List + from search_lists import ( + deprecated_c_identifiers, obsolescent_intrinsics, - unseparated_keywords_list, retired_ifdefs, - deprecated_c_identifiers, + unseparated_keywords_list, ) -from dataclasses import dataclass, field """ TODO : Several of the test functions are poor shadows of the original diff --git a/script_umdp3_checker/umdp3_conformance.py b/script_umdp3_checker/umdp3_conformance.py index 5bdaf5f7..0e986ca6 100644 --- a/script_umdp3_checker/umdp3_conformance.py +++ b/script_umdp3_checker/umdp3_conformance.py @@ -4,20 +4,21 @@ # under which the code may be used. # ----------------------------------------------------------------------------- -import subprocess -from abc import ABC, abstractmethod -from pathlib import Path -from typing import Callable, List, Set, Optional -from dataclasses import dataclass, field import argparse -from checker_dispatch_tables import CheckerDispatchTables -from umdp3_checker_rules import TestResult -from umdp3_rules_S3 import list_O_tests import concurrent.futures +import subprocess # Add custom modules to Python path if needed # Add the repository root to access fcm_bdiff and git_bdiff packages import sys +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from pathlib import Path +from typing import Callable, List, Optional, Set + +from checker_dispatch_tables import CheckerDispatchTables +from umdp3_checker_rules import TestResult +from umdp3_rules_S3 import list_O_tests sys.path.insert(0, str(Path(__file__).parent.parent)) diff --git a/script_umdp3_checker/umdp3_rules_S3.py b/script_umdp3_checker/umdp3_rules_S3.py index a5c12071..37a37334 100644 --- a/script_umdp3_checker/umdp3_rules_S3.py +++ b/script_umdp3_checker/umdp3_rules_S3.py @@ -8,9 +8,10 @@ For now, the document has been copied into this file as a placeholder for the rules as they appear.""" import re -from typing import List, Dict -from umdp3_checker_rules import TestResult +from typing import Dict, List + from fortran_keywords import fortran_keywords +from umdp3_checker_rules import TestResult """ToDo: This lot will need putting back as and when the tests that use them get imported/re-created""" # from fortran_keywords import fortran_keywords diff --git a/suite_report.py b/suite_report.py index 67148cdf..8eb719a0 100755 --- a/suite_report.py +++ b/suite_report.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # PYTHON_ARGCOMPLETE_OK -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ ## NOTE ## @@ -186,7 +186,7 @@ def _read_file(filename): def _write_file(filename, lines, newline=False): - """Takes filemname and list of strings and opt newline boolean. + """Takes filename and list of strings and opt newline boolean. Writes array to file of given name. The optional newline argument adds a newline at the end of each element of the list. @@ -263,8 +263,8 @@ def _dict_merge(main_dict, addon_dict, force=False): def _select_preferred(option_list): """Takes a list of strings, returns the fist one that is not None. - If the strings are report text in preffered order it essentially - ensures you get the prefered option from a list of choices.""" + If the strings are report text in preferred order it essentially + ensures you get the preferred option from a list of choices.""" pref_opt = None for choice in option_list: if choice is not None: @@ -650,7 +650,7 @@ def parse_rose_suite_run(self): # This test is a little problematic when running this script # on a JULES rose-stem suite as JULES has no 'need' of the two # compare variables and to prevent the warning their absence - # would produce from occuring unnecessarily in JULES they have + # would produce from occurring unnecessarily in JULES they have # been added to rose-suite.conf for now compare_output = _parse_string("COMPARE_OUTPUT", lines) compare_wallclock = _parse_string("COMPARE_WALLCLOCK", lines) diff --git a/umdp3_fixer/ampersands.py b/umdp3_fixer/ampersands.py index 27bf99ad..0370b166 100755 --- a/umdp3_fixer/ampersands.py +++ b/umdp3_fixer/ampersands.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ ## NOTE ## This module is one of several for which the Master copy is in the @@ -32,16 +32,17 @@ # import traceback from optparse import OptionParser + from fstring_parse import ( + DQUOTE, + SQUOTE, + ParsingError, find_commented_char, find_quoted_char, is_continuation, is_pp_continuation, is_str_continuation, - ParsingError, replace_characters, - DQUOTE, - SQUOTE, ) # Default column in which to place ampersands. diff --git a/umdp3_fixer/fstring_parse.py b/umdp3_fixer/fstring_parse.py index fdbdc189..a81533bb 100644 --- a/umdp3_fixer/fstring_parse.py +++ b/umdp3_fixer/fstring_parse.py @@ -1,8 +1,8 @@ -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ ## NOTE ## This module is one of several for which the Master copy is in the diff --git a/umdp3_fixer/indentation.py b/umdp3_fixer/indentation.py index 8b3747db..d10f67d8 100755 --- a/umdp3_fixer/indentation.py +++ b/umdp3_fixer/indentation.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ ## NOTE ## This module is one of several for which the Master copy is in the @@ -18,6 +18,7 @@ import re import sys + from fstring_parse import ( is_continuation, is_pp_continuation, diff --git a/umdp3_fixer/rosestem_branch_checker.py b/umdp3_fixer/rosestem_branch_checker.py index c8328d8d..bcbb5f8f 100755 --- a/umdp3_fixer/rosestem_branch_checker.py +++ b/umdp3_fixer/rosestem_branch_checker.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ Rose-stem test for checking the branch working copy with the trunk. @@ -22,12 +22,12 @@ """ -from argparse import ArgumentParser -import sys import os import shutil import subprocess +import sys import tempfile +from argparse import ArgumentParser def copy_working_branch(model_source): diff --git a/umdp3_fixer/styling.py b/umdp3_fixer/styling.py index 98e76fdb..c3b7479d 100755 --- a/umdp3_fixer/styling.py +++ b/umdp3_fixer/styling.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ ## NOTE ## This module is one of several for which the Master copy is in the @@ -17,18 +17,19 @@ import re import sys + from fstring_parse import ( + DQUOTE, + SQUOTE, + ParsingError, blank_fcomments, blank_fstring, clean_str_continuation, is_continuation, is_pp_continuation, is_str_continuation, - ParsingError, partial_blank_fstring, simplify_line, - DQUOTE, - SQUOTE, ) CODE_REPLACEMENTS = [ diff --git a/umdp3_fixer/umdp3_fixer.py b/umdp3_fixer/umdp3_fixer.py index 9b95dcb0..e90d38a2 100755 --- a/umdp3_fixer/umdp3_fixer.py +++ b/umdp3_fixer/umdp3_fixer.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 # -# *****************************COPYRIGHT******************************* +# ----------------------------------------------------------------------------- # (C) Crown copyright Met Office. All rights reserved. -# For further details please refer to the file COPYRIGHT.txt -# which you should have received as part of this distribution. -# *****************************COPYRIGHT******************************* +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- """ ## NOTE ## This module is one of several for which the Master copy is in the @@ -34,13 +34,14 @@ import os import re -import sys import subprocess +import sys from argparse import ArgumentParser from shutil import which + +from ampersands import apply_ampersand_shift from indentation import apply_indentation from styling import apply_styling -from ampersands import apply_ampersand_shift from whitespace import apply_whitespace_fixes diff --git a/umdp3_fixer/whitespace.py b/umdp3_fixer/whitespace.py index 1b3cd6ab..a2be4e11 100755 --- a/umdp3_fixer/whitespace.py +++ b/umdp3_fixer/whitespace.py @@ -1,4 +1,9 @@ #!/usr/bin/env python3 +# ----------------------------------------------------------------------------- +# (C) Crown copyright Met Office. All rights reserved. +# The file LICENCE, distributed with this code, contains details of the terms +# under which the code may be used. +# ----------------------------------------------------------------------------- ## NOTE ## # This module is one of several for which the Master copy is in the UM @@ -7,19 +12,20 @@ # is copied over. # import sys -import re import argparse +import re + from fstring_parse import ( + DQUOTE, + SQUOTE, + ParsingError, blank_fcomments, blank_fstring, clean_str_continuation, is_continuation, is_pp_continuation, is_str_continuation, - ParsingError, partial_blank_fstring, - DQUOTE, - SQUOTE, ) # These 4 are defined globally for visibility. They are actually only used in diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..a969bd8a --- /dev/null +++ b/uv.lock @@ -0,0 +1,285 @@ +version = 1 +revision = 5 +requires-python = ">=3.12" + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/68/bc/d36966de7304047a1001a7559fbf266c464bb5d8cc85464536ef8c9bcc32/distlib-0.4.4.tar.gz", hash = "sha256:ca4175b4ac7f71223cc30ed7248c45d2b38c2439d8ee6e498ab67ed63cf3508d", size = 621304, upload-time = "2026-10-16T13:56:34.203Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/2b/238f02a4927fa2e27aa995371d240a468b52e42eae44a678434aff736b38/distlib-0.4.4-py2.py3-none-any.whl", hash = "sha256:cf09d55b1e85a89576b036aeb71550bfd68320f3f21761ca329eadadad918638", size = 472099, upload-time = "2026-10-16T13:56:32.053Z" }, +] + +[[package]] +name = "filelock" +version = "4.2.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/1d/d730c664fcb4f1d264a722722a3134cf0881854f73c1fd58abf87572f232/filelock-4.2.9.tar.gz", hash = "sha256:96530b7c538d4ea08493ab067247aab7948c54d8cd622cce711c97cccc5d9896", size = 663116, upload-time = "2026-11-04T18:37:10.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/05/76184fe08339723d643ab47887a8e4de8f2921ca4e008617ab7490af7857/filelock-4.2.9-py3-none-any.whl", hash = "sha256:a379c50a729e5b68c8519a4d26489f755d9769960a236ecd838190e2a78db2e9", size = 164734, upload-time = "2026-11-04T18:37:08.273Z" }, +] + +[[package]] +name = "identify" +version = "2.6.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/35/d70c0006c7cee65999ea94a6273e60b2094f600a3d8b71b04318253fc643/identify-2.6.20.tar.gz", hash = "sha256:ad729860a923858d26917c2f4fb0a1d83d27a75b1e090c06440c573f048f3285", size = 99681, upload-time = "2026-09-26T20:29:07.187Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/70/fffc9613501877c0a10a1ef73a165e6ded3e53f6e7502227fef56d973933/identify-2.6.20-py2.py3-none-any.whl", hash = "sha256:6a16b69b93187244e0548cbfd25b3e4a6f9a7a2ad784625c3bec2b8d27b81aaa", size = 99506, upload-time = "2026-09-26T20:29:06.054Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/e1/2069291243c926a2ff1cd706c7f3eeb9b62144bf60f77c9fb9ff2fb26bd3/iniconfig-2.3.1.tar.gz", hash = "sha256:67f4b9c50da0dedf52af349e7749a80a9057a5031199791b906c3bb3ae878960", size = 21209, upload-time = "2026-10-06T22:48:38.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/43/4ca9e49d27a1fcf6bece6f6aec0ea46bb9112489b93d4b688fb415457bdb/iniconfig-2.3.1-py3-none-any.whl", hash = "sha256:9121e2c1fdb355232495be3194c8dfe87ccc2d5dee45947b78e68f499790d7a7", size = 7552, upload-time = "2026-10-06T22:48:36.959Z" }, +] + +[[package]] +name = "networkx" +version = "3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/76/3af777226b63a5e64a6b36b1ec5855c14e2b94a37096d4760e595fc43511/networkx-3.7.tar.gz", hash = "sha256:fd77a511bd90f39f3d016351345b52cf5319b813bdca01de3f755d3cca62e96a", size = 1866482, upload-time = "2026-09-21T16:45:16.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/cd/fe58041e9011f307c490e3e17dd48cc516448f7c698a3f2d9d9d65d7e6a8/networkx-3.7-py3-none-any.whl", hash = "sha256:e3fd2c13a7814cee3746340d8d7f8598a67f16a58bf47fb7f8793fab6efca1b0", size = 2142205, upload-time = "2026-09-21T16:45:14.609Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/99/d27cfc9518f49e4cbc25c683ec7cf8704a9d31311341c291c0e78ddf791b/nodeenv-1.12.0.tar.gz", hash = "sha256:4ea7f88339310afebc3253f2c7a91b94bacf97d73a36a5c108962abdfaca501e", size = 131292, upload-time = "2026-10-31T19:03:18.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/6a/8a9ac7d5e2f7cf53c459bfef90ebc17a2f8cd54b665af96799d4c534840e/nodeenv-1.12.0-py3-none-any.whl", hash = "sha256:80644d65c10cc2be12e3e4b54b3367ff288c7ad24ae75c2dcc65454fc7449d4d", size = 39442, upload-time = "2026-10-31T19:03:17.427Z" }, +] + +[[package]] +name = "packaging" +version = "26.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/fa/3944b40b07da9ce895c0e6303a5ab7d53da063554f534556b134a54d6093/packaging-26.3.tar.gz", hash = "sha256:94edc256424af38762eb31306eed28beb9f0efc50a8837492c9d6fd6004aed79", size = 313412, upload-time = "2026-08-04T18:15:28.737Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9a/8475be631fb33ca1466915e70fd594c42a4e84b512db650eaa14f90fe75d/platformdirs-4.14.5.tar.gz", hash = "sha256:32e9921db7385aa8169bee4fe9501923e5715ba9fb7ea3b9c69b0d8e0639f7a1", size = 71464, upload-time = "2026-11-04T16:54:50.358Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/ae/ecbbbfd0e4343a6a521d49b3c8656fb4643dee2a4f6eaf941cf3280391a8/platformdirs-4.14.5-py3-none-any.whl", hash = "sha256:8e778c6fa421516f4d3369449f8df3b0e1b72c0e0005fe88e3ec772d0a14e750", size = 35402, upload-time = "2026-11-04T16:54:48.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/db/7fc19e6f2dc92a966727031389fc2e08b558f0f25eb7403c1119ad4713cd/pluggy-1.7.0.tar.gz", hash = "sha256:d1eaa46ebb595891b860ab086b4d09c8588af65ebd4361b8e8f4bb8920b90ba8", size = 123304, upload-time = "2026-10-15T09:50:58.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/9e/2b38731e0fc536806f16490e1a12d7f0dc2a1235aa8cc07bcc75416a7daa/pluggy-1.7.0-py3-none-any.whl", hash = "sha256:7dd7b0d8832ba3cb632c306926ded123429211b83641b35dc5c41ad2d34f9bec", size = 27082, upload-time = "2026-10-15T09:50:56.808Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/c3/6965c2690ddce885ec75121572d065a5b94c5a5d8d3e657927ecda9fd715/pre_commit-4.7.0.tar.gz", hash = "sha256:1f3deee12b914c8fa184bd2b5953391c2e4f36763e403d4a23dc9983fa7c6e86", size = 198120, upload-time = "2026-10-12T20:45:40.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/83/ce0a196e4f7b20b11ce3aa1fc72fb973f64b7628d51c1b8aebad37fb7f81/pre_commit-4.7.0-py2.py3-none-any.whl", hash = "sha256:2e229038ad3656081b70c27913157e4b636ef306f643f024a8a8e3f0f7b2f877", size = 226261, upload-time = "2026-10-12T20:45:38.858Z" }, +] + +[[package]] +name = "pygments" +version = "2.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/49/2e/ced460408999b33da6b31b0021b0f37d329e202d4169aeb164493778f25b/pygments-2.21.0.tar.gz", hash = "sha256:610ca751c9bc2492b38eb9a38a7fbc93edbbb2d7182edaf34e66ae493dee5c8c", size = 5005329, upload-time = "2026-08-17T08:02:48.824Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/46/17f022dd3e953bf20a04a028a21ec746d942f8d2af30fa0f124fa0e6a684/pygments-2.21.0-py3-none-any.whl", hash = "sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9", size = 1250147, upload-time = "2026-08-17T08:02:44.912Z" }, +] + +[[package]] +name = "pytest" +version = "9.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/42/bb4641652e58dedeac2ec51abc1f8cae4fee4ec63899a0999016229a0f2a/pytest-9.2.0.tar.gz", hash = "sha256:97c4f3b1f3c7414aa36465e09b8220f1d03627993bd51b1282e193753c80557b", size = 1797555, upload-time = "2026-10-27T21:30:20.728Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/39/1f5d8c8877a0fa327db959c19341d4be61660de1ab6b7391f9eb805bcd67/pytest-9.2.0-py3-none-any.whl", hash = "sha256:07acdcf88ec174f907dc01af70bf52d49b014bbab03faeeade31c96b18c2e588", size = 411776, upload-time = "2026-10-27T21:30:19.131Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.9.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/55/70b46fae646bc6f5cb5e82569949d925cff1c7b5ce20ec4a11e0d977f8c6/python_discovery-1.9.5.tar.gz", hash = "sha256:86279f5d2728bc067de28b7ddc571be2eacc2ae0e7048f8629d2481a0f8848a2", size = 141121, upload-time = "2026-11-06T15:32:11.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/35/25bd092ca8a7e71192afdca67fbff4f797da74408e74ac3626e13b409ec0/python_discovery-1.9.5-py3-none-any.whl", hash = "sha256:a1fae8fca11e4bc61e38491c2aafff29e430b3c5e272d51ae52fb895bc4c0fac", size = 44377, upload-time = "2026-11-06T15:32:09.821Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "ruff" +version = "0.17.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/84/0e/3d154514b5bad104f7e1c13169c2d09aa9959fccdc06eb47610ccdfa83ed/ruff-0.17.4.tar.gz", hash = "sha256:f32d178b50e826e00f6c6c1be3f56333068acb9b67fe167143527fa9b81e890d", size = 5357750, upload-time = "2026-11-05T18:52:17.638Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/61/c83c78bbb6108f6c6270a106cf12b54175f4f42da088b9bb880f18e31289/ruff-0.17.4-py3-none-linux_armv6l.whl", hash = "sha256:2b08c4ff98dc234f4a3871a3105353b922fa875ea1073ca31001eb8c06bb4d89", size = 10252195, upload-time = "2026-11-05T18:51:36.594Z" }, + { url = "https://files.pythonhosted.org/packages/0a/12/dd158708378866e8b53e145a465dbc51a7cccb9fa197a74d67eaedde7234/ruff-0.17.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:13e781c3c5f663e7fba737eaaeb70dd943dd55171daf316afb25cd5268cfe0c7", size = 10266044, upload-time = "2026-11-05T18:51:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/28/32/89a94a8eb11fd505a381ae6f04e190d038d9958695209dc1786ce5cfcda3/ruff-0.17.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7ed2f416349944f908f9574215994602242b0836844eba48eec61473033afca0", size = 9432227, upload-time = "2026-11-05T18:51:41.968Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/a392dc38b215798ae72e639d228b7f6c02d826a95d1c0ad7c4c0ee49ef5b/ruff-0.17.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ea6e0017c785f7282092b11e46338e4ebed1b9e575ebf2e55a8a1eda968241a", size = 10136900, upload-time = "2026-11-05T18:51:44.562Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8f/a880f60f42005f6ffdf58f77af34c7ee327000b5607f48a2f6a69d8b242c/ruff-0.17.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e061dcc0933c7031a616ac8d66ac540309b2aa21ab558101a8f53e9ac7a1185", size = 10051245, upload-time = "2026-11-05T18:51:46.7Z" }, + { url = "https://files.pythonhosted.org/packages/09/55/98844264108af7d5ec27ca3a9cd5a989094ca657a14599c4b69a537a0e9a/ruff-0.17.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e95a1515161138be3cbd1b06c06e739d8feb38ade35564556999fffc3686e", size = 10704341, upload-time = "2026-11-05T18:51:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/28/a2/5edf97659c3ac7c73f978003bd314936f671037fdac6b978e9de3e4b2b65/ruff-0.17.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f28cf0dd37cb172fa5a8d2e92e6ada3a9dbd0609f094ed24039929eef0b7524", size = 11394465, upload-time = "2026-11-05T18:51:51.534Z" }, + { url = "https://files.pythonhosted.org/packages/f2/8f/51371ae856b2bef4d15a044a574695fbc0f6ae63aafa42a2a22428070fb6/ruff-0.17.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b21c771b296888e8a35c6f48555f4b44e4483f61675d6ed51810a5203469ce6", size = 10988262, upload-time = "2026-11-05T18:51:53.91Z" }, + { url = "https://files.pythonhosted.org/packages/af/1b/62874f14c7873f27c7887d2b1ea41de990e14ba9518801ffe8eacb5d51a6/ruff-0.17.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cd025509b2e15721b426fc70fd946d71f43b3db3b08e946dfec2d696d911690", size = 10572829, upload-time = "2026-11-05T18:51:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/98/30/860b9dae67432b16b88558a9cb2d3194825fa62838f8b9cc49f5d9022095/ruff-0.17.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:49b77f7b3921669466882a4881168801bdb06ec2cd83fb936049016cf5fdece1", size = 10617545, upload-time = "2026-11-05T18:51:58.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/08/781a4b41a1c83fd6e30fea610d6502a6f94e49cf94693fd37bdcbc6bfe11/ruff-0.17.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e3b84eb74f260d1c2dbd236fd9d4c011d4fdd0a52bdf8aea57a10e3a1b71fca5", size = 10111955, upload-time = "2026-11-05T18:52:00.59Z" }, + { url = "https://files.pythonhosted.org/packages/3c/e9/94b1c7b677120f30839254518924f73a8157e9bcfa200edcd5796fecdb99/ruff-0.17.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:20e43d7a08e712e33067ba887e6574546c79bd4831596ce8f16803ac7ff9ad90", size = 10058355, upload-time = "2026-11-05T18:52:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/27/5f/a825910b9f309336dc33cb8c0774126662c1d2f765699b3e0a1bd538d01f/ruff-0.17.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0d1f4848fd2ec0499be4046db9b7dfcc952666885f00233cc7033515282556a8", size = 10415932, upload-time = "2026-11-05T18:52:06.264Z" }, + { url = "https://files.pythonhosted.org/packages/8b/b8/0796468423413ea4df1a6e2442ae26cf49b8f7dda0eecf246e0d5d0dde54/ruff-0.17.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6007e7f3a0e55b0fbdb947af69cf24c21b4928df6be8ef3e767c69b5ccccf496", size = 10826033, upload-time = "2026-11-05T18:52:08.675Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ab/d8e407669ffe5014429baf780c2df47e001968a6e4c17f615e2e7b13a9ba/ruff-0.17.4-py3-none-win32.whl", hash = "sha256:3971a78cba76298b284c8321e6d40d93045ed348bec5c369d85099efc97fb7cf", size = 9931993, upload-time = "2026-11-05T18:52:11.169Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/42af8a72d8946e3c9efd2bbf6641e462b6832783b0d043d63ef5b280ba7f/ruff-0.17.4-py3-none-win_amd64.whl", hash = "sha256:67c5a894fc054cf41f75f7afa24815de5a74329d97634126c2a1015740ff4e8c", size = 10292922, upload-time = "2026-11-05T18:52:13.436Z" }, + { url = "https://files.pythonhosted.org/packages/68/9e/f1dbffcfb8ff25dadbc21f87a8b73c3848abe4abada9d4b08ccef8bec40c/ruff-0.17.4-py3-none-win_arm64.whl", hash = "sha256:181ae38a9a8fb94925bf7d0794327b48d7fd7f4b50cfd84c0e999cc09ef8d131", size = 9889396, upload-time = "2026-11-05T18:52:15.549Z" }, +] + +[[package]] +name = "rumdl" +version = "0.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/ec/826c3e86e97a20266da6f5d265ecc4228e692f32071b4eca733afd81860e/rumdl-0.3.2.tar.gz", hash = "sha256:cfabc5bc98b4379496b7feeb9caf7f51888081e5a3bb4b1972cb629bcffb65a1", size = 6006614, upload-time = "2026-10-28T15:30:14.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/54/e16c69b61222798221111f28b63f4c8d97d5a72c89ddd0d244bd6198f3d0/rumdl-0.3.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e42076d20f798f2a006671ce9b647f238466957d5363c057afc6a46e8960f0db", size = 8287795, upload-time = "2026-10-28T15:30:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/5a/6e/9189a61f3b8a9b1c5e6d508b2958023b111f2b8291f96b8b677e03d6d2b7/rumdl-0.3.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8cbd42b21ae554bce0452d9fc366b3c20585102ee024033e191dd7c24d34ea67", size = 7706426, upload-time = "2026-10-28T15:29:56.576Z" }, + { url = "https://files.pythonhosted.org/packages/f2/8e/a0ab280bffbd951544d6e64fe41cb30087cec7eb3754ebc80f6b1778f517/rumdl-0.3.2-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:f2013176864b10984a5c385727db804ff9823b7516fde5786a9e5da654b341e8", size = 8005951, upload-time = "2026-10-28T15:29:59.269Z" }, + { url = "https://files.pythonhosted.org/packages/be/b8/4d1596cbdb92d3df69e23e3e232a866e1bb02ac441d63d8e90f860c42c24/rumdl-0.3.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:621ec2bee5195f4ce017ee4fc6fa62c23fe4ade1250e4fa3dcfb51992fef72b3", size = 8577137, upload-time = "2026-10-28T15:32:45.675Z" }, + { url = "https://files.pythonhosted.org/packages/33/7b/370f9c7d170e1256a71b853c04c88a51e07e849fede2b43a560822f499e4/rumdl-0.3.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ee9dc51af07de10d7cda83c4c3af4cf47307678104911dac8340983df187d331", size = 7972059, upload-time = "2026-10-28T15:30:01.563Z" }, + { url = "https://files.pythonhosted.org/packages/45/f6/4350b53b764926f5bd8db429d36296822d0e2adac3cd8c68a7fab65b2d98/rumdl-0.3.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:aa198a9d4352459602832b85c6c7a487278df901f98c6b5e9322345ac427d082", size = 8568077, upload-time = "2026-10-28T15:30:12.01Z" }, + { url = "https://files.pythonhosted.org/packages/99/0a/c36054b97d3e497827d89e0c2767c805af8db7edc5bc0521560c1e7a836d/rumdl-0.3.2-py3-none-win_amd64.whl", hash = "sha256:50295352d5ee1b284ed982b06c6bbe73e01522223872e3b620f80ab3bb5e69cb", size = 8700946, upload-time = "2026-10-28T15:30:06.701Z" }, +] + +[[package]] +name = "simsys-scripts" +version = "2026.11.1" +source = { editable = "." } + +[package.optional-dependencies] +dev = [ + { name = "pre-commit" }, + { name = "ruff" }, + { name = "rumdl" }, +] +test = [ + { name = "networkx" }, + { name = "pytest" }, + { name = "pyyaml" }, +] + +[package.metadata] +requires-dist = [ + { name = "networkx", marker = "extra == 'test'", specifier = "==3.7" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = "==4.7.0" }, + { name = "pytest", marker = "extra == 'test'", specifier = "==9.2.0" }, + { name = "pyyaml", marker = "extra == 'test'", specifier = "==6.0.3" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.17.4" }, + { name = "rumdl", marker = "extra == 'dev'", specifier = "==0.3.2" }, +] +provides-extras = ["dev", "test"] + +[[package]] +name = "virtualenv" +version = "21.15.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "python-discovery" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/bd/e40ad2df6a9efc1f859ea23d63fd7371cff68252833f182ca9af819bb4d3/virtualenv-21.15.6.tar.gz", hash = "sha256:c8b65c7d206ca6ef18de9352c160b3f04f66fff9f6b0ec8fd31f14688eba52c4", size = 5641659, upload-time = "2026-11-06T18:17:32.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/63/fd514dee3159f74a96ac01beb8f460b65e4ef0b972f63f4fe9f0cd2aa0e3/virtualenv-21.15.6-py3-none-any.whl", hash = "sha256:4b99ab9b95ff11043939bb4cad2fa0449175fa5a478372864f62160cb6c1a7ed", size = 5680963, upload-time = "2026-11-06T18:17:30.128Z" }, +]