crackerjack 0.14.4__py3-none-any.whl → 0.14.6__py3-none-any.whl

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.
@@ -17,7 +17,7 @@ repos:
17
17
  - id: check-added-large-files
18
18
  name: check-added-large-files
19
19
  - repo: https://github.com/astral-sh/ruff-pre-commit
20
- rev: v0.9.10
20
+ rev: v0.11.2
21
21
  hooks:
22
22
  - id: ruff-format
23
23
  - id: ruff
@@ -65,11 +65,11 @@ repos:
65
65
  - id: bandit
66
66
  args: ["-c", "pyproject.toml"]
67
67
  - repo: https://github.com/RobertCraigie/pyright-python
68
- rev: v1.1.396
68
+ rev: v1.1.397
69
69
  hooks:
70
70
  - id: pyright
71
71
  - repo: https://github.com/astral-sh/ruff-pre-commit
72
- rev: v0.9.10
72
+ rev: v0.11.2
73
73
  hooks:
74
74
  - id: ruff
75
75
  - id: ruff-format
@@ -5,13 +5,13 @@ import subprocess
5
5
  import tokenize
6
6
  import typing as t
7
7
  from contextlib import suppress
8
+ from dataclasses import dataclass, field
8
9
  from pathlib import Path
9
10
  from subprocess import CompletedProcess
10
11
  from subprocess import run as execute
11
12
  from token import STRING
12
13
  from tomllib import loads
13
14
 
14
- from pydantic import BaseModel
15
15
  from rich.console import Console
16
16
  from tomli_w import dumps
17
17
 
@@ -20,7 +20,8 @@ interactive_hooks = ("refurb", "bandit", "pyright")
20
20
  default_python_version = "3.13"
21
21
 
22
22
 
23
- class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
23
+ @dataclass
24
+ class CodeCleaner:
24
25
  console: Console
25
26
 
26
27
  def clean_files(self, pkg_dir: Path | None) -> None:
@@ -35,20 +36,20 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
35
36
  def clean_file(self, file_path: Path) -> None:
36
37
  try:
37
38
  if file_path.resolve() == Path(__file__).resolve():
38
- print(f"Skipping cleaning of {file_path} (self file).")
39
+ self.console.print(f"Skipping cleaning of {file_path} (self file).")
39
40
  return
40
41
  except Exception as e:
41
- print(f"Error comparing file paths: {e}")
42
+ self.console.print(f"Error comparing file paths: {e}")
42
43
  try:
43
44
  code = file_path.read_text()
44
45
  code = self.remove_docstrings(code)
45
46
  code = self.remove_line_comments(code)
46
47
  code = self.remove_extra_whitespace(code)
47
48
  code = self.reformat_code(code)
48
- file_path.write_text(code)
49
- print(f"Cleaned: {file_path}")
49
+ file_path.write_text(code) # type: ignore
50
+ self.console.print(f"Cleaned: {file_path}")
50
51
  except Exception as e:
51
- print(f"Error cleaning {file_path}: {e}")
52
+ self.console.print(f"Error cleaning {file_path}: {e}")
52
53
 
53
54
  def remove_line_comments(self, code: str) -> str:
54
55
  new_lines = []
@@ -59,38 +60,98 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
59
60
  idx = line.find("#")
60
61
  code_part = line[:idx].rstrip()
61
62
  comment_part = line[idx:]
62
- if "type: ignore" in comment_part or "noqa" in comment_part:
63
+ if (
64
+ "type: ignore" in comment_part
65
+ or "noqa" in comment_part
66
+ or "nosec" in comment_part
67
+ ):
63
68
  new_lines.append(line)
64
69
  else:
65
70
  if code_part:
66
71
  new_lines.append(code_part)
67
72
  return "\n".join(new_lines)
68
73
 
74
+ def _is_triple_quoted(self, token_string: str) -> bool:
75
+ triple_quote_patterns = [
76
+ ('"""', '"""'),
77
+ ("'''", "'''"),
78
+ ('r"""', '"""'),
79
+ ("r'''", "'''"),
80
+ ]
81
+ return any(
82
+ token_string.startswith(start) and token_string.endswith(end)
83
+ for start, end in triple_quote_patterns
84
+ )
85
+
86
+ def _is_module_docstring(
87
+ self, tokens: list[tokenize.TokenInfo], i: int, indent_level: int
88
+ ) -> bool:
89
+ if i <= 0 or indent_level != 0:
90
+ return False
91
+ preceding_tokens = tokens[:i]
92
+ return not preceding_tokens
93
+
94
+ def _is_function_or_class_docstring(
95
+ self,
96
+ tokens: list[tokenize.TokenInfo],
97
+ i: int,
98
+ last_token_type: t.Any,
99
+ last_token_string: str,
100
+ ) -> bool:
101
+ if last_token_type != tokenize.OP or last_token_string != ":": # nosec B105
102
+ return False
103
+ for prev_idx in range(i - 1, max(0, i - 20), -1):
104
+ prev_token = tokens[prev_idx]
105
+ if prev_token[1] in ("def", "class") and prev_token[0] == tokenize.NAME:
106
+ return True
107
+ elif prev_token[0] == tokenize.DEDENT:
108
+ break
109
+ return False
110
+
111
+ def _is_variable_docstring(
112
+ self, tokens: list[tokenize.TokenInfo], i: int, indent_level: int
113
+ ) -> bool:
114
+ if indent_level <= 0:
115
+ return False
116
+ for prev_idx in range(i - 1, max(0, i - 10), -1):
117
+ if tokens[prev_idx][0]:
118
+ return True
119
+ return False
120
+
69
121
  def remove_docstrings(self, source: str) -> str:
70
122
  try:
71
123
  io_obj = io.StringIO(source)
72
- output_tokens = []
73
- first_token_stack = [True]
74
124
  tokens = list(tokenize.generate_tokens(io_obj.readline))
75
- for tok in tokens:
76
- token_type = tok.type
125
+ result_tokens = []
126
+ indent_level = 0
127
+ last_non_ws_token_type = None
128
+ last_non_ws_token_string = "" # nosec B105
129
+ for i, token in enumerate(tokens):
130
+ token_type, token_string, _, _, _ = token
77
131
  if token_type == tokenize.INDENT:
78
- first_token_stack.append(True)
132
+ indent_level += 1
79
133
  elif token_type == tokenize.DEDENT:
80
- if len(first_token_stack) > 1:
81
- first_token_stack.pop()
82
- if token_type == STRING and first_token_stack[-1]:
83
- first_token_stack[-1] = False
84
- continue
85
- else:
86
- if token_type not in (
87
- tokenize.NEWLINE,
88
- tokenize.NL,
89
- tokenize.COMMENT,
90
- ):
91
- first_token_stack[-1] = False
92
- output_tokens.append(tok)
93
- return tokenize.untokenize(output_tokens)
134
+ indent_level -= 1
135
+ if token_type == STRING and self._is_triple_quoted(token_string):
136
+ is_docstring = (
137
+ self._is_module_docstring(tokens, i, indent_level)
138
+ or self._is_function_or_class_docstring(
139
+ tokens, i, last_non_ws_token_type, last_non_ws_token_string
140
+ )
141
+ or self._is_variable_docstring(tokens, i, indent_level)
142
+ )
143
+ if is_docstring:
144
+ continue
145
+ if token_type not in (
146
+ tokenize.NL,
147
+ tokenize.NEWLINE,
148
+ tokenize.INDENT,
149
+ tokenize.DEDENT,
150
+ ):
151
+ last_non_ws_token_type = token_type
152
+ last_non_ws_token_string = token_string
153
+ result_tokens.append(token)
154
+ return tokenize.untokenize(result_tokens)
94
155
  except Exception as e:
95
156
  self.console.print(f"Error removing docstrings: {e}")
96
157
  return source
@@ -105,7 +166,7 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
105
166
  cleaned_lines.append(line)
106
167
  return "\n".join(cleaned_lines)
107
168
 
108
- def reformat_code(self, code: str) -> str:
169
+ def reformat_code(self, code: str) -> str | None:
109
170
  try:
110
171
  import tempfile
111
172
 
@@ -124,21 +185,22 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
124
185
  if result.returncode == 0:
125
186
  formatted_code = temp_path.read_text()
126
187
  else:
127
- print(f"Ruff formatting failed: {result.stderr}")
188
+ self.console.print(f"Ruff formatting failed: {result.stderr}")
128
189
  formatted_code = code
129
190
  except Exception as e:
130
- print(f"Error running Ruff: {e}")
191
+ self.console.print(f"Error running Ruff: {e}")
131
192
  formatted_code = code
132
193
  finally:
133
194
  with suppress(FileNotFoundError):
134
195
  temp_path.unlink()
135
196
  return formatted_code
136
197
  except Exception as e:
137
- print(f"Error during reformatting: {e}")
198
+ self.console.print(f"Error during reformatting: {e}")
138
199
  return code
139
200
 
140
201
 
141
- class ConfigManager(BaseModel, arbitrary_types_allowed=True):
202
+ @dataclass
203
+ class ConfigManager:
142
204
  our_path: Path
143
205
  pkg_path: Path
144
206
  pkg_name: str
@@ -262,14 +324,15 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
262
324
  return execute(cmd, **kwargs)
263
325
 
264
326
 
265
- class ProjectManager(BaseModel, arbitrary_types_allowed=True):
327
+ @dataclass
328
+ class ProjectManager:
266
329
  our_path: Path
267
330
  pkg_path: Path
268
- pkg_dir: Path | None = None
269
- pkg_name: str = "crackerjack"
270
331
  console: Console
271
332
  code_cleaner: CodeCleaner
272
333
  config_manager: ConfigManager
334
+ pkg_dir: Path | None = None
335
+ pkg_name: str = "crackerjack"
273
336
  dry_run: bool = False
274
337
 
275
338
  def run_interactive(self, hook: str) -> None:
@@ -321,39 +384,42 @@ class ProjectManager(BaseModel, arbitrary_types_allowed=True):
321
384
  return execute(cmd, **kwargs)
322
385
 
323
386
 
324
- class Crackerjack(BaseModel, arbitrary_types_allowed=True):
325
- our_path: Path = Path(__file__).parent
326
- pkg_path: Path = Path(Path.cwd())
387
+ @dataclass
388
+ class Crackerjack:
389
+ our_path: Path = field(default_factory=lambda: Path(__file__).parent)
390
+ pkg_path: Path = field(default_factory=lambda: Path(Path.cwd()))
327
391
  pkg_dir: Path | None = None
328
392
  pkg_name: str = "crackerjack"
329
393
  python_version: str = default_python_version
330
- console: Console = Console(force_terminal=True)
394
+ console: Console = field(default_factory=lambda: Console(force_terminal=True))
331
395
  dry_run: bool = False
332
396
  code_cleaner: CodeCleaner | None = None
333
397
  config_manager: ConfigManager | None = None
334
398
  project_manager: ProjectManager | None = None
335
399
 
336
- def __init__(self, **data: t.Any) -> None:
337
- super().__init__(**data)
338
- self.code_cleaner = CodeCleaner(console=self.console)
339
- self.config_manager = ConfigManager(
340
- our_path=self.our_path,
341
- pkg_path=self.pkg_path,
342
- pkg_name=self.pkg_name,
343
- console=self.console,
344
- python_version=self.python_version,
345
- dry_run=self.dry_run,
346
- )
347
- self.project_manager = ProjectManager(
348
- our_path=self.our_path,
349
- pkg_path=self.pkg_path,
350
- pkg_dir=self.pkg_dir,
351
- pkg_name=self.pkg_name,
352
- console=self.console,
353
- code_cleaner=self.code_cleaner,
354
- config_manager=self.config_manager,
355
- dry_run=self.dry_run,
356
- )
400
+ def __post_init__(self) -> None:
401
+ if self.code_cleaner is None:
402
+ self.code_cleaner = CodeCleaner(console=self.console)
403
+ if self.config_manager is None:
404
+ self.config_manager = ConfigManager(
405
+ our_path=self.our_path,
406
+ pkg_path=self.pkg_path,
407
+ pkg_name=self.pkg_name,
408
+ console=self.console,
409
+ python_version=self.python_version,
410
+ dry_run=self.dry_run,
411
+ )
412
+ if self.project_manager is None:
413
+ self.project_manager = ProjectManager(
414
+ our_path=self.our_path,
415
+ pkg_path=self.pkg_path,
416
+ pkg_dir=self.pkg_dir,
417
+ pkg_name=self.pkg_name,
418
+ console=self.console,
419
+ code_cleaner=self.code_cleaner,
420
+ config_manager=self.config_manager,
421
+ dry_run=self.dry_run,
422
+ )
357
423
 
358
424
  def _setup_package(self) -> None:
359
425
  self.pkg_name = self.pkg_path.stem.lower().replace("-", "_")
@@ -2,9 +2,10 @@
2
2
  addopts = "--cov=crackerjack"
3
3
  asyncio_default_fixture_loop_scope = "function"
4
4
  python_files = ["test_*.py", "*_test.py"]
5
- python_classes = "Test*"
6
- python_functions = "test_*"
7
5
  asyncio_mode = "auto"
6
+ testpaths = ["tests", "crackerjack"]
7
+ python_classes = ["Test*"]
8
+ python_functions = ["test_*"]
8
9
 
9
10
  [tool.coverage.run]
10
11
  branch = true
@@ -14,6 +15,7 @@ omit = [
14
15
  "*/site-packages/*",
15
16
  "*/__pycache__/*",
16
17
  "*/__init__.py",
18
+ "*/_version.py", "*/conftest.py", "*/test_*.py", "*/_test.py"
17
19
  ]
18
20
 
19
21
  [tool.coverage.report]
@@ -147,7 +149,7 @@ pythonPlatform = "Darwin"
147
149
 
148
150
  [project]
149
151
  name = "crackerjack"
150
- version = "0.14.3"
152
+ version = "0.14.5"
151
153
  description = "Default template for PDM package"
152
154
  requires-python = ">=3.13"
153
155
  readme = "README.md"
@@ -174,12 +176,12 @@ classifiers = [
174
176
  ]
175
177
  dependencies = [
176
178
  "autotyping>=24.9.0",
177
- "pre-commit>=4.1.0",
179
+ "pre-commit>=4.2.0",
178
180
  "pytest>=8.3.5",
179
181
  "pydantic>=2.10.6",
180
182
  "pdm-bump>=0.9.10",
181
183
  "pdm>=2.22.4",
182
- "uv>=0.6.6",
184
+ "uv>=0.6.9",
183
185
  "pytest-cov>=6.0.0",
184
186
  "pytest-mock>=3.14.0",
185
187
  "tomli-w>=1.2.0",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.14.4
3
+ Version: 0.14.6
4
4
  Summary: Default template for PDM package
5
5
  Keywords: black,ruff,mypy,creosote,refurb
6
6
  Author-Email: lesleslie <les@wedgwoodwebworks.com>
@@ -23,12 +23,12 @@ Project-URL: documentation, https://github.com/lesleslie/crackerjack
23
23
  Project-URL: repository, https://github.com/lesleslie/crackerjack
24
24
  Requires-Python: >=3.13
25
25
  Requires-Dist: autotyping>=24.9.0
26
- Requires-Dist: pre-commit>=4.1.0
26
+ Requires-Dist: pre-commit>=4.2.0
27
27
  Requires-Dist: pytest>=8.3.5
28
28
  Requires-Dist: pydantic>=2.10.6
29
29
  Requires-Dist: pdm-bump>=0.9.10
30
30
  Requires-Dist: pdm>=2.22.4
31
- Requires-Dist: uv>=0.6.6
31
+ Requires-Dist: uv>=0.6.9
32
32
  Requires-Dist: pytest-cov>=6.0.0
33
33
  Requires-Dist: pytest-mock>=3.14.0
34
34
  Requires-Dist: tomli-w>=1.2.0
@@ -1,12 +1,12 @@
1
- crackerjack-0.14.4.dist-info/METADATA,sha256=wC0ceNrYPUl_nUQzVFYKYXYHSPC5tpw1jOsezSoXsbA,11033
2
- crackerjack-0.14.4.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
- crackerjack-0.14.4.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- crackerjack-0.14.4.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
1
+ crackerjack-0.14.6.dist-info/METADATA,sha256=bij1KNnKB8-YQUvZKjue_kQvCQdbW9-oCu_o7h03PlI,11033
2
+ crackerjack-0.14.6.dist-info/WHEEL,sha256=thaaA2w1JzcGC48WYufAs8nrYZjJm8LqNfnXFOFyCC4,90
3
+ crackerjack-0.14.6.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ crackerjack-0.14.6.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
5
5
  crackerjack/.coverage,sha256=dLzPzp72qZEXohNfxnOAlRwvM9dqF06-HoFqfvXZd1U,53248
6
6
  crackerjack/.gitignore,sha256=l8ErBAypC3rI6N9lhc7ZMdOw87t0Tz69ZW5C6uj15Wg,214
7
7
  crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
8
8
  crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
9
- crackerjack/.pre-commit-config.yaml,sha256=cLIzulHerHmCHjo5UWA2JAMm5d5PjwGpIp5Lx-nL3FQ,2267
9
+ crackerjack/.pre-commit-config.yaml,sha256=rb_3cBRHFSzWYC1OW8mLkFC7A8Retb0zdyeIM5FUQ1I,2267
10
10
  crackerjack/.pytest_cache/.gitignore,sha256=Ptcxtl0GFQwTji2tsL4Gl1UIiKa0frjEXsya26i46b0,37
11
11
  crackerjack/.pytest_cache/CACHEDIR.TAG,sha256=N9yI75oKvt2-gQU6bdj9-xOvthMEXqHrSlyBWnSjveQ,191
12
12
  crackerjack/.pytest_cache/README.md,sha256=c_1vzN2ALEGaay2YPWwxc7fal1WKxLWJ7ewt_kQ9ua0,302
@@ -19,6 +19,7 @@ crackerjack/.ruff_cache/0.1.4/10355199064880463147,sha256=kmqNg5WySQYPeAqa5elfaV
19
19
  crackerjack/.ruff_cache/0.1.6/15140459877605758699,sha256=oQy5boAXeskdm5M0Abh_nyBtitWj5N5wtx_4gsDgu7c,248
20
20
  crackerjack/.ruff_cache/0.1.7/1790508110482614856,sha256=De7Puq32XF0925xrGehWSKX6cw5Wi2bpt1cnqh__f54,248
21
21
  crackerjack/.ruff_cache/0.1.9/17041001205004563469,sha256=tKP_k8HaHhQJyrHbDfJ93kM7vahjrU8cKQ1f_-OUzZY,248
22
+ crackerjack/.ruff_cache/0.11.2/4070660268492669020,sha256=kE8I-PoRXnXRfzmPQW3GO_fgkN4H43CtWvW_L0PLQHc,224
22
23
  crackerjack/.ruff_cache/0.2.0/10047773857155985907,sha256=j9LNa_RQ4Plor7go1uTYgz17cEENKvZQ-dP6b9MX0ik,248
23
24
  crackerjack/.ruff_cache/0.2.1/8522267973936635051,sha256=u_aPBMibtAp_iYvLwR88GMAECMcIgHezxMyuapmU2P4,248
24
25
  crackerjack/.ruff_cache/0.2.2/18053836298936336950,sha256=Xb_ebP0pVuUfSqPEZKlhQ70so_vqkEfMYpuHQ06iR5U,248
@@ -48,6 +49,6 @@ crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsa
48
49
  crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
49
50
  crackerjack/__init__.py,sha256=XTWW_XQkWR6dSydFSLg-T--eY3TPKUp4jUwZP11kgwY,142
50
51
  crackerjack/__main__.py,sha256=7SHrcRFYhMaSV0S-EG8q2w8udURwdvIsS7vi9AW4naU,3784
51
- crackerjack/crackerjack.py,sha256=6FXuzNiS46eE9L_mLDHwGi7hMEdMwQKFuWmgSVgdvsg,18927
52
- crackerjack/pyproject.toml,sha256=HuWlXCj4FR_W9hlgh-3kT1uQQTsLhfCbqeHVXgkN194,3981
53
- crackerjack-0.14.4.dist-info/RECORD,,
52
+ crackerjack/crackerjack.py,sha256=TupEH0hqRaB5hp4ZBMyGzdw4UjF_KT00wYhyo4o0khU,21338
53
+ crackerjack/pyproject.toml,sha256=0YJzFZP2CxyWMc4d4eF85zD8Y2N5hmb04xf9p0VFATY,4088
54
+ crackerjack-0.14.6.dist-info/RECORD,,