crackerjack 0.15.10__py3-none-any.whl → 0.16.1__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/__main__.py CHANGED
@@ -33,6 +33,7 @@ class Options(BaseModel):
33
33
  test: bool = False
34
34
  all: BumpOption | None = None
35
35
  ai_agent: bool = False
36
+ create_pr: bool = False
36
37
 
37
38
  @classmethod
38
39
  @field_validator("publish", "bump", mode="before")
@@ -89,6 +90,12 @@ cli_options = {
89
90
  help="Run with `-x -t -p <micro|minor|major> -c` development options).",
90
91
  case_sensitive=False,
91
92
  ),
93
+ "create_pr": typer.Option(
94
+ False,
95
+ "-r",
96
+ "--pr",
97
+ help="Create a pull request to the upstream repository.",
98
+ ),
92
99
  "ai_agent": typer.Option(
93
100
  False,
94
101
  "--ai-agent",
@@ -111,6 +118,7 @@ def main(
111
118
  bump: BumpOption | None = cli_options["bump"],
112
119
  clean: bool = cli_options["clean"],
113
120
  test: bool = cli_options["test"],
121
+ create_pr: bool = cli_options["create_pr"],
114
122
  ai_agent: bool = cli_options["ai_agent"],
115
123
  ) -> None:
116
124
  options = Options(
@@ -126,6 +134,7 @@ def main(
126
134
  test=test,
127
135
  all=all,
128
136
  ai_agent=ai_agent,
137
+ create_pr=create_pr,
129
138
  )
130
139
 
131
140
  if ai_agent:
@@ -43,6 +43,7 @@ class OptionsProtocol(t.Protocol):
43
43
  bump: t.Any | None
44
44
  all: t.Any | None
45
45
  ai_agent: bool = False
46
+ create_pr: bool = False
46
47
 
47
48
 
48
49
  @dataclass
@@ -490,12 +491,9 @@ class Crackerjack:
490
491
  self.code_cleaner.clean_files(tests_dir)
491
492
 
492
493
  def _prepare_pytest_command(self, options: OptionsProtocol) -> list[str]:
493
- """Prepare the pytest command with appropriate options."""
494
494
  test = ["pytest"]
495
495
  if options.verbose:
496
496
  test.append("-v")
497
-
498
- # Add optimized options for all packages to prevent hanging
499
497
  test.extend(
500
498
  [
501
499
  "--no-cov", # Disable coverage which can cause hanging
@@ -510,21 +508,15 @@ class Crackerjack:
510
508
  return test
511
509
 
512
510
  def _setup_test_environment(self) -> None:
513
- """Set environment variables for test execution."""
514
- # Set environment variables to improve asyncio behavior
515
511
  os.environ["PYTHONASYNCIO_DEBUG"] = "0" # Disable asyncio debug mode
516
512
  os.environ["RUNNING_UNDER_CRACKERJACK"] = "1" # Signal to conftest.py
517
-
518
- # Set asyncio mode to strict to help prevent hanging
519
513
  if "PYTEST_ASYNCIO_MODE" not in os.environ:
520
514
  os.environ["PYTEST_ASYNCIO_MODE"] = "strict"
521
515
 
522
516
  def _run_pytest_process(
523
517
  self, test_command: list[str]
524
518
  ) -> subprocess.CompletedProcess[str]:
525
- """Run pytest as a subprocess with timeout handling and output capture."""
526
519
  try:
527
- # Use a timeout to ensure the process doesn't hang indefinitely
528
520
  process = subprocess.Popen(
529
521
  test_command,
530
522
  stdout=subprocess.PIPE,
@@ -533,15 +525,10 @@ class Crackerjack:
533
525
  bufsize=1,
534
526
  universal_newlines=True,
535
527
  )
536
-
537
- # Set a timeout (5 minutes)
538
528
  timeout = 300
539
529
  start_time = time.time()
540
-
541
530
  stdout_data = []
542
531
  stderr_data = []
543
-
544
- # Read output while process is running, with timeout
545
532
  while process.poll() is None:
546
533
  if time.time() - start_time > timeout:
547
534
  self.console.print(
@@ -553,38 +540,28 @@ class Crackerjack:
553
540
  except subprocess.TimeoutExpired:
554
541
  process.kill()
555
542
  break
556
-
557
- # Read output without blocking
558
543
  if process.stdout:
559
544
  line = process.stdout.readline()
560
545
  if line:
561
546
  stdout_data.append(line)
562
547
  self.console.print(line, end="")
563
-
564
548
  if process.stderr:
565
549
  line = process.stderr.readline()
566
550
  if line:
567
551
  stderr_data.append(line)
568
552
  self.console.print(f"[red]{line}[/red]", end="")
569
-
570
553
  time.sleep(0.1)
571
-
572
- # Get any remaining output
573
554
  if process.stdout:
574
555
  for line in process.stdout:
575
556
  stdout_data.append(line)
576
557
  self.console.print(line, end="")
577
-
578
558
  if process.stderr:
579
559
  for line in process.stderr:
580
560
  stderr_data.append(line)
581
561
  self.console.print(f"[red]{line}[/red]", end="")
582
-
583
562
  returncode = process.returncode or 0
584
563
  stdout = "".join(stdout_data)
585
564
  stderr = "".join(stderr_data)
586
-
587
- # Create a CompletedProcess object to match the expected interface
588
565
  return subprocess.CompletedProcess(
589
566
  args=test_command, returncode=returncode, stdout=stdout, stderr=stderr
590
567
  )
@@ -596,13 +573,10 @@ class Crackerjack:
596
573
  def _report_test_results(
597
574
  self, result: subprocess.CompletedProcess[str], ai_agent: str
598
575
  ) -> None:
599
- """Report test results and handle AI agent output if needed."""
600
576
  if result.returncode > 0:
601
577
  if result.stderr:
602
578
  self.console.print(result.stderr)
603
-
604
579
  if ai_agent:
605
- # Use structured output for AI agents
606
580
  self.console.print(
607
581
  '[json]{"status": "failed", "action": "tests", "returncode": '
608
582
  + str(result.returncode)
@@ -613,35 +587,22 @@ class Crackerjack:
613
587
  raise SystemExit(1)
614
588
 
615
589
  if ai_agent:
616
- # Use structured output for AI agents
617
590
  self.console.print('[json]{"status": "success", "action": "tests"}[/json]')
618
591
  else:
619
592
  self.console.print("\n\n✅ Tests passed successfully!\n")
620
593
 
621
594
  def _run_tests(self, options: OptionsProtocol) -> None:
622
- """Run tests if the test option is enabled."""
623
595
  if options.test:
624
- # Check if we're being called by an AI agent
625
596
  ai_agent = os.environ.get("AI_AGENT", "")
626
-
627
597
  if ai_agent:
628
- # Use structured output for AI agents
629
598
  self.console.print(
630
599
  '[json]{"status": "running", "action": "tests"}[/json]'
631
600
  )
632
601
  else:
633
602
  self.console.print("\n\nRunning tests...\n")
634
-
635
- # Prepare the test command
636
603
  test_command = self._prepare_pytest_command(options)
637
-
638
- # Set up the test environment
639
604
  self._setup_test_environment()
640
-
641
- # Run the tests
642
605
  result = self._run_pytest_process(test_command)
643
-
644
- # Report the results
645
606
  self._report_test_results(result, ai_agent)
646
607
 
647
608
  def _bump_version(self, options: OptionsProtocol) -> None:
@@ -679,6 +640,131 @@ class Crackerjack:
679
640
  )
680
641
  self.execute_command(["git", "push", "origin", "main"])
681
642
 
643
+ def _create_pull_request(self, options: OptionsProtocol) -> None:
644
+ if options.create_pr:
645
+ self.console.print("\nCreating pull request...")
646
+ current_branch = self.execute_command(
647
+ ["git", "branch", "--show-current"], capture_output=True, text=True
648
+ ).stdout.strip()
649
+ remote_url = self.execute_command(
650
+ ["git", "remote", "get-url", "origin"], capture_output=True, text=True
651
+ ).stdout.strip()
652
+ is_github = "github.com" in remote_url
653
+ is_gitlab = "gitlab.com" in remote_url
654
+ if is_github:
655
+ gh_installed = (
656
+ self.execute_command(
657
+ ["which", "gh"], capture_output=True, text=True
658
+ ).returncode
659
+ == 0
660
+ )
661
+ if not gh_installed:
662
+ self.console.print(
663
+ "\n[red]GitHub CLI (gh) is not installed. Please install it first:[/red]\n"
664
+ " brew install gh # for macOS\n"
665
+ " or visit https://cli.github.com/ for other installation methods"
666
+ )
667
+ return
668
+ auth_status = self.execute_command(
669
+ ["gh", "auth", "status"], capture_output=True, text=True
670
+ ).returncode
671
+ if auth_status != 0:
672
+ self.console.print(
673
+ "\n[red]You need to authenticate with GitHub first. Run:[/red]\n"
674
+ " gh auth login"
675
+ )
676
+ return
677
+ pr_title = input("\nEnter a title for your pull request: ")
678
+ self.console.print(
679
+ "Enter a description for your pull request (press Ctrl+D when done):"
680
+ )
681
+ pr_description = ""
682
+ with suppress(EOFError):
683
+ pr_description = "".join(iter(input, ""))
684
+ self.console.print("Creating pull request to GitHub repository...")
685
+ result = self.execute_command(
686
+ [
687
+ "gh",
688
+ "pr",
689
+ "create",
690
+ "--title",
691
+ pr_title,
692
+ "--body",
693
+ pr_description,
694
+ ],
695
+ capture_output=True,
696
+ text=True,
697
+ )
698
+ if result.returncode == 0:
699
+ self.console.print(
700
+ f"\n[green]Pull request created successfully![/green]\n{result.stdout}"
701
+ )
702
+ else:
703
+ self.console.print(
704
+ f"\n[red]Failed to create pull request:[/red]\n{result.stderr}"
705
+ )
706
+ elif is_gitlab:
707
+ glab_installed = (
708
+ self.execute_command(
709
+ ["which", "glab"], capture_output=True, text=True
710
+ ).returncode
711
+ == 0
712
+ )
713
+ if not glab_installed:
714
+ self.console.print(
715
+ "\n[red]GitLab CLI (glab) is not installed. Please install it first:[/red]\n"
716
+ " brew install glab # for macOS\n"
717
+ " or visit https://gitlab.com/gitlab-org/cli for other installation methods"
718
+ )
719
+ return
720
+ auth_status = self.execute_command(
721
+ ["glab", "auth", "status"], capture_output=True, text=True
722
+ ).returncode
723
+ if auth_status != 0:
724
+ self.console.print(
725
+ "\n[red]You need to authenticate with GitLab first. Run:[/red]\n"
726
+ " glab auth login"
727
+ )
728
+ return
729
+ mr_title = input("\nEnter a title for your merge request: ")
730
+ self.console.print(
731
+ "Enter a description for your merge request (press Ctrl+D when done):"
732
+ )
733
+ mr_description = ""
734
+ with suppress(EOFError):
735
+ mr_description = "".join(iter(input, ""))
736
+ self.console.print("Creating merge request to GitLab repository...")
737
+ result = self.execute_command(
738
+ [
739
+ "glab",
740
+ "mr",
741
+ "create",
742
+ "--title",
743
+ mr_title,
744
+ "--description",
745
+ mr_description,
746
+ "--source-branch",
747
+ current_branch,
748
+ "--target-branch",
749
+ "main",
750
+ ],
751
+ capture_output=True,
752
+ text=True,
753
+ )
754
+ if result.returncode == 0:
755
+ self.console.print(
756
+ f"\n[green]Merge request created successfully![/green]\n{result.stdout}"
757
+ )
758
+ else:
759
+ self.console.print(
760
+ f"\n[red]Failed to create merge request:[/red]\n{result.stderr}"
761
+ )
762
+ else:
763
+ self.console.print(
764
+ f"\n[red]Unsupported git hosting service: {remote_url}[/red]\n"
765
+ "This command currently supports GitHub and GitLab."
766
+ )
767
+
682
768
  def execute_command(
683
769
  self, cmd: list[str], **kwargs: t.Any
684
770
  ) -> subprocess.CompletedProcess[str]:
@@ -688,9 +774,7 @@ class Crackerjack:
688
774
  return execute(cmd, **kwargs)
689
775
 
690
776
  def process(self, options: OptionsProtocol) -> None:
691
- # Track actions performed for AI agent output
692
777
  actions_performed = []
693
-
694
778
  if options.all:
695
779
  options.clean = True
696
780
  options.test = True
@@ -734,9 +818,11 @@ class Crackerjack:
734
818
  if options.commit:
735
819
  actions_performed.append("commit_and_push")
736
820
 
737
- # Check if we're being called by an AI agent
821
+ self._create_pull_request(options)
822
+ if options.create_pr:
823
+ actions_performed.append("create_pull_request")
824
+
738
825
  if getattr(options, "ai_agent", False):
739
- # Use structured output for AI agents
740
826
  import json
741
827
 
742
828
  result = {
@@ -1,5 +1,5 @@
1
1
  [tool.pytest.ini_options]
2
- addopts = "--cov=crackerjack"
2
+ addopts = "--cov=crackerjack --cov-report=term"
3
3
  asyncio_default_fixture_loop_scope = "function"
4
4
  python_files = ["test_*.py", "*_test.py"]
5
5
  asyncio_mode = "auto"
@@ -8,8 +8,10 @@ python_classes = ["Test*"]
8
8
  python_functions = ["test_*"]
9
9
 
10
10
  [tool.coverage.run]
11
- branch = true
11
+ branch = false
12
12
  source = ["crackerjack"]
13
+ data_file = ".coverage"
14
+ parallel = false
13
15
  omit = [
14
16
  "*/tests/*",
15
17
  "*/site-packages/*",
@@ -150,7 +152,7 @@ pythonPlatform = "Darwin"
150
152
 
151
153
  [project]
152
154
  name = "crackerjack"
153
- version = "0.15.9"
155
+ version = "0.16.0"
154
156
  description = "Default template for PDM package"
155
157
  requires-python = ">=3.13"
156
158
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: crackerjack
3
- Version: 0.15.10
3
+ Version: 0.16.1
4
4
  Summary: Default template for PDM package
5
5
  Keywords: black,ruff,mypy,creosote,refurb
6
6
  Author-Email: lesleslie <les@wedgwoodwebworks.com>
@@ -103,6 +103,7 @@ Crackerjack provides:
103
103
  - **Easy Version Bumping:** Provides commands to bump the project version (micro, minor, or major).
104
104
  - **Simplified Publishing:** Automates publishing to PyPI via PDM.
105
105
  - **Commit and Push:** Commits and pushes your changes.
106
+ - **Pull Request Creation:** Creates pull requests to upstream repositories on GitHub or GitLab.
106
107
 
107
108
  ## Pre-commit Hooks
108
109
 
@@ -188,6 +189,7 @@ class MyOptions:
188
189
  self.publish = None
189
190
  self.bump = "micro"
190
191
  self.all = None
192
+ self.create_pr = False
191
193
 
192
194
  # Create a Crackerjack runner with custom settings
193
195
  runner = create_crackerjack_runner(
@@ -211,6 +213,7 @@ runner.process(MyOptions())
211
213
  - `-v`, `--verbose`: Enable verbose output.
212
214
  - `-p`, `--publish <micro|minor|major>`: Bump the project version and publish to PyPI using PDM.
213
215
  - `-b`, `--bump <micro|minor|major>`: Bump the project version without publishing.
216
+ - `-r`, `--pr`: Create a pull request to the upstream repository.
214
217
  - `-x`, `--clean`: Clean code by removing docstrings, line comments, and extra whitespace.
215
218
  - `-t`, `--test`: Run tests using `pytest`.
216
219
  - `-a`, `--all`: Run with `-x -t -p <micro|minor|major> -c` development options.
@@ -248,6 +251,11 @@ runner.process(MyOptions())
248
251
  python -m crackerjack -u
249
252
  ```
250
253
 
254
+ - **Create a pull request to the upstream repository:**
255
+ ```
256
+ python -m crackerjack -r
257
+ ```
258
+
251
259
  - **Get help:**
252
260
  ```
253
261
  python -m crackerjack --help
@@ -1,8 +1,7 @@
1
- crackerjack-0.15.10.dist-info/METADATA,sha256=InLwW916jxaIvZ3PzWakEG1yXKNHrAN1pXy0b-6jcHw,12899
2
- crackerjack-0.15.10.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- crackerjack-0.15.10.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
- crackerjack-0.15.10.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
5
- crackerjack/.coverage,sha256=dLzPzp72qZEXohNfxnOAlRwvM9dqF06-HoFqfvXZd1U,53248
1
+ crackerjack-0.16.1.dist-info/METADATA,sha256=LcPI6pQw65EjvvvfYskS2uXl5dYBwr9-gKrnDv1E_7A,13198
2
+ crackerjack-0.16.1.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ crackerjack-0.16.1.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
4
+ crackerjack-0.16.1.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
6
5
  crackerjack/.gitignore,sha256=oho3dNx7a7y36_y9AsalCkssU4in0MMsNAANWdc-h1c,153
7
6
  crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
8
7
  crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
@@ -25,7 +24,7 @@ crackerjack/.ruff_cache/0.11.4/9818742842212983150,sha256=QF9j6-3MH_d0pDNotdbF2h
25
24
  crackerjack/.ruff_cache/0.11.6/3557596832929915217,sha256=yR2iXWDkSHVRw2eTiaCE8Eh34JPRUGc8vE3HYEEBk9k,224
26
25
  crackerjack/.ruff_cache/0.11.7/10386934055395314831,sha256=lBNwN5zAgM4OzbkXIOzCczUtfooATrD10htj9ASlFkc,224
27
26
  crackerjack/.ruff_cache/0.11.7/3557596832929915217,sha256=fKlwUbsvT3YIKV6UR-aA_i64lLignWeVfVu-MMmVbU0,207
28
- crackerjack/.ruff_cache/0.11.8/530407680854991027,sha256=3SpPDsyKWKQbLDpgEY5rAdUjW_hF4HuOe7ZCrAc1hi0,224
27
+ crackerjack/.ruff_cache/0.11.8/530407680854991027,sha256=YcYcc-evwInMm79oLuKBlIm6Ppm5dvfCMyEU6FChvdM,224
29
28
  crackerjack/.ruff_cache/0.2.0/10047773857155985907,sha256=j9LNa_RQ4Plor7go1uTYgz17cEENKvZQ-dP6b9MX0ik,248
30
29
  crackerjack/.ruff_cache/0.2.1/8522267973936635051,sha256=u_aPBMibtAp_iYvLwR88GMAECMcIgHezxMyuapmU2P4,248
31
30
  crackerjack/.ruff_cache/0.2.2/18053836298936336950,sha256=Xb_ebP0pVuUfSqPEZKlhQ70so_vqkEfMYpuHQ06iR5U,248
@@ -54,7 +53,7 @@ crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=tmr8_vhRD2OxsVuMfbJPdT
54
53
  crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsaTvs1uA3_fI216Qq9qCAM,136
55
54
  crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
56
55
  crackerjack/__init__.py,sha256=r9SuEjHUrW99hFWifRk4ofmYPSgf9rblcnzqhdV5bP0,157
57
- crackerjack/__main__.py,sha256=vgylaqzoqYUjYstAOQLOU-odCtYarb2oAFBQdzYdar0,4037
58
- crackerjack/crackerjack.py,sha256=IHe1JDIS3Ng_0VW4SjS0HNaKQrRLEu6KAr0bMOFZrro,29114
59
- crackerjack/pyproject.toml,sha256=V0Ru1LG-E5mRnNFMAJCkYOA8J_xvsLvR0NQguse4gc0,4139
60
- crackerjack-0.15.10.dist-info/RECORD,,
56
+ crackerjack/__main__.py,sha256=O2BZxkwH8FKIhQJyWEaLAxvFT2pRGSyr4J7nXpDKV9U,4291
57
+ crackerjack/crackerjack.py,sha256=opucnTRRlBfV4jU5qZcHS7p4R9yuS3Tl7mcAD7JRNO4,33439
58
+ crackerjack/pyproject.toml,sha256=s_hdetdy4oH4zIQ8VdZVr8yUPWi-WIIOKEyhQuHSwsI,4199
59
+ crackerjack-0.16.1.dist-info/RECORD,,
crackerjack/.coverage DELETED
Binary file