portable-python 1.8.4__tar.gz → 1.8.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 (39) hide show
  1. {portable-python-1.8.4/src/portable_python.egg-info → portable-python-1.8.6}/PKG-INFO +1 -1
  2. portable-python-1.8.6/pyproject.toml +76 -0
  3. {portable-python-1.8.4 → portable-python-1.8.6}/setup.py +0 -1
  4. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/__init__.py +51 -36
  5. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/cli.py +7 -7
  6. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/config.py +68 -42
  7. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/cpython.py +41 -31
  8. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/_inspect.py +10 -11
  9. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/tkinter.py +3 -2
  10. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/xcpython.py +33 -16
  11. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/inspector.py +21 -17
  12. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/tracking.py +0 -3
  13. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/versions.py +31 -9
  14. {portable-python-1.8.4 → portable-python-1.8.6/src/portable_python.egg-info}/PKG-INFO +1 -1
  15. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_cleanup.py +1 -1
  16. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_failed.py +1 -1
  17. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_inspector.py +1 -1
  18. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_list.py +6 -5
  19. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_setup.py +7 -6
  20. portable-python-1.8.4/pyproject.toml +0 -2
  21. {portable-python-1.8.4 → portable-python-1.8.6}/DEVELOP.md +0 -0
  22. {portable-python-1.8.4 → portable-python-1.8.6}/LICENSE +0 -0
  23. {portable-python-1.8.4 → portable-python-1.8.6}/MANIFEST.in +0 -0
  24. {portable-python-1.8.4 → portable-python-1.8.6}/README.rst +0 -0
  25. {portable-python-1.8.4 → portable-python-1.8.6}/SECURITY.md +0 -0
  26. {portable-python-1.8.4 → portable-python-1.8.6}/requirements.txt +0 -0
  27. {portable-python-1.8.4 → portable-python-1.8.6}/setup.cfg +0 -0
  28. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/__main__.py +0 -0
  29. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python/external/__init__.py +0 -0
  30. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/SOURCES.txt +0 -0
  31. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/dependency_links.txt +0 -0
  32. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/entry_points.txt +0 -0
  33. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/requires.txt +0 -0
  34. {portable-python-1.8.4 → portable-python-1.8.6}/src/portable_python.egg-info/top_level.txt +0 -0
  35. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_build.py +0 -0
  36. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_invoker.py +0 -0
  37. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_prefix.py +0 -0
  38. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_recompress.py +0 -0
  39. {portable-python-1.8.4 → portable-python-1.8.6}/tests/test_report.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: portable-python
3
- Version: 1.8.4
3
+ Version: 1.8.6
4
4
  Summary: Portable python binaries
5
5
  Home-page: https://github.com/codrsquad/portable-python
6
6
  Author: Zoran Simic
@@ -0,0 +1,76 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+
4
+ [tool.ruff]
5
+ cache-dir = ".tox/.ruff_cache"
6
+ line-length = 140
7
+ src = ["src", "tests"]
8
+
9
+ [tool.ruff.lint]
10
+ extend-select = [
11
+ "A", # flake8-builtins
12
+ # "ARG", # flake8-unused-arguments
13
+ "B", # flake8-bugbear
14
+ "C4", # flake8-comprehensions
15
+ "C90", # mccabe
16
+ "D", # pydocstyle
17
+ "DTZ", # flake8-datetimez
18
+ "E", # pycodestyle errors
19
+ "EM", # flake8-errmsg
20
+ "ERA", # eradicate
21
+ "EXE", # flake8-executable
22
+ "F", # pyflakes
23
+ "FLY", # flynt
24
+ "G", # flake8-logging-format
25
+ "I", # isort
26
+ "INT", # flake8-gettext
27
+ "PGH", # pygrep-hooks
28
+ "PIE", # flake8-pie
29
+ "PT", # flake8-pytest
30
+ "PYI", # flake8-pyi
31
+ "Q", # flake8-quotes
32
+ "RSE", # flake8-raise
33
+ "RET", # flake8-return
34
+ "RUF", # ruff-specific
35
+ "S", # flake8-bandit
36
+ # "SIM", # flake8-simplify
37
+ "SLF", # flake8-self
38
+ "SLOT", # flake8-slots
39
+ "T10", # flake8-debugger
40
+ "TID", # flake8-tidy-imports
41
+ "TCH", # flake8-type-checking
42
+ # "TD", # flake8-todos
43
+ "TRY", # tryceratops
44
+ "W", # pycodestyle warnings
45
+ ]
46
+ ignore = [
47
+ # TODO: gradually remove these (document all the things)
48
+ "D100", # Missing docstring in public module
49
+ "D101", # Missing docstring in public class
50
+ "D102", # Missing docstring in public method
51
+ "D103", # Missing docstring in public function
52
+ "D104", # Missing docstring in public package
53
+ "D105", # Missing docstring in magic method
54
+ "D200", # One-line docstring should fit on one line with quotes
55
+ "D205", # 1 blank line required between summary line and description
56
+ "D400", # First line should end with a period
57
+ # Not useful:
58
+ "RET503", # Missing explicit `return` at the end of function able to return non-`None` value
59
+ ]
60
+
61
+ [tool.ruff.lint.flake8-builtins]
62
+ builtins-ignorelist = ["bin", "compile"]
63
+
64
+ [tool.ruff.lint.isort]
65
+ order-by-type = false
66
+
67
+ [tool.ruff.lint.mccabe]
68
+ max-complexity = 14
69
+
70
+ [tool.ruff.lint.pydocstyle]
71
+ convention = "numpy"
72
+
73
+ [tool.ruff.per-file-ignores]
74
+ "tests/*" = [
75
+ "S", # No security checks for tests
76
+ ]
@@ -1,6 +1,5 @@
1
1
  from setuptools import setup
2
2
 
3
-
4
3
  setup(
5
4
  name="portable-python",
6
5
  setup_requires="setupmeta",
@@ -15,7 +15,7 @@ import multiprocessing
15
15
  import os
16
16
  import pathlib
17
17
  import re
18
- from typing import List
18
+ from typing import ClassVar, List
19
19
 
20
20
  import runez
21
21
  from runez.http import RestClient
@@ -24,7 +24,6 @@ from runez.render import Header, PrettyTable
24
24
 
25
25
  from portable_python.versions import PPG
26
26
 
27
-
28
27
  LOG = logging.getLogger(__name__)
29
28
  RX_BINARY = re.compile(r"^.*\.(dylib|gmo|icns|ico|nib|prof.*|tar)$")
30
29
 
@@ -36,11 +35,16 @@ def is_binary_file(path):
36
35
  def patch_folder(folder, regex, replacement, ignore=None):
37
36
  """Replace all occurrences of 'old_text' by 'new_text' in all files in 'folder'
38
37
 
39
- Args:
40
- folder (pathlib.Path): Folder to scan
41
- regex: Regex to replace
42
- replacement (str): Replacement text
43
- ignore: Regex stating what to ignore
38
+ Parameters
39
+ ----------
40
+ folder : pathlib.Path
41
+ Folder to scan
42
+ regex : str
43
+ Regex to replace
44
+ replacement : str
45
+ Replacement text
46
+ ignore : re.Pattern | None
47
+ Regex stating what to ignore
44
48
  """
45
49
  for path in runez.ls_dir(folder):
46
50
  if not path.is_symlink() and (not ignore or not ignore.match(path.name)):
@@ -53,21 +57,21 @@ def patch_folder(folder, regex, replacement, ignore=None):
53
57
 
54
58
  def patch_file(path, regex, replacement):
55
59
  try:
56
- with open(path, "rt") as fh:
60
+ with open(path) as fh:
57
61
  text = fh.read()
58
62
 
59
63
  new_text = re.sub(regex, replacement, text, flags=re.MULTILINE)
60
64
  if text != new_text:
61
- with open(path, "wt") as fh:
65
+ with open(path, "w") as fh:
62
66
  fh.write(new_text)
63
67
 
64
- LOG.info("Patched '%s' in %s" % (regex, runez.short(path)))
68
+ LOG.info("Patched '%s' in %s", regex, runez.short(path))
65
69
 
66
70
  except Exception as e:
67
- with open(path, "rt", errors="ignore") as fh:
71
+ with open(path, errors="ignore") as fh:
68
72
  text = fh.read()
69
73
  if re.search(regex, text):
70
- LOG.warning("Can't patch '%s': %s" % (runez.short(path), e))
74
+ LOG.warning("Can't patch '%s': %s", runez.short(path), e)
71
75
 
72
76
 
73
77
  class FolderMask:
@@ -81,7 +85,7 @@ class FolderMask:
81
85
  """
82
86
 
83
87
  def __init__(self, target_folder):
84
- LOG.info("Applying isolation hack/mask to %s" % target_folder)
88
+ LOG.info("Applying isolation hack/mask to %s", target_folder)
85
89
  self.target_folder = target_folder
86
90
  r = runez.run("hdiutil", "attach", "-nomount", "ram://2048", fatal=Exception)
87
91
  self.ram_disk = r.output.strip()
@@ -93,7 +97,7 @@ class FolderMask:
93
97
  self.mounted = True
94
98
 
95
99
  def cleanup(self):
96
- LOG.info("Cleaning up isolation hack/mask for %s" % self.target_folder)
100
+ LOG.info("Cleaning up isolation hack/mask for %s", self.target_folder)
97
101
  if self.mounted:
98
102
  runez.run("umount", self.target_folder, fatal=False)
99
103
 
@@ -118,8 +122,12 @@ class BuildContext:
118
122
 
119
123
  def _resolved_isolation(self):
120
124
  """
121
- Returns:
122
- (str | None): What strategy to use to work around the fact that python's ./configure script looks at /usr/local
125
+ Isolation setting currently configured.
126
+
127
+ Returns
128
+ -------
129
+ str | None
130
+ What strategy to use to work around the fact that python's ./configure script looks at /usr/local
123
131
  """
124
132
  v = PPG.config.get_value("isolate-usr-local")
125
133
  if v == "auto":
@@ -182,7 +190,7 @@ class BuildContext:
182
190
 
183
191
  class BuildSetup:
184
192
  """
185
- This class drives the compilation, external modules first, then the target python itself.
193
+ Drives the compilation, external modules first, then the target python itself.
186
194
  All modules are compiled in the same manner, follow the same conventional build layout.
187
195
  """
188
196
 
@@ -191,10 +199,14 @@ class BuildSetup:
191
199
 
192
200
  def __init__(self, python_spec=None, modules=None, prefix=None):
193
201
  """
194
- Args:
195
- python_spec (str | PythonSpec | None): Python to build (family and version)
196
- modules (str | None): Modules to build (default: from config)
197
- prefix (str | None): --prefix to use
202
+ Parameters
203
+ ----------
204
+ python_spec : str | PythonSpec | None
205
+ Python to build (family and version)
206
+ modules : str | None
207
+ Modules to build (default: from config)
208
+ prefix : str | None
209
+ --prefix to use
198
210
  """
199
211
  if not python_spec or python_spec == "latest":
200
212
  python_spec = PPG.cpython.latest
@@ -267,11 +279,11 @@ class BuildSetup:
267
279
  with BuildContext(self) as build_context:
268
280
  self.build_context = build_context
269
281
  modules = self.python_builder.modules
270
- LOG.info("portable-python v%s, current folder: %s" % (runez.get_version(__name__), os.getcwd()))
282
+ LOG.info("portable-python v%s, current folder: %s", runez.get_version(__name__), os.getcwd())
271
283
  LOG.info(runez.joined(modules, list(modules)))
272
284
  LOG.info(PPG.config.config_files_report())
273
- LOG.info("Platform: %s" % PPG.target)
274
- LOG.info("Build report:\n%s" % self.python_builder.modules.report())
285
+ LOG.info("Platform: %s", PPG.target)
286
+ LOG.info("Build report:\n%s", self.python_builder.modules.report())
275
287
  self.validate_module_selection(fatal=not runez.DRYRUN and not self.x_debug)
276
288
  self.ensure_clean_folder(self.folders.components)
277
289
  self.ensure_clean_folder(self.folders.deps)
@@ -375,7 +387,6 @@ class ModuleCollection:
375
387
 
376
388
 
377
389
  class LinkerOutcome(enum.Enum):
378
-
379
390
  absent = "orange"
380
391
  failed = "red"
381
392
  shared = "blue"
@@ -389,6 +400,7 @@ class ModuleBuilder:
389
400
  m_build_cwd: str = None # Optional: relative (to unpacked source) folder where to run configure/make from
390
401
  m_debian = None
391
402
  m_include: str = None # Optional: subfolder to automatically list in CPATH when this module is active
403
+ m_telltale: ClassVar[list] # Optional: list of files that, if present, indicate this module is installed
392
404
 
393
405
  setup: BuildSetup
394
406
  parent_module: "ModuleBuilder" = None
@@ -396,8 +408,10 @@ class ModuleBuilder:
396
408
 
397
409
  def __init__(self, parent_module):
398
410
  """
399
- Args:
400
- parent_module (BuildSetup | ModuleBuilder): Associated parent
411
+ Parameters
412
+ ----------
413
+ parent_module : BuildSetup | ModuleBuilder
414
+ Associated parent
401
415
  """
402
416
  self.m_name = ModuleCollection.get_module_name(self.__class__)
403
417
  if isinstance(parent_module, BuildSetup):
@@ -458,7 +472,7 @@ class ModuleBuilder:
458
472
  if self.resolved_telltale:
459
473
  return "has %s" % self.resolved_telltale
460
474
 
461
- return "no %s" % getattr(self, "m_telltale")
475
+ return "no %s" % self.m_telltale
462
476
 
463
477
  def _find_telltale(self):
464
478
  telltales = getattr(self, "m_telltale", runez.UNSET)
@@ -525,8 +539,7 @@ class ModuleBuilder:
525
539
 
526
540
  def run_configure(self, program, *args, prefix=None):
527
541
  """
528
- Calling ./configure is similar across all components.
529
- This allows to have descendants customize each part relatively elegantly
542
+ Run ./configure script for this module.
530
543
  """
531
544
  if prefix is None:
532
545
  prefix = self.deps
@@ -564,7 +577,8 @@ class ModuleBuilder:
564
577
  yield
565
578
 
566
579
  except Exception as e:
567
- LOG.error("Error while compiling %r: %r", self, e)
580
+ overview = repr(e)
581
+ LOG.exception("Error while compiling %s: %s", self, overview)
568
582
  raise
569
583
 
570
584
  finally:
@@ -598,7 +612,7 @@ class ModuleBuilder:
598
612
  env_vars = self._get_env_vars()
599
613
  prev_env_vars = {}
600
614
  for var_name, value in env_vars.items():
601
- LOG.info("env %s=%s" % (var_name, runez.short(value, size=2048)))
615
+ LOG.info("env %s=%s", var_name, runez.short(value, size=2048))
602
616
  prev_env_vars[var_name] = os.environ.get(var_name)
603
617
  os.environ[var_name] = value
604
618
 
@@ -672,7 +686,6 @@ class ModuleBuilder:
672
686
 
673
687
 
674
688
  class PythonBuilder(ModuleBuilder):
675
-
676
689
  _bin_python: pathlib.Path = None
677
690
 
678
691
  def __init__(self, parent_module):
@@ -692,8 +705,10 @@ class PythonBuilder(ModuleBuilder):
692
705
  @property
693
706
  def bin_python(self):
694
707
  """
695
- Returns:
696
- (pathlib.Path | None): Path to freshly compiled bin/python, real file (not symlink)
708
+ Returns
709
+ -------
710
+ pathlib.Path | None
711
+ Path to freshly compiled bin/python, real file (not symlink)
697
712
  """
698
713
  if self._bin_python is None:
699
714
  self._bin_python = PPG.config.find_main_file(self.bin_folder / "python", self.version)
@@ -724,5 +739,5 @@ class PythonBuilder(ModuleBuilder):
724
739
  expected = 0o755 if path.is_dir() else 0o644
725
740
  current = path.stat().st_mode & 0o777
726
741
  if current != expected:
727
- LOG.info("Corrected permissions for %s (was %s)" % (runez.short(path), oct(current)))
742
+ LOG.info("Corrected permissions for %s (was %s)", runez.short(path), oct(current))
728
743
  path.chmod(expected)
@@ -8,7 +8,6 @@ from runez.render import PrettyTable
8
8
  from portable_python import BuildSetup, PPG
9
9
  from portable_python.inspector import LibAutoCorrect, PythonInspector
10
10
 
11
-
12
11
  LOG = logging.getLogger(__name__)
13
12
 
14
13
 
@@ -60,10 +59,6 @@ def build_report(modules, python_spec):
60
59
  def diagnostics():
61
60
  """Show diagnostics info"""
62
61
  with runez.Anchored("."):
63
- def _diagnostics():
64
- yield "invoker python", runez.SYS_INFO.invoker_python
65
- yield from runez.SYS_INFO.diagnostics()
66
-
67
62
  config = PPG.config.represented()
68
63
  print(PrettyTable.two_column_diagnostics(_diagnostics(), config))
69
64
 
@@ -106,6 +101,11 @@ def list_cmd(json, family):
106
101
  print(" %s: %s" % (runez.bold(mm), v))
107
102
 
108
103
 
104
+ def _diagnostics():
105
+ yield "invoker python", runez.SYS_INFO.invoker_python
106
+ yield from runez.SYS_INFO.diagnostics()
107
+
108
+
109
109
  def _find_recompress_source(folders, path):
110
110
  candidate = runez.to_path(path)
111
111
  if candidate.exists() or candidate.is_absolute():
@@ -158,7 +158,7 @@ def recompress(path, ext):
158
158
 
159
159
  \b
160
160
  Mildly useful for comparing sizes from different compressions
161
- """
161
+ """ # noqa: D301
162
162
  extension = runez.SYS_INFO.platform_id.canonical_compress_extension(ext)
163
163
  pspec = PythonSpec.from_text(path)
164
164
  folders = PPG.get_folders(base=".", family=pspec and pspec.family, version=pspec and pspec.version)
@@ -203,6 +203,6 @@ def lib_auto_correct(commit, prefix, path):
203
203
 
204
204
 
205
205
  if __name__ == "__main__":
206
- from portable_python.cli import main # noqa, re-import with proper package
206
+ from portable_python.cli import main
207
207
 
208
208
  main()
@@ -9,7 +9,6 @@ import runez
9
9
  import yaml
10
10
  from runez.pyenv import Version
11
11
 
12
-
13
12
  LOG = logging.getLogger(__name__)
14
13
 
15
14
  DEFAULT_CONFIG = """
@@ -79,9 +78,12 @@ class Config:
79
78
 
80
79
  def __init__(self, paths=None, target=None):
81
80
  """
82
- Args:
83
- paths (str | list | None): Path(s) to config file(s)
84
- target (str | runez.system.PlatformId | None): Target platform (for testing, defaults to current platform)
81
+ Parameters
82
+ ----------
83
+ paths : str | list | None
84
+ Path(s) to config file(s)
85
+ target : str | runez.system.PlatformId | None
86
+ Target platform (for testing, defaults to current platform)
85
87
  """
86
88
  self.paths = runez.flattened(paths, split=",")
87
89
  if not isinstance(target, runez.system.PlatformId):
@@ -98,17 +100,21 @@ class Config:
98
100
  return "%s [%s]" % (runez.plural(self._sources, "config source"), self.target)
99
101
 
100
102
  def completions(self, **given):
101
- res = dict(arch=self.target.arch, platform=self.target.platform, subsystem=self.target.subsystem, target=str(self.target))
103
+ res = {"arch": self.target.arch, "platform": self.target.platform, "subsystem": self.target.subsystem, "target": str(self.target)}
102
104
  res.update(given)
103
105
  return res
104
106
 
105
107
  def get_value(self, *key, by_platform=True):
106
108
  """
107
- Args:
108
- key (str | tuple): Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
109
- by_platform (bool): If True, value can be configured by platform
110
-
111
- Returns:
109
+ Parameters
110
+ ----------
111
+ key : strzz | tuple
112
+ Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
113
+ by_platform : bool
114
+ If True, value can be configured by platform
115
+
116
+ Returns
117
+ -------
112
118
  Associated value, if any
113
119
  """
114
120
  value, _ = self.get_entry(*key, by_platform=by_platform)
@@ -116,18 +122,23 @@ class Config:
116
122
 
117
123
  def get_entry(self, *key, by_platform=True):
118
124
  """
119
- Args:
120
- key (str | tuple): Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
121
- by_platform (bool): If True, value can be configured by platform
122
-
123
- Returns:
124
- Associated value, if any
125
+ Parameters
126
+ ----------
127
+ key : str | tuple
128
+ Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
129
+ by_platform : bool
130
+ If True, value can be configured by platform
131
+
132
+ Returns
133
+ -------
134
+ (str | int | float | bool | dict | list | None, ConfigSource | None)
135
+ Associated value (if any), together with the source that defined it
125
136
  """
126
137
  if by_platform:
127
138
  keys = (self.target.platform, self.target.arch, *key), (self.target.platform, *key), key
128
139
 
129
140
  else:
130
- keys = (key, )
141
+ keys = (key,)
131
142
 
132
143
  for k in keys:
133
144
  for source in self._sources:
@@ -150,7 +161,7 @@ class Config:
150
161
  return value
151
162
 
152
163
  def config_files_report(self):
153
- """One liner describing which config files are used, if any"""
164
+ """One-liner describing which config files are used, if any"""
154
165
  if len(self._sources) > 1:
155
166
  return "Config files: %s" % runez.joined(self._sources[:-1], delimiter=", ")
156
167
 
@@ -174,7 +185,7 @@ class Config:
174
185
  def delete(path):
175
186
  size = runez.filesize(path)
176
187
  runez.delete(path, logger=None)
177
- LOG.info("Deleted %s (%s)" % (runez.short(path), runez.represented_bytesize(size)))
188
+ LOG.info("Deleted %s (%s)", runez.short(path), runez.represented_bytesize(size))
178
189
  return size
179
190
 
180
191
  @staticmethod
@@ -187,10 +198,14 @@ class Config:
187
198
 
188
199
  def cleanup_configured_globs(self, title, module, *keys):
189
200
  """
190
- Args:
191
- title (str): Title to use in log messages
192
- module (portable_python.PythonBuilder): Associated python builder module
193
- *keys (str): Config keys to lookup
201
+ Parameters
202
+ ----------
203
+ title : str
204
+ Title to use in log messages
205
+ module : portable_python.PythonBuilder
206
+ Associated python builder module
207
+ *keys : str
208
+ Config keys to lookup
194
209
  """
195
210
  globs = [(x, f"{x}-{self.target.platform}") for x in keys]
196
211
  globs = runez.flattened(globs, transform=self.get_value)
@@ -199,16 +214,20 @@ class Config:
199
214
 
200
215
  def cleanup_globs(self, title, module, *globs):
201
216
  """
202
- Args:
203
- title (str): Title to use in log messages
204
- module (portable_python.PythonBuilder): Associated python builder module
205
- *globs (str): Glob patterns to clean up
217
+ Parameters
218
+ ----------
219
+ title : str
220
+ Title to use in log messages
221
+ module : portable_python.PythonBuilder
222
+ Associated python builder module
223
+ *globs : str
224
+ Glob patterns to clean up
206
225
  """
207
226
  if globs:
208
227
  spec = [module.setup.folders.formatted(x) for x in globs]
209
228
  deleted_size = 0
210
229
  matcher = FileMatcher(spec)
211
- LOG.info("Applying clean-up spec: %s" % matcher)
230
+ LOG.info("Applying clean-up spec: %s", matcher)
212
231
  cleaned = []
213
232
  for dirpath, dirnames, filenames in os.walk(module.install_folder):
214
233
  removed = []
@@ -241,7 +260,7 @@ class Config:
241
260
  _find_file_duplicates(seen, folder)
242
261
  duplicates = {k: v for k, v in seen.items() if len(v) > 1}
243
262
  for dupes in duplicates.values():
244
- LOG.info("Found duplicates: %s" % runez.joined(dupes, delimiter=", "))
263
+ LOG.info("Found duplicates: %s", runez.joined(dupes, delimiter=", "))
245
264
  dupes = sorted(dupes, key=lambda x: len(str(x)))
246
265
  if len(dupes) == 2:
247
266
  shorter, longer = dupes
@@ -261,14 +280,19 @@ class Config:
261
280
  return basename, "%s%s" % (basename, version.major), "%s%s" % (basename, version.mm)
262
281
 
263
282
  @staticmethod
264
- def find_main_file(desired: pathlib.Path, version: Version):
283
+ def find_main_file(desired, version):
265
284
  """
266
- Args:
267
- desired: Desired path (base name considered if path not directly available)
268
- version: Associated version
269
-
270
- Returns:
271
- (pathlib.Path | None): Path to associated real file (not symlink)
285
+ Parameters
286
+ ----------
287
+ desired : pathlib.Path
288
+ Desired path (base name considered if path not directly available)
289
+ version : Version
290
+ Associated version
291
+
292
+ Returns
293
+ -------
294
+ pathlib.Path | None
295
+ Path to associated real file (not symlink)
272
296
  """
273
297
  p = Config.real_path(desired)
274
298
  if p:
@@ -335,10 +359,14 @@ class ConfigSource:
335
359
 
336
360
  def get_value(self, key):
337
361
  """
338
- Args:
339
- key (str | tuple): Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
340
-
341
- Returns:
362
+ Parameters
363
+ ----------
364
+ key : str | tuple
365
+ Key to look up, tuple represents hierarchy, ie: a/b -> (a, b)
366
+
367
+ Returns
368
+ -------
369
+ str | int | float | bool | dict | list | None
342
370
  Associated value, if any
343
371
  """
344
372
  return self._deep_get(self.data, key)
@@ -360,7 +388,6 @@ class ConfigSource:
360
388
 
361
389
 
362
390
  class FileMatcher:
363
-
364
391
  def __init__(self, clean_spec):
365
392
  self.matches = []
366
393
  for spec in clean_spec:
@@ -376,7 +403,6 @@ class FileMatcher:
376
403
 
377
404
 
378
405
  class SingleFileMatch:
379
-
380
406
  _on_folder = False
381
407
  _rx_basename = None
382
408
  _rx_path = None