config-cli-gui 0.2.7__tar.gz → 0.2.8__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 (83) hide show
  1. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/HISTORY.md +9 -0
  2. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/PKG-INFO +2 -1
  3. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/README.md +1 -1
  4. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/config.yaml +6 -1
  5. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/usage/config.md +6 -5
  6. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/pyproject.toml +1 -0
  7. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui/_version.py +3 -3
  8. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui/config.py +8 -78
  9. config_cli_gui-0.2.8/src/config_cli_gui/configtypes/color.py +43 -0
  10. config_cli_gui-0.2.8/src/config_cli_gui/configtypes/font.py +74 -0
  11. config_cli_gui-0.2.8/src/config_cli_gui/configtypes/vector.py +31 -0
  12. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui/gui.py +108 -4
  13. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui.egg-info/PKG-INFO +2 -1
  14. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui.egg-info/SOURCES.txt +4 -0
  15. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui.egg-info/requires.txt +1 -0
  16. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/config/config_example.py +9 -2
  17. config_cli_gui-0.2.8/tests/example_project/gui/__init__.py +0 -0
  18. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/test_config_manager.py +9 -2
  19. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/test_docs.py +1 -1
  20. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/uv.lock +40 -1
  21. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/FUNDING.yml +0 -0
  22. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  23. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  24. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  25. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/actions/setup-environment/action.yml +0 -0
  26. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/dependabot.yml +0 -0
  27. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/init.sh +0 -0
  28. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/release_message.sh +0 -0
  29. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/update_funding.py +0 -0
  30. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/workflows/main.yml +0 -0
  31. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/workflows/release.yml +0 -0
  32. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.github/workflows/update_readme.yml +0 -0
  33. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.gitignore +0 -0
  34. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.idea/runConfigurations/config_generate.xml +0 -0
  35. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.idea/runConfigurations/example_project_cli.xml +0 -0
  36. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.idea/runConfigurations/example_project_gui.xml +0 -0
  37. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.pre-commit-config.yaml +0 -0
  38. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/.readthedocs.yaml +0 -0
  39. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/LICENSE +0 -0
  40. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/Makefile +0 -0
  41. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/.nav.yml +0 -0
  42. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/_static/img/favicon.png +0 -0
  43. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/_static/img/logo.png +0 -0
  44. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/css/custom.css +0 -0
  45. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/develop/contributing.md +0 -0
  46. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/develop/make_windows.md +0 -0
  47. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/develop/naming_convention.md +0 -0
  48. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/funding/funding.md +0 -0
  49. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/getting-started/install.md +0 -0
  50. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/getting-started/virtual-environment.md +0 -0
  51. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/index.md +0 -0
  52. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/docs/usage/cli.md +0 -0
  53. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/mkdocs.yml +0 -0
  54. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/scripts/show_filelist.ps1 +0 -0
  55. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/scripts/show_tree.ps1 +0 -0
  56. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/scripts/show_tree.py +0 -0
  57. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/scripts/update_readme.py +0 -0
  58. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/setup.cfg +0 -0
  59. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/__init__.py +0 -0
  60. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui/__init__.py +0 -0
  61. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui/cli.py +0 -0
  62. {config_cli_gui-0.2.7/tests → config_cli_gui-0.2.8/src/config_cli_gui/configtypes}/__init__.py +0 -0
  63. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui/docs.py +0 -0
  64. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui.egg-info/dependency_links.txt +0 -0
  65. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui.egg-info/entry_points.txt +0 -0
  66. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/src/config_cli_gui.egg-info/top_level.txt +0 -0
  67. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/template.yml.url +0 -0
  68. {config_cli_gui-0.2.7/tests/example_project → config_cli_gui-0.2.8/tests}/__init__.py +0 -0
  69. {config_cli_gui-0.2.7/tests/example_project/cli → config_cli_gui-0.2.8/tests/example_project}/__init__.py +0 -0
  70. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/__main__.py +0 -0
  71. {config_cli_gui-0.2.7/tests/example_project/config → config_cli_gui-0.2.8/tests/example_project/cli}/__init__.py +0 -0
  72. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/cli/__main__.py +0 -0
  73. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/cli/cli_example.py +0 -0
  74. {config_cli_gui-0.2.7/tests/example_project/core → config_cli_gui-0.2.8/tests/example_project/config}/__init__.py +0 -0
  75. {config_cli_gui-0.2.7/tests/example_project/gui → config_cli_gui-0.2.8/tests/example_project/core}/__init__.py +0 -0
  76. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/core/base.py +0 -0
  77. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/core/logging.py +0 -0
  78. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/example.gpx +0 -0
  79. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/gui/__main__.py +0 -0
  80. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/gui/config.yaml +0 -0
  81. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/example_project/gui/gui_example.py +0 -0
  82. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/test_cli.py +0 -0
  83. {config_cli_gui-0.2.7 → config_cli_gui-0.2.8}/tests/test_generic_cli.py +0 -0
@@ -4,6 +4,15 @@ Changelog
4
4
 
5
5
  (unreleased)
6
6
  ------------
7
+ - Refactoring: get_image_font will look for the actual font path in the
8
+ system. [Paul Magister]
9
+ - Font preview. [Paul Magister]
10
+ - Add font category in config.py. [Paul Magister]
11
+
12
+
13
+ 0.2.7 (2025-11-29)
14
+ ------------------
15
+ - Docs: Update HISTORY.md for release 0.2.7. [Paul Magister]
7
16
  - Also show required params in gui. [Paul Magister]
8
17
 
9
18
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: config-cli-gui
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Feature-rich Python project template for config-cli-gui.
5
5
  Author: pamagister
6
6
  Requires-Python: <3.12,>=3.10
@@ -8,6 +8,7 @@ Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: pydantic>=2.11.7
10
10
  Requires-Dist: pyyaml>=6.0.2
11
+ Requires-Dist: pillow>=12.0.0
11
12
  Provides-Extra: dev
12
13
  Requires-Dist: pytest>=8.4.0; extra == "dev"
13
14
  Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
@@ -62,11 +62,11 @@ from datetime import datetime
62
62
  from pathlib import Path
63
63
 
64
64
  from config_cli_gui.config import (
65
- Color,
66
65
  ConfigCategory,
67
66
  ConfigManager,
68
67
  ConfigParameter,
69
68
  )
69
+ from config_cli_gui.configtypes.color import Color
70
70
  from config_cli_gui.docs import DocumentationGenerator
71
71
 
72
72
 
@@ -49,4 +49,9 @@ misc:
49
49
  # Date setting for the application | type=datetime
50
50
  some_date: '2025-12-31T10:30:45'
51
51
  # Path to the file to use | type=PosixPath
52
- some_file: some_file.txt
52
+ some_file: some_file.txt
53
+ # Font setting for the application | type=Font
54
+ some_font:
55
+ - DejaVuSans.ttf
56
+ - 12
57
+ - '#0000ff'
@@ -41,9 +41,10 @@ The parameters in the cli category can be accessed via the command line interfac
41
41
 
42
42
  ## Category "misc"
43
43
 
44
- | Name | Type | Description | Default | Choices |
45
- |------------|-----------|-----------------------------------|---------------------------------------------|---------|
46
- | some_file | PosixPath | Path to the file to use | PosixPath('some_file.txt') | - |
47
- | some_color | Color | Color setting for the application | Color(255, 0, 0) | - |
48
- | some_date | datetime | Date setting for the application | datetime.datetime(2025, 12, 31, 10, 30, 45) | - |
44
+ | Name | Type | Description | Default | Choices |
45
+ |------------|-----------|-----------------------------------|--------------------------------------------------------------|---------|
46
+ | some_file | PosixPath | Path to the file to use | PosixPath('some_file.txt') | - |
47
+ | some_color | Color | Color setting for the application | Color(255, 0, 0) | - |
48
+ | some_date | datetime | Date setting for the application | datetime.datetime(2025, 12, 31, 10, 30, 45) | - |
49
+ | some_font | Font | Font setting for the application | Font(type='DejaVuSans.ttf', size=12, color=Color(0, 0, 255)) | - |
49
50
 
@@ -10,6 +10,7 @@ requires-python = ">=3.10,<3.12"
10
10
  dependencies = [
11
11
  "pydantic>=2.11.7",
12
12
  "pyyaml>=6.0.2",
13
+ "pillow>=12.0.0",
13
14
  ]
14
15
 
15
16
  # Dev dependencies as optional dependencies
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.2.7'
32
- __version_tuple__ = version_tuple = (0, 2, 7)
31
+ __version__ = version = '0.2.8'
32
+ __version_tuple__ = version_tuple = (0, 2, 8)
33
33
 
34
- __commit_id__ = commit_id = 'g56f4529b2'
34
+ __commit_id__ = commit_id = 'g013f3a747'
@@ -8,83 +8,9 @@ from typing import Any
8
8
  import yaml
9
9
  from pydantic import BaseModel
10
10
 
11
-
12
- class Vector:
13
- """Class that represents a vector or point in 2D or 3D"""
14
-
15
- def __init__(self, x: int | float, y: int | float, z: int | float | None = None):
16
- self.x = x
17
- self.y = y
18
- self.z = z
19
-
20
- def to_list(self) -> list[int]:
21
- return [self.x, self.y, self.z] if self.z is not None else [self.x, self.y]
22
-
23
- @classmethod
24
- def from_list(cls, coordinate: list[int | str | float]) -> "Vector":
25
- if len(coordinate) == 2:
26
- return cls(float(coordinate[0]), float(coordinate[1]))
27
- if len(coordinate) >= 3:
28
- return cls(float(coordinate[0]), float(coordinate[1]), float(coordinate[2]))
29
- return cls()
30
-
31
- @classmethod
32
- def from_str(cls, coordinate: str) -> "Vector":
33
- return cls.from_list(coordinate.strip("()[]").split(","))
34
-
35
- def to_str(self) -> str:
36
- return f"({self.x}, {self.y}, {self.z})" if self.z is not None else f"({self.x}, {self.y})" # type: ignore
37
-
38
- def __str__(self):
39
- return self.to_str()
40
-
41
- def __repr__(self):
42
- return f"Vector{str(self)}"
43
-
44
-
45
- class Color:
46
- """Simple color class for RGB values."""
47
-
48
- def __init__(self, r: int = 0, g: int = 0, b: int = 0):
49
- self.r = max(0, min(255, r))
50
- self.g = max(0, min(255, g))
51
- self.b = max(0, min(255, b))
52
-
53
- def to_list(self) -> list[int]:
54
- return [self.r, self.g, self.b]
55
-
56
- def to_rgb(self) -> tuple[float]:
57
- return (self.r / 255, self.g / 255, self.b / 255)
58
-
59
- def to_pil(self) -> tuple[int, ...]:
60
- """Convert Color object to Pillow-compatible RGB tuple."""
61
- return tuple(int(c) for c in self.to_list())
62
-
63
- def to_hex(self) -> str:
64
- return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
65
-
66
- @classmethod
67
- def from_list(cls, rgb_list: list[int | str]) -> "Color":
68
- if len(rgb_list) >= 3:
69
- return cls(int(rgb_list[0]), int(rgb_list[1]), int(rgb_list[2]))
70
- return cls()
71
-
72
- @classmethod
73
- def from_hex(cls, hex_color: str) -> "Color":
74
- hex_color = hex_color.lstrip("#")
75
- if len(hex_color) == 6:
76
- return cls(
77
- int(hex_color[0:2], 16),
78
- int(hex_color[2:4], 16),
79
- int(hex_color[4:6], 16),
80
- )
81
- return cls()
82
-
83
- def __str__(self):
84
- return self.to_hex()
85
-
86
- def __repr__(self):
87
- return f"Color({self.r}, {self.g}, {self.b})"
11
+ from config_cli_gui.configtypes.color import Color
12
+ from config_cli_gui.configtypes.font import Font
13
+ from config_cli_gui.configtypes.vector import Vector
88
14
 
89
15
 
90
16
  @dataclass
@@ -197,6 +123,8 @@ class ConfigManager:
197
123
  continue
198
124
 
199
125
  # Type conversions
126
+ if isinstance(param.value, Font) and isinstance(param_value, list):
127
+ param_value = Font.from_list(param_value)
200
128
  if isinstance(param.value, Color) and isinstance(param_value, list):
201
129
  param_value = Color.from_list(param_value)
202
130
  if isinstance(param.value, Color) and isinstance(param_value, str):
@@ -237,7 +165,9 @@ class ConfigManager:
237
165
  result[category_name] = {}
238
166
  for param in category.get_parameters():
239
167
  val = getattr(category, param.name).value
240
- if isinstance(val, Color):
168
+ if isinstance(val, Font):
169
+ val = val.to_list()
170
+ elif isinstance(val, Color):
241
171
  val = val.to_hex()
242
172
  elif isinstance(val, Vector):
243
173
  val = str(val.to_str())
@@ -0,0 +1,43 @@
1
+ class Color:
2
+ """Simple color class for RGB values."""
3
+
4
+ def __init__(self, r: int = 0, g: int = 0, b: int = 0):
5
+ self.r = max(0, min(255, r))
6
+ self.g = max(0, min(255, g))
7
+ self.b = max(0, min(255, b))
8
+
9
+ def to_list(self) -> list[int]:
10
+ return [self.r, self.g, self.b]
11
+
12
+ def to_rgb(self) -> tuple[float, float, float]:
13
+ return self.r / 255, self.g / 255, self.b / 255
14
+
15
+ def to_pil(self) -> tuple[int, ...]:
16
+ """Convert Color object to Pillow-compatible RGB tuple."""
17
+ return tuple(int(c) for c in self.to_list())
18
+
19
+ def to_hex(self) -> str:
20
+ return f"#{self.r:02x}{self.g:02x}{self.b:02x}"
21
+
22
+ @classmethod
23
+ def from_list(cls, rgb_list: list[int | str]) -> "Color":
24
+ if len(rgb_list) >= 3:
25
+ return cls(int(rgb_list[0]), int(rgb_list[1]), int(rgb_list[2]))
26
+ return cls()
27
+
28
+ @classmethod
29
+ def from_hex(cls, hex_color: str) -> "Color":
30
+ hex_color = hex_color.lstrip("#")
31
+ if len(hex_color) == 6:
32
+ return cls(
33
+ int(hex_color[0:2], 16),
34
+ int(hex_color[2:4], 16),
35
+ int(hex_color[4:6], 16),
36
+ )
37
+ return cls()
38
+
39
+ def __str__(self):
40
+ return self.to_hex()
41
+
42
+ def __repr__(self):
43
+ return f"Color({self.r}, {self.g}, {self.b})"
@@ -0,0 +1,74 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Any
4
+
5
+ from PIL import ImageFont
6
+
7
+ from config_cli_gui.configtypes.color import Color
8
+
9
+
10
+ def list_system_fonts() -> list[str]:
11
+ font_dirs = [
12
+ "/usr/share/fonts",
13
+ "/usr/local/share/fonts",
14
+ str(Path.home() / ".fonts"),
15
+ "/Library/Fonts",
16
+ "/System/Library/Fonts",
17
+ "C:/Windows/Fonts",
18
+ ]
19
+
20
+ fonts: list[str] = []
21
+ for d in font_dirs:
22
+ if os.path.isdir(d):
23
+ for root, _, files in os.walk(d):
24
+ for f in files:
25
+ if f.lower().endswith((".ttf", ".otf")):
26
+ fonts.append(os.path.join(root, f))
27
+ return fonts
28
+
29
+
30
+ class Font:
31
+ """Represents a font with type, size and color."""
32
+
33
+ # Klassenattribute nach Klassendefinition setzen
34
+ font_files = list_system_fonts()
35
+ font_names = sorted([os.path.basename(f) for f in font_files])
36
+ font_files_sorted = sorted(font_files, key=os.path.basename)
37
+
38
+ def __init__(self, font_type: str, size: float, color: "Color"):
39
+ self.name = font_type
40
+ self.size = size
41
+ self.color = color
42
+
43
+ def to_list(self) -> list[Any]:
44
+ return [self.name, self.size, self.color.to_hex()]
45
+
46
+ @classmethod
47
+ def from_list(cls, font_data: list[Any]) -> "Font":
48
+ if len(font_data) < 3:
49
+ return cls("Arial", 12, Color(0, 0, 0))
50
+
51
+ font_type, size, color_val = font_data
52
+ color = (
53
+ Color.from_hex(color_val) if isinstance(color_val, str) else Color.from_list(color_val)
54
+ )
55
+ return cls(str(font_type), float(size), color)
56
+
57
+ def get_image_font(self) -> ImageFont.FreeTypeFont:
58
+ """Return a PIL FreeTypeFont, with fallback to default."""
59
+ try:
60
+ if self.name in self.font_names:
61
+ idx = self.font_names.index(self.name)
62
+ path = self.font_files_sorted[idx]
63
+ return ImageFont.truetype(path, int(self.size))
64
+ except Exception as e:
65
+ print(f"Fehler beim Laden der Schrift '{self.name}': {e}")
66
+
67
+ print("Fallback: Nutze Default-Font.")
68
+ return ImageFont.load_default()
69
+
70
+ def __repr__(self) -> str:
71
+ return f"Font(type='{self.name}', size={self.size}, color={self.color!r})"
72
+
73
+ def __str__(self) -> str:
74
+ return f"{self.name}, {self.size}pt, {self.color}"
@@ -0,0 +1,31 @@
1
+ class Vector:
2
+ """Class that represents a vector or point in 2D or 3D"""
3
+
4
+ def __init__(self, x: int | float, y: int | float, z: int | float | None = None):
5
+ self.x = x
6
+ self.y = y
7
+ self.z = z
8
+
9
+ def to_list(self) -> list[int]:
10
+ return [self.x, self.y, self.z] if self.z is not None else [self.x, self.y]
11
+
12
+ @classmethod
13
+ def from_list(cls, coordinate: list[int | str | float]) -> "Vector":
14
+ if len(coordinate) == 2:
15
+ return cls(float(coordinate[0]), float(coordinate[1]))
16
+ if len(coordinate) >= 3:
17
+ return cls(float(coordinate[0]), float(coordinate[1]), float(coordinate[2]))
18
+ return cls()
19
+
20
+ @classmethod
21
+ def from_str(cls, coordinate: str) -> "Vector":
22
+ return cls.from_list(coordinate.strip("()[]").split(","))
23
+
24
+ def to_str(self) -> str:
25
+ return f"({self.x}, {self.y}, {self.z})" if self.z is not None else f"({self.x}, {self.y})" # type: ignore
26
+
27
+ def __str__(self):
28
+ return self.to_str()
29
+
30
+ def __repr__(self):
31
+ return f"Vector{str(self)}"
@@ -2,12 +2,22 @@
2
2
  """Generic GUI settings dialog generator for configuration framework."""
3
3
 
4
4
  import calendar
5
+ import os
5
6
  import tkinter as tk
6
7
  from datetime import datetime
7
8
  from pathlib import Path
8
9
  from tkinter import colorchooser, filedialog, messagebox, ttk
9
10
 
10
- from config_cli_gui.config import Color, ConfigCategory, ConfigManager, ConfigParameter, Vector
11
+ from PIL import Image, ImageDraw, ImageTk
12
+
13
+ from config_cli_gui.config import (
14
+ ConfigCategory,
15
+ ConfigManager,
16
+ ConfigParameter,
17
+ )
18
+ from config_cli_gui.configtypes.color import Color
19
+ from config_cli_gui.configtypes.font import Font
20
+ from config_cli_gui.configtypes.vector import Vector
11
21
 
12
22
 
13
23
  class ToolTip:
@@ -318,6 +328,10 @@ class GenericSettingsDialog:
318
328
  elif isinstance(param.value, Color):
319
329
  return self._create_color_widget(parent, param)
320
330
 
331
+ # Font type - Font picker
332
+ elif isinstance(param.value, Font):
333
+ return self._create_font_widget(parent, param)
334
+
321
335
  # Vector type - Vector editor
322
336
  elif isinstance(param.value, Vector):
323
337
  return self._create_vector_widget(parent, param)
@@ -428,6 +442,86 @@ class GenericSettingsDialog:
428
442
  frame.entry_widget = entry
429
443
  return frame
430
444
 
445
+ def _create_font_widget(self, parent, param: ConfigParameter):
446
+ """Create font picker widget."""
447
+ frame = ttk.Frame(parent)
448
+ font_value = param.value if isinstance(param.value, Font) else Font("Arial", 12, Color())
449
+
450
+ # Font type
451
+ font_type_var = tk.StringVar(value=os.path.basename(font_value.name))
452
+ font_type_combo = ttk.Combobox(
453
+ frame, textvariable=font_type_var, values=Font.font_names, state="readonly", width=25
454
+ )
455
+ font_type_combo.pack(side=tk.LEFT, padx=(0, 5))
456
+
457
+ # Font size
458
+ font_size_var = tk.DoubleVar(value=font_value.size)
459
+ font_size_spinbox = ttk.Spinbox(frame, from_=1, to=100, textvariable=font_size_var, width=5)
460
+ font_size_spinbox.pack(side=tk.LEFT, padx=(0, 5))
461
+
462
+ # Font color
463
+ color_var = tk.StringVar(value=font_value.color.to_hex())
464
+ color_display = tk.Label(frame, width=8, bg=font_value.color.to_hex())
465
+ color_display.pack(side=tk.LEFT, padx=(8, 2))
466
+
467
+ def pick_color():
468
+ color = colorchooser.askcolor(color=color_var.get())
469
+ if color[1]:
470
+ color_var.set(color[1])
471
+ color_display.config(bg=color[1])
472
+
473
+ pick_btn = ttk.Button(frame, text="Pick Color", command=pick_color)
474
+ pick_btn.pack(side=tk.LEFT, padx=(5, 0))
475
+
476
+ def on_color_change(*args):
477
+ try:
478
+ color_display.config(bg=color_var.get())
479
+ except tk.TclError:
480
+ pass
481
+
482
+ color_var.trace("w", on_color_change)
483
+
484
+ def show_preview():
485
+ font_size = int(font_size_var.get())
486
+ img_width = 170 + 3 * font_size
487
+ img_height = 20 + font_size
488
+
489
+ preview_win = tk.Toplevel(self.dialog)
490
+ preview_win.title("Font Preview")
491
+ preview_win.geometry(f"{img_width}x{img_height}")
492
+ preview_win.transient(self.dialog)
493
+ preview_win.grab_set()
494
+
495
+ font_color_hex = color_var.get()
496
+
497
+ font_obj = Font(font_type_var.get(), font_size, Color.from_hex(font_color_hex))
498
+ font = font_obj.get_image_font()
499
+
500
+ img = Image.new("RGB", (img_width, img_height), "white")
501
+ draw = ImageDraw.Draw(img)
502
+
503
+ text = "Sample"
504
+
505
+ draw.text(
506
+ (img_width / 2, img_height / 2), text, fill=font_color_hex, font=font, anchor="mm"
507
+ )
508
+
509
+ photo = ImageTk.PhotoImage(img)
510
+
511
+ img_label = tk.Label(preview_win, image=photo)
512
+ img_label.image = photo
513
+ img_label.pack()
514
+
515
+ preview_btn = ttk.Button(frame, text="Preview", command=show_preview)
516
+ preview_btn.pack(side=tk.LEFT, padx=(5, 0))
517
+
518
+ # Store variables in the frame for later access
519
+ frame.font_type_var = font_type_var
520
+ frame.font_size_var = font_size_var
521
+ frame.color_var = color_var
522
+ frame.entry_widget = font_type_combo
523
+ return frame
524
+
431
525
  def _create_vector_widget(self, parent, param: ConfigParameter):
432
526
  """Create vector editor widget."""
433
527
  frame = ttk.Frame(parent)
@@ -512,13 +606,23 @@ class GenericSettingsDialog:
512
606
  # Update configuration with widget values
513
607
  overrides = {}
514
608
  for key, widget in self.widgets.items():
515
- value = widget.var.get()
516
-
517
- # Parse value based on parameter type
518
609
  category_name, param_name = key.split("__", 1)
519
610
  category = self.config_manager.get_category(category_name)
520
611
  param_value = getattr(category, param_name).value
521
612
 
613
+ if isinstance(param_value, Font):
614
+ selected_font_name = widget.font_type_var.get()
615
+ font_type = selected_font_name
616
+ if selected_font_name in widget.font_names:
617
+ font_type = widget.font_files[widget.font_names.index(selected_font_name)]
618
+
619
+ font_size = widget.font_size_var.get()
620
+ font_color = Color.from_hex(widget.color_var.get())
621
+ overrides[key] = Font(font_type, font_size, font_color)
622
+ continue
623
+
624
+ value = widget.var.get()
625
+
522
626
  # Convert value to appropriate type
523
627
  if type(param_value) == bool:
524
628
  overrides[key] = value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: config-cli-gui
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Feature-rich Python project template for config-cli-gui.
5
5
  Author: pamagister
6
6
  Requires-Python: <3.12,>=3.10
@@ -8,6 +8,7 @@ Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
9
  Requires-Dist: pydantic>=2.11.7
10
10
  Requires-Dist: pyyaml>=6.0.2
11
+ Requires-Dist: pillow>=12.0.0
11
12
  Provides-Extra: dev
12
13
  Requires-Dist: pytest>=8.4.0; extra == "dev"
13
14
  Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
@@ -55,6 +55,10 @@ src/config_cli_gui.egg-info/dependency_links.txt
55
55
  src/config_cli_gui.egg-info/entry_points.txt
56
56
  src/config_cli_gui.egg-info/requires.txt
57
57
  src/config_cli_gui.egg-info/top_level.txt
58
+ src/config_cli_gui/configtypes/__init__.py
59
+ src/config_cli_gui/configtypes/color.py
60
+ src/config_cli_gui/configtypes/font.py
61
+ src/config_cli_gui/configtypes/vector.py
58
62
  tests/__init__.py
59
63
  tests/test_cli.py
60
64
  tests/test_config_manager.py
@@ -1,5 +1,6 @@
1
1
  pydantic>=2.11.7
2
2
  pyyaml>=6.0.2
3
+ pillow>=12.0.0
3
4
 
4
5
  [dev]
5
6
  pytest>=8.4.0
@@ -9,12 +9,13 @@ from datetime import datetime
9
9
  from pathlib import Path
10
10
 
11
11
  from config_cli_gui.config import (
12
- Color,
13
12
  ConfigCategory,
14
13
  ConfigManager,
15
14
  ConfigParameter,
16
- Vector,
17
15
  )
16
+ from config_cli_gui.configtypes.color import Color
17
+ from config_cli_gui.configtypes.font import Font
18
+ from config_cli_gui.configtypes.vector import Vector
18
19
  from config_cli_gui.docs import DocumentationGenerator
19
20
 
20
21
 
@@ -198,6 +199,12 @@ class MiscConfig(ConfigCategory):
198
199
  help="Date setting for the application",
199
200
  )
200
201
 
202
+ some_font: ConfigParameter = ConfigParameter(
203
+ name="some_font",
204
+ value=Font("DejaVuSans.ttf", size=12, color=Color(0, 0, 255)),
205
+ help="Font setting for the application",
206
+ )
207
+
201
208
 
202
209
  class ProjectConfigManager(ConfigManager): # Inherit from ConfigManager
203
210
  """Main configuration manager that handles all parameter categories."""
@@ -6,12 +6,13 @@ from unittest.mock import patch
6
6
  import pytest
7
7
 
8
8
  from config_cli_gui.config import (
9
- Color,
10
9
  ConfigCategory,
11
10
  ConfigManager,
12
11
  ConfigParameter,
13
- Vector,
14
12
  )
13
+ from config_cli_gui.configtypes.color import Color
14
+ from config_cli_gui.configtypes.font import Font
15
+ from config_cli_gui.configtypes.vector import Vector
15
16
 
16
17
 
17
18
  class ExampleCategory(ConfigCategory):
@@ -39,6 +40,12 @@ def test_color_basic():
39
40
  assert str(c) == "#0033ff"
40
41
 
41
42
 
43
+ def test_font_basic():
44
+ f = Font("DejaVuSans.ttf", size=12, color=Color(255, 0, 0))
45
+ assert f.to_list() == ["DejaVuSans.ttf", 12, "#ff0000"]
46
+ assert str(f) == "DejaVuSans.ttf, 12pt, #ff0000"
47
+
48
+
42
49
  def test_vector_2d():
43
50
  v = Vector(1, 2)
44
51
  assert v.to_list() == [1, 2]
@@ -1,11 +1,11 @@
1
1
  from unittest.mock import patch
2
2
 
3
3
  from config_cli_gui.config import (
4
- Color,
5
4
  ConfigCategory,
6
5
  ConfigManager,
7
6
  ConfigParameter,
8
7
  )
8
+ from config_cli_gui.configtypes.color import Color
9
9
  from config_cli_gui.docs import DocumentationGenerator
10
10
 
11
11
  # ---------------------------------------
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 2
2
+ revision = 3
3
3
  requires-python = ">=3.10, <3.12"
4
4
  resolution-markers = [
5
5
  "python_full_version >= '3.11'",
@@ -93,6 +93,7 @@ wheels = [
93
93
  name = "config-cli-gui"
94
94
  source = { editable = "." }
95
95
  dependencies = [
96
+ { name = "pillow" },
96
97
  { name = "pydantic" },
97
98
  { name = "pyyaml" },
98
99
  ]
@@ -128,6 +129,7 @@ requires-dist = [
128
129
  { name = "mkdocs", marker = "extra == 'docs'", specifier = ">=1.6.1" },
129
130
  { name = "mkdocs-awesome-nav", marker = "extra == 'docs'", specifier = ">=2.6.1" },
130
131
  { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.16.0" },
132
+ { name = "pillow", specifier = ">=12.0.0" },
131
133
  { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.2.0" },
132
134
  { name = "pydantic", specifier = ">=2.11.7" },
133
135
  { name = "pygments", marker = "extra == 'docs'", specifier = ">=2.19.1" },
@@ -481,6 +483,43 @@ wheels = [
481
483
  { url = "https://files.pythonhosted.org/packages/55/26/d0ad8b448476d0a1e8d3ea5622dc77b916db84c6aa3cb1e1c0965af948fc/pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6", size = 71791, upload-time = "2023-02-07T12:28:36.678Z" },
482
484
  ]
483
485
 
486
+ [[package]]
487
+ name = "pillow"
488
+ version = "12.0.0"
489
+ source = { registry = "https://pypi.org/simple" }
490
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" }
491
+ wheels = [
492
+ { url = "https://files.pythonhosted.org/packages/5d/08/26e68b6b5da219c2a2cb7b563af008b53bb8e6b6fcb3fa40715fcdb2523a/pillow-12.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:3adfb466bbc544b926d50fe8f4a4e6abd8c6bffd28a26177594e6e9b2b76572b", size = 5289809, upload-time = "2025-10-15T18:21:27.791Z" },
493
+ { url = "https://files.pythonhosted.org/packages/cb/e9/4e58fb097fb74c7b4758a680aacd558810a417d1edaa7000142976ef9d2f/pillow-12.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1ac11e8ea4f611c3c0147424eae514028b5e9077dd99ab91e1bd7bc33ff145e1", size = 4650606, upload-time = "2025-10-15T18:21:29.823Z" },
494
+ { url = "https://files.pythonhosted.org/packages/4b/e0/1fa492aa9f77b3bc6d471c468e62bfea1823056bf7e5e4f1914d7ab2565e/pillow-12.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d49e2314c373f4c2b39446fb1a45ed333c850e09d0c59ac79b72eb3b95397363", size = 6221023, upload-time = "2025-10-15T18:21:31.415Z" },
495
+ { url = "https://files.pythonhosted.org/packages/c1/09/4de7cd03e33734ccd0c876f0251401f1314e819cbfd89a0fcb6e77927cc6/pillow-12.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7b2a63fd6d5246349f3d3f37b14430d73ee7e8173154461785e43036ffa96ca", size = 8024937, upload-time = "2025-10-15T18:21:33.453Z" },
496
+ { url = "https://files.pythonhosted.org/packages/2e/69/0688e7c1390666592876d9d474f5e135abb4acb39dcb583c4dc5490f1aff/pillow-12.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d64317d2587c70324b79861babb9c09f71fbb780bad212018874b2c013d8600e", size = 6334139, upload-time = "2025-10-15T18:21:35.395Z" },
497
+ { url = "https://files.pythonhosted.org/packages/ed/1c/880921e98f525b9b44ce747ad1ea8f73fd7e992bafe3ca5e5644bf433dea/pillow-12.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d77153e14b709fd8b8af6f66a3afbb9ed6e9fc5ccf0b6b7e1ced7b036a228782", size = 7026074, upload-time = "2025-10-15T18:21:37.219Z" },
498
+ { url = "https://files.pythonhosted.org/packages/28/03/96f718331b19b355610ef4ebdbbde3557c726513030665071fd025745671/pillow-12.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:32ed80ea8a90ee3e6fa08c21e2e091bba6eda8eccc83dbc34c95169507a91f10", size = 6448852, upload-time = "2025-10-15T18:21:39.168Z" },
499
+ { url = "https://files.pythonhosted.org/packages/3a/a0/6a193b3f0cc9437b122978d2c5cbce59510ccf9a5b48825096ed7472da2f/pillow-12.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c828a1ae702fc712978bda0320ba1b9893d99be0badf2647f693cc01cf0f04fa", size = 7117058, upload-time = "2025-10-15T18:21:40.997Z" },
500
+ { url = "https://files.pythonhosted.org/packages/a7/c4/043192375eaa4463254e8e61f0e2ec9a846b983929a8d0a7122e0a6d6fff/pillow-12.0.0-cp310-cp310-win32.whl", hash = "sha256:bd87e140e45399c818fac4247880b9ce719e4783d767e030a883a970be632275", size = 6295431, upload-time = "2025-10-15T18:21:42.518Z" },
501
+ { url = "https://files.pythonhosted.org/packages/92/c6/c2f2fc7e56301c21827e689bb8b0b465f1b52878b57471a070678c0c33cd/pillow-12.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:455247ac8a4cfb7b9bc45b7e432d10421aea9fc2e74d285ba4072688a74c2e9d", size = 7000412, upload-time = "2025-10-15T18:21:44.404Z" },
502
+ { url = "https://files.pythonhosted.org/packages/b2/d2/5f675067ba82da7a1c238a73b32e3fd78d67f9d9f80fbadd33a40b9c0481/pillow-12.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6ace95230bfb7cd79ef66caa064bbe2f2a1e63d93471c3a2e1f1348d9f22d6b7", size = 2435903, upload-time = "2025-10-15T18:21:46.29Z" },
503
+ { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" },
504
+ { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" },
505
+ { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" },
506
+ { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" },
507
+ { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" },
508
+ { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" },
509
+ { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" },
510
+ { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" },
511
+ { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" },
512
+ { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" },
513
+ { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" },
514
+ { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" },
515
+ { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" },
516
+ { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" },
517
+ { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" },
518
+ { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" },
519
+ { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" },
520
+ { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" },
521
+ ]
522
+
484
523
  [[package]]
485
524
  name = "platformdirs"
486
525
  version = "4.3.8"
File without changes
File without changes
File without changes