crackerjack 0.20.0__py3-none-any.whl → 0.20.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/.pre-commit-config.yaml +2 -2
- crackerjack/.ruff_cache/0.11.13/1867267426380906393 +0 -0
- crackerjack/__main__.py +16 -0
- crackerjack/crackerjack.py +182 -29
- crackerjack/pyproject.toml +2 -2
- {crackerjack-0.20.0.dist-info → crackerjack-0.20.1.dist-info}/METADATA +2 -2
- {crackerjack-0.20.0.dist-info → crackerjack-0.20.1.dist-info}/RECORD +10 -9
- {crackerjack-0.20.0.dist-info → crackerjack-0.20.1.dist-info}/WHEEL +0 -0
- {crackerjack-0.20.0.dist-info → crackerjack-0.20.1.dist-info}/entry_points.txt +0 -0
- {crackerjack-0.20.0.dist-info → crackerjack-0.20.1.dist-info}/licenses/LICENSE +0 -0
@@ -34,7 +34,7 @@ repos:
|
|
34
34
|
- keyring
|
35
35
|
|
36
36
|
- repo: https://github.com/astral-sh/uv-pre-commit
|
37
|
-
rev: 0.7.
|
37
|
+
rev: 0.7.12
|
38
38
|
hooks:
|
39
39
|
- id: uv-lock
|
40
40
|
files: ^pyproject\.toml$
|
@@ -55,7 +55,7 @@ repos:
|
|
55
55
|
- tomli
|
56
56
|
|
57
57
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
58
|
-
rev: v0.11.
|
58
|
+
rev: v0.11.13
|
59
59
|
hooks:
|
60
60
|
- id: ruff-check
|
61
61
|
- id: ruff-format
|
Binary file
|
crackerjack/__main__.py
CHANGED
@@ -34,6 +34,8 @@ class Options(BaseModel):
|
|
34
34
|
benchmark: bool = False
|
35
35
|
benchmark_regression: bool = False
|
36
36
|
benchmark_regression_threshold: float = 5.0
|
37
|
+
test_workers: int = 0
|
38
|
+
test_timeout: int = 0
|
37
39
|
all: BumpOption | None = None
|
38
40
|
ai_agent: bool = False
|
39
41
|
create_pr: bool = False
|
@@ -103,6 +105,16 @@ cli_options = {
|
|
103
105
|
"--benchmark-regression-threshold",
|
104
106
|
help="Maximum allowed performance regression percentage (default: 5.0%).",
|
105
107
|
),
|
108
|
+
"test_workers": typer.Option(
|
109
|
+
0,
|
110
|
+
"--test-workers",
|
111
|
+
help="Number of parallel workers for running tests (0 = auto-detect, 1 = disable parallelization).",
|
112
|
+
),
|
113
|
+
"test_timeout": typer.Option(
|
114
|
+
0,
|
115
|
+
"--test-timeout",
|
116
|
+
help="Timeout in seconds for individual tests (0 = use default based on project size).",
|
117
|
+
),
|
106
118
|
"skip_hooks": typer.Option(
|
107
119
|
False,
|
108
120
|
"-s",
|
@@ -154,6 +166,8 @@ def main(
|
|
154
166
|
benchmark_regression_threshold: float = cli_options[
|
155
167
|
"benchmark_regression_threshold"
|
156
168
|
],
|
169
|
+
test_workers: int = cli_options["test_workers"],
|
170
|
+
test_timeout: int = cli_options["test_timeout"],
|
157
171
|
skip_hooks: bool = cli_options["skip_hooks"],
|
158
172
|
create_pr: bool = cli_options["create_pr"],
|
159
173
|
rich_ui: bool = cli_options["rich_ui"],
|
@@ -173,6 +187,8 @@ def main(
|
|
173
187
|
benchmark=benchmark,
|
174
188
|
benchmark_regression=benchmark_regression,
|
175
189
|
benchmark_regression_threshold=benchmark_regression_threshold,
|
190
|
+
test_workers=test_workers,
|
191
|
+
test_timeout=test_timeout,
|
176
192
|
skip_hooks=skip_hooks,
|
177
193
|
all=all,
|
178
194
|
ai_agent=ai_agent,
|
crackerjack/crackerjack.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
import io
|
2
2
|
import os
|
3
3
|
import platform
|
4
|
+
import queue
|
4
5
|
import re
|
5
6
|
import subprocess
|
7
|
+
import threading
|
6
8
|
import time
|
7
9
|
import tokenize
|
8
10
|
import typing as t
|
@@ -42,6 +44,8 @@ class OptionsProtocol(t.Protocol):
|
|
42
44
|
benchmark: bool
|
43
45
|
benchmark_regression: bool
|
44
46
|
benchmark_regression_threshold: float
|
47
|
+
test_workers: int = 0
|
48
|
+
test_timeout: int = 0
|
45
49
|
publish: t.Any | None
|
46
50
|
bump: t.Any | None
|
47
51
|
all: t.Any | None
|
@@ -683,6 +687,22 @@ class Crackerjack:
|
|
683
687
|
if options.verbose:
|
684
688
|
test.append("-v")
|
685
689
|
|
690
|
+
# Detect project size to adjust timeouts and parallelization
|
691
|
+
project_size = self._detect_project_size()
|
692
|
+
|
693
|
+
# User can override the timeout, otherwise use project size to determine
|
694
|
+
if options.test_timeout > 0:
|
695
|
+
test_timeout = options.test_timeout
|
696
|
+
else:
|
697
|
+
# Use a longer timeout for larger projects
|
698
|
+
test_timeout = (
|
699
|
+
300
|
700
|
+
if project_size == "large"
|
701
|
+
else 120
|
702
|
+
if project_size == "medium"
|
703
|
+
else 60
|
704
|
+
)
|
705
|
+
|
686
706
|
test.extend(
|
687
707
|
[
|
688
708
|
"--capture=fd", # Capture stdout/stderr at file descriptor level
|
@@ -690,7 +710,7 @@ class Crackerjack:
|
|
690
710
|
"--no-header", # Reduce output noise
|
691
711
|
"--disable-warnings", # Disable warning capture
|
692
712
|
"--durations=0", # Show slowest tests to identify potential hanging tests
|
693
|
-
"--timeout=
|
713
|
+
f"--timeout={test_timeout}", # Dynamic timeout based on project size or user override
|
694
714
|
]
|
695
715
|
)
|
696
716
|
|
@@ -711,11 +731,60 @@ class Crackerjack:
|
|
711
731
|
]
|
712
732
|
)
|
713
733
|
else:
|
714
|
-
#
|
715
|
-
|
734
|
+
# Use user-specified number of workers if provided
|
735
|
+
if options.test_workers > 0:
|
736
|
+
# User explicitly set number of workers
|
737
|
+
if options.test_workers == 1:
|
738
|
+
# Single worker means no parallelism, just use normal pytest mode
|
739
|
+
test.append("-vs")
|
740
|
+
else:
|
741
|
+
# Use specified number of workers
|
742
|
+
test.extend(["-xvs", "-n", str(options.test_workers)])
|
743
|
+
else:
|
744
|
+
# Auto-detect based on project size
|
745
|
+
if project_size == "large":
|
746
|
+
# For large projects, use a fixed number of workers to avoid overwhelming the system
|
747
|
+
test.extend(
|
748
|
+
["-xvs", "-n", "2"]
|
749
|
+
) # Only 2 parallel processes for large projects
|
750
|
+
elif project_size == "medium":
|
751
|
+
test.extend(
|
752
|
+
["-xvs", "-n", "auto"]
|
753
|
+
) # Auto-detect number of processes but limit it
|
754
|
+
else:
|
755
|
+
test.append("-xvs") # Default behavior for small projects
|
716
756
|
|
717
757
|
return test
|
718
758
|
|
759
|
+
def _detect_project_size(self) -> str:
|
760
|
+
"""Detect the approximate size of the project to adjust test parameters.
|
761
|
+
|
762
|
+
Returns:
|
763
|
+
"small", "medium", or "large" based on codebase size
|
764
|
+
"""
|
765
|
+
# Check for known large projects by name
|
766
|
+
if self.pkg_name in ("acb", "fastblocks"):
|
767
|
+
return "large"
|
768
|
+
|
769
|
+
# Count Python files to estimate project size
|
770
|
+
try:
|
771
|
+
py_files = list(self.pkg_path.rglob("*.py"))
|
772
|
+
test_files = list(self.pkg_path.rglob("test_*.py"))
|
773
|
+
|
774
|
+
total_files = len(py_files)
|
775
|
+
num_test_files = len(test_files)
|
776
|
+
|
777
|
+
# Rough heuristics for project size
|
778
|
+
if total_files > 100 or num_test_files > 50:
|
779
|
+
return "large"
|
780
|
+
elif total_files > 50 or num_test_files > 20:
|
781
|
+
return "medium"
|
782
|
+
else:
|
783
|
+
return "small"
|
784
|
+
except Exception:
|
785
|
+
# Default to medium in case of error
|
786
|
+
return "medium"
|
787
|
+
|
719
788
|
def _setup_test_environment(self) -> None:
|
720
789
|
os.environ["PYTHONASYNCIO_DEBUG"] = "0" # Disable asyncio debug mode
|
721
790
|
os.environ["RUNNING_UNDER_CRACKERJACK"] = "1" # Signal to conftest.py
|
@@ -725,9 +794,29 @@ class Crackerjack:
|
|
725
794
|
def _run_pytest_process(
|
726
795
|
self, test_command: list[str]
|
727
796
|
) -> subprocess.CompletedProcess[str]:
|
797
|
+
import queue
|
798
|
+
|
728
799
|
from .errors import ErrorCode, ExecutionError, handle_error
|
729
800
|
|
730
801
|
try:
|
802
|
+
# Detect project size to determine appropriate timeout
|
803
|
+
project_size = self._detect_project_size()
|
804
|
+
# Longer timeouts for larger projects
|
805
|
+
global_timeout = (
|
806
|
+
1200
|
807
|
+
if project_size == "large"
|
808
|
+
else 600
|
809
|
+
if project_size == "medium"
|
810
|
+
else 300
|
811
|
+
)
|
812
|
+
|
813
|
+
# Show timeout information
|
814
|
+
self.console.print(f"[blue]Project size detected as: {project_size}[/blue]")
|
815
|
+
self.console.print(
|
816
|
+
f"[blue]Using global timeout of {global_timeout} seconds[/blue]"
|
817
|
+
)
|
818
|
+
|
819
|
+
# Use non-blocking IO to avoid deadlocks
|
731
820
|
process = subprocess.Popen(
|
732
821
|
test_command,
|
733
822
|
stdout=subprocess.PIPE,
|
@@ -736,21 +825,62 @@ class Crackerjack:
|
|
736
825
|
bufsize=1,
|
737
826
|
universal_newlines=True,
|
738
827
|
)
|
739
|
-
|
740
|
-
start_time = time.time()
|
828
|
+
|
741
829
|
stdout_data = []
|
742
830
|
stderr_data = []
|
831
|
+
|
832
|
+
# Output collection queues
|
833
|
+
stdout_queue = queue.Queue()
|
834
|
+
stderr_queue = queue.Queue()
|
835
|
+
|
836
|
+
# Use separate threads to read from stdout and stderr to prevent deadlocks
|
837
|
+
def read_output(
|
838
|
+
pipe: t.TextIO,
|
839
|
+
output_queue: "queue.Queue[str]",
|
840
|
+
data_collector: list[str],
|
841
|
+
) -> None:
|
842
|
+
try:
|
843
|
+
for line in iter(pipe.readline, ""):
|
844
|
+
output_queue.put(line)
|
845
|
+
data_collector.append(line)
|
846
|
+
except (OSError, ValueError):
|
847
|
+
# Pipe has been closed
|
848
|
+
pass
|
849
|
+
finally:
|
850
|
+
pipe.close()
|
851
|
+
|
852
|
+
# Start output reader threads
|
853
|
+
stdout_thread = threading.Thread(
|
854
|
+
target=read_output,
|
855
|
+
args=(process.stdout, stdout_queue, stdout_data),
|
856
|
+
daemon=True,
|
857
|
+
)
|
858
|
+
stderr_thread = threading.Thread(
|
859
|
+
target=read_output,
|
860
|
+
args=(process.stderr, stderr_queue, stderr_data),
|
861
|
+
daemon=True,
|
862
|
+
)
|
863
|
+
|
864
|
+
stdout_thread.start()
|
865
|
+
stderr_thread.start()
|
866
|
+
|
867
|
+
# Start time for timeout tracking
|
868
|
+
start_time = time.time()
|
869
|
+
|
870
|
+
# Process is running, monitor and display output until completion or timeout
|
743
871
|
while process.poll() is None:
|
744
|
-
|
872
|
+
# Check for timeout
|
873
|
+
elapsed = time.time() - start_time
|
874
|
+
if elapsed > global_timeout:
|
745
875
|
error = ExecutionError(
|
746
|
-
message="Test execution timed out after
|
876
|
+
message=f"Test execution timed out after {global_timeout // 60} minutes.",
|
747
877
|
error_code=ErrorCode.COMMAND_TIMEOUT,
|
748
|
-
details=f"Command: {' '.join(test_command)}\nTimeout: {
|
878
|
+
details=f"Command: {' '.join(test_command)}\nTimeout: {global_timeout} seconds",
|
749
879
|
recovery="Check for infinite loops or deadlocks in your tests. Consider increasing the timeout or optimizing your tests.",
|
750
880
|
)
|
751
881
|
|
752
882
|
self.console.print(
|
753
|
-
"[red]Test execution timed out after
|
883
|
+
f"[red]Test execution timed out after {global_timeout // 60} minutes. Terminating...[/red]"
|
754
884
|
)
|
755
885
|
process.terminate()
|
756
886
|
try:
|
@@ -762,26 +892,27 @@ class Crackerjack:
|
|
762
892
|
)
|
763
893
|
break
|
764
894
|
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
779
|
-
|
780
|
-
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
895
|
+
# Print any available output
|
896
|
+
self._process_output_queue(stdout_queue, stderr_queue)
|
897
|
+
|
898
|
+
# Small sleep to avoid CPU spinning but still be responsive
|
899
|
+
time.sleep(0.05)
|
900
|
+
|
901
|
+
# Periodically output a heartbeat for very long-running tests
|
902
|
+
if elapsed > 60 and elapsed % 60 < 0.1: # Roughly every minute
|
903
|
+
self.console.print(
|
904
|
+
f"[blue]Tests still running, elapsed time: {int(elapsed)} seconds...[/blue]"
|
905
|
+
)
|
906
|
+
|
907
|
+
# Process has exited, get remaining output
|
908
|
+
time.sleep(0.1) # Allow threads to flush final output
|
909
|
+
self._process_output_queue(stdout_queue, stderr_queue)
|
910
|
+
|
911
|
+
# Ensure threads are done
|
912
|
+
if stdout_thread.is_alive():
|
913
|
+
stdout_thread.join(1.0)
|
914
|
+
if stderr_thread.is_alive():
|
915
|
+
stderr_thread.join(1.0)
|
785
916
|
|
786
917
|
returncode = process.returncode or 0
|
787
918
|
stdout = "".join(stdout_data)
|
@@ -807,6 +938,28 @@ class Crackerjack:
|
|
807
938
|
|
808
939
|
return subprocess.CompletedProcess(test_command, 1, "", str(e))
|
809
940
|
|
941
|
+
def _process_output_queue(
|
942
|
+
self, stdout_queue: "queue.Queue[str]", stderr_queue: "queue.Queue[str]"
|
943
|
+
) -> None:
|
944
|
+
"""Process and display output from the queues without blocking."""
|
945
|
+
# Process stdout
|
946
|
+
while not stdout_queue.empty():
|
947
|
+
try:
|
948
|
+
line = stdout_queue.get_nowait()
|
949
|
+
if line:
|
950
|
+
self.console.print(line, end="")
|
951
|
+
except queue.Empty:
|
952
|
+
break
|
953
|
+
|
954
|
+
# Process stderr
|
955
|
+
while not stderr_queue.empty():
|
956
|
+
try:
|
957
|
+
line = stderr_queue.get_nowait()
|
958
|
+
if line:
|
959
|
+
self.console.print(f"[red]{line}[/red]", end="")
|
960
|
+
except queue.Empty:
|
961
|
+
break
|
962
|
+
|
810
963
|
def _report_test_results(
|
811
964
|
self, result: subprocess.CompletedProcess[str], ai_agent: str
|
812
965
|
) -> None:
|
crackerjack/pyproject.toml
CHANGED
@@ -4,7 +4,7 @@ requires = [ "pdm-backend" ]
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "crackerjack"
|
7
|
-
version = "0.
|
7
|
+
version = "0.20.0"
|
8
8
|
description = "Crackerjack: code quality toolkit"
|
9
9
|
readme = "README.md"
|
10
10
|
keywords = [
|
@@ -56,7 +56,7 @@ dependencies = [
|
|
56
56
|
"rich>=14",
|
57
57
|
"tomli-w>=1.2",
|
58
58
|
"typer>=0.16",
|
59
|
-
"uv>=0.7.
|
59
|
+
"uv>=0.7.12",
|
60
60
|
]
|
61
61
|
urls.documentation = "https://github.com/lesleslie/crackerjack"
|
62
62
|
urls.homepage = "https://github.com/lesleslie/crackerjack"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: crackerjack
|
3
|
-
Version: 0.20.
|
3
|
+
Version: 0.20.1
|
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>
|
@@ -37,7 +37,7 @@ Requires-Dist: pytest-xdist>=3.7
|
|
37
37
|
Requires-Dist: rich>=14
|
38
38
|
Requires-Dist: tomli-w>=1.2
|
39
39
|
Requires-Dist: typer>=0.16
|
40
|
-
Requires-Dist: uv>=0.7.
|
40
|
+
Requires-Dist: uv>=0.7.12
|
41
41
|
Description-Content-Type: text/markdown
|
42
42
|
|
43
43
|
# Crackerjack: Elevate Your Python Development
|
@@ -1,11 +1,11 @@
|
|
1
|
-
crackerjack-0.20.
|
2
|
-
crackerjack-0.20.
|
3
|
-
crackerjack-0.20.
|
4
|
-
crackerjack-0.20.
|
1
|
+
crackerjack-0.20.1.dist-info/METADATA,sha256=dl2tB6kLCgYAilZFtJ6-dwGjBrOysINM3ODPIBfVRW0,24058
|
2
|
+
crackerjack-0.20.1.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
|
3
|
+
crackerjack-0.20.1.dist-info/entry_points.txt,sha256=6OYgBcLyFCUgeqLgnvMyOJxPCWzgy7se4rLPKtNonMs,34
|
4
|
+
crackerjack-0.20.1.dist-info/licenses/LICENSE,sha256=fDt371P6_6sCu7RyqiZH_AhT1LdN3sN1zjBtqEhDYCk,1531
|
5
5
|
crackerjack/.gitignore,sha256=4DYG7ZoVEHR5Tv1gQliRWmsNku5Fw2_k756cG_t12Cg,185
|
6
6
|
crackerjack/.libcst.codemod.yaml,sha256=a8DlErRAIPV1nE6QlyXPAzTOgkB24_spl2E9hphuf5s,772
|
7
7
|
crackerjack/.pdm.toml,sha256=dZe44HRcuxxCFESGG8SZIjmc-cGzSoyK3Hs6t4NYA8w,23
|
8
|
-
crackerjack/.pre-commit-config.yaml,sha256=
|
8
|
+
crackerjack/.pre-commit-config.yaml,sha256=r7T5Valb9febof0yH4tBu2u8PhQRfQBmf1W-gJIkJSI,2935
|
9
9
|
crackerjack/.pytest_cache/.gitignore,sha256=Ptcxtl0GFQwTji2tsL4Gl1UIiKa0frjEXsya26i46b0,37
|
10
10
|
crackerjack/.pytest_cache/CACHEDIR.TAG,sha256=N9yI75oKvt2-gQU6bdj9-xOvthMEXqHrSlyBWnSjveQ,191
|
11
11
|
crackerjack/.pytest_cache/README.md,sha256=c_1vzN2ALEGaay2YPWwxc7fal1WKxLWJ7ewt_kQ9ua0,302
|
@@ -23,6 +23,7 @@ crackerjack/.ruff_cache/0.11.12/16869036553936192448,sha256=pYYUCDrYh7fPq8xkFLxv
|
|
23
23
|
crackerjack/.ruff_cache/0.11.12/1867267426380906393,sha256=2w4M0Lrjd9flwuq6uJxehTbm7FVUcK5sL2sz1gS2Yvo,256
|
24
24
|
crackerjack/.ruff_cache/0.11.12/4240757255861806333,sha256=uph5uIRG-XnF7ywAEcCxqqgIkWALPCvJFcwCgnNfTI4,77
|
25
25
|
crackerjack/.ruff_cache/0.11.12/4441409093023629623,sha256=eHrESew3XCFJ2WqmKvtGLO1r4mY5Q_mv7yGlDmM1sSc,153
|
26
|
+
crackerjack/.ruff_cache/0.11.13/1867267426380906393,sha256=AbmV8fLx3w4rzckJWjY4fxme4qFC8RaJefGdR6WFJkM,256
|
26
27
|
crackerjack/.ruff_cache/0.11.2/4070660268492669020,sha256=FTRTUmvj6nZw_QQBp_WHI-h3_iqRejzL39api-9wTvs,224
|
27
28
|
crackerjack/.ruff_cache/0.11.3/9818742842212983150,sha256=U-4mT__a-OljovvAJvv5M6X7TCMa3dReLXx3kTNGgwU,224
|
28
29
|
crackerjack/.ruff_cache/0.11.4/9818742842212983150,sha256=QF9j6-3MH_d0pDNotdbF2hlqCL66SxN8OLVKR3PZyZU,224
|
@@ -58,10 +59,10 @@ crackerjack/.ruff_cache/0.9.9/12813592349865671909,sha256=tmr8_vhRD2OxsVuMfbJPdT
|
|
58
59
|
crackerjack/.ruff_cache/0.9.9/8843823720003377982,sha256=e4ymkXfQsUg5e_mtO34xTsaTvs1uA3_fI216Qq9qCAM,136
|
59
60
|
crackerjack/.ruff_cache/CACHEDIR.TAG,sha256=WVMVbX4MVkpCclExbq8m-IcOZIOuIZf5FrYw5Pk-Ma4,43
|
60
61
|
crackerjack/__init__.py,sha256=w5jukdION0D0fyeKYl-7hfCPzI0isXbEjzdjw8RecKA,840
|
61
|
-
crackerjack/__main__.py,sha256=
|
62
|
-
crackerjack/crackerjack.py,sha256=
|
62
|
+
crackerjack/__main__.py,sha256=jg-eO0Z1VZkx5F-97dsd1rxOQ0uwHWabuKma8thUJVw,6779
|
63
|
+
crackerjack/crackerjack.py,sha256=OWw-EyQzDZsoxH-snikhxuQL8t0Es4MD9bsGfu6zwv4,52600
|
63
64
|
crackerjack/errors.py,sha256=OtbmtA912kzDOWVo6JASuFbaMU-VhmQD_fUNsvnWCZc,4311
|
64
65
|
crackerjack/interactive.py,sha256=Ay7_s3pc4ntc_3F_bRKBsWxmjor6nkN9v6tqqJe1iRw,15904
|
65
66
|
crackerjack/py313.py,sha256=VgthlcpLL6nNDcg3evvEmGNYWCdMuojnMhow58ISEdY,6184
|
66
|
-
crackerjack/pyproject.toml,sha256=
|
67
|
-
crackerjack-0.20.
|
67
|
+
crackerjack/pyproject.toml,sha256=UGmBtEKuo7DxmbiWwC1DQvOyhtCKXY_08j1T0lxGU3Q,4843
|
68
|
+
crackerjack-0.20.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|