crackerjack 0.14.4__tar.gz → 0.14.6__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 (61) hide show
  1. {crackerjack-0.14.4 → crackerjack-0.14.6}/PKG-INFO +3 -3
  2. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pre-commit-config.yaml +3 -3
  3. crackerjack-0.14.6/crackerjack/.ruff_cache/0.11.2/4070660268492669020 +0 -0
  4. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/crackerjack.py +126 -60
  5. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/pyproject.toml +7 -5
  6. {crackerjack-0.14.4 → crackerjack-0.14.6}/pyproject.toml +17 -5
  7. crackerjack-0.14.6/tests/data/docstrings_sample.txt +20 -0
  8. {crackerjack-0.14.4 → crackerjack-0.14.6}/tests/test_crackerjack.py +26 -10
  9. crackerjack-0.14.4/tests/data/docstrings_sample.txt +0 -10
  10. {crackerjack-0.14.4 → crackerjack-0.14.6}/LICENSE +0 -0
  11. {crackerjack-0.14.4 → crackerjack-0.14.6}/README.md +0 -0
  12. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.coverage +0 -0
  13. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.gitignore +0 -0
  14. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.libcst.codemod.yaml +0 -0
  15. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pdm.toml +0 -0
  16. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pytest_cache/.gitignore +0 -0
  17. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pytest_cache/CACHEDIR.TAG +0 -0
  18. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pytest_cache/README.md +0 -0
  19. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pytest_cache/v/cache/nodeids +0 -0
  20. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.pytest_cache/v/cache/stepwise +0 -0
  21. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/.gitignore +0 -0
  22. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.1.11/3256171999636029978 +0 -0
  23. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.1.14/602324811142551221 +0 -0
  24. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.1.4/10355199064880463147 +0 -0
  25. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.1.6/15140459877605758699 +0 -0
  26. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.1.7/1790508110482614856 +0 -0
  27. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.1.9/17041001205004563469 +0 -0
  28. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.2.0/10047773857155985907 +0 -0
  29. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.2.1/8522267973936635051 +0 -0
  30. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.2.2/18053836298936336950 +0 -0
  31. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.3.0/12548816621480535786 +0 -0
  32. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.3.3/11081883392474770722 +0 -0
  33. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.3.4/676973378459347183 +0 -0
  34. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.3.5/16311176246009842383 +0 -0
  35. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.5.7/1493622539551733492 +0 -0
  36. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.5.7/6231957614044513175 +0 -0
  37. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.5.7/9932762556785938009 +0 -0
  38. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.0/11982804814124138945 +0 -0
  39. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.0/12055761203849489982 +0 -0
  40. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.2/1206147804896221174 +0 -0
  41. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.4/1206147804896221174 +0 -0
  42. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.5/1206147804896221174 +0 -0
  43. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.7/3657366982708166874 +0 -0
  44. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.6.9/285614542852677309 +0 -0
  45. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.7.1/1024065805990144819 +0 -0
  46. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.7.1/285614542852677309 +0 -0
  47. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.7.3/16061516852537040135 +0 -0
  48. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.8.4/16354268377385700367 +0 -0
  49. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.9.10/12813592349865671909 +0 -0
  50. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.9.10/923908772239632759 +0 -0
  51. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.9.3/13948373885254993391 +0 -0
  52. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.9.9/12813592349865671909 +0 -0
  53. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/0.9.9/8843823720003377982 +0 -0
  54. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/.ruff_cache/CACHEDIR.TAG +0 -0
  55. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/__init__.py +0 -0
  56. {crackerjack-0.14.4 → crackerjack-0.14.6}/crackerjack/__main__.py +0 -0
  57. {crackerjack-0.14.4 → crackerjack-0.14.6}/tests/__init__.py +0 -0
  58. {crackerjack-0.14.4 → crackerjack-0.14.6}/tests/data/comments_sample.txt +0 -0
  59. {crackerjack-0.14.4 → crackerjack-0.14.6}/tests/data/expected_comments_sample.txt +0 -0
  60. {crackerjack-0.14.4 → crackerjack-0.14.6}/tests/data/init.py +0 -0
  61. {crackerjack-0.14.4 → crackerjack-0.14.6}/tests/test_main.py +0 -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
@@ -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",
@@ -5,9 +5,17 @@ python_files = [
5
5
  "test_*.py",
6
6
  "*_test.py",
7
7
  ]
8
- python_classes = "Test*"
9
- python_functions = "test_*"
10
8
  asyncio_mode = "auto"
9
+ testpaths = [
10
+ "tests",
11
+ "crackerjack",
12
+ ]
13
+ python_classes = [
14
+ "Test*",
15
+ ]
16
+ python_functions = [
17
+ "test_*",
18
+ ]
11
19
 
12
20
  [tool.coverage.run]
13
21
  branch = true
@@ -19,6 +27,10 @@ omit = [
19
27
  "*/site-packages/*",
20
28
  "*/__pycache__/*",
21
29
  "*/__init__.py",
30
+ "*/_version.py",
31
+ "*/conftest.py",
32
+ "*/test_*.py",
33
+ "*/_test.py",
22
34
  ]
23
35
 
24
36
  [tool.coverage.report]
@@ -156,7 +168,7 @@ pythonPlatform = "Darwin"
156
168
 
157
169
  [project]
158
170
  name = "crackerjack"
159
- version = "0.14.4"
171
+ version = "0.14.6"
160
172
  description = "Default template for PDM package"
161
173
  requires-python = ">=3.13"
162
174
  readme = "README.md"
@@ -183,12 +195,12 @@ classifiers = [
183
195
  ]
184
196
  dependencies = [
185
197
  "autotyping>=24.9.0",
186
- "pre-commit>=4.1.0",
198
+ "pre-commit>=4.2.0",
187
199
  "pytest>=8.3.5",
188
200
  "pydantic>=2.10.6",
189
201
  "pdm-bump>=0.9.10",
190
202
  "pdm>=2.22.4",
191
- "uv>=0.6.6",
203
+ "uv>=0.6.9",
192
204
  "pytest-cov>=6.0.0",
193
205
  "pytest-mock>=3.14.0",
194
206
  "tomli-w>=1.2.0",
@@ -0,0 +1,20 @@
1
+
2
+ def test_func():
3
+ """This is a docstring."""
4
+ return True
5
+
6
+ class TestClass:
7
+ """Class docstring."""
8
+
9
+ def method1(self):
10
+ '''Method docstring.'''
11
+ pass
12
+
13
+ def method2(self):
14
+ """
15
+ Method docstring.
16
+
17
+ This is a multi-line docstring.
18
+
19
+ """
20
+ pass
@@ -1,12 +1,12 @@
1
1
  import os
2
2
  import typing as t
3
3
  from contextlib import suppress
4
+ from dataclasses import dataclass
4
5
  from enum import Enum
5
6
  from pathlib import Path
6
7
  from unittest.mock import MagicMock, patch
7
8
 
8
9
  import pytest
9
- from pydantic import BaseModel
10
10
  from rich.console import Console
11
11
  from crackerjack.crackerjack import (
12
12
  CodeCleaner,
@@ -25,18 +25,19 @@ class BumpOption(str, Enum):
25
25
  return self.value
26
26
 
27
27
 
28
- class OptionsForTesting(BaseModel):
28
+ @dataclass
29
+ class OptionsForTesting:
29
30
  commit: bool = False
30
31
  interactive: bool = False
31
32
  doc: bool = False
32
33
  no_config_updates: bool = False
33
- publish: t.Optional[BumpOption] = None
34
- bump: t.Optional[BumpOption] = None
34
+ publish: BumpOption | None = None
35
+ bump: BumpOption | None = None
35
36
  verbose: bool = False
36
37
  update_precommit: bool = False
37
38
  clean: bool = False
38
39
  test: bool = False
39
- all: t.Optional[BumpOption] = None
40
+ all: BumpOption | None = None
40
41
 
41
42
 
42
43
  @pytest.fixture
@@ -98,6 +99,8 @@ class TestCrackerjackProcess:
98
99
  kwargs["publish"] = BumpOption(kwargs["publish"])
99
100
  if "bump" in kwargs and isinstance(kwargs["bump"], str):
100
101
  kwargs["bump"] = BumpOption(kwargs["bump"])
102
+ if "all" in kwargs and isinstance(kwargs["all"], str):
103
+ kwargs["all"] = BumpOption(kwargs["all"])
101
104
  return OptionsForTesting(**kwargs)
102
105
 
103
106
  return _create_options
@@ -901,11 +904,15 @@ class TestCrackerjackProcess:
901
904
  Path(__file__).parent / "data" / "docstrings_sample.txt"
902
905
  ).read_text()
903
906
  cleaned_code = code_cleaner.remove_docstrings(code_with_docstrings)
907
+ print(cleaned_code)
904
908
  assert '"""This is a docstring."""' not in cleaned_code, (
905
909
  f"Got: {cleaned_code!r}"
906
910
  )
907
911
  assert '"""Class docstring."""' not in cleaned_code, f"Got: {cleaned_code!r}"
908
- assert '"""Method docstring."""' not in cleaned_code, f"Got: {cleaned_code!r}"
912
+ assert "'''Method docstring.'''" not in cleaned_code, f"Got: {cleaned_code!r}"
913
+ assert "This is a multi-line docstring." not in cleaned_code, (
914
+ f"Got: {cleaned_code!r}"
915
+ )
909
916
 
910
917
  def test_code_cleaner_remove_line_comments(self) -> None:
911
918
  from pathlib import Path
@@ -961,16 +968,25 @@ class TestCrackerjackProcess:
961
968
  def test_code_cleaner_reformat_code_failure(
962
969
  self, mock_execute: MagicMock, mock_console_print: MagicMock, tmp_path: Path
963
970
  ) -> None:
964
- code_cleaner = CodeCleaner(console=Console())
971
+ from rich.console import Console
972
+
973
+ console = Console()
974
+
975
+ code_cleaner = CodeCleaner(console=console)
965
976
  code_to_format = "def test_func():\n return True\n"
977
+
966
978
  with patch("subprocess.run") as mock_run:
967
979
  mock_run.return_value = MagicMock(returncode=1, stderr="Formatting error")
968
- with patch("builtins.print") as mock_print:
969
- with patch("pathlib.Path.write_text"):
980
+
981
+ with patch("pathlib.Path.write_text"):
982
+ with patch.object(console, "print") as mock_console_print_method:
970
983
  formatted_code = code_cleaner.reformat_code(code_to_format)
984
+
971
985
  assert formatted_code == code_to_format
986
+
972
987
  mock_run.assert_called_once()
973
- mock_print.assert_any_call(
988
+
989
+ mock_console_print_method.assert_any_call(
974
990
  "Ruff formatting failed: Formatting error"
975
991
  )
976
992
 
@@ -1,10 +0,0 @@
1
- """def test_func():
2
- \"\"\"This is a docstring.\"\"\"
3
- return True
4
-
5
- class TestClass:
6
- \"\"\"Class docstring.\"\"\"
7
- def method(self):
8
- \"\"\"Method docstring.\"\"\"
9
- pass
10
- """
File without changes
File without changes