dycw-pre-commit-hooks 0.11.0__tar.gz → 0.11.2__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.

Potentially problematic release.


This version of dycw-pre-commit-hooks might be problematic. Click here for more details.

Files changed (21) hide show
  1. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/PKG-INFO +5 -2
  2. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/README.md +1 -0
  3. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/pyproject.toml +28 -19
  4. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/__init__.py +1 -1
  5. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/common.py +1 -1
  6. dycw_pre_commit_hooks-0.11.2/src/pre_commit_hooks/format_requirements/__init__.py +109 -0
  7. dycw_pre_commit_hooks-0.11.2/src/pre_commit_hooks/format_requirements/__main__.py +6 -0
  8. dycw_pre_commit_hooks-0.11.2/src/pre_commit_hooks/replace_sequence_str/__init__.py +55 -0
  9. dycw_pre_commit_hooks-0.11.2/src/pre_commit_hooks/replace_sequence_str/__main__.py +6 -0
  10. dycw_pre_commit_hooks-0.11.2/src/tests/format_requirements/__init__.py +0 -0
  11. dycw_pre_commit_hooks-0.11.2/src/tests/format_requirements/in.toml +30 -0
  12. dycw_pre_commit_hooks-0.11.2/src/tests/format_requirements/out.toml +30 -0
  13. dycw_pre_commit_hooks-0.11.2/src/tests/format_requirements/test_format_requirements.py +15 -0
  14. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/.gitignore +0 -0
  15. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/py.typed +0 -0
  16. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/run_bump_my_version/__init__.py +0 -0
  17. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/run_bump_my_version/__main__.py +0 -0
  18. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/run_ruff_format/__init__.py +0 -0
  19. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/pre_commit_hooks/run_ruff_format/__main__.py +0 -0
  20. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/tests/__init__.py +0 -0
  21. {dycw_pre_commit_hooks-0.11.0 → dycw_pre_commit_hooks-0.11.2}/src/tests/test_main.py +0 -0
@@ -1,11 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dycw-pre-commit-hooks
3
- Version: 0.11.0
3
+ Version: 0.11.2
4
4
  Author-email: Derek Wan <d.wan@icloud.com>
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: click<8.3,>=8.2.1
7
- Requires-Dist: dycw-utilities<0.126,>=0.125.10
7
+ Requires-Dist: dycw-utilities<0.151,>=0.150.11
8
+ Requires-Dist: libcst<1.9,>=1.8.2
8
9
  Requires-Dist: loguru<0.8,>=0.7.3
10
+ Requires-Dist: packaging<25.1,>=25.0
9
11
  Requires-Dist: tomlkit<0.14,>=0.13.2
10
12
  Description-Content-Type: text/markdown
11
13
 
@@ -26,6 +28,7 @@ My [`pre-commit`](https://pre-commit.com/) hooks.
26
28
  - repo: https://github.com/dycw/pre-commit-hooks
27
29
  rev: master
28
30
  hooks:
31
+ - id: replace-sequence-str
29
32
  - id: run-bump-my-version
30
33
  - id: run-ruff-format
31
34
  ```
@@ -15,6 +15,7 @@ My [`pre-commit`](https://pre-commit.com/) hooks.
15
15
  - repo: https://github.com/dycw/pre-commit-hooks
16
16
  rev: master
17
17
  hooks:
18
+ - id: replace-sequence-str
18
19
  - id: run-bump-my-version
19
20
  - id: run-ruff-format
20
21
  ```
@@ -9,7 +9,8 @@ requires = ["hatchling"]
9
9
  [dependency-groups]
10
10
  dev = [
11
11
  "dycw-utilities[test]",
12
- "pyright[nodejs]>=1.1.401",
12
+ "pyright[nodejs]",
13
+ "rich",
13
14
  ]
14
15
 
15
16
  # project
@@ -17,24 +18,27 @@ dev = [
17
18
  authors = [{name = "Derek Wan", email = "d.wan@icloud.com"}]
18
19
  dependencies = [
19
20
  "click >= 8.2.1, < 8.3",
20
- "dycw-utilities >= 0.125.10, < 0.126",
21
+ "dycw-utilities >= 0.150.11, < 0.151",
22
+ "libcst >= 1.8.2, < 1.9",
21
23
  "loguru >= 0.7.3, < 0.8",
24
+ "packaging >= 25.0, < 25.1",
22
25
  "tomlkit >= 0.13.2, < 0.14",
23
26
  ]
24
27
  name = "dycw-pre-commit-hooks"
25
28
  readme = "README.md"
26
29
  requires-python = ">= 3.12"
27
- version = "0.11.0"
30
+ version = "0.11.2"
28
31
 
29
32
  [project.scripts]
33
+ format-requirements = "pre_commit_hooks.format_requirements:main"
34
+ replace-sequence-str = "pre_commit_hooks.replace_sequence_str:main"
30
35
  run-bump-my-version = "pre_commit_hooks.run_bump_my_version:main"
31
- run-bump2version = "pre_commit_hooks.run_bump2version:main"
32
36
  run-ruff-format = "pre_commit_hooks.run_ruff_format:main"
33
37
 
34
38
  # bump-my-version
35
39
  [tool.bumpversion]
36
40
  allow_dirty = true
37
- current_version = "0.11.0"
41
+ current_version = "0.11.2"
38
42
 
39
43
  [[tool.bumpversion.files]]
40
44
  filename = "src/pre_commit_hooks/__init__.py"
@@ -106,7 +110,15 @@ typeCheckingMode = "strict"
106
110
  [tool.pytest]
107
111
 
108
112
  [tool.pytest.ini_options]
109
- addopts = ["-ra", "-vv", "--color=auto", "--strict-markers"]
113
+ addopts = [
114
+ "-ra",
115
+ "-vv",
116
+ "--color=auto",
117
+ "--strict-markers",
118
+ ]
119
+ asyncio_default_fixture_loop_scope = "function"
120
+ asyncio_mode = "auto"
121
+ collect_imported_tests = false
110
122
  filterwarnings = ["error"]
111
123
  minversion = "8.0"
112
124
  testpaths = ["src/tests"]
@@ -120,8 +132,10 @@ unsafe-fixes = true
120
132
 
121
133
  [tool.ruff.format]
122
134
  preview = true
135
+ skip-magic-trailing-comma = true
123
136
 
124
137
  [tool.ruff.lint]
138
+ explicit-preview-rules = true
125
139
  fixable = ["ALL"]
126
140
  ignore = [
127
141
  "ANN401", # any-type
@@ -137,14 +151,17 @@ ignore = [
137
151
  "D107", # undocumented-public-init
138
152
  "D203", # one-blank-line-before-class
139
153
  "D213", # multi-line-summary-second-line
154
+ "DOC", # pydoclint
140
155
  "E501", # line-too-long
141
156
  "PD", # pandas-vet
142
157
  "PERF203", # try-except-in-loop
158
+ "PLC0415", # import-outside-top-level
143
159
  "PLR0911", # too-many-return-statements
144
160
  "PLR0912", # too-many-branches
145
161
  "PLR0913", # too-many-arguments
146
162
  "PLR0915", # too-many-statements
147
163
  "PLR2004", # magic-value-comparison
164
+ "PT012", # pytest-raises-with-multiple-statements
148
165
  "PT013", # pytest-incorrect-pytest-import
149
166
  "S311", # suspicious-non-cryptographic-random-usage
150
167
  "S603", # subprocess-without-shell-equals-true
@@ -167,19 +184,10 @@ ignore = [
167
184
  "ISC001", # single-line-implicit-string-concatenation
168
185
  "ISC002", # multi-line-implicit-string-concatenation
169
186
  ]
170
- select = ["ALL"]
171
- unfixable = [
172
- "B007", # unused-loop-control-variable
173
- "F541", # f-string-missing-placeholders
174
- "F601", # multi-value-repeated-key-literal
175
- "PIE794", # duplicate-class-field-definition
176
- "PLR5501", # collapsible-else-if
177
- "PT014", # pytest-duplicate-parametrize-test-cases
178
- "RET504", # unnecessary-assign
179
- "SIM102", # collapsible-if
180
- "SIM105", # suppressible-exception
181
- "SIM114", # if-with-same-arms
182
- "T201", # print
187
+ preview = true
188
+ select = [
189
+ "ALL",
190
+ "RUF022", # unsorted-dunder-all
183
191
  ]
184
192
 
185
193
  [tool.ruff.lint.extend-per-file-ignores]
@@ -199,3 +207,4 @@ ban-relative-imports = "all"
199
207
 
200
208
  [tool.ruff.lint.isort]
201
209
  required-imports = ["from __future__ import annotations"]
210
+ split-on-trailing-comma = false
@@ -1,3 +1,3 @@
1
1
  from __future__ import annotations
2
2
 
3
- __version__ = "0.11.0"
3
+ __version__ = "0.11.2"
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
 
5
5
  from loguru import logger
6
6
  from tomlkit import TOMLDocument, parse
7
- from utilities.git import get_repo_root
7
+ from utilities.pathlib import get_repo_root
8
8
 
9
9
  _ROOT = get_repo_root()
10
10
  PYPROJECT_TOML = _ROOT.joinpath("pyproject.toml")
@@ -0,0 +1,109 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, override
4
+
5
+ from click import argument, command
6
+ from packaging._tokenizer import ParserSyntaxError
7
+ from packaging.requirements import (
8
+ InvalidRequirement,
9
+ Requirement,
10
+ _parse_requirement, # pyright: ignore[reportPrivateImportUsage]
11
+ )
12
+ from packaging.specifiers import Specifier, SpecifierSet
13
+ from tomlkit import array, dumps, loads, string
14
+ from tomlkit.items import Array, Table
15
+ from utilities.atomicwrites import writer
16
+ from utilities.click import FilePath
17
+
18
+ from pre_commit_hooks.common import PYPROJECT_TOML
19
+
20
+ if TYPE_CHECKING:
21
+ from collections.abc import Iterator
22
+ from pathlib import Path
23
+
24
+ from tomlkit.toml_document import TOMLDocument
25
+
26
+
27
+ @command()
28
+ @argument("paths", nargs=-1, type=FilePath)
29
+ def main(*, paths: tuple[Path, ...]) -> bool:
30
+ """CLI for the `format_requirements` hook."""
31
+ results = list(map(_process, paths))
32
+ return all(results)
33
+
34
+
35
+ def _process(path: Path, /) -> bool:
36
+ doc = loads(path.read_text())
37
+ expected = _format_path(path)
38
+ if doc == expected:
39
+ return True
40
+ with writer(path, overwrite=True) as temp:
41
+ _ = temp.write_text(dumps(expected))
42
+ return False
43
+
44
+
45
+ def _format_path(path: Path, /) -> TOMLDocument:
46
+ doc = loads(path.read_text())
47
+ if isinstance(dep_grps := doc.get("dependency-groups"), Table):
48
+ for key, value in dep_grps.items():
49
+ if isinstance(value, Array):
50
+ dep_grps[key] = _format_array(value)
51
+ if isinstance(project := doc["project"], Table):
52
+ if isinstance(deps := project["dependencies"], Array):
53
+ project["dependencies"] = _format_array(deps)
54
+ if isinstance(optional := project.get("optional-dependencies"), Table):
55
+ for key, value in optional.items():
56
+ if isinstance(value, Array):
57
+ optional[key] = _format_array(value)
58
+ return doc
59
+
60
+
61
+ def _format_array(dependencies: Array, /) -> Array:
62
+ new = array().multiline(multiline=True)
63
+ new.extend(map(_format_item, dependencies))
64
+ return new
65
+
66
+
67
+ def _format_item(item: Any, /) -> Any:
68
+ if not isinstance(item, str):
69
+ return item
70
+ return string(str(_CustomRequirement(item)))
71
+
72
+
73
+ class _CustomRequirement(Requirement):
74
+ @override
75
+ def __init__(self, requirement_string: str) -> None:
76
+ super().__init__(requirement_string)
77
+ try:
78
+ parsed = _parse_requirement(requirement_string)
79
+ except ParserSyntaxError as e:
80
+ raise InvalidRequirement(str(e)) from e
81
+ self.specifier = _CustomSpecifierSet(parsed.specifier)
82
+
83
+ @override
84
+ def _iter_parts(self, name: str) -> Iterator[str]:
85
+ yield name
86
+ if self.extras:
87
+ formatted_extras = ",".join(sorted(self.extras))
88
+ yield f"[{formatted_extras}]"
89
+ if self.specifier:
90
+ yield f" {self.specifier}"
91
+ if self.url:
92
+ yield f"@ {self.url}"
93
+ if self.marker:
94
+ yield " "
95
+ if self.marker:
96
+ yield f"; {self.marker}"
97
+
98
+
99
+ class _CustomSpecifierSet(SpecifierSet):
100
+ @override
101
+ def __str__(self) -> str:
102
+ specs = sorted(self._specs, key=self._key)
103
+ return ", ".join(map(str, specs))
104
+
105
+ def _key(self, spec: Specifier, /) -> int:
106
+ return [">=", "<"].index(spec.operator)
107
+
108
+
109
+ __all__ = ["PYPROJECT_TOML", "main"]
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from pre_commit_hooks.format_requirements import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(int(not main()))
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, override
4
+
5
+ from click import argument, command
6
+ from libcst import CSTTransformer, Name, Subscript, parse_module
7
+ from libcst.matchers import Index as MIndex
8
+ from libcst.matchers import Name as MName
9
+ from libcst.matchers import Subscript as MSubscript
10
+ from libcst.matchers import SubscriptElement as MSubscriptElement
11
+ from libcst.matchers import matches
12
+ from libcst.metadata import MetadataWrapper
13
+ from utilities.click import FilePath
14
+
15
+ if TYPE_CHECKING:
16
+ from pathlib import Path
17
+
18
+
19
+ @command()
20
+ @argument("paths", nargs=-1, type=FilePath)
21
+ def main(*, paths: tuple[Path, ...]) -> bool:
22
+ """CLI for the `replace_sequence_str` hook."""
23
+ results = list(map(_process, paths))
24
+ return all(results)
25
+
26
+
27
+ def _process(path: Path, /) -> bool:
28
+ existing = path.read_text()
29
+ wrapper = MetadataWrapper(parse_module(existing))
30
+ transformed = wrapper.module.visit(SequenceToListTransformer())
31
+ new = transformed.code
32
+ if existing == new:
33
+ return True
34
+ _ = path.write_text(new)
35
+ return False
36
+
37
+
38
+ class SequenceToListTransformer(CSTTransformer):
39
+ @override
40
+ def leave_Subscript(
41
+ self, original_node: Subscript, updated_node: Subscript
42
+ ) -> Subscript:
43
+ _ = original_node
44
+ if matches(
45
+ updated_node,
46
+ MSubscript(
47
+ value=MName("Sequence"),
48
+ slice=[MSubscriptElement(slice=MIndex(value=MName("str")))],
49
+ ),
50
+ ):
51
+ return updated_node.with_changes(value=Name("list"))
52
+ return updated_node
53
+
54
+
55
+ __all__ = ["main"]
@@ -0,0 +1,6 @@
1
+ from __future__ import annotations
2
+
3
+ from pre_commit_hooks.replace_sequence_str import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(int(not main()))
@@ -0,0 +1,30 @@
1
+ [dependency-groups]
2
+ group = [
3
+ "unbounded",
4
+ "lower>=1.2.3",
5
+ "upper<1.3",
6
+ "lower-and-upper1>=1.2.3,<1.3",
7
+ ]
8
+
9
+ [project]
10
+ dependencies = [
11
+ "unbounded",
12
+ "lower1>=1.2.3",
13
+ "lower2 >= 1.2.3",
14
+ "upper1<1.3",
15
+ "upper2 < 1.3",
16
+ "lower-and-upper1>=1.2.3,<1.3",
17
+ "lower-and-upper2<1.3,>=1.2.3",
18
+ "lower-and-upper3 >= 1.2.3 , <1.3",
19
+ "with-extra[extra]",
20
+ "with-extra[extra]>=1.2.3",
21
+ "with-extra[extra] >= 1.2.3",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ group = [
26
+ "unbounded",
27
+ "lower>=1.2.3",
28
+ "upper<1.3",
29
+ "lower-and-upper1>=1.2.3,<1.3",
30
+ ]
@@ -0,0 +1,30 @@
1
+ [dependency-groups]
2
+ group = [
3
+ "unbounded",
4
+ "lower >=1.2.3",
5
+ "upper <1.3",
6
+ "lower-and-upper1 >=1.2.3, <1.3",
7
+ ]
8
+
9
+ [project]
10
+ dependencies = [
11
+ "unbounded",
12
+ "lower1 >=1.2.3",
13
+ "lower2 >=1.2.3",
14
+ "upper1 <1.3",
15
+ "upper2 <1.3",
16
+ "lower-and-upper1 >=1.2.3, <1.3",
17
+ "lower-and-upper2 >=1.2.3, <1.3",
18
+ "lower-and-upper3 >=1.2.3, <1.3",
19
+ "with-extra[extra]",
20
+ "with-extra[extra] >=1.2.3",
21
+ "with-extra[extra] >=1.2.3",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ group = [
26
+ "unbounded",
27
+ "lower >=1.2.3",
28
+ "upper <1.3",
29
+ "lower-and-upper1 >=1.2.3, <1.3",
30
+ ]
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ from tomlkit import dumps
6
+
7
+ from pre_commit_hooks.format_requirements import _format_path
8
+
9
+
10
+ class TestFormatRequirements:
11
+ def test_basic(self) -> None:
12
+ root = Path(__file__).parent
13
+ result = dumps(_format_path(root.joinpath("in.toml")))
14
+ expected = root.joinpath("out.toml").read_text()
15
+ assert result == expected