crackerjack 0.21.7__py3-none-any.whl → 0.21.8__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.
crackerjack/__init__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import typing as t
2
+
2
3
  from .crackerjack import Crackerjack, create_crackerjack_runner
3
4
  from .errors import (
4
5
  CleaningError,
crackerjack/__main__.py CHANGED
@@ -1,4 +1,5 @@
1
1
  from enum import Enum
2
+
2
3
  import typer
3
4
  from pydantic import BaseModel, field_validator
4
5
  from rich.console import Console
@@ -6,6 +6,7 @@ from pathlib import Path
6
6
  from subprocess import CompletedProcess
7
7
  from subprocess import run as execute
8
8
  from tomllib import loads
9
+
9
10
  from pydantic import BaseModel
10
11
  from rich.console import Console
11
12
  from tomli_w import dumps
@@ -169,9 +170,12 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
169
170
  cleaned_lines = []
170
171
  docstring_state = self._initialize_docstring_state()
171
172
  for i, line in enumerate(lines):
172
- _, result_line = self._process_line(lines, i, line, docstring_state)
173
- if result_line:
174
- cleaned_lines.append(result_line)
173
+ handled, result_line = self._process_line(lines, i, line, docstring_state)
174
+ if handled:
175
+ if result_line is not None:
176
+ cleaned_lines.append(result_line)
177
+ else:
178
+ cleaned_lines.append(line)
175
179
  return "\n".join(cleaned_lines)
176
180
 
177
181
  def _is_function_or_class_definition(self, stripped_line: str) -> bool:
@@ -287,13 +291,15 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
287
291
  lines = code.split("\n")
288
292
  cleaned_lines = []
289
293
  function_tracker = {"in_function": False, "function_indent": 0}
294
+ import_tracker = {"in_imports": False, "last_import_type": None}
290
295
  for i, line in enumerate(lines):
291
296
  line = line.rstrip()
292
297
  stripped_line = line.lstrip()
293
298
  self._update_function_state(line, stripped_line, function_tracker)
299
+ self._update_import_state(line, stripped_line, import_tracker)
294
300
  if not line:
295
301
  if self._should_skip_empty_line(
296
- i, lines, cleaned_lines, function_tracker
302
+ i, lines, cleaned_lines, function_tracker, import_tracker
297
303
  ):
298
304
  continue
299
305
  cleaned_lines.append(line)
@@ -309,6 +315,113 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
309
315
  function_tracker["in_function"] = False
310
316
  function_tracker["function_indent"] = 0
311
317
 
318
+ def _update_import_state(
319
+ self, line: str, stripped_line: str, import_tracker: dict[str, t.Any]
320
+ ) -> None:
321
+ if stripped_line.startswith(("import ", "from ")):
322
+ import_tracker["in_imports"] = True
323
+ if self._is_stdlib_import(stripped_line):
324
+ current_type = "stdlib"
325
+ elif self._is_local_import(stripped_line):
326
+ current_type = "local"
327
+ else:
328
+ current_type = "third_party"
329
+ import_tracker["last_import_type"] = current_type
330
+ elif stripped_line and not stripped_line.startswith("#"):
331
+ import_tracker["in_imports"] = False
332
+ import_tracker["last_import_type"] = None
333
+
334
+ def _is_stdlib_import(self, stripped_line: str) -> bool:
335
+ try:
336
+ if stripped_line.startswith("from "):
337
+ module = stripped_line.split()[1].split(".")[0]
338
+ else:
339
+ module = stripped_line.split()[1].split(".")[0]
340
+ except IndexError:
341
+ return False
342
+ stdlib_modules = {
343
+ "os",
344
+ "sys",
345
+ "re",
346
+ "json",
347
+ "datetime",
348
+ "time",
349
+ "pathlib",
350
+ "typing",
351
+ "collections",
352
+ "itertools",
353
+ "functools",
354
+ "operator",
355
+ "math",
356
+ "random",
357
+ "uuid",
358
+ "urllib",
359
+ "http",
360
+ "html",
361
+ "xml",
362
+ "email",
363
+ "csv",
364
+ "sqlite3",
365
+ "subprocess",
366
+ "threading",
367
+ "multiprocessing",
368
+ "asyncio",
369
+ "contextlib",
370
+ "dataclasses",
371
+ "enum",
372
+ "abc",
373
+ "io",
374
+ "tempfile",
375
+ "shutil",
376
+ "glob",
377
+ "pickle",
378
+ "copy",
379
+ "heapq",
380
+ "bisect",
381
+ "array",
382
+ "struct",
383
+ "zlib",
384
+ "hashlib",
385
+ "hmac",
386
+ "secrets",
387
+ "base64",
388
+ "binascii",
389
+ "codecs",
390
+ "locale",
391
+ "platform",
392
+ "socket",
393
+ "ssl",
394
+ "ipaddress",
395
+ "logging",
396
+ "warnings",
397
+ "inspect",
398
+ "ast",
399
+ "dis",
400
+ "tokenize",
401
+ "keyword",
402
+ "linecache",
403
+ "traceback",
404
+ "weakref",
405
+ "gc",
406
+ "ctypes",
407
+ "unittest",
408
+ "doctest",
409
+ "pdb",
410
+ "profile",
411
+ "cProfile",
412
+ "timeit",
413
+ "trace",
414
+ "calendar",
415
+ "decimal",
416
+ "fractions",
417
+ "statistics",
418
+ "tomllib",
419
+ }
420
+ return module in stdlib_modules
421
+
422
+ def _is_local_import(self, stripped_line: str) -> bool:
423
+ return stripped_line.startswith("from .") or " . " in stripped_line
424
+
312
425
  def _is_function_end(
313
426
  self, line: str, stripped_line: str, function_tracker: dict[str, t.Any]
314
427
  ) -> bool:
@@ -325,13 +438,44 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
325
438
  lines: list[str],
326
439
  cleaned_lines: list[str],
327
440
  function_tracker: dict[str, t.Any],
441
+ import_tracker: dict[str, t.Any],
328
442
  ) -> bool:
329
443
  if line_idx > 0 and cleaned_lines and (not cleaned_lines[-1]):
330
444
  return True
445
+
446
+ if self._is_import_section_separator(line_idx, lines, import_tracker):
447
+ return False
448
+
331
449
  if function_tracker["in_function"]:
332
450
  return self._should_skip_function_empty_line(line_idx, lines)
333
451
  return False
334
452
 
453
+ def _is_import_section_separator(
454
+ self, line_idx: int, lines: list[str], import_tracker: dict[str, t.Any]
455
+ ) -> bool:
456
+ if not import_tracker["in_imports"]:
457
+ return False
458
+
459
+ next_line_idx = line_idx + 1
460
+ while next_line_idx < len(lines) and not lines[next_line_idx].strip():
461
+ next_line_idx += 1
462
+
463
+ if next_line_idx >= len(lines):
464
+ return False
465
+
466
+ next_line = lines[next_line_idx].strip()
467
+ if not next_line.startswith(("import ", "from ")):
468
+ return False
469
+
470
+ if self._is_stdlib_import(next_line):
471
+ next_type = "stdlib"
472
+ elif self._is_local_import(next_line):
473
+ next_type = "local"
474
+ else:
475
+ next_type = "third_party"
476
+
477
+ return import_tracker["last_import_type"] != next_type
478
+
335
479
  def _should_skip_function_empty_line(self, line_idx: int, lines: list[str]) -> bool:
336
480
  next_line_idx = line_idx + 1
337
481
  if next_line_idx >= len(lines):
crackerjack/errors.py CHANGED
@@ -2,6 +2,7 @@ import sys
2
2
  import typing as t
3
3
  from enum import Enum
4
4
  from pathlib import Path
5
+
5
6
  from rich.console import Console
6
7
  from rich.panel import Panel
7
8
 
@@ -2,6 +2,7 @@ import time
2
2
  import typing as t
3
3
  from enum import Enum, auto
4
4
  from pathlib import Path
5
+
5
6
  from rich.box import ROUNDED
6
7
  from rich.console import Console
7
8
  from rich.layout import Layout
@@ -18,6 +19,7 @@ from rich.prompt import Confirm, Prompt
18
19
  from rich.table import Table
19
20
  from rich.text import Text
20
21
  from rich.tree import Tree
22
+
21
23
  from .errors import CrackerjackError, ErrorCode, handle_error
22
24
 
23
25
 
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.21.6"
7
+ version = "0.21.7"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.21.7
3
+ Version: 0.21.8
4
4
  Summary: Crackerjack: code quality toolkit
5
5
  Keywords: bandit,black,creosote,mypy,pyright,pytest,refurb,ruff
6
6
  Author-Email: lesleslie <les@wedgwoodwebworks.com>
@@ -1,7 +1,7 @@
1
- crackerjack-0.21.7.dist-info/METADATA,sha256=suf-qK6MQQTYL6v8ZXcTUT70x6ADX6u1Dd8YvhQSV60,26442
2
- crackerjack-0.21.7.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- crackerjack-0.21.7.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- crackerjack-0.21.7.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
1
+ crackerjack-0.21.8.dist-info/METADATA,sha256=IPe32XzA-hFDy247TIpvfAOWuWZwr64gbhZCUbsP4jQ,26442
2
+ crackerjack-0.21.8.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ crackerjack-0.21.8.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ crackerjack-0.21.8.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
5
5
  crackerjack/.gitignore,sha256=n8cD6U16L3XZn__PvhYm_F7-YeFHFucHCyxWj2NZCGs,259
6
6
  crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
7
7
  crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
@@ -34,7 +34,7 @@ crackerjack/.ruff_cache/0.11.7/10386934055395314831,sha256=lBNwN5zAgM4OzbkXIOzCc
34
34
  crackerjack/.ruff_cache/0.11.7/3557596832929915217,sha256=fKlwUbsvT3YIKV6UR-aA_i64lLignWeVfVu-MMmVbU0,207
35
35
  crackerjack/.ruff_cache/0.11.8/530407680854991027,sha256=xAMAL3Vu_HR6M-h5ojCTaak0By5ii8u-14pXULLgLqw,224
36
36
  crackerjack/.ruff_cache/0.12.0/5056746222905752453,sha256=MqrIT5qymJcgAOBZyn-TvYoGCFfDFCgN9IwSULq8n14,256
37
- crackerjack/.ruff_cache/0.12.1/5056746222905752453,sha256=CHy2IV23GK0EauutmCmddO7Ck-fHMzbvpvZ1ZO9Cp8Y,256
37
+ crackerjack/.ruff_cache/0.12.1/5056746222905752453,sha256=g9fAkLYZwfeJKgDNXhdsOwHnHBgTG5KKHfMoy5Lut1s,256
38
38
  crackerjack/.ruff_cache/0.2.0/10047773857155985907,sha256=j9LNa_RQ4Plor7go1uTYgz17cEENKvZQ-dP6b9MX0ik,248
39
39
  crackerjack/.ruff_cache/0.2.1/8522267973936635051,sha256=u_aPBMibtAp_iYvLwR88GMAECMcIgHezxMyuapmU2P4,248
40
40
  crackerjack/.ruff_cache/0.2.2/18053836298936336950,sha256=Xb_ebP0pVuUfSqPEZKlhQ70so_vqkEfMYpuHQ06iR5U,248
@@ -62,11 +62,11 @@ crackerjack/.ruff_cache/0.9.3/13948373885254993391,sha256=kGhtIkzPUtKAgvlKs3D8j4
62
62
  crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=tmr8_vhRD2OxsVuMfbJPdT9fDFX-d5tfC5U9jgziyho,224
63
63
  crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsaTvs1uA3_fI216Qq9qCAM,136
64
64
  crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
65
- crackerjack/__init__.py,sha256=LyIPEfjBcqWp7UVuJ-b7cc8oiFxrQqyL8RJ7g6c17Qs,838
66
- crackerjack/__main__.py,sha256=GP5GBbWK5lDTQanCHKVEyS3hDWvshwlrhWagzQlRuD4,6560
67
- crackerjack/crackerjack.py,sha256=jlepiHgMkvTyl83O8HUqC4mCsQbZIejerOHNQD15JcU,35854
68
- crackerjack/errors.py,sha256=xvXO3bk0VrxQz0lysIuVOju6SxxDgf04Wne1BedLFB8,4061
69
- crackerjack/interactive.py,sha256=ierA2jeUsigTJhKOcJJsjhBZm2WwsQpvow3Y5ppF7IE,16151
65
+ crackerjack/__init__.py,sha256=8tBSPAru_YDuPpjz05cL7pNbZjYFoRT_agGd_FWa3gY,839
66
+ crackerjack/__main__.py,sha256=AknITUlFjq3YUK9s2xeL62dM0GN82JBQyDkPzQ_hCUg,6561
67
+ crackerjack/crackerjack.py,sha256=Vzt-oT989OJcw0He40BnnTUlmV8-siiZy5-a5fRjL1E,40046
68
+ crackerjack/errors.py,sha256=QEPtVuMtKtQHuawgr1ToMaN1KbUg5h9-4mS33YB5Znk,4062
69
+ crackerjack/interactive.py,sha256=y5QbyR2Wp8WkC_iC89ZqETm-wjAN9X5DK1L3yetpjN4,16153
70
70
  crackerjack/py313.py,sha256=buYE7LO11Q64ffowEhTZRFQoAGj_8sg3DTlZuv8M9eo,5890
71
- crackerjack/pyproject.toml,sha256=OIoNmIktC1abFPgePb95Zg9BDTGfRxXftK-KccWgalc,4988
72
- crackerjack-0.21.7.dist-info/RECORD,,
71
+ crackerjack/pyproject.toml,sha256=CT9hhv56Ax8h1vljzxcEAn4s2wlQh1oZ0Iq3kiA6-m0,4988
72
+ crackerjack-0.21.8.dist-info/RECORD,,