pre-commit-localupdate 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (17) hide show
  1. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/PKG-INFO +3 -3
  2. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/README.md +1 -1
  3. pre_commit_localupdate-0.3.0/pre_commit_localupdate/__init__.py +1 -0
  4. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/pre_commit_localupdate/cli.py +2 -1
  5. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/pre_commit_localupdate/io.py +10 -9
  6. pre_commit_localupdate-0.3.0/pre_commit_localupdate/packages/__init__.py +15 -0
  7. {pre_commit_localupdate-0.2.0/pre_commit_localupdate → pre_commit_localupdate-0.3.0/pre_commit_localupdate/packages}/julia.py +2 -1
  8. {pre_commit_localupdate-0.2.0/pre_commit_localupdate → pre_commit_localupdate-0.3.0/pre_commit_localupdate/packages}/node.py +1 -1
  9. {pre_commit_localupdate-0.2.0/pre_commit_localupdate → pre_commit_localupdate-0.3.0/pre_commit_localupdate/packages}/python.py +1 -1
  10. pre_commit_localupdate-0.3.0/pre_commit_localupdate/packages/rust.py +103 -0
  11. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/pre_commit_localupdate/pre_commit_config.py +10 -21
  12. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/pyproject.toml +2 -2
  13. pre_commit_localupdate-0.2.0/pre_commit_localupdate/__init__.py +0 -1
  14. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/LICENSE +0 -0
  15. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/pre_commit_localupdate/__main__.py +0 -0
  16. {pre_commit_localupdate-0.2.0 → pre_commit_localupdate-0.3.0}/pre_commit_localupdate/logs.py +0 -0
  17. {pre_commit_localupdate-0.2.0/pre_commit_localupdate → pre_commit_localupdate-0.3.0/pre_commit_localupdate/packages}/package.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pre-commit-localupdate
3
- Version: 0.2.0
4
- Summary: A CLI tool to automatically update additional dependencies within local Python and Node.js hooks in pre-commit config files.
3
+ Version: 0.3.0
4
+ Summary: A CLI tool to automatically update additional dependencies within local Python, Julia, Rust, and Node.js hooks in pre-commit config files.
5
5
  Author: M. Farzalipour Tabriz
6
6
  Classifier: Development Status :: 3 - Alpha
7
7
  Classifier: Environment :: Console
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
15
15
 
16
16
  # pre-commit-localupdate
17
17
 
18
- A CLI tool to automatically update dependencies in `pre-commit-config.yml` files. It specifically targets `additional_dependencies` within local Python, Julia, and Node.js hooks to ensure your tooling stays up-to-date. It also adds version to unversioned packages and pins exact version of loosely defined ones.
18
+ A CLI tool to automatically update dependencies in `pre-commit-config.yml` files. It specifically targets `additional_dependencies` within local Python, Julia, Rust, and Node.js hooks to ensure your tooling stays up-to-date. It also adds version to unversioned packages and pins exact version of loosely defined ones.
19
19
 
20
20
  ## Installation
21
21
 
@@ -1,6 +1,6 @@
1
1
  # pre-commit-localupdate
2
2
 
3
- A CLI tool to automatically update dependencies in `pre-commit-config.yml` files. It specifically targets `additional_dependencies` within local Python, Julia, and Node.js hooks to ensure your tooling stays up-to-date. It also adds version to unversioned packages and pins exact version of loosely defined ones.
3
+ A CLI tool to automatically update dependencies in `pre-commit-config.yml` files. It specifically targets `additional_dependencies` within local Python, Julia, Rust, and Node.js hooks to ensure your tooling stays up-to-date. It also adds version to unversioned packages and pins exact version of loosely defined ones.
4
4
 
5
5
  ## Installation
6
6
 
@@ -0,0 +1 @@
1
+ __version__ = '0.3.0'
@@ -2,13 +2,14 @@
2
2
  # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  import argparse
5
+
5
6
  from pre_commit_localupdate import __version__
6
7
 
7
8
 
8
9
  def parse_args() -> argparse.Namespace:
9
10
  """Parse command line arguments."""
10
11
  parser = argparse.ArgumentParser(
11
- description="Automatically update additional dependencies within local Python and Node.js hooks in pre-commit config files."
12
+ description="Automatically update additional dependencies within local Python, Julia, Rust, and Node.js hooks in pre-commit config files."
12
13
  )
13
14
  parser.add_argument(
14
15
  "--debug",
@@ -2,17 +2,18 @@
2
2
  # SPDX-License-Identifier: LGPL-3.0-or-later
3
3
 
4
4
  import logging
5
- from pathlib import Path
6
5
  import sys
6
+ from pathlib import Path
7
7
  from typing import List, Tuple
8
8
 
9
9
 
10
10
  def load_config_file(file_path: Path) -> Tuple[List[str], str]:
11
+ """Reads pre-commit configuration file and returns its header lines and content."""
11
12
  logging.debug("Reading configuration file: %s", file_path)
12
13
 
13
14
  raw_lines: List[str] = []
14
15
  header_lines: List[str] = []
15
- yaml_start_index: int = 0
16
+ content_start_index: int = 0
16
17
 
17
18
  try:
18
19
  with file_path.open("r", encoding="utf-8") as f:
@@ -24,19 +25,19 @@ def load_config_file(file_path: Path) -> Tuple[List[str], str]:
24
25
  logging.exception("IOError while reading file %s: %s", file_path, exc)
25
26
  sys.exit(2)
26
27
 
27
- logging.debug("Parsing document header...")
28
- yaml_start_index = 0
28
+ logging.debug("Parsing file header...")
29
+ content_start_index = 0
29
30
  for i, line in enumerate(raw_lines):
30
31
  stripped: str = line.strip()
31
32
  if stripped == "---":
32
33
  logging.debug("YAML marker found.")
33
- yaml_start_index = i + 1
34
+ content_start_index = i + 1
34
35
  break
35
36
  if stripped and not stripped.startswith("#"):
36
- yaml_start_index = i
37
+ content_start_index = i
37
38
  break
38
39
 
39
- header_lines = raw_lines[:yaml_start_index]
40
- yaml_content: str = "".join(raw_lines[yaml_start_index:])
40
+ header_lines = raw_lines[:content_start_index]
41
+ content: str = "".join(raw_lines[content_start_index:])
41
42
 
42
- return header_lines, yaml_content
43
+ return header_lines, content
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2026 M. Farzalipour Tabriz, Max Planck Institute for Physics
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ from .julia import JuliaPackage
5
+ from .node import NodePackage
6
+ from .package import PackageBase
7
+ from .python import PyPackage
8
+ from .rust import RustPackage
9
+
10
+ SUPPORTED_PACKAGES: dict[str, type[PackageBase]] = {
11
+ "julia": JuliaPackage,
12
+ "node": NodePackage,
13
+ "python": PyPackage,
14
+ "rust": RustPackage,
15
+ }
@@ -4,9 +4,10 @@
4
4
  import logging
5
5
  import re
6
6
  import tomllib
7
+
7
8
  import requests
8
9
 
9
- from pre_commit_localupdate.package import PackageBase
10
+ from .package import PackageBase
10
11
 
11
12
  # Julia General Registry Source
12
13
  JULIA_PKG_API_URL = "https://raw.githubusercontent.com/JuliaRegistries/General/refs/heads/master/{package_name_first_letter}/{package_name}/Versions.toml"
@@ -8,7 +8,7 @@ from typing import cast
8
8
 
9
9
  import requests
10
10
 
11
- from pre_commit_localupdate.package import PackageBase
11
+ from .package import PackageBase
12
12
 
13
13
  NPM_API_URL = "https://registry.npmjs.org/{package_name}"
14
14
  REQUEST_TIMEOUT = 10
@@ -7,7 +7,7 @@ from typing import cast
7
7
 
8
8
  import requests
9
9
 
10
- from pre_commit_localupdate.package import PackageBase
10
+ from .package import PackageBase
11
11
 
12
12
  PYPI_API_URL = "https://pypi.org/pypi/{package_name}/json"
13
13
  REQUEST_TIMEOUT = 10
@@ -0,0 +1,103 @@
1
+ # SPDX-FileCopyrightText: 2026 M. Farzalipour Tabriz, Max Planck Institute for Physics
2
+ # SPDX-License-Identifier: LGPL-3.0-or-later
3
+
4
+ import logging
5
+ import re
6
+ from typing import Any, cast
7
+
8
+ import requests
9
+
10
+ from .package import PackageBase
11
+
12
+ CRATES_API_URL = "https://crates.io/api/v1/crates/{package_name}"
13
+ REQUEST_TIMEOUT = 10
14
+
15
+
16
+ class RustPackage(PackageBase):
17
+ """Represents a Rust package.
18
+
19
+ Attributes:
20
+ cli: Package is a CLI tool.
21
+ """
22
+
23
+ cli: bool = False
24
+
25
+ def __init__(self, **kwargs: Any) -> None:
26
+ self.cli = kwargs.pop("cli", False)
27
+ super().__init__(**kwargs)
28
+
29
+ @classmethod
30
+ def from_specification(package, spec: str) -> "RustPackage" | None:
31
+ """Parse a package specification string into a Package object.
32
+
33
+ Handles formats like 'package', 'package:1.2.3', or 'cli:package:1.2.3'.
34
+
35
+ Args:
36
+ spec: The package specification string.
37
+
38
+ Returns:
39
+ A Package instance if parsing succeeds, otherwise None.
40
+
41
+ """
42
+ clean_spec = spec.strip().strip('"').strip("'")
43
+
44
+ match = re.match(
45
+ r"^(?:(?P<cli>cli):)?(?P<name>[a-zA-Z0-9_-]+)(?::(?P<version>[^:]+))?$",
46
+ clean_spec,
47
+ )
48
+ if not match:
49
+ logging.warning("Could not parse the package specification: %s", clean_spec)
50
+ return None
51
+
52
+ name = match.group("name")
53
+ version = match.group("version")
54
+ cli = True if match.group("cli") else False
55
+
56
+ raw_spec = f":{version}" if version else None
57
+ latest_version = package._get_latest_version(name)
58
+
59
+ return package(
60
+ name=name, raw_spec=raw_spec, latest_version=latest_version, cli=cli
61
+ )
62
+
63
+ @staticmethod
64
+ def _get_latest_version(package_name: str) -> str | None:
65
+ """Fetch the latest version of a package from crates.io.
66
+
67
+ Returns:
68
+ The latest version string if found, otherwise None.
69
+
70
+ """
71
+ logging.debug("Fetching latest version for %s", package_name)
72
+ try:
73
+ url = CRATES_API_URL.format(package_name=package_name)
74
+ response = requests.get(url, timeout=REQUEST_TIMEOUT)
75
+ response.raise_for_status()
76
+ data = response.json()
77
+ version = data.get("crate", {}).get("max_version")
78
+ if version and isinstance(version, str):
79
+ return cast("str", version)
80
+
81
+ return None
82
+ except requests.exceptions.RequestException as exc:
83
+ logging.warning(
84
+ "Could not fetch latest version for %s: %s", package_name, exc
85
+ )
86
+ return None
87
+ except (KeyError, ValueError) as exc:
88
+ logging.warning("Could not parse response for %s: %s", package_name, exc)
89
+ return None
90
+
91
+ def latest_version_specification(package) -> str | None:
92
+ """Version specification of package pinned to the latest version.
93
+
94
+ Returns:
95
+ The latest version specification string if found, otherwise None.
96
+
97
+ """
98
+ if not package.latest_version:
99
+ return None
100
+ if package.cli:
101
+ return f"cli:{package.name}:{package.latest_version}"
102
+ else:
103
+ return f"{package.name}:{package.latest_version}"
@@ -3,33 +3,24 @@
3
3
 
4
4
  import io
5
5
  import logging
6
- from pathlib import Path
7
6
  import sys
7
+ from pathlib import Path
8
8
 
9
9
  import ruamel.yaml
10
10
 
11
- from pre_commit_localupdate.node import NodePackage
12
- from pre_commit_localupdate.package import PackageBase
13
- from pre_commit_localupdate.python import PyPackage
14
- from pre_commit_localupdate.julia import JuliaPackage
15
11
  from pre_commit_localupdate.io import load_config_file
16
-
17
- LANGUAGE_PACKAGE_MAP: dict[str, type[PackageBase]] = {
18
- "python": PyPackage,
19
- "node": NodePackage,
20
- "julia": JuliaPackage,
21
- }
12
+ from pre_commit_localupdate.packages import SUPPORTED_PACKAGES
22
13
 
23
14
 
24
15
  def update_additional_dependencies(file_path: Path, *, dry_run: bool = False) -> bool:
25
- """Reads the configuration file, identifies outdated Python packages in
26
- local hooks, and updates them directly in the file.
16
+ """Reads the pre-commit configuration file, identifies outdated packages
17
+ in local hooks, and updates them directly in the file.
27
18
 
28
19
  Preserves structure, quote styles, the document start marker (---),
29
20
  and comments preceding it.
30
21
 
31
22
  Args:
32
- file_path: Path to the YAML configuration file.
23
+ file_path: Path to the pre-commit configuration file.
33
24
 
34
25
  Returns:
35
26
  True if the file was modified and successfully written, False otherwise.
@@ -71,23 +62,21 @@ def update_additional_dependencies(file_path: Path, *, dry_run: bool = False) ->
71
62
  hook_id = hook.get("id")
72
63
  logging.debug("Processing hook with ID: %s", hook_id)
73
64
 
74
- if hook.get("language") not in LANGUAGE_PACKAGE_MAP:
65
+ if hook.get("language") not in SUPPORTED_PACKAGES:
75
66
  continue
76
67
 
77
- PackageClass = LANGUAGE_PACKAGE_MAP[hook.get("language")]
78
-
79
68
  if "additional_dependencies" not in hook:
80
69
  continue
81
70
 
82
71
  deps_list = list(hook["additional_dependencies"])
83
72
 
73
+ Package = SUPPORTED_PACKAGES[hook.get("language")]
74
+
84
75
  for i, dep_spec in enumerate(deps_list):
85
76
  dep_str = str(dep_spec)
86
77
 
87
78
  logging.debug("Checking dependency: %s", dep_str)
88
- package: PackageBase | None = PackageClass.from_specification(
89
- dep_str
90
- )
79
+ package = Package.from_specification(dep_str)
91
80
  if not package:
92
81
  continue
93
82
 
@@ -119,7 +108,7 @@ def update_additional_dependencies(file_path: Path, *, dry_run: bool = False) ->
119
108
  if dry_run:
120
109
  sys.exit(1)
121
110
  else:
122
- logging.debug("Writing modified content to disk")
111
+ logging.debug("Writing modifications to disk")
123
112
  try:
124
113
  stream = io.StringIO()
125
114
  yaml.dump(config, stream)
@@ -23,12 +23,12 @@ dependencies = [
23
23
  "requests>=2.32.5",
24
24
  "ruamel-yaml>=0.19.1",
25
25
  ]
26
- description = "A CLI tool to automatically update additional dependencies within local Python and Node.js hooks in pre-commit config files."
26
+ description = "A CLI tool to automatically update additional dependencies within local Python, Julia, Rust, and Node.js hooks in pre-commit config files."
27
27
  dynamic = []
28
28
  name = "pre-commit-localupdate"
29
29
  readme = "README.md"
30
30
  requires-python = "<3.15,>=3.11"
31
- version = "0.2.0"
31
+ version = "0.3.0"
32
32
 
33
33
  [project.scripts]
34
34
  pre-commit-localupdate = "pre_commit_localupdate.__main__:main"
@@ -1 +0,0 @@
1
- __version__ = '0.2.0'