crackerjack 0.27.1__tar.gz → 0.27.4__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.

Potentially problematic release.


This version of crackerjack might be problematic. Click here for more details.

Files changed (45) hide show
  1. {crackerjack-0.27.1/crackerjack → crackerjack-0.27.4}/.gitignore +3 -0
  2. {crackerjack-0.27.1 → crackerjack-0.27.4}/PKG-INFO +1 -1
  3. {crackerjack-0.27.1 → crackerjack-0.27.4/crackerjack}/.gitignore +3 -0
  4. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/crackerjack.py +128 -23
  5. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/py313.py +5 -1
  6. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/pyproject.toml +1 -1
  7. {crackerjack-0.27.1 → crackerjack-0.27.4}/pyproject.toml +1 -1
  8. {crackerjack-0.27.1 → crackerjack-0.27.4}/uv.lock +1 -1
  9. {crackerjack-0.27.1 → crackerjack-0.27.4}/.envrc +0 -0
  10. {crackerjack-0.27.1 → crackerjack-0.27.4}/.github/FUNDING.yml +0 -0
  11. {crackerjack-0.27.1 → crackerjack-0.27.4}/.libcst.codemod.yaml +0 -0
  12. {crackerjack-0.27.1 → crackerjack-0.27.4}/.pre-commit-config-ai.yaml +0 -0
  13. {crackerjack-0.27.1 → crackerjack-0.27.4}/.pre-commit-config-fast.yaml +0 -0
  14. {crackerjack-0.27.1 → crackerjack-0.27.4}/.pre-commit-config.yaml +0 -0
  15. {crackerjack-0.27.1 → crackerjack-0.27.4}/CLAUDE.md +0 -0
  16. {crackerjack-0.27.1 → crackerjack-0.27.4}/LICENSE +0 -0
  17. {crackerjack-0.27.1 → crackerjack-0.27.4}/README-AI-AGENT.md +0 -0
  18. {crackerjack-0.27.1 → crackerjack-0.27.4}/README.md +0 -0
  19. {crackerjack-0.27.1 → crackerjack-0.27.4}/RULES.md +0 -0
  20. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/.libcst.codemod.yaml +0 -0
  21. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/.pdm.toml +0 -0
  22. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/.pre-commit-config-ai.yaml +0 -0
  23. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/.pre-commit-config.yaml +0 -0
  24. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/__init__.py +0 -0
  25. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/__main__.py +0 -0
  26. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/errors.py +0 -0
  27. {crackerjack-0.27.1 → crackerjack-0.27.4}/crackerjack/interactive.py +0 -0
  28. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/TESTING.md +0 -0
  29. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/__init__.py +0 -0
  30. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/conftest.py +0 -0
  31. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/data/comments_sample.txt +0 -0
  32. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/data/docstrings_sample.txt +0 -0
  33. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/data/expected_comments_sample.txt +0 -0
  34. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/data/init.py +0 -0
  35. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_crackerjack.py +0 -0
  36. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_crackerjack_runner.py +0 -0
  37. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_errors.py +0 -0
  38. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_interactive.py +0 -0
  39. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_interactive_run.py +0 -0
  40. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_main.py +0 -0
  41. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_multiline_functions.py +0 -0
  42. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_py313_advanced.py +0 -0
  43. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_py313_features.py +0 -0
  44. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_pytest_features.py +0 -0
  45. {crackerjack-0.27.1 → crackerjack-0.27.4}/tests/test_structured_errors.py +0 -0
@@ -29,3 +29,6 @@
29
29
 
30
30
  # Autotyping cache
31
31
  .autotyping-cache/
32
+
33
+ # Crackerjack state file
34
+ .crackerjack-state
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: crackerjack
3
- Version: 0.27.1
3
+ Version: 0.27.4
4
4
  Summary: Crackerjack: code quality toolkit
5
5
  Project-URL: documentation, https://github.com/lesleslie/crackerjack
6
6
  Project-URL: homepage, https://github.com/lesleslie/crackerjack
@@ -29,3 +29,6 @@
29
29
 
30
30
  # Autotyping cache
31
31
  .autotyping-cache/
32
+
33
+ # Crackerjack state file
34
+ .crackerjack-state
@@ -540,9 +540,15 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
540
540
 
541
541
  def process_lines():
542
542
  lines = code.split("\n")
543
- function_tracker = {"in_function": False, "function_indent": 0}
544
- import_tracker = {"in_imports": False, "last_import_type": None}
545
- previous_lines = []
543
+ function_tracker: dict[str, t.Any] = {
544
+ "in_function": False,
545
+ "function_indent": 0,
546
+ }
547
+ import_tracker: dict[str, t.Any] = {
548
+ "in_imports": False,
549
+ "last_import_type": None,
550
+ }
551
+ previous_lines: list[str] = []
546
552
  for i, line in enumerate(lines):
547
553
  line = line.rstrip()
548
554
  stripped_line = line.lstrip()
@@ -856,8 +862,8 @@ class CodeCleaner(BaseModel, arbitrary_types_allowed=True):
856
862
 
857
863
  try:
858
864
  async with aiofiles.open(file_path, encoding="utf-8") as f: # type: ignore[misc]
859
- code = await f.read() # type: ignore[misc]
860
- original_code = code
865
+ code: str = await f.read() # type: ignore[misc]
866
+ original_code: str = code
861
867
  cleaning_failed = False
862
868
  try:
863
869
  code = self.remove_line_comments_streaming(code)
@@ -1115,7 +1121,9 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
1115
1121
  ) -> None:
1116
1122
  for setting, value in our_config.items():
1117
1123
  if isinstance(value, dict):
1118
- self._merge_nested_config(setting, value, pkg_config)
1124
+ self._merge_nested_config(
1125
+ setting, t.cast(dict[str, t.Any], value), pkg_config
1126
+ )
1119
1127
  else:
1120
1128
  self._merge_direct_config(setting, value, pkg_config)
1121
1129
 
@@ -1134,11 +1142,13 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
1134
1142
  self, key: str, value: t.Any, nested_config: dict[str, t.Any]
1135
1143
  ) -> None:
1136
1144
  if isinstance(value, str | list) and "crackerjack" in str(value):
1137
- nested_config[key] = self.swap_package_name(value)
1145
+ nested_config[key] = self.swap_package_name(t.cast(str | list[str], value))
1138
1146
  elif self._is_mergeable_list(key, value):
1139
1147
  existing = nested_config.get(key, [])
1140
1148
  if isinstance(existing, list) and isinstance(value, list):
1141
- nested_config[key] = list(set(existing + value))
1149
+ nested_config[key] = list(
1150
+ set(t.cast(list[str], existing) + t.cast(list[str], value))
1151
+ )
1142
1152
  else:
1143
1153
  nested_config[key] = value
1144
1154
  elif key not in nested_config:
@@ -1148,11 +1158,13 @@ class ConfigManager(BaseModel, arbitrary_types_allowed=True):
1148
1158
  self, setting: str, value: t.Any, pkg_config: dict[str, t.Any]
1149
1159
  ) -> None:
1150
1160
  if isinstance(value, str | list) and "crackerjack" in str(value):
1151
- pkg_config[setting] = self.swap_package_name(value)
1161
+ pkg_config[setting] = self.swap_package_name(t.cast(str | list[str], value))
1152
1162
  elif self._is_mergeable_list(setting, value):
1153
1163
  existing = pkg_config.get(setting, [])
1154
1164
  if isinstance(existing, list) and isinstance(value, list):
1155
- pkg_config[setting] = list(set(existing + value))
1165
+ pkg_config[setting] = list(
1166
+ set(t.cast(list[str], existing) + t.cast(list[str], value))
1167
+ )
1156
1168
  else:
1157
1169
  pkg_config[setting] = value
1158
1170
  elif setting not in pkg_config:
@@ -1611,7 +1623,9 @@ class ProjectManager(BaseModel, arbitrary_types_allowed=True):
1611
1623
  with open("complexipy.json", encoding="utf-8") as f:
1612
1624
  complexity_data = json.load(f)
1613
1625
  if isinstance(complexity_data, list):
1614
- return self._parse_complexity_list(complexity_data)
1626
+ return self._parse_complexity_list(
1627
+ t.cast(list[dict[str, t.Any]], complexity_data)
1628
+ )
1615
1629
  return self._parse_complexity_dict(complexity_data)
1616
1630
  except (FileNotFoundError, json.JSONDecodeError):
1617
1631
  return {"status": "complexity_analysis_not_available"}
@@ -1768,7 +1782,7 @@ class ProjectManager(BaseModel, arbitrary_types_allowed=True):
1768
1782
  return {"directories": directories, "total_directories": len(directories)}
1769
1783
 
1770
1784
  def _analyze_file_distribution(self) -> dict[str, t.Any]:
1771
- file_types = {}
1785
+ file_types: dict[str, int] = {}
1772
1786
  total_files = 0
1773
1787
  for file_path in self.pkg_path.rglob("*"):
1774
1788
  if file_path.is_file() and not str(file_path).startswith(
@@ -2115,11 +2129,13 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2115
2129
  project_manager: ProjectManager | None = None
2116
2130
  _file_cache: dict[str, list[Path]] = {}
2117
2131
  _file_cache_with_mtime: dict[str, tuple[float, list[Path]]] = {}
2132
+ _state_file: Path = Path(".crackerjack-state")
2118
2133
 
2119
2134
  def __init__(self, **data: t.Any) -> None:
2120
2135
  super().__init__(**data)
2121
2136
  self._file_cache = {}
2122
2137
  self._file_cache_with_mtime = {}
2138
+ self._state_file = Path(".crackerjack-state")
2123
2139
  self.code_cleaner = CodeCleaner(console=self.console)
2124
2140
  self.config_manager = ConfigManager(
2125
2141
  our_path=self.our_path,
@@ -2140,6 +2156,73 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2140
2156
  dry_run=self.dry_run,
2141
2157
  )
2142
2158
 
2159
+ def _read_state(self) -> dict[str, t.Any]:
2160
+ import json
2161
+
2162
+ if self._state_file.exists():
2163
+ try:
2164
+ return json.loads(self._state_file.read_text(encoding="utf-8"))
2165
+ except (json.JSONDecodeError, OSError):
2166
+ return {}
2167
+ return {}
2168
+
2169
+ def _write_state(self, state: dict[str, t.Any]) -> None:
2170
+ from contextlib import suppress
2171
+
2172
+ with suppress(OSError):
2173
+ import json
2174
+
2175
+ self._state_file.write_text(json.dumps(state, indent=2), encoding="utf-8")
2176
+
2177
+ def _clear_state(self) -> None:
2178
+ if self._state_file.exists():
2179
+ from contextlib import suppress
2180
+
2181
+ with suppress(OSError):
2182
+ self._state_file.unlink()
2183
+
2184
+ def _has_version_been_bumped(self, version_type: str) -> bool:
2185
+ state = self._read_state()
2186
+ current_version = self._get_current_version()
2187
+ last_bumped_version = state.get("last_bumped_version")
2188
+ last_bump_type = state.get("last_bump_type")
2189
+
2190
+ return (
2191
+ last_bumped_version == current_version
2192
+ and last_bump_type == version_type
2193
+ and not state.get("publish_completed", False)
2194
+ )
2195
+
2196
+ def _mark_version_bumped(self, version_type: str) -> None:
2197
+ current_version = self._get_current_version()
2198
+ state = self._read_state()
2199
+ state.update(
2200
+ {
2201
+ "last_bumped_version": current_version,
2202
+ "last_bump_type": version_type,
2203
+ "publish_completed": False,
2204
+ }
2205
+ )
2206
+ self._write_state(state)
2207
+
2208
+ def _mark_publish_completed(self) -> None:
2209
+ state = self._read_state()
2210
+ state["publish_completed"] = True
2211
+ self._write_state(state)
2212
+
2213
+ def _get_current_version(self) -> str:
2214
+ from contextlib import suppress
2215
+
2216
+ with suppress(Exception):
2217
+ import tomllib
2218
+
2219
+ pyproject_path = Path("pyproject.toml")
2220
+ if pyproject_path.exists():
2221
+ with pyproject_path.open("rb") as f:
2222
+ data = tomllib.load(f)
2223
+ return data.get("project", {}).get("version", "unknown")
2224
+ return "unknown"
2225
+
2143
2226
  def _setup_package(self) -> None:
2144
2227
  self.pkg_name = self.pkg_path.stem.lower().replace("-", "_")
2145
2228
  self.pkg_dir = self.pkg_path / self.pkg_name
@@ -2156,6 +2239,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2156
2239
  self.project_manager.pkg_dir = self.pkg_dir
2157
2240
 
2158
2241
  def _update_project(self, options: t.Any) -> None:
2242
+ assert self.project_manager is not None
2159
2243
  if not options.no_config_updates:
2160
2244
  self.project_manager.update_pkg_configs()
2161
2245
  result: CompletedProcess[str] = self.execute_command(
@@ -2178,6 +2262,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2178
2262
  self.execute_command(update_cmd)
2179
2263
 
2180
2264
  def _clean_project(self, options: t.Any) -> None:
2265
+ assert self.code_cleaner is not None
2181
2266
  if options.clean:
2182
2267
  if self.pkg_dir:
2183
2268
  self.console.print("\n" + "-" * 80)
@@ -2197,6 +2282,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2197
2282
  self.code_cleaner.clean_files(tests_dir)
2198
2283
 
2199
2284
  async def _clean_project_async(self, options: t.Any) -> None:
2285
+ assert self.code_cleaner is not None
2200
2286
  if options.clean:
2201
2287
  if self.pkg_dir:
2202
2288
  self.console.print("\n" + "-" * 80)
@@ -2483,12 +2569,20 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2483
2569
  def _bump_version(self, options: OptionsProtocol) -> None:
2484
2570
  for option in (options.publish, options.bump):
2485
2571
  if option:
2572
+ version_type = str(option)
2573
+ if self._has_version_been_bumped(version_type):
2574
+ self.console.print("\n" + "-" * 80)
2575
+ self.console.print(
2576
+ f"[bold yellow]📦 VERSION[/bold yellow] [bold bright_white]Version already bumped ({version_type}), skipping to avoid duplicate bump[/bold bright_white]"
2577
+ )
2578
+ self.console.print("-" * 80 + "\n")
2579
+ return
2486
2580
  self.console.print("\n" + "-" * 80)
2487
2581
  self.console.print(
2488
2582
  f"[bold bright_magenta]📦 VERSION[/bold bright_magenta] [bold bright_white]Bumping {option} version[/bold bright_white]"
2489
2583
  )
2490
2584
  self.console.print("-" * 80 + "\n")
2491
- if str(option) in ("minor", "major"):
2585
+ if version_type in ("minor", "major"):
2492
2586
  from rich.prompt import Confirm
2493
2587
 
2494
2588
  if not Confirm.ask(
@@ -2500,6 +2594,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2500
2594
  )
2501
2595
  return
2502
2596
  self.execute_command(["uv", "version", "--bump", option])
2597
+ self._mark_version_bumped(version_type)
2503
2598
  break
2504
2599
 
2505
2600
  def _publish_project(self, options: OptionsProtocol) -> None:
@@ -2519,7 +2614,18 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2519
2614
  "[bold bright_red]❌ Build failed. Please fix errors.[/bold bright_red]"
2520
2615
  )
2521
2616
  raise SystemExit(1)
2522
- self.execute_command(["uv", "publish"])
2617
+ try:
2618
+ self.execute_command(["uv", "publish"])
2619
+ self._mark_publish_completed()
2620
+ self._clear_state()
2621
+ self.console.print(
2622
+ "\n[bold bright_green]✅ Package published successfully![/bold bright_green]"
2623
+ )
2624
+ except SystemExit:
2625
+ self.console.print(
2626
+ "\n[bold bright_red]❌ Publish failed. Run crackerjack again to retry publishing without re-bumping version.[/bold bright_red]"
2627
+ )
2628
+ raise
2523
2629
 
2524
2630
  def _commit_and_push(self, options: OptionsProtocol) -> None:
2525
2631
  if options.commit:
@@ -2594,21 +2700,18 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2594
2700
  "-c",
2595
2701
  ".pre-commit-config.yaml",
2596
2702
  ]
2597
- result = self.execute_command(cmd, capture_output=True, text=True)
2703
+ result = self.execute_command(cmd)
2598
2704
  if result.returncode > 0:
2599
2705
  self.console.print(
2600
2706
  "\n[bold bright_red]❌ Comprehensive quality checks failed![/bold bright_red]"
2601
2707
  )
2602
- self.console.print(f"[dim]STDOUT:[/dim]\n{result.stdout}")
2603
- if result.stderr:
2604
- self.console.print(f"[dim]STDERR:[/dim]\n{result.stderr}")
2605
2708
  self.console.print(
2606
- "\n[bold red]Cannot proceed with publishing/committing until all quality checks pass. [/bold red]"
2709
+ "\n[bold red]Cannot proceed with publishing/committing until all quality checks pass.[/bold red]\n"
2607
2710
  )
2608
2711
  raise SystemExit(1)
2609
2712
  else:
2610
2713
  self.console.print(
2611
- "[bold bright_green]✅ All comprehensive quality checks passed![/bold bright_green]"
2714
+ "\n[bold bright_green]✅ All comprehensive quality checks passed![/bold bright_green]"
2612
2715
  )
2613
2716
 
2614
2717
  async def _run_comprehensive_quality_checks_async(
@@ -2653,7 +2756,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2653
2756
  if result.stderr:
2654
2757
  self.console.print(f"[dim]Error details: {result.stderr}[/dim]")
2655
2758
  self.console.print(
2656
- "\n[bold red]Cannot proceed with publishing/committing until all quality checks pass. [/bold red]"
2759
+ "\n[bold red]Cannot proceed with publishing/committing until all quality checks pass.[/bold red]\n"
2657
2760
  )
2658
2761
  raise SystemExit(1)
2659
2762
  else:
@@ -2662,6 +2765,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2662
2765
  )
2663
2766
 
2664
2767
  def process(self, options: OptionsProtocol) -> None:
2768
+ assert self.project_manager is not None
2665
2769
  self.console.print("\n" + "-" * 80)
2666
2770
  self.console.print(
2667
2771
  "[bold bright_cyan]⚒️ CRACKERJACKING[/bold bright_cyan] [bold bright_white]Starting workflow execution[/bold bright_white]"
@@ -2689,8 +2793,8 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2689
2793
  self._run_tests(options)
2690
2794
  self._run_comprehensive_quality_checks(options)
2691
2795
  self._bump_version(options)
2692
- self._publish_project(options)
2693
2796
  self._commit_and_push(options)
2797
+ self._publish_project(options)
2694
2798
  self.console.print("\n" + "-" * 80)
2695
2799
  self.console.print(
2696
2800
  "[bold bright_green]✨ CRACKERJACK COMPLETE[/bold bright_green] [bold bright_white]Workflow completed successfully![/bold bright_white]"
@@ -2698,6 +2802,7 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2698
2802
  self.console.print("-" * 80 + "\n")
2699
2803
 
2700
2804
  async def process_async(self, options: OptionsProtocol) -> None:
2805
+ assert self.project_manager is not None
2701
2806
  self.console.print("\n" + "-" * 80)
2702
2807
  self.console.print(
2703
2808
  "[bold bright_cyan]⚒️ CRACKERJACKING[/bold bright_cyan] [bold bright_white]Starting workflow execution (async optimized)[/bold bright_white]"
@@ -2725,8 +2830,8 @@ class Crackerjack(BaseModel, arbitrary_types_allowed=True):
2725
2830
  await self._run_tests_async(options)
2726
2831
  await self._run_comprehensive_quality_checks_async(options)
2727
2832
  self._bump_version(options)
2728
- self._publish_project(options)
2729
2833
  self._commit_and_push(options)
2834
+ self._publish_project(options)
2730
2835
  self.console.print("\n" + "-" * 80)
2731
2836
  self.console.print(
2732
2837
  "[bold bright_green]✨ CRACKERJACK COMPLETE[/bold bright_green] [bold bright_white]Workflow completed successfully![/bold bright_white]"
@@ -111,7 +111,11 @@ def process_hook_results[T, R](
111
111
  ) -> list[R]:
112
112
  processed_results: list[R] = []
113
113
  for result in results:
114
- if isinstance(result, dict) and result.get("status") == HookStatus.SUCCESS:
114
+ if (
115
+ isinstance(result, dict)
116
+ and "status" in result
117
+ and result["status"] == HookStatus.SUCCESS
118
+ ):
115
119
  processed_results.append(success_handler(result))
116
120
  else:
117
121
  processed_results.append(failure_handler(result))
@@ -4,7 +4,7 @@ requires = [ "hatchling" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.27.0"
7
+ version = "0.27.3"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -4,7 +4,7 @@ requires = [ "hatchling" ]
4
4
 
5
5
  [project]
6
6
  name = "crackerjack"
7
- version = "0.27.1"
7
+ version = "0.27.4"
8
8
  description = "Crackerjack: code quality toolkit"
9
9
  readme = "README.md"
10
10
  keywords = [
@@ -114,7 +114,7 @@ wheels = [
114
114
 
115
115
  [[package]]
116
116
  name = "crackerjack"
117
- version = "0.27.1"
117
+ version = "0.27.4"
118
118
  source = { editable = "." }
119
119
  dependencies = [
120
120
  { name = "aiofiles" },
File without changes
File without changes
File without changes
File without changes
File without changes