diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 66de4cd4..714c85d3 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -23,6 +23,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Initialize CodeQL uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 with: diff --git a/.github/workflows/manual.yaml b/.github/workflows/manual.yaml index 2c159a93..9017fb94 100644 --- a/.github/workflows/manual.yaml +++ b/.github/workflows/manual.yaml @@ -15,11 +15,20 @@ on: permissions: contents: read +env: + # Only resolve distributions published at least one week ago, reducing the + # chance of installing packages affected by undetected supply chain attacks + UV_EXCLUDE_NEWER: 7 days + # Make uv pip install into the python provided by setup-python + UV_SYSTEM_PYTHON: 1 + jobs: tox-coverage: runs-on: ${{ inputs.OS }} steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: | @@ -28,7 +37,12 @@ jobs: 3.12 3.13 3.14 - - run: pip install -e ".[dev]" + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: true + cache-suffix: tox-coverage + cache-dependency-glob: pyproject.toml + - run: uv pip install -e ".[dev]" - run: tox -- --cov=../jsonargparse --cov-append - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d5cd299b..387d314c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -16,6 +16,13 @@ concurrency: permissions: contents: read +env: + # Only resolve distributions published at least one week ago, reducing the + # chance of installing packages affected by undetected supply chain attacks + UV_EXCLUDE_NEWER: 7 days + # Make uv pip install into the python provided by setup-python + UV_SYSTEM_PYTHON: 1 + jobs: linux: @@ -25,20 +32,26 @@ jobs: python: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python }} - cache: pip + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: true + cache-suffix: py${{ matrix.python }} + cache-dependency-glob: pyproject.toml - name: Test without optional dependencies and without pyyaml run: | - pip install .[coverage] - pip uninstall -y pyyaml types-PyYAML + uv pip install .[coverage] + uv pip uninstall pyyaml types-PyYAML pytest --cov --cov-report=term --cov-report=xml --junit-xml=junit.xml mv coverage.xml coverage_py${{ matrix.python }}_bare.xml mv junit.xml junit_py${{ matrix.python }}_bare.xml - name: Test with all optional dependencies run: | - pip install .[test,all] + uv pip install .[test,all] pytest --cov --cov-report=term --cov-report=xml --junit-xml=junit.xml mv coverage.xml coverage_py${{ matrix.python }}_all.xml mv junit.xml junit_py${{ matrix.python }}_all.xml @@ -64,6 +77,8 @@ jobs: python: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python }} @@ -79,6 +94,8 @@ jobs: python: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python }} @@ -94,6 +111,8 @@ jobs: python: ["3.12", "3.14"] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: ${{ matrix.python }} @@ -105,6 +124,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" @@ -116,14 +137,20 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - cache: pip + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: true + cache-suffix: pydantic-v1 + cache-dependency-glob: pyproject.toml - name: With pydantic<2 run: | - pip install .[coverage] - pip install "pydantic<2" + uv pip install .[coverage] + uv pip install "pydantic<2" pytest --cov --cov-report=term --cov-report=xml --junit-xml=junit.xml jsonargparse_tests/test_pydantic.py mv coverage.xml coverage_pydantic1.xml mv junit.xml junit_pydantic1.xml @@ -131,7 +158,7 @@ jobs: run: | sed -i "s|import pydantic|import pydantic.v1 as pydantic|" jsonargparse_tests/test_pydantic.py sed -i "s|^annotated = .*|annotated = False|" jsonargparse_tests/test_pydantic.py - pip install "pydantic>=2" + uv pip install "pydantic>=2" pytest --cov --cov-report=term --cov-report=xml --junit-xml=junit.xml jsonargparse_tests/test_pydantic.py mv coverage.xml coverage_pydantic2.xml mv junit.xml junit_pydantic2.xml @@ -145,9 +172,15 @@ jobs: path: ./junit_py* build-package: + # This job builds the artifacts that pypi-publish publishes. To protect + # against cache poisoning attacks, it must not use any cache (actions/cache, + # setup-python's cache or setup-uv's enable-cache) and dependencies must be + # installed with --require-hashes from a lock file. runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" @@ -168,10 +201,16 @@ jobs: needs: [build-package] steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - cache: pip + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: true + cache-suffix: installed-package + cache-dependency-glob: pyproject.toml - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: name: package @@ -179,24 +218,30 @@ jobs: - name: Test without optional dependencies and without pyyaml run: | cd dist - pip install $(ls jsonargparse-*.whl)[test-no-urls] $(ls jsonargparse_tests-*.whl) - pip uninstall -y pyyaml + uv pip install $(ls jsonargparse-*.whl)[test-no-urls] $(ls jsonargparse_tests-*.whl) + uv pip uninstall pyyaml python -m jsonargparse_tests - name: Test with all optional dependencies run: | cd dist - pip install $(ls jsonargparse-*.whl)[test,all] + uv pip install $(ls jsonargparse-*.whl)[test,all] python -m jsonargparse_tests doctest: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" - cache: pip - - run: pip install -e .[all,shtab,doc] + - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 + with: + enable-cache: true + cache-suffix: doctest + cache-dependency-glob: pyproject.toml + - run: uv pip install -e .[all,shtab,doc] - name: Run doc tests run: sphinx-build -M doctest sphinx sphinx/_build sphinx/index.rst @@ -204,6 +249,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: "3.12" @@ -222,6 +269,8 @@ jobs: id-token: write steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: merge-multiple: true @@ -247,6 +296,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: + persist-credentials: false fetch-depth: 0 # Shallow clone disabled for a better relevancy of analysis - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: @@ -276,6 +326,8 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} pypi-publish: + # This job publishes to PyPI the package created by build-package. Same as + # there, it must not use any cache to protect against cache poisoning. if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest needs: [linux, windows, macos, argparse-compatibility, omegaconf, pydantic-v1, installed-package, doctest, mypy] diff --git a/.github/workflows/tox.lock b/.github/workflows/tox.lock index 515479c1..b739e2d8 100644 --- a/.github/workflows/tox.lock +++ b/.github/workflows/tox.lock @@ -6,13 +6,13 @@ colorama==0.4.6 \ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 # via tox -distlib==0.4.1 \ - --hash=sha256:9c2c552c68cbadc619f2d0ed3a69e27c351a3f4c9baa9ffb7df9e9cdc3d19a97 \ - --hash=sha256:c3804d0d2d4b5fcd44036eb860cb6660485fcdf5c2aba53dc324d805837ea65b +distlib==0.4.3 \ + --hash=sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b \ + --hash=sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed # via virtualenv -filelock==3.29.1 \ - --hash=sha256:85199dfd706869641b72b2e8955d5416a4b2b7dc4b0e8e6d97b4cc1299a6983b \ - --hash=sha256:d97e6b1b9757569626c58caa07dc4beb1613f4a2938b1e8cc81afca398906c9e +filelock==3.29.7 \ + --hash=sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d \ + --hash=sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51 # via # python-discovery # tox @@ -23,6 +23,7 @@ packaging==26.2 \ # via # pyproject-api # tox + # tox-uv-bare platformdirs==4.10.0 \ --hash=sha256:31e761a6a0ca04faf7353ea759bdba55652be214725111e5aac52dfa29d4bef7 \ --hash=sha256:fb516cdb12eb0d857d0cd85a7c57cea4d060bee4578d6cf5a14dfdf8cbf8784a @@ -38,9 +39,9 @@ pyproject-api==1.10.1 \ --hash=sha256:c2b2726bd7aa9217b6c50b621fef5b2ae5def4d55b779c9e0694c15e0a8517ba \ --hash=sha256:fa9e6f66c35b5017e909825d8f2b5d5482ea699d7be809d21c03bd1f7317f36a # via tox -python-discovery==1.4.0 \ - --hash=sha256:26ed78d703e234879a66244c7d4114563fb13ec5cd30a2d1357e5fb4850782da \ - --hash=sha256:eb8bc7daad3c226c147e45bb4e970a1feb1bf4048ee178e6db59e197b8010ce3 +python-discovery==1.4.4 \ + --hash=sha256:5cad33982d412c1f3ffb8f9ca4ea292c9680bca3942451d30b69c37fce53a4a3 \ + --hash=sha256:abebe9120b43453b68c908acfb1e72a19d1a959ed2cb620ad38fc57d08056dbe # via # tox # virtualenv @@ -95,20 +96,49 @@ tomli==2.4.1 ; python_version < "3.11" \ # via # pyproject-api # tox + # tox-uv-bare tomli-w==1.2.0 \ --hash=sha256:188306098d013b691fcadc011abd66727d3c414c571bb01b1a174ba8c983cf90 \ --hash=sha256:2dd14fac5a47c27be9cd4c976af5a12d87fb1f0b4512f81d69cce3b35ae25021 # via tox -tox==4.55.1 \ - --hash=sha256:0678fbf26dd5b559b1ef128fa4388325920219322ebc8cc5f3497627c00f4472 \ - --hash=sha256:e2084be6dfdef96ba1bed4948e6a1f73613d6952e1477be5dca45653d4c053c8 -typing-extensions==4.15.0 ; python_version < "3.11" \ - --hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \ - --hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548 +tox==4.56.4 \ + --hash=sha256:53bac88382b9638a5ce40fbaddd6076e1c8f638751af7bf8e759392c953df2f0 \ + --hash=sha256:d49e371119ebfafb15054ad7ccff3d03027ccb65195fae3ac656b431b5524055 + # via tox-uv-bare +tox-uv==1.35.2 \ + --hash=sha256:2d99b0e3c782ba49e7cbe521c8d344758595961b17a3633738d67096641c1bde +tox-uv-bare==1.35.2 \ + --hash=sha256:49e28a804c97f23ea17e25859960c0fa78f35bccb7e14344cfd840e89a9aade9 \ + --hash=sha256:c0d590a41d1054a1ad0874e9e5943ff52402786e3d4599d8f8d37a65b566ef53 + # via tox-uv +typing-extensions==4.16.0 ; python_version < "3.11" \ + --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 \ + --hash=sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5 # via # tox # virtualenv -virtualenv==21.4.2 \ - --hash=sha256:38e6ee0a555615c0ea9da2ac7e9998fe8dc3b911dd33ad8eaad2020957653b0c \ - --hash=sha256:854210ca524a1a4d0d744734f4acbc721c3ffe163b85bbf5d56d14d5ae2f0fae +uv==0.11.28 \ + --hash=sha256:041e4b80bebc58d7142ac9394370cacd73185fd8d066d6675d14707d83408f6d \ + --hash=sha256:185416a5316df8c5442b47178349f1f27fc1034468670ac1fb499eae3b25bd68 \ + --hash=sha256:2e91eb8a0b00d5f4427195fc818bcaa4d8bb4fccb79f4e973e74802419ab06ca \ + --hash=sha256:3fcfda468448093f4d5961ca8c068b0aeec2d02f7226d58ee8513321a929fe4f \ + --hash=sha256:47e3f12fe6f5c80a01639d8df36efde7bdddfc3bbc52250df623547d8d393105 \ + --hash=sha256:49fe42df9f42056037473f3876adec1615709b57d3470ed39178ff420f3afb9f \ + --hash=sha256:692edef9cf1d2dd69bb9d9fc01f281a82610547900ce227a3cb269cdf988b5ce \ + --hash=sha256:6b3d0ea11e83b373a2166b82dd0864f5677fbadf98db64541ab2e59c42968905 \ + --hash=sha256:6f7ce6f6015a3e857bc6a663514afa62856b669ee5c1bd120e4c58ac2ef5513d \ + --hash=sha256:8c60294e3be4fa203a04015fc02ac8a31d936e86fde06dcb43c7f8f22661dfff \ + --hash=sha256:a4a9fe246cb2882532277f5d5e5bd8a59462981462a2f98426f35ecfca82460e \ + --hash=sha256:ae5bbdb6150adcd625f5fa720b04abf2014247d878d1035f19751bb0e7274543 \ + --hash=sha256:bb11d94cb848ff58af79e0bb5e4037cd324d27dbe2dabb7746db698b724a9a20 \ + --hash=sha256:d01c7c665511c047f350e587b8b6557c96b61b2eddafbcd8964f0cc2f5b9afbe \ + --hash=sha256:df86cfd135542a833e9f84708b3b8dbaa987a3b9db85b267062db49ab639d242 \ + --hash=sha256:e94560995737c50525d586da553521fbafe9ef06641e7d885db4b270f53ee84d \ + --hash=sha256:e9eb317b1cdb249887df77ac232d8a9448f26858b2399f9f2949c6a7b9bedf88 \ + --hash=sha256:f4fcf2c8d9f1444b900e6b8dbbb828825fb76eca01acd18aeaa5c90240408cda \ + --hash=sha256:fab3c31007a611866475824a666f5a721bf0c9335db806355a97fcfba2a6bbb7 + # via tox-uv +virtualenv==21.6.1 \ + --hash=sha256:15f978b7cd329f24855ff4a0c4b4899cc7678589f49adbdcbbb4d3232e641128 \ + --hash=sha256:afe991df855715a2b2f60edfcc0107ef95a79fdfd8cb4cdaa71603d1c12e463b # via tox diff --git a/pyproject.toml b/pyproject.toml index aa4b36fc..3e95605a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -109,7 +109,8 @@ dev = [ "jsonargparse[coverage]", "jsonargparse[doc]", "pre-commit>=2.19.0", - "tox>=3.25.0", + "tox>=4.0.0", + "tox-uv>=1.0.0", "build>=0.10.0", ] doc = [ @@ -178,9 +179,6 @@ ignore = [ "E721", # Allow comparing types with type() ] -[tool.ruff.lint.pydocstyle] -convention = "google" - [tool.typos.default.extend-identifiers] Villegas = "Villegas" @@ -199,6 +197,10 @@ extras = argparse: coverage changedir = jsonargparse_tests allowlist_externals = rm +passenv = UV_EXCLUDE_NEWER +# Ensure uv installs into the tox env even if UV_SYSTEM_PYTHON=1 set outside +setenv = + UV_SYSTEM_PYTHON = 0 commands = all-extras: python -m pytest {posargs} no-extras: python -m pytest {posargs} @@ -207,10 +209,15 @@ commands = argparse: python -m pytest -W ignore::pytest.PytestUnknownMarkWarning --tb=no -m "not (not_supported or implementation_specific or investigate)" tests_argparse_{env_name}.py {posargs} usedevelop = true +[testenv:.pkg] +setenv = + {[testenv]setenv} + [testenv:omegaconf] extras = test,coverage,all changedir = jsonargparse_tests setenv = + {[testenv]setenv} JSONARGPARSE_OMEGACONF_FULL_TEST = true commands = python -m pytest {posargs} @@ -226,7 +233,7 @@ commands = copy('conftest.py', Path(r'{envtmpdir}', 'conftest.py')); \ copy('test_pydantic.py', Path(r'{envtmpdir}', 'test_pydantic.py'))\ " - pip install "pydantic<2" + uv pip install --python {envpython} "pydantic<2" python -m pytest {posargs} {envtmpdir}/test_pydantic.py # Test with pydantic>=2 importing from pydantic.v1 @@ -238,13 +245,13 @@ commands = content = re.sub(r'^annotated = [^\\n]*', 'annotated = False', content, flags=re.MULTILINE); \ path.write_text(content)\ " - pip install "pydantic>=2" + uv pip install --python {envpython} "pydantic>=2" python -m pytest {posargs} {envtmpdir}/test_pydantic.py [testenv:without-pyyaml] extras = test,coverage,all commands = - pip uninstall -y argcomplete omegaconf pyyaml responses ruamel.yaml ruamel.yaml.clib types-PyYAML + uv pip uninstall --python {envpython} argcomplete omegaconf pyyaml responses ruamel.yaml ruamel.yaml.clib types-PyYAML python -m pytest {posargs} [testenv:without-future-annotations]