autopkg-wrapper 2026.2.5__tar.gz → 2026.2.6__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.
Files changed (43) hide show
  1. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/PKG-INFO +1 -1
  2. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/autopkg_wrapper.py +25 -139
  3. autopkg_wrapper-2026.2.6/autopkg_wrapper/models/recipe.py +139 -0
  4. autopkg_wrapper-2026.2.6/autopkg_wrapper/utils/recipe_batching.py +41 -0
  5. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/pyproject.toml +1 -1
  6. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_autopkg_commands.py +41 -6
  7. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/uv.lock +1 -1
  8. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/dependabot.yml +0 -0
  9. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/build-publish.yml +0 -0
  10. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/codeql.yml +0 -0
  11. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.github/workflows/dependency-review.yml +0 -0
  12. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.gitignore +0 -0
  13. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/.pre-commit-config.yaml +0 -0
  14. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/CONTRIBUTING +0 -0
  15. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/LICENSE +0 -0
  16. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/README.md +0 -0
  17. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/.github/workflows/autopkg-wrapper-demo.yml +0 -0
  18. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/overrides/Google_Chrome.pkg.recipe.yaml +0 -0
  19. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/repo_list.txt +0 -0
  20. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/actions-demo/requirements.txt +0 -0
  21. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/__init__.py +0 -0
  22. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/notifier/__init__.py +0 -0
  23. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/notifier/slack.py +0 -0
  24. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/__init__.py +0 -0
  25. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/args.py +0 -0
  26. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/git_functions.py +0 -0
  27. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/logging.py +0 -0
  28. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/recipe_ordering.py +0 -0
  29. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/autopkg_wrapper/utils/report_processor.py +0 -0
  30. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/mise.toml +0 -0
  31. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/__init__.py +0 -0
  32. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/prefs.json +0 -0
  33. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/prefs.plist +0 -0
  34. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/recipe_list.json +0 -0
  35. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/recipe_list.txt +0 -0
  36. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/recipe_list.yaml +0 -0
  37. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_args_utils.py +0 -0
  38. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_git_functions.py +0 -0
  39. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_order_recipe_list.py +0 -0
  40. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_parse_recipe_list.py +0 -0
  41. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_report_processor.py +0 -0
  42. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_setup_logger.py +0 -0
  43. {autopkg_wrapper-2026.2.5 → autopkg_wrapper-2026.2.6}/tests/test_slack_notifier.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autopkg-wrapper
3
- Version: 2026.2.5
3
+ Version: 2026.2.6
4
4
  Summary: A package used to execute some autopkg functions, primarily within the context of a GitHub Actions runner.
5
5
  Project-URL: Repository, https://github.com/smithjw/autopkg-wrapper
6
6
  Author-email: James Smith <james@smithjw.me>
@@ -2,151 +2,20 @@
2
2
  import json
3
3
  import logging
4
4
  import plistlib
5
- import subprocess
6
5
  import sys
7
6
  from concurrent.futures import ThreadPoolExecutor, as_completed
8
- from datetime import datetime
9
- from itertools import chain
10
7
  from pathlib import Path
11
8
 
12
9
  import autopkg_wrapper.utils.git_functions as git
10
+ from autopkg_wrapper.models.recipe import Recipe
13
11
  from autopkg_wrapper.notifier import slack
14
12
  from autopkg_wrapper.utils.args import setup_args
15
13
  from autopkg_wrapper.utils.logging import setup_logger
14
+ from autopkg_wrapper.utils.recipe_batching import build_recipe_batches, recipe_type_for
16
15
  from autopkg_wrapper.utils.recipe_ordering import order_recipe_list
17
16
  from autopkg_wrapper.utils.report_processor import process_reports
18
17
 
19
18
 
20
- class Recipe(object):
21
- def __init__(self, name: str, post_processors: list = None):
22
- self.filename = name
23
- self.error = False
24
- self.results = {}
25
- self.updated = False
26
- self.verified = None
27
- self.pr_url = None
28
- self.post_processors = post_processors
29
-
30
- self._keys = None
31
- self._has_run = False
32
-
33
- @property
34
- def name(self):
35
- name = self.filename.split(".")[0]
36
-
37
- return name
38
-
39
- def verify_trust_info(self, args):
40
- verbose_output = ["-vvvv"] if args.debug else []
41
- prefs_file = (
42
- ["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
43
- )
44
- autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
45
- cmd = (
46
- [autopkg_bin, "verify-trust-info", self.filename]
47
- + verbose_output
48
- + prefs_file
49
- )
50
- logging.debug(f"cmd: {cmd}")
51
-
52
- result = subprocess.run(cmd, capture_output=True, text=True)
53
- if result.returncode == 0:
54
- self.verified = True
55
- else:
56
- self.results["message"] = (result.stderr or "").strip()
57
- self.verified = False
58
- return self.verified
59
-
60
- def update_trust_info(self, args):
61
- prefs_file = (
62
- ["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
63
- )
64
- autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
65
- cmd = [autopkg_bin, "update-trust-info", self.filename] + prefs_file
66
- logging.debug(f"cmd: {cmd}")
67
-
68
- # Fail loudly if this exits 0
69
- try:
70
- subprocess.check_call(cmd)
71
- except subprocess.CalledProcessError as e:
72
- logging.error(str(e))
73
- raise e
74
-
75
- def _parse_report(self, report):
76
- with open(report, "rb") as f:
77
- report_data = plistlib.load(f)
78
-
79
- failed_items = report_data.get("failures", [])
80
- imported_items = []
81
- if report_data["summary_results"]:
82
- # This means something happened
83
- munki_results = report_data["summary_results"].get(
84
- "munki_importer_summary_result", {}
85
- )
86
- imported_items.extend(munki_results.get("data_rows", []))
87
-
88
- return {"imported": imported_items, "failed": failed_items}
89
-
90
- def run(self, args):
91
- if self.verified is False:
92
- self.error = True
93
- self.results["failed"] = True
94
- self.results["imported"] = ""
95
- else:
96
- report_dir = Path("/private/tmp/autopkg")
97
- report_time = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
98
- report_name = Path(f"{self.name}-{report_time}.plist")
99
-
100
- report_dir.mkdir(parents=True, exist_ok=True)
101
- report = report_dir / report_name
102
- report.touch(exist_ok=True)
103
-
104
- try:
105
- prefs_file = (
106
- ["--prefs", args.autopkg_prefs.as_posix()]
107
- if args.autopkg_prefs
108
- else []
109
- )
110
- verbose_output = ["-vvvv"] if args.debug else []
111
- post_processor_cmd = (
112
- list(
113
- chain.from_iterable(
114
- [
115
- ("--post", processor)
116
- for processor in self.post_processors
117
- ]
118
- )
119
- )
120
- if self.post_processors
121
- else []
122
- )
123
- autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
124
- cmd = [
125
- autopkg_bin,
126
- "run",
127
- self.filename,
128
- "--report-plist",
129
- str(report),
130
- ]
131
- cmd = cmd + post_processor_cmd + verbose_output + prefs_file
132
-
133
- logging.debug(f"cmd: {cmd}")
134
-
135
- result = subprocess.run(cmd, capture_output=True, text=True)
136
- if result.returncode != 0:
137
- self.error = True
138
-
139
- except Exception:
140
- self.error = True
141
-
142
- self._has_run = True
143
- self.results = self._parse_report(report)
144
- if not self.results["failed"] and not self.error:
145
- self.updated = True
146
-
147
- return self.results
148
-
149
-
150
19
  def get_override_repo_info(args):
151
20
  if args.overrides_repo_path:
152
21
  recipe_override_dirs = args.overrides_repo_path
@@ -387,12 +256,29 @@ def main():
387
256
  # Git updates and notifications are applied serially after all recipes finish
388
257
  return r
389
258
 
390
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
391
- futures = [executor.submit(run_one, r) for r in recipe_list]
392
- for fut in as_completed(futures):
393
- r = fut.result()
394
- if r.error or r.results.get("failed"):
395
- failed_recipes.append(r)
259
+ if args.recipe_processing_order:
260
+ batches = build_recipe_batches(
261
+ recipe_list=recipe_list,
262
+ recipe_processing_order=args.recipe_processing_order,
263
+ )
264
+ for batch in batches:
265
+ batch_type = recipe_type_for(batch[0]) if batch else ""
266
+ logging.info(
267
+ f"Running {len(batch)} recipes for type={batch_type or 'unknown'}"
268
+ )
269
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
270
+ futures = [executor.submit(run_one, r) for r in batch]
271
+ for fut in as_completed(futures):
272
+ r = fut.result()
273
+ if r.error or r.results.get("failed"):
274
+ failed_recipes.append(r)
275
+ else:
276
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
277
+ futures = [executor.submit(run_one, r) for r in recipe_list]
278
+ for fut in as_completed(futures):
279
+ r = fut.result()
280
+ if r.error or r.results.get("failed"):
281
+ failed_recipes.append(r)
396
282
 
397
283
  # Apply git updates serially to avoid branch/commit conflicts when concurrency > 1
398
284
  for r in recipe_list:
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ import plistlib
5
+ import subprocess
6
+ from datetime import datetime
7
+ from itertools import chain
8
+ from pathlib import Path
9
+
10
+
11
+ class Recipe(object):
12
+ def __init__(self, name: str, post_processors: list = None):
13
+ self.filename = name
14
+ self.error = False
15
+ self.results = {}
16
+ self.updated = False
17
+ self.verified = None
18
+ self.pr_url = None
19
+ self.post_processors = post_processors
20
+
21
+ self._keys = None
22
+ self._has_run = False
23
+
24
+ @property
25
+ def name(self):
26
+ name = self.filename.split(".")[0]
27
+
28
+ return name
29
+
30
+ def verify_trust_info(self, args):
31
+ verbose_output = ["-vvvv"] if args.debug else []
32
+ prefs_file = (
33
+ ["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
34
+ )
35
+ autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
36
+ cmd = (
37
+ [autopkg_bin, "verify-trust-info", self.filename]
38
+ + verbose_output
39
+ + prefs_file
40
+ )
41
+ logging.debug(f"cmd: {cmd}")
42
+
43
+ result = subprocess.run(cmd, capture_output=True, text=True)
44
+ if result.returncode == 0:
45
+ self.verified = True
46
+ else:
47
+ self.results["message"] = (result.stderr or "").strip()
48
+ self.verified = False
49
+ return self.verified
50
+
51
+ def update_trust_info(self, args):
52
+ prefs_file = (
53
+ ["--prefs", args.autopkg_prefs.as_posix()] if args.autopkg_prefs else []
54
+ )
55
+ autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
56
+ cmd = [autopkg_bin, "update-trust-info", self.filename] + prefs_file
57
+ logging.debug(f"cmd: {cmd}")
58
+
59
+ # Fail loudly if this exits 0
60
+ try:
61
+ subprocess.check_call(cmd)
62
+ except subprocess.CalledProcessError as e:
63
+ logging.error(str(e))
64
+ raise e
65
+
66
+ def _parse_report(self, report):
67
+ with open(report, "rb") as f:
68
+ report_data = plistlib.load(f)
69
+
70
+ failed_items = report_data.get("failures", [])
71
+ imported_items = []
72
+ if report_data["summary_results"]:
73
+ # This means something happened
74
+ munki_results = report_data["summary_results"].get(
75
+ "munki_importer_summary_result", {}
76
+ )
77
+ imported_items.extend(munki_results.get("data_rows", []))
78
+
79
+ return {"imported": imported_items, "failed": failed_items}
80
+
81
+ def run(self, args):
82
+ if self.verified is False:
83
+ self.error = True
84
+ self.results["failed"] = True
85
+ self.results["imported"] = ""
86
+ else:
87
+ report_dir = Path("/private/tmp/autopkg")
88
+ report_time = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
89
+ report_name = Path(f"{self.name}-{report_time}.plist")
90
+
91
+ report_dir.mkdir(parents=True, exist_ok=True)
92
+ report = report_dir / report_name
93
+ report.touch(exist_ok=True)
94
+
95
+ try:
96
+ prefs_file = (
97
+ ["--prefs", args.autopkg_prefs.as_posix()]
98
+ if args.autopkg_prefs
99
+ else []
100
+ )
101
+ verbose_output = ["-vvvv"] if args.debug else []
102
+ post_processor_cmd = (
103
+ list(
104
+ chain.from_iterable(
105
+ [
106
+ ("--post", processor)
107
+ for processor in self.post_processors
108
+ ]
109
+ )
110
+ )
111
+ if self.post_processors
112
+ else []
113
+ )
114
+ autopkg_bin = getattr(args, "autopkg_bin", "/usr/local/bin/autopkg")
115
+ cmd = (
116
+ [autopkg_bin, "run", self.filename, "--report-plist", report]
117
+ + verbose_output
118
+ + prefs_file
119
+ + post_processor_cmd
120
+ )
121
+ logging.debug(f"cmd: {cmd}")
122
+
123
+ result = subprocess.run(cmd, capture_output=True, text=True)
124
+ if result.returncode == 0:
125
+ report_info = self._parse_report(report)
126
+ self.results = report_info
127
+ else:
128
+ self.error = True
129
+ self.results["failed"] = True
130
+ self.results["message"] = (result.stderr or "").strip()
131
+ self.results["imported"] = ""
132
+ except Exception as e: # pylint: disable=broad-exception-caught
133
+ logging.error(f"Recipe run failed: {e}")
134
+ self.error = True
135
+ self.results["failed"] = True
136
+ self.results["message"] = (result.stderr or "").strip()
137
+ self.results["imported"] = ""
138
+
139
+ return self
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, Protocol, TypeVar
4
+
5
+
6
+ class HasFilename(Protocol):
7
+ filename: str
8
+
9
+
10
+ T = TypeVar("T", bound=HasFilename)
11
+
12
+
13
+ def recipe_type_for(recipe: HasFilename) -> str:
14
+ parts = recipe.filename.split(".", 1)
15
+ return parts[1] if len(parts) == 2 else ""
16
+
17
+
18
+ def build_recipe_batches(
19
+ recipe_list: Iterable[T], recipe_processing_order
20
+ ) -> list[list[T]]:
21
+ recipe_list = list(recipe_list)
22
+ if not recipe_list:
23
+ return []
24
+ if not recipe_processing_order:
25
+ return [recipe_list]
26
+
27
+ batches: list[list[T]] = []
28
+ current_batch: list[T] = []
29
+ current_type = None
30
+ for recipe in recipe_list:
31
+ r_type = recipe_type_for(recipe)
32
+ if current_type is None or r_type == current_type:
33
+ current_batch.append(recipe)
34
+ current_type = r_type
35
+ continue
36
+ batches.append(current_batch)
37
+ current_batch = [recipe]
38
+ current_type = r_type
39
+ if current_batch:
40
+ batches.append(current_batch)
41
+ return batches
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "autopkg-wrapper"
3
- version = "2026.2.5"
3
+ version = "2026.2.6"
4
4
  description = "A package used to execute some autopkg functions, primarily within the context of a GitHub Actions runner."
5
5
  readme = "README.md"
6
6
  requires-python = "~=3.14.0"
@@ -4,7 +4,8 @@ from pathlib import Path as RealPath
4
4
  from types import SimpleNamespace
5
5
  from unittest.mock import patch
6
6
 
7
- from autopkg_wrapper.autopkg_wrapper import Recipe
7
+ from autopkg_wrapper.models.recipe import Recipe
8
+ from autopkg_wrapper.utils.recipe_batching import build_recipe_batches
8
9
 
9
10
 
10
11
  class TestAutopkgCommands(unittest.TestCase):
@@ -14,7 +15,7 @@ class TestAutopkgCommands(unittest.TestCase):
14
15
  debug=False, autopkg_prefs=None, autopkg_bin="/custom/autopkg"
15
16
  )
16
17
 
17
- with patch("autopkg_wrapper.autopkg_wrapper.subprocess.run") as run:
18
+ with patch("autopkg_wrapper.models.recipe.subprocess.run") as run:
18
19
  run.return_value = SimpleNamespace(returncode=0, stderr="", stdout="")
19
20
  ok = r.verify_trust_info(args)
20
21
 
@@ -30,7 +31,7 @@ class TestAutopkgCommands(unittest.TestCase):
30
31
  debug=True, autopkg_prefs=None, autopkg_bin="/usr/local/bin/autopkg"
31
32
  )
32
33
 
33
- with patch("autopkg_wrapper.autopkg_wrapper.subprocess.run") as run:
34
+ with patch("autopkg_wrapper.models.recipe.subprocess.run") as run:
34
35
  run.return_value = SimpleNamespace(
35
36
  returncode=1, stderr="bad trust", stdout=""
36
37
  )
@@ -43,7 +44,7 @@ class TestAutopkgCommands(unittest.TestCase):
43
44
  r = Recipe("Foo.download")
44
45
  args = SimpleNamespace(autopkg_prefs=None, autopkg_bin="/custom/autopkg")
45
46
 
46
- with patch("autopkg_wrapper.autopkg_wrapper.subprocess.check_call") as cc:
47
+ with patch("autopkg_wrapper.models.recipe.subprocess.check_call") as cc:
47
48
  r.update_trust_info(args)
48
49
 
49
50
  called_cmd = cc.call_args.args[0]
@@ -66,8 +67,8 @@ class TestAutopkgCommands(unittest.TestCase):
66
67
  return RealPath(td)
67
68
  return RealPath(arg)
68
69
 
69
- with patch("autopkg_wrapper.autopkg_wrapper.Path", side_effect=fake_path):
70
- with patch("autopkg_wrapper.autopkg_wrapper.subprocess.run") as run:
70
+ with patch("autopkg_wrapper.models.recipe.Path", side_effect=fake_path):
71
+ with patch("autopkg_wrapper.models.recipe.subprocess.run") as run:
71
72
  run.return_value = SimpleNamespace(
72
73
  returncode=0, stderr="", stdout=""
73
74
  )
@@ -84,6 +85,40 @@ class TestAutopkgCommands(unittest.TestCase):
84
85
  self.assertEqual(called_cmd[1], "run")
85
86
  self.assertIn("--report-plist", called_cmd)
86
87
 
88
+ def test_build_recipe_batches_without_processing_order(self):
89
+ recipes = [Recipe("Foo.upload.jamf"), Recipe("Foo.auto_install.jamf")]
90
+ batches = build_recipe_batches(
91
+ recipe_list=recipes, recipe_processing_order=None
92
+ )
93
+ self.assertEqual(len(batches), 1)
94
+ self.assertEqual(
95
+ [r.filename for r in batches[0]], [r.filename for r in recipes]
96
+ )
97
+
98
+ def test_build_recipe_batches_groups_by_type(self):
99
+ recipes = [
100
+ Recipe("Foo.upload.jamf"),
101
+ Recipe("Bar.upload.jamf"),
102
+ Recipe("Foo.auto_install.jamf"),
103
+ Recipe("Foo.self_service.jamf"),
104
+ ]
105
+ batches = build_recipe_batches(
106
+ recipe_list=recipes, recipe_processing_order=["upload", "auto_install"]
107
+ )
108
+ self.assertEqual(len(batches), 3)
109
+ self.assertEqual(
110
+ [r.filename for r in batches[0]],
111
+ ["Foo.upload.jamf", "Bar.upload.jamf"],
112
+ )
113
+ self.assertEqual(
114
+ [r.filename for r in batches[1]],
115
+ ["Foo.auto_install.jamf"],
116
+ )
117
+ self.assertEqual(
118
+ [r.filename for r in batches[2]],
119
+ ["Foo.self_service.jamf"],
120
+ )
121
+
87
122
 
88
123
  if __name__ == "__main__":
89
124
  unittest.main()
@@ -13,7 +13,7 @@ wheels = [
13
13
 
14
14
  [[package]]
15
15
  name = "autopkg-wrapper"
16
- version = "2026.2.5"
16
+ version = "2026.2.6"
17
17
  source = { editable = "." }
18
18
  dependencies = [
19
19
  { name = "chardet" },