crackerjack 0.15.8__py3-none-any.whl → 0.15.9__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/.gitignore +0 -2
- crackerjack/.ruff_cache/0.11.7/10386934055395314831 +0 -0
- crackerjack/.ruff_cache/0.11.7/3557596832929915217 +0 -0
- crackerjack/__main__.py +14 -0
- crackerjack/crackerjack.py +200 -15
- crackerjack/pyproject.toml +4 -2
- {crackerjack-0.15.8.dist-info → crackerjack-0.15.9.dist-info}/METADATA +2 -1
- {crackerjack-0.15.8.dist-info → crackerjack-0.15.9.dist-info}/RECORD +11 -10
- {crackerjack-0.15.8.dist-info → crackerjack-0.15.9.dist-info}/WHEEL +0 -0
- {crackerjack-0.15.8.dist-info → crackerjack-0.15.9.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.15.8.dist-info → crackerjack-0.15.9.dist-info}/licenses/LICENSE +0 -0
crackerjack/.gitignore
CHANGED
Binary file
|
Binary file
|
crackerjack/__main__.py
CHANGED
@@ -32,6 +32,7 @@ class Options(BaseModel):
|
|
32
32
|
clean: bool = False
|
33
33
|
test: bool = False
|
34
34
|
all: BumpOption | None = None
|
35
|
+
ai_agent: bool = False
|
35
36
|
|
36
37
|
@classmethod
|
37
38
|
@field_validator("publish", "bump", mode="before")
|
@@ -88,6 +89,12 @@ cli_options = {
|
|
88
89
|
help="Run with `-x -t -p <micro|minor|major> -c` development options).",
|
89
90
|
case_sensitive=False,
|
90
91
|
),
|
92
|
+
"ai_agent": typer.Option(
|
93
|
+
False,
|
94
|
+
"--ai-agent",
|
95
|
+
help="Enable AI agent mode with structured output.",
|
96
|
+
hidden=True,
|
97
|
+
),
|
91
98
|
}
|
92
99
|
|
93
100
|
|
@@ -104,6 +111,7 @@ def main(
|
|
104
111
|
bump: BumpOption | None = cli_options["bump"],
|
105
112
|
clean: bool = cli_options["clean"],
|
106
113
|
test: bool = cli_options["test"],
|
114
|
+
ai_agent: bool = cli_options["ai_agent"],
|
107
115
|
) -> None:
|
108
116
|
options = Options(
|
109
117
|
commit=commit,
|
@@ -117,8 +125,14 @@ def main(
|
|
117
125
|
clean=clean,
|
118
126
|
test=test,
|
119
127
|
all=all,
|
128
|
+
ai_agent=ai_agent,
|
120
129
|
)
|
121
130
|
|
131
|
+
if ai_agent:
|
132
|
+
import os
|
133
|
+
|
134
|
+
os.environ["AI_AGENT"] = "1"
|
135
|
+
|
122
136
|
runner = create_crackerjack_runner(console=console)
|
123
137
|
runner.process(options)
|
124
138
|
|
crackerjack/crackerjack.py
CHANGED
@@ -1,7 +1,9 @@
|
|
1
1
|
import io
|
2
|
+
import os
|
2
3
|
import platform
|
3
4
|
import re
|
4
5
|
import subprocess
|
6
|
+
import time
|
5
7
|
import tokenize
|
6
8
|
import typing as t
|
7
9
|
from contextlib import suppress
|
@@ -40,6 +42,7 @@ class OptionsProtocol(t.Protocol):
|
|
40
42
|
publish: t.Any | None
|
41
43
|
bump: t.Any | None
|
42
44
|
all: t.Any | None
|
45
|
+
ai_agent: bool = False
|
43
46
|
|
44
47
|
|
45
48
|
@dataclass
|
@@ -53,7 +56,7 @@ class CodeCleaner:
|
|
53
56
|
if not str(file_path.parent).startswith("__"):
|
54
57
|
self.clean_file(file_path)
|
55
58
|
if pkg_dir.parent.joinpath("__pycache__").exists():
|
56
|
-
pkg_dir.parent.joinpath("__pycache__").
|
59
|
+
pkg_dir.parent.joinpath("__pycache__").unlink()
|
57
60
|
|
58
61
|
def clean_file(self, file_path: Path) -> None:
|
59
62
|
try:
|
@@ -486,22 +489,161 @@ class Crackerjack:
|
|
486
489
|
self.console.print("\nCleaning tests directory...\n")
|
487
490
|
self.code_cleaner.clean_files(tests_dir)
|
488
491
|
|
489
|
-
def
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
492
|
+
def _prepare_pytest_command(self, options: OptionsProtocol) -> list[str]:
|
493
|
+
"""Prepare the pytest command with appropriate options."""
|
494
|
+
test = ["pytest"]
|
495
|
+
if options.verbose:
|
496
|
+
test.append("-v")
|
497
|
+
|
498
|
+
# Add optimized options for all packages to prevent hanging
|
499
|
+
test.extend(
|
500
|
+
[
|
501
|
+
"--no-cov", # Disable coverage which can cause hanging
|
502
|
+
"--capture=fd", # Capture stdout/stderr at file descriptor level
|
503
|
+
"--tb=short", # Shorter traceback format
|
504
|
+
"--no-header", # Reduce output noise
|
505
|
+
"--disable-warnings", # Disable warning capture
|
506
|
+
"--durations=0", # Show slowest tests to identify potential hanging tests
|
507
|
+
"--timeout=300", # 5-minute timeout for tests
|
508
|
+
]
|
509
|
+
)
|
510
|
+
return test
|
511
|
+
|
512
|
+
def _setup_test_environment(self) -> None:
|
513
|
+
"""Set environment variables for test execution."""
|
514
|
+
# Set environment variables to improve asyncio behavior
|
515
|
+
os.environ["PYTHONASYNCIO_DEBUG"] = "0" # Disable asyncio debug mode
|
516
|
+
os.environ["RUNNING_UNDER_CRACKERJACK"] = "1" # Signal to conftest.py
|
517
|
+
|
518
|
+
# Set asyncio mode to strict to help prevent hanging
|
519
|
+
if "PYTEST_ASYNCIO_MODE" not in os.environ:
|
520
|
+
os.environ["PYTEST_ASYNCIO_MODE"] = "strict"
|
521
|
+
|
522
|
+
def _run_pytest_process(
|
523
|
+
self, test_command: list[str]
|
524
|
+
) -> subprocess.CompletedProcess[str]:
|
525
|
+
"""Run pytest as a subprocess with timeout handling and output capture."""
|
526
|
+
try:
|
527
|
+
# Use a timeout to ensure the process doesn't hang indefinitely
|
528
|
+
process = subprocess.Popen(
|
529
|
+
test_command,
|
530
|
+
stdout=subprocess.PIPE,
|
531
|
+
stderr=subprocess.PIPE,
|
532
|
+
text=True,
|
533
|
+
bufsize=1,
|
534
|
+
universal_newlines=True,
|
535
|
+
)
|
536
|
+
|
537
|
+
# Set a timeout (5 minutes)
|
538
|
+
timeout = 300
|
539
|
+
start_time = time.time()
|
540
|
+
|
541
|
+
stdout_data = []
|
542
|
+
stderr_data = []
|
543
|
+
|
544
|
+
# Read output while process is running, with timeout
|
545
|
+
while process.poll() is None:
|
546
|
+
if time.time() - start_time > timeout:
|
547
|
+
self.console.print(
|
548
|
+
"[red]Test execution timed out after 5 minutes. Terminating...[/red]"
|
549
|
+
)
|
550
|
+
process.terminate()
|
551
|
+
try:
|
552
|
+
process.wait(timeout=5)
|
553
|
+
except subprocess.TimeoutExpired:
|
554
|
+
process.kill()
|
555
|
+
break
|
556
|
+
|
557
|
+
# Read output without blocking
|
558
|
+
if process.stdout:
|
559
|
+
line = process.stdout.readline()
|
560
|
+
if line:
|
561
|
+
stdout_data.append(line)
|
562
|
+
self.console.print(line, end="")
|
563
|
+
|
564
|
+
if process.stderr:
|
565
|
+
line = process.stderr.readline()
|
566
|
+
if line:
|
567
|
+
stderr_data.append(line)
|
568
|
+
self.console.print(f"[red]{line}[/red]", end="")
|
569
|
+
|
570
|
+
time.sleep(0.1)
|
571
|
+
|
572
|
+
# Get any remaining output
|
573
|
+
if process.stdout:
|
574
|
+
for line in process.stdout:
|
575
|
+
stdout_data.append(line)
|
576
|
+
self.console.print(line, end="")
|
577
|
+
|
578
|
+
if process.stderr:
|
579
|
+
for line in process.stderr:
|
580
|
+
stderr_data.append(line)
|
581
|
+
self.console.print(f"[red]{line}[/red]", end="")
|
582
|
+
|
583
|
+
returncode = process.returncode or 0
|
584
|
+
stdout = "".join(stdout_data)
|
585
|
+
stderr = "".join(stderr_data)
|
586
|
+
|
587
|
+
# Create a CompletedProcess object to match the expected interface
|
588
|
+
return subprocess.CompletedProcess(
|
589
|
+
args=test_command, returncode=returncode, stdout=stdout, stderr=stderr
|
590
|
+
)
|
591
|
+
|
592
|
+
except Exception as e:
|
593
|
+
self.console.print(f"[red]Error running tests: {e}[/red]")
|
594
|
+
return subprocess.CompletedProcess(test_command, 1, "", str(e))
|
595
|
+
|
596
|
+
def _report_test_results(
|
597
|
+
self, result: subprocess.CompletedProcess[str], ai_agent: str
|
598
|
+
) -> None:
|
599
|
+
"""Report test results and handle AI agent output if needed."""
|
600
|
+
if result.returncode > 0:
|
601
|
+
if result.stderr:
|
602
|
+
self.console.print(result.stderr)
|
603
|
+
|
604
|
+
if ai_agent:
|
605
|
+
# Use structured output for AI agents
|
606
|
+
self.console.print(
|
607
|
+
'[json]{"status": "failed", "action": "tests", "returncode": '
|
608
|
+
+ str(result.returncode)
|
609
|
+
+ "}[/json]"
|
610
|
+
)
|
611
|
+
else:
|
501
612
|
self.console.print("\n\n❌ Tests failed. Please fix errors.\n")
|
502
|
-
|
613
|
+
raise SystemExit(1)
|
614
|
+
|
615
|
+
if ai_agent:
|
616
|
+
# Use structured output for AI agents
|
617
|
+
self.console.print('[json]{"status": "success", "action": "tests"}[/json]')
|
618
|
+
else:
|
503
619
|
self.console.print("\n\n✅ Tests passed successfully!\n")
|
504
620
|
|
621
|
+
def _run_tests(self, options: OptionsProtocol) -> None:
|
622
|
+
"""Run tests if the test option is enabled."""
|
623
|
+
if options.test:
|
624
|
+
# Check if we're being called by an AI agent
|
625
|
+
ai_agent = os.environ.get("AI_AGENT", "")
|
626
|
+
|
627
|
+
if ai_agent:
|
628
|
+
# Use structured output for AI agents
|
629
|
+
self.console.print(
|
630
|
+
'[json]{"status": "running", "action": "tests"}[/json]'
|
631
|
+
)
|
632
|
+
else:
|
633
|
+
self.console.print("\n\nRunning tests...\n")
|
634
|
+
|
635
|
+
# Prepare the test command
|
636
|
+
test_command = self._prepare_pytest_command(options)
|
637
|
+
|
638
|
+
# Set up the test environment
|
639
|
+
self._setup_test_environment()
|
640
|
+
|
641
|
+
# Run the tests
|
642
|
+
result = self._run_pytest_process(test_command)
|
643
|
+
|
644
|
+
# Report the results
|
645
|
+
self._report_test_results(result, ai_agent)
|
646
|
+
|
505
647
|
def _bump_version(self, options: OptionsProtocol) -> None:
|
506
648
|
for option in (options.publish, options.bump):
|
507
649
|
if option:
|
@@ -546,22 +688,65 @@ class Crackerjack:
|
|
546
688
|
return execute(cmd, **kwargs)
|
547
689
|
|
548
690
|
def process(self, options: OptionsProtocol) -> None:
|
691
|
+
# Track actions performed for AI agent output
|
692
|
+
actions_performed = []
|
693
|
+
|
549
694
|
if options.all:
|
550
695
|
options.clean = True
|
551
696
|
options.test = True
|
552
697
|
options.publish = options.all
|
553
698
|
options.commit = True
|
699
|
+
|
554
700
|
self._setup_package()
|
701
|
+
actions_performed.append("setup_package")
|
702
|
+
|
555
703
|
self._update_project(options)
|
704
|
+
actions_performed.append("update_project")
|
705
|
+
|
556
706
|
self._update_precommit(options)
|
707
|
+
if options.update_precommit:
|
708
|
+
actions_performed.append("update_precommit")
|
709
|
+
|
557
710
|
self._run_interactive_hooks(options)
|
711
|
+
if options.interactive:
|
712
|
+
actions_performed.append("run_interactive_hooks")
|
713
|
+
|
558
714
|
self._clean_project(options)
|
715
|
+
if options.clean:
|
716
|
+
actions_performed.append("clean_project")
|
717
|
+
|
559
718
|
self.project_manager.run_pre_commit()
|
719
|
+
actions_performed.append("run_pre_commit")
|
720
|
+
|
560
721
|
self._run_tests(options)
|
722
|
+
if options.test:
|
723
|
+
actions_performed.append("run_tests")
|
724
|
+
|
561
725
|
self._bump_version(options)
|
726
|
+
if options.bump or options.publish:
|
727
|
+
actions_performed.append("bump_version")
|
728
|
+
|
562
729
|
self._publish_project(options)
|
730
|
+
if options.publish:
|
731
|
+
actions_performed.append("publish_project")
|
732
|
+
|
563
733
|
self._commit_and_push(options)
|
564
|
-
|
734
|
+
if options.commit:
|
735
|
+
actions_performed.append("commit_and_push")
|
736
|
+
|
737
|
+
# Check if we're being called by an AI agent
|
738
|
+
if getattr(options, "ai_agent", False):
|
739
|
+
# Use structured output for AI agents
|
740
|
+
import json
|
741
|
+
|
742
|
+
result = {
|
743
|
+
"status": "complete",
|
744
|
+
"package": self.pkg_name,
|
745
|
+
"actions": actions_performed,
|
746
|
+
}
|
747
|
+
self.console.print(f"[json]{json.dumps(result)}[/json]")
|
748
|
+
else:
|
749
|
+
self.console.print("\n🍺 Crackerjack complete!\n")
|
565
750
|
|
566
751
|
|
567
752
|
def create_crackerjack_runner(
|
crackerjack/pyproject.toml
CHANGED
@@ -72,7 +72,7 @@ no-lines-before = [
|
|
72
72
|
]
|
73
73
|
|
74
74
|
[tool.ruff.lint.mccabe]
|
75
|
-
max-complexity =
|
75
|
+
max-complexity = 13
|
76
76
|
|
77
77
|
[tool.ruff.lint.pydocstyle]
|
78
78
|
convention = "google"
|
@@ -101,6 +101,7 @@ exclude-deps = [
|
|
101
101
|
"uv",
|
102
102
|
"tomli-w",
|
103
103
|
"google-crc32c",
|
104
|
+
"pytest-timeout",
|
104
105
|
]
|
105
106
|
|
106
107
|
[tool.refurb]
|
@@ -149,7 +150,7 @@ pythonPlatform = "Darwin"
|
|
149
150
|
|
150
151
|
[project]
|
151
152
|
name = "crackerjack"
|
152
|
-
version = "0.15.
|
153
|
+
version = "0.15.8"
|
153
154
|
description = "Default template for PDM package"
|
154
155
|
requires-python = ">=3.13"
|
155
156
|
readme = "README.md"
|
@@ -188,6 +189,7 @@ dependencies = [
|
|
188
189
|
"pytest-asyncio>=0.26.0",
|
189
190
|
"rich>=14.0.0",
|
190
191
|
"typer>=0.15.2",
|
192
|
+
"pytest-timeout>=2.3.1",
|
191
193
|
]
|
192
194
|
authors = [
|
193
195
|
{ name = "lesleslie", email = "les@wedgwoodwebworks.com" },
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: crackerjack
|
3
|
-
Version: 0.15.
|
3
|
+
Version: 0.15.9
|
4
4
|
Summary: Default template for PDM package
|
5
5
|
Keywords: black,ruff,mypy,creosote,refurb
|
6
6
|
Author-Email: lesleslie <les@wedgwoodwebworks.com>
|
@@ -35,6 +35,7 @@ Requires-Dist: tomli-w>=1.2.0
|
|
35
35
|
Requires-Dist: pytest-asyncio>=0.26.0
|
36
36
|
Requires-Dist: rich>=14.0.0
|
37
37
|
Requires-Dist: typer>=0.15.2
|
38
|
+
Requires-Dist: pytest-timeout>=2.3.1
|
38
39
|
Description-Content-Type: text/markdown
|
39
40
|
|
40
41
|
# Crackerjack: Elevate Your Python Development
|
@@ -1,9 +1,9 @@
|
|
1
|
-
crackerjack-0.15.
|
2
|
-
crackerjack-0.15.
|
3
|
-
crackerjack-0.15.
|
4
|
-
crackerjack-0.15.
|
1
|
+
crackerjack-0.15.9.dist-info/METADATA,sha256=1I_OWQRrzahxxT8f5F8EieXlGDXSuAD0F9JGpMfhbco,12899
|
2
|
+
crackerjack-0.15.9.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
crackerjack-0.15.9.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
4
|
+
crackerjack-0.15.9.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
|
5
5
|
crackerjack/.coverage,sha256=dLzPzp72qZEXohNfxnOAlRwvM9dqF06-HoFqfvXZd1U,53248
|
6
|
-
crackerjack/.gitignore,sha256=
|
6
|
+
crackerjack/.gitignore,sha256=oho3dNx7a7y36_y9AsalCkssU4in0MMsNAANWdc-h1c,153
|
7
7
|
crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
|
8
8
|
crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
|
9
9
|
crackerjack/.pre-commit-config.yaml,sha256=69iH07bdH1eF45gNrJAp3LKQkmxTg38lniLg9U9oVq4,2456
|
@@ -23,7 +23,8 @@ crackerjack/.ruff_cache/0.11.2/4070660268492669020,sha256=FTRTUmvj6nZw_QQBp_WHI-
|
|
23
23
|
crackerjack/.ruff_cache/0.11.3/9818742842212983150,sha256=U-4mT__a-OljovvAJvv5M6X7TCMa3dReLXx3kTNGgwU,224
|
24
24
|
crackerjack/.ruff_cache/0.11.4/9818742842212983150,sha256=QF9j6-3MH_d0pDNotdbF2hlqCL66SxN8OLVKR3PZyZU,224
|
25
25
|
crackerjack/.ruff_cache/0.11.6/3557596832929915217,sha256=yR2iXWDkSHVRw2eTiaCE8Eh34JPRUGc8vE3HYEEBk9k,224
|
26
|
-
crackerjack/.ruff_cache/0.11.7/
|
26
|
+
crackerjack/.ruff_cache/0.11.7/10386934055395314831,sha256=lBNwN5zAgM4OzbkXIOzCczUtfooATrD10htj9ASlFkc,224
|
27
|
+
crackerjack/.ruff_cache/0.11.7/3557596832929915217,sha256=fKlwUbsvT3YIKV6UR-aA_i64lLignWeVfVu-MMmVbU0,207
|
27
28
|
crackerjack/.ruff_cache/0.2.0/10047773857155985907,sha256=j9LNa_RQ4Plor7go1uTYgz17cEENKvZQ-dP6b9MX0ik,248
|
28
29
|
crackerjack/.ruff_cache/0.2.1/8522267973936635051,sha256=u_aPBMibtAp_iYvLwR88GMAECMcIgHezxMyuapmU2P4,248
|
29
30
|
crackerjack/.ruff_cache/0.2.2/18053836298936336950,sha256=Xb_ebP0pVuUfSqPEZKlhQ70so_vqkEfMYpuHQ06iR5U,248
|
@@ -52,7 +53,7 @@ crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=tmr8_vhRD2OxsVuMfbJPdT
|
|
52
53
|
crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsaTvs1uA3_fI216Qq9qCAM,136
|
53
54
|
crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
|
54
55
|
crackerjack/__init__.py,sha256=r9SuEjHUrW99hFWifRk4ofmYPSgf9rblcnzqhdV5bP0,157
|
55
|
-
crackerjack/__main__.py,sha256=
|
56
|
-
crackerjack/crackerjack.py,sha256=
|
57
|
-
crackerjack/pyproject.toml,sha256=
|
58
|
-
crackerjack-0.15.
|
56
|
+
crackerjack/__main__.py,sha256=vgylaqzoqYUjYstAOQLOU-odCtYarb2oAFBQdzYdar0,4037
|
57
|
+
crackerjack/crackerjack.py,sha256=IHe1JDIS3Ng_0VW4SjS0HNaKQrRLEu6KAr0bMOFZrro,29114
|
58
|
+
crackerjack/pyproject.toml,sha256=uUQOPlQd4ULCUAMcurTger5BHxiZZJg3d6Z764HVXTQ,4140
|
59
|
+
crackerjack-0.15.9.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|