Repository: theupdateframework/python-tuf
Pull request #2810 (2025-03-08 13:01:18)

build(deps): bump ruff from 0.9.9 to 0.9.10 in the test-and-lint-dependencies group
Bumps the test-and-lint-dependencies group with 1 update: [ruff](https://github.com/astral-sh/ruff).

Updates `ruff` from 0.9.9 to 0.9.10
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/astral-sh/ruff/releases">ruff's releases</a>.</em></p>
<blockquote>
<h2>0.9.10</h2>
<h2>Release Notes</h2>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Add new rule <code>RUF059</code>: Unused unpacked assignment (<a href="https://redirect.github.com/astral-sh/ruff/pull/16449">#16449</a>)</li>
<li>[<code>syntax-errors</code>] Detect assignment expressions before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16383">#16383</a>)</li>
<li>[<code>syntax-errors</code>] Named expressions in decorators before Python 3.9 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16386">#16386</a>)</li>
<li>[<code>syntax-errors</code>] Parenthesized keyword argument names after Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16482">#16482</a>)</li>
<li>[<code>syntax-errors</code>] Positional-only parameters before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16481">#16481</a>)</li>
<li>[<code>syntax-errors</code>] Tuple unpacking in <code>return</code> and <code>yield</code> before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16485">#16485</a>)</li>
<li>[<code>syntax-errors</code>] Type parameter defaults before Python 3.13 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16447">#16447</a>)</li>
<li>[<code>syntax-errors</code>] Type parameter lists before Python 3.12 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16479">#16479</a>)</li>
<li>[<code>syntax-errors</code>] <code>except*</code> before Python 3.11 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16446">#16446</a>)</li>
<li>[<code>syntax-errors</code>] <code>type</code> statements before Python 3.12 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16478">#16478</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Escape template filenames in glob patterns in configuration (<a href="https://redirect.github.com/astral-sh/ruff/pull/16407">#16407</a>)</li>
<li>[<code>flake8-simplify</code>] Exempt unittest context methods for <code>SIM115</code> rule (<a href="https://redirect.github.com/astral-sh/ruff/pull/16439">#16439</a>)</li>
<li>Formatter: Fix syntax error location in notebooks (<a href="https://redirect.github.com/astral-sh/ruff/pull/16499">#16499</a>)</li>
<li>[<code>pyupgrade</code>] Do not offer fix when at least one target is <code>global</code>/<code>nonlocal</code> (<code>UP028</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16451">#16451</a>)</li>
<li>[<code>flake8-builtins</code>] Ignore variables matching module attribute names (<code>A001</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16454">#16454</a>)</li>
<li>[<code>pylint</code>] Convert <code>code</code> keyword argument to a positional argument in fix for (<code>PLR1722</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16424">#16424</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Move rule code from <code>description</code> to <code>check_name</code> in GitLab output serializer (<a href="https://redirect.github.com/astral-sh/ruff/pull/16437">#16437</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pydocstyle</code>] Clarify that <code>D417</code> only checks docstrings with an arguments section (<a href="https://redirect.github.com/astral-sh/ruff/pull/16494">#16494</a>)</li>
</ul>
<h2>Contributors</h2>
<ul>
<li><a href="https://github.com/AlexWaygood"><code>@​AlexWaygood</code></a></li>
<li><a href="https://github.com/BurntSushi"><code>@​BurntSushi</code></a></li>
<li><a href="https://github.com/Glyphack"><code>@​Glyphack</code></a></li>
<li><a href="https://github.com/InSyncWithFoo"><code>@​InSyncWithFoo</code></a></li>
<li><a href="https://github.com/JelleZijlstra"><code>@​JelleZijlstra</code></a></li>
<li><a href="https://github.com/MichaReiser"><code>@​MichaReiser</code></a></li>
<li><a href="https://github.com/VascoSch92"><code>@​VascoSch92</code></a></li>
<li><a href="https://github.com/adamchainz"><code>@​adamchainz</code></a></li>
<li><a href="https://github.com/carljm"><code>@​carljm</code></a></li>
<li><a href="https://github.com/charliermarsh"><code>@​charliermarsh</code></a></li>
<li><a href="https://github.com/dcreager"><code>@​dcreager</code></a></li>
<li><a href="https://github.com/dhruvmanila"><code>@​dhruvmanila</code></a></li>
<li><a href="https://github.com/ericmarkmartin"><code>@​ericmarkmartin</code></a></li>
<li><a href="https://github.com/github-actions"><code>@​github-actions</code></a></li>
<li><a href="https://github.com/mishamsk"><code>@​mishamsk</code></a></li>
<li><a href="https://github.com/mtshiba"><code>@​mtshiba</code></a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a href="https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md">ruff's changelog</a>.</em></p>
<blockquote>
<h2>0.9.10</h2>
<h3>Preview features</h3>
<ul>
<li>[<code>ruff</code>] Add new rule <code>RUF059</code>: Unused unpacked assignment (<a href="https://redirect.github.com/astral-sh/ruff/pull/16449">#16449</a>)</li>
<li>[<code>syntax-errors</code>] Detect assignment expressions before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16383">#16383</a>)</li>
<li>[<code>syntax-errors</code>] Named expressions in decorators before Python 3.9 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16386">#16386</a>)</li>
<li>[<code>syntax-errors</code>] Parenthesized keyword argument names after Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16482">#16482</a>)</li>
<li>[<code>syntax-errors</code>] Positional-only parameters before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16481">#16481</a>)</li>
<li>[<code>syntax-errors</code>] Tuple unpacking in <code>return</code> and <code>yield</code> before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16485">#16485</a>)</li>
<li>[<code>syntax-errors</code>] Type parameter defaults before Python 3.13 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16447">#16447</a>)</li>
<li>[<code>syntax-errors</code>] Type parameter lists before Python 3.12 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16479">#16479</a>)</li>
<li>[<code>syntax-errors</code>] <code>except*</code> before Python 3.11 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16446">#16446</a>)</li>
<li>[<code>syntax-errors</code>] <code>type</code> statements before Python 3.12 (<a href="https://redirect.github.com/astral-sh/ruff/pull/16478">#16478</a>)</li>
</ul>
<h3>Bug fixes</h3>
<ul>
<li>Escape template filenames in glob patterns in configuration (<a href="https://redirect.github.com/astral-sh/ruff/pull/16407">#16407</a>)</li>
<li>[<code>flake8-simplify</code>] Exempt unittest context methods for <code>SIM115</code> rule (<a href="https://redirect.github.com/astral-sh/ruff/pull/16439">#16439</a>)</li>
<li>Formatter: Fix syntax error location in notebooks (<a href="https://redirect.github.com/astral-sh/ruff/pull/16499">#16499</a>)</li>
<li>[<code>pyupgrade</code>] Do not offer fix when at least one target is <code>global</code>/<code>nonlocal</code> (<code>UP028</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16451">#16451</a>)</li>
<li>[<code>flake8-builtins</code>] Ignore variables matching module attribute names (<code>A001</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16454">#16454</a>)</li>
<li>[<code>pylint</code>] Convert <code>code</code> keyword argument to a positional argument in fix for (<code>PLR1722</code>) (<a href="https://redirect.github.com/astral-sh/ruff/pull/16424">#16424</a>)</li>
</ul>
<h3>CLI</h3>
<ul>
<li>Move rule code from <code>description</code> to <code>check_name</code> in GitLab output serializer (<a href="https://redirect.github.com/astral-sh/ruff/pull/16437">#16437</a>)</li>
</ul>
<h3>Documentation</h3>
<ul>
<li>[<code>pydocstyle</code>] Clarify that <code>D417</code> only checks docstrings with an arguments section (<a href="https://redirect.github.com/astral-sh/ruff/pull/16494">#16494</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/astral-sh/ruff/commit/0dfa810e9aad9a465596768b0211c31dd41d3e73"><code>0dfa810</code></a> Bump 0.9.10 (<a href="https://redirect.github.com/astral-sh/ruff/issues/16556">#16556</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/9cd0cdefd383c5b9ab1f2515b2b7304128f56612"><code>9cd0cde</code></a> Assert that formatted code doesn't introduce any new unsupported syntax error...</li>
<li><a href="https://github.com/astral-sh/ruff/commit/05a4c293443ff2f104e4fc6899d8980eea9862ee"><code>05a4c29</code></a> print MDTEST_TEST_FILTER value in single-quotes (and escaped) (<a href="https://redirect.github.com/astral-sh/ruff/issues/16548">#16548</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/b3c884f4f33a81cd08e9ebc13fd0d5a538c8afc7"><code>b3c884f</code></a> [syntax-errors] Parenthesized keyword argument names after Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/issues/16482">#16482</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/6c14225c662148d680f7026d52d01de2e47f3938"><code>6c14225</code></a> [syntax-errors] Tuple unpacking in <code>return</code> and <code>yield</code> before Python 3.8 (<a href="https://redirect.github.com/astral-sh/ruff/issues/1">#1</a>...</li>
<li><a href="https://github.com/astral-sh/ruff/commit/0a627ef216bc7a085a4915a8b59ff6440181147b"><code>0a627ef</code></a> [red-knot] Never is callable and iterable. Arbitrary attributes can be access...</li>
<li><a href="https://github.com/astral-sh/ruff/commit/a25be4610a1fd3f7e9a4e593da9ccb25e1aae49a"><code>a25be46</code></a> Clarify that D417 only checks docstrings with an arguments section (<a href="https://redirect.github.com/astral-sh/ruff/issues/16494">#16494</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/ce0018c3cb22e39c12d0b88c83953ee215e72552"><code>ce0018c</code></a> Add <code>OsSystem</code> support to mdtests (<a href="https://redirect.github.com/astral-sh/ruff/issues/16518">#16518</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/48f906e06c73fc3ee01dcf3ba312e6a780f5a1cb"><code>48f906e</code></a> Add tests for case-sensitive module resolution (<a href="https://redirect.github.com/astral-sh/ruff/issues/16517">#16517</a>)</li>
<li><a href="https://github.com/astral-sh/ruff/commit/ebd172e732dbc05c7e721c6a441e624873c9ab43"><code>ebd172e</code></a> [red-knot] Several failing tests for generics (<a href="https://redirect.github.com/astral-sh/ruff/issues/16509">#16509</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/astral-sh/ruff/compare/0.9.9...0.9.10">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ruff&package-manager=pip&previous-version=0.9.9&new-version=0.9.10)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions


</details>

coveralls: 
[![Coverage Status](https://coveralls.io/builds/72609827/badge)](https://coveralls.io/builds/72609827)

coverage: 97.077%. remained the same
when pulling **1d857fb13d070d167659a5a63745a6e16c6dcbb5 on dependabot/pip/test-and-lint-dependencies-678da85fb0**
into **067ba1ad92c434dfccbaa8eb77dbd79b778292aa on develop**.


Patch:
diff --git a/examples/client/client b/examples/client/client
index eeab472d..883fd52c 100755
--- a/examples/client/client
+++ b/examples/client/client
@@ -38,9 +38,6 @@ def init_tofu(base_url: str) -> bool:
 
     metadata_dir = build_metadata_dir(base_url)
 
-    if not os.path.isdir(metadata_dir):
-        os.makedirs(metadata_dir)
-
     response = urllib3.request("GET", f"{base_url}/metadata/1.root.json")
     if response.status != 200:
         print(f"Failed to download initial root {base_url}/metadata/1.root.json")
@@ -81,9 +78,6 @@ def download(base_url: str, target: str) -> bool:
 
     print(f"Using trusted root in {metadata_dir}")
 
-    if not os.path.isdir(DOWNLOAD_DIR):
-        os.mkdir(DOWNLOAD_DIR)
-
     try:
         # NOTE: initial root should be provided with ``bootstrap``  argument:
         # This examples uses unsafe Trust-On-First-Use initialization so it is
diff --git a/requirements/lint.txt b/requirements/lint.txt
index 98a609c2..b4ae77b5 100644
--- a/requirements/lint.txt
+++ b/requirements/lint.txt
@@ -6,7 +6,7 @@
 # Lint tools
 # (We are not so interested in the specific versions of the tools: the versions
 # are pinned to prevent unexpected linting failures when tools update)
-ruff==0.9.9
+ruff==0.9.10
 mypy==1.15.0
 zizmor==1.4.1
 
diff --git a/tuf/ngclient/updater.py b/tuf/ngclient/updater.py
index a40c1fca..020f67a2 100644
--- a/tuf/ngclient/updater.py
+++ b/tuf/ngclient/updater.py
@@ -58,6 +58,7 @@
 import os
 import shutil
 import tempfile
+from pathlib import Path
 from typing import TYPE_CHECKING, cast
 from urllib import parse
 
@@ -267,6 +268,7 @@ def download_target(
 
         if filepath is None:
             filepath = self._generate_target_file_path(targetinfo)
+        Path(filepath).parent.mkdir(exist_ok=True, parents=True)
 
         if target_base_url is None:
             if self._target_base_url is None:
@@ -332,10 +334,9 @@ def _persist_root(self, version: int, data: bytes) -> None:
         The metadata is stored with version prefix (e.g.
         "root_history/1.root.json").
         """
-        rootdir = os.path.join(self._dir, "root_history")
-        with contextlib.suppress(FileExistsError):
-            os.mkdir(rootdir)
-        self._persist_file(os.path.join(rootdir, f"{version}.root.json"), data)
+        rootdir = Path(self._dir, "root_history")
+        rootdir.mkdir(exist_ok=True, parents=True)
+        self._persist_file(str(rootdir / f"{version}.root.json"), data)
 
     def _persist_file(self, filename: str, data: bytes) -> None:
         """Write a file to disk atomically to avoid data loss."""


Test patch:
diff --git a/tests/test_updater_top_level_update.py b/tests/test_updater_top_level_update.py
index f4342f5f..68a2a74e 100644
--- a/tests/test_updater_top_level_update.py
+++ b/tests/test_updater_top_level_update.py
@@ -12,6 +12,7 @@
 import tempfile
 import unittest
 from datetime import timezone
+from pathlib import Path
 from typing import TYPE_CHECKING
 from unittest.mock import MagicMock, call, patch
 
@@ -57,8 +58,6 @@ def setUp(self) -> None:
         self.temp_dir = tempfile.TemporaryDirectory()
         self.metadata_dir = os.path.join(self.temp_dir.name, "metadata")
         self.targets_dir = os.path.join(self.temp_dir.name, "targets")
-        os.mkdir(self.metadata_dir)
-        os.mkdir(self.targets_dir)
 
         self.sim = RepositorySimulator()
 
@@ -134,7 +133,8 @@ def test_cached_root_missing_without_bootstrap(self) -> None:
             self._run_refresh(skip_bootstrap=True)
 
         # Metadata dir is empty
-        self.assertFalse(os.listdir(self.metadata_dir))
+        with self.assertRaises(FileNotFoundError):
+            os.listdir(self.metadata_dir)
 
     def test_trusted_root_expired(self) -> None:
         # Create an expired root version
@@ -166,6 +166,7 @@ def test_trusted_root_expired(self) -> None:
 
     def test_trusted_root_unsigned_without_bootstrap(self) -> None:
         # Cached root is not signed, bootstrap root is not used
+        Path(self.metadata_dir).mkdir(parents=True)
         root_path = os.path.join(self.metadata_dir, "root.json")
         md_root = Metadata.from_bytes(self.sim.signed_roots[0])
         md_root.signatures.clear()