Skip to content

Commit 23a0329

Browse files
committed
WIP: add stub files & make all type hints stricter
1 parent 0ae4433 commit 23a0329

13 files changed

Lines changed: 260 additions & 231 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ __pycache__/
33
__pypackages__/
44
.mypy_cache/
55
.pytest_cache/
6-
*.py[cod]
6+
*.py[cdio]
77
*$py.class
88

99
# BUILD ARTIFACTS

CHANGELOG.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,21 @@
1515
# <br><b>Changelog</b><br>
1616

1717

18+
<span id="v1-9-6" />
19+
20+
## ... `v1.9.6`
21+
22+
* The compiled version of the library now includes the type stub files (`.pyi`), so type checkers can properly check types.
23+
* Made all type hints in the whole library way more strict and accurate.
24+
* Removed leftover unnecessary runtime type-checks in several methods throughout the whole library.
25+
26+
1827
<span id="v1-9-5" />
1928

2029
## 25.01.2026 `v1.9.5`
2130

22-
* Add new class property `Console.encoding`, which returns the encoding used by the console (*e.g.* `utf-8`*,* `cp1252`*, …*).
23-
* Add multiple new class properties to the `System` class:
31+
* Added a new class property `Console.encoding`, which returns the encoding used by the console (*e.g.* `utf-8`*,* `cp1252`*, …*).
32+
* Added multiple new class properties to the `System` class:
2433
- `is_linux` Whether the current OS is Linux or not.
2534
- `is_mac` Whether the current OS is macOS or not.
2635
- `is_unix` Whether the current OS is a Unix-like OS (Linux, macOS, BSD, …) or not.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ build-backend = "setuptools.build_meta"
1414

1515
[project]
1616
name = "xulbux"
17-
version = "1.9.5"
17+
version = "1.9.6"
1818
description = "A Python library to simplify common programming tasks."
1919
readme = "README.md"
2020
authors = [{ name = "XulbuX", email = "xulbux.real@gmail.com" }]
@@ -130,6 +130,9 @@ package-dir = { "" = "src" }
130130
[tool.setuptools.packages.find]
131131
where = ["src"]
132132

133+
[tool.setuptools.package-data]
134+
xulbux = ["py.typed", "*.pyi", "**/*.pyi"]
135+
133136
[tool.pytest.ini_options]
134137
minversion = "7.0"
135138
addopts = "-ra -q"

setup.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from setuptools import setup
22
from pathlib import Path
3+
import subprocess
4+
import sys
35
import os
46

57

@@ -10,14 +12,65 @@ def find_python_files(directory: str) -> list[str]:
1012
return python_files
1113

1214

13-
# OPTIONALLY USE MYPYC COMPILATION
15+
def generate_stubs_for_package():
16+
print("\nGenerating stub files with stubgen...\n")
17+
18+
try:
19+
skip_stubgen = {
20+
Path("src/xulbux/base/types.py"), # COMPLEX TYPE DEFINITIONS
21+
}
22+
23+
src_dir = Path("src/xulbux")
24+
generated_count = 0
25+
skipped_count = 0
26+
27+
for py_file in src_dir.rglob("*.py"):
28+
pyi_file = py_file.with_suffix(".pyi")
29+
rel_path = py_file.relative_to(src_dir.parent)
30+
31+
if py_file in skip_stubgen:
32+
pyi_file.write_text(py_file.read_text(encoding="utf-8"), encoding="utf-8")
33+
print(f" copied {rel_path.with_suffix('.pyi')} (preserving type definitions)")
34+
skipped_count += 1
35+
continue
36+
37+
result = subprocess.run(
38+
[sys.executable, "-m", "mypy.stubgen",
39+
str(py_file),
40+
"-o", "src",
41+
"--include-private",
42+
"--export-less"],
43+
capture_output=True,
44+
text=True
45+
)
46+
47+
if result.returncode == 0:
48+
print(f" generated {rel_path.with_suffix('.pyi')}")
49+
generated_count += 1
50+
else:
51+
print(f" failed {rel_path}")
52+
if result.stderr:
53+
print(f" {result.stderr.strip()}")
54+
55+
print(f"\nStub generation complete! ({generated_count} generated, {skipped_count} copied)\n")
56+
57+
except Exception as e:
58+
fmt_error = "\n ".join(str(e).splitlines())
59+
print(f"[WARNING] Could not generate stubs:\n {fmt_error}\n")
60+
61+
1462
ext_modules = []
63+
64+
# OPTIONALLY USE MYPYC COMPILATION
1565
if os.environ.get("XULBUX_USE_MYPYC", "1") == "1":
1666
try:
1767
from mypyc.build import mypycify
68+
1869
print("\nCompiling with mypyc...\n")
1970
source_files = find_python_files("src/xulbux")
20-
ext_modules = mypycify(source_files)
71+
ext_modules = mypycify(source_files, opt_level="3")
72+
73+
generate_stubs_for_package()
2174

2275
except (ImportError, Exception) as e:
2376
fmt_error = "\n ".join(str(e).splitlines())

src/xulbux/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
__package_name__ = "xulbux"
2-
__version__ = "1.9.5"
2+
__version__ = "1.9.6"
33
__description__ = "A Python library to simplify common programming tasks."
44
__status__ = "Production/Stable"
55

src/xulbux/base/types.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,48 +27,48 @@
2727
#
2828
################################################## TypeAlias ##################################################
2929

30-
PathsList: TypeAlias = Union[list[Path], list[str], list[Path | str]]
30+
PathsList: TypeAlias = Union[list[Path], list[str], list[Union[Path, str]]]
3131
"""Union of all supported list types for a list of paths."""
3232

33-
DataStructure: TypeAlias = Union[list, tuple, set, frozenset, dict]
33+
DataStructure: TypeAlias = Union[list[Any], tuple[Any, ...], set[Any], frozenset[Any], dict[Any, Any]]
3434
"""Union of supported data structures used in the `data` module."""
3535
DataStructureTypes = (list, tuple, set, frozenset, dict)
3636
"""Tuple of supported data structures used in the `data` module."""
3737

38-
IndexIterable: TypeAlias = Union[list, tuple, set, frozenset]
38+
IndexIterable: TypeAlias = Union[list[Any], tuple[Any, ...], set[Any], frozenset[Any]]
3939
"""Union of all iterable types that support indexing operations."""
4040
IndexIterableTypes = (list, tuple, set, frozenset)
4141
"""Tuple of all iterable types that support indexing operations."""
4242

4343
Rgba: TypeAlias = Union[
4444
tuple[Int_0_255, Int_0_255, Int_0_255],
45-
tuple[Int_0_255, Int_0_255, Int_0_255, Float_0_1],
45+
tuple[Int_0_255, Int_0_255, Int_0_255, Optional[Float_0_1]],
4646
list[Int_0_255],
47-
list[Union[Int_0_255, Float_0_1]],
48-
dict[str, Union[int, float]],
47+
list[Union[Int_0_255, Optional[Float_0_1]]],
48+
"RgbaDict",
4949
"rgba",
5050
str,
5151
]
5252
"""Matches all supported RGBA color value formats."""
5353
Hsla: TypeAlias = Union[
5454
tuple[Int_0_360, Int_0_100, Int_0_100],
55-
tuple[Int_0_360, Int_0_100, Int_0_100, Float_0_1],
55+
tuple[Int_0_360, Int_0_100, Int_0_100, Optional[Float_0_1]],
5656
list[Union[Int_0_360, Int_0_100]],
57-
list[Union[Int_0_360, Int_0_100, Float_0_1]],
58-
dict[str, Union[int, float]],
57+
list[Union[Int_0_360, Int_0_100, Optional[Float_0_1]]],
58+
"HslaDict",
5959
"hsla",
6060
str,
6161
]
6262
"""Matches all supported HSLA color value formats."""
6363
Hexa: TypeAlias = Union[str, int, "hexa"]
64-
"""Matches all supported hexadecimal color value formats."""
64+
"""Matches all supported HEXA color value formats."""
6565

6666
AnyRgba: TypeAlias = Any
67-
"""Generic type alias for RGBA color values in any supported format (type checking disabled)."""
67+
"""Generic type alias for RGBA color values in any format (type checking disabled)."""
6868
AnyHsla: TypeAlias = Any
69-
"""Generic type alias for HSLA color values in any supported format (type checking disabled)."""
69+
"""Generic type alias for HSLA color values in any format (type checking disabled)."""
7070
AnyHexa: TypeAlias = Any
71-
"""Generic type alias for hexadecimal color values in any supported format (type checking disabled)."""
71+
"""Generic type alias for HEXA color values in any format (type checking disabled)."""
7272

7373
ArgParseConfig: TypeAlias = Union[set[str], "ArgConfigWithDefault", Literal["before", "after"]]
7474
"""Matches the command-line-parsing configuration of a single argument."""
@@ -92,7 +92,6 @@ class ArgConfigWithDefault(TypedDict):
9292
flags: set[str]
9393
default: str
9494

95-
9695
class ArgData(TypedDict):
9796
"""Schema for the resulting data of parsing a single command-line argument."""
9897
exists: bool
@@ -101,6 +100,28 @@ class ArgData(TypedDict):
101100
flag: Optional[str]
102101

103102

103+
class RgbaDict(TypedDict):
104+
"""Dictionary schema for RGBA color components."""
105+
r: Int_0_255
106+
g: Int_0_255
107+
b: Int_0_255
108+
a: Optional[Float_0_1]
109+
110+
class HslaDict(TypedDict):
111+
"""Dictionary schema for HSLA color components."""
112+
h: Int_0_360
113+
s: Int_0_100
114+
l: Int_0_100
115+
a: Optional[Float_0_1]
116+
117+
class HexaDict(TypedDict):
118+
"""Dictionary schema for HEXA color components."""
119+
r: str
120+
g: str
121+
b: str
122+
a: Optional[str]
123+
124+
104125
class MissingLibsMsgs(TypedDict):
105126
"""Configuration schema for custom messages in `System.check_libs()` when checking library dependencies."""
106127
found_missing: str

src/xulbux/code.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .regex import Regex
77
from .data import Data
88

9+
from typing import Any
910
import regex as _rx
1011

1112

@@ -48,19 +49,19 @@ def change_tab_size(cls, code: str, new_tab_size: int, remove_empty_lines: bool
4849
return "\n".join(code_lines)
4950
return code
5051

51-
result = []
52+
result: list[str] = []
5253
for line in code_lines:
5354
indent_level = (len(line) - len(stripped := line.lstrip())) // tab_spaces
5455
result.append((" " * (indent_level * new_tab_size)) + stripped)
5556

5657
return "\n".join(result)
5758

5859
@classmethod
59-
def get_func_calls(cls, code: str) -> list:
60+
def get_func_calls(cls, code: str) -> list[list[Any]]:
6061
"""Will try to get all function calls and return them as a list.\n
6162
-------------------------------------------------------------------
6263
- `code` -⠀the code to analyze"""
63-
nested_func_calls = []
64+
nested_func_calls: list[list[Any]] = []
6465

6566
for _, func_attrs in (funcs := _rx.findall(r"(?i)" + Regex.func_call(), code)):
6667
if (nested_calls := _rx.findall(r"(?i)" + Regex.func_call(), func_attrs)):

0 commit comments

Comments
 (0)