Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ __pycache__/
__pypackages__/
.mypy_cache/
.pytest_cache/
*.py[cod]
*.py[cdio]
*$py.class

# BUILD ARTIFACTS
Expand Down
14 changes: 12 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,22 @@
# <br><b>Changelog</b><br>


<span id="v1-9-6" />

## ... `v1.9.6`

* The compiled version of the library now includes the type stub files (`.pyi`), so type checkers can properly check types.
* Made all type hints in the whole library way more strict and accurate.
* Removed leftover unnecessary runtime type-checks in several methods throughout the whole library.
* Renamed the `Spinner` class from the `console` module to `Throbber`, since that name is closer to what it's actually used for.


<span id="v1-9-5" />

## 25.01.2026 `v1.9.5`

* Add new class property `Console.encoding`, which returns the encoding used by the console (*e.g.* `utf-8`*,* `cp1252`*, …*).
* Add multiple new class properties to the `System` class:
* Added a new class property `Console.encoding`, which returns the encoding used by the console (*e.g.* `utf-8`*,* `cp1252`*, …*).
* Added multiple new class properties to the `System` class:
- `is_linux` Whether the current OS is Linux or not.
- `is_mac` Whether the current OS is macOS or not.
- `is_unix` Whether the current OS is a Unix-like OS (Linux, macOS, BSD, …) or not.
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "xulbux"
version = "1.9.5"
version = "1.9.6"
description = "A Python library to simplify common programming tasks."
readme = "README.md"
authors = [{ name = "XulbuX", email = "xulbux.real@gmail.com" }]
Expand Down Expand Up @@ -130,6 +130,9 @@ package-dir = { "" = "src" }
[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools.package-data]
xulbux = ["py.typed", "*.pyi", "**/*.pyi"]

[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-ra -q"
Expand Down
57 changes: 55 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from setuptools import setup
from pathlib import Path
import subprocess
import sys
import os


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


# OPTIONALLY USE MYPYC COMPILATION
def generate_stubs_for_package():
print("\nGenerating stub files with stubgen...\n")

try:
skip_stubgen = {
Path("src/xulbux/base/types.py"), # COMPLEX TYPE DEFINITIONS
}

src_dir = Path("src/xulbux")
generated_count = 0
skipped_count = 0

for py_file in src_dir.rglob("*.py"):
pyi_file = py_file.with_suffix(".pyi")
rel_path = py_file.relative_to(src_dir.parent)

if py_file in skip_stubgen:
pyi_file.write_text(py_file.read_text(encoding="utf-8"), encoding="utf-8")
print(f" copied {rel_path.with_suffix('.pyi')} (preserving type definitions)")
skipped_count += 1
continue

result = subprocess.run(
[sys.executable, "-m", "mypy.stubgen",
str(py_file),
"-o", "src",
"--include-private",
"--export-less"],
capture_output=True,
text=True
)

if result.returncode == 0:
print(f" generated {rel_path.with_suffix('.pyi')}")
generated_count += 1
else:
print(f" failed {rel_path}")
if result.stderr:
print(f" {result.stderr.strip()}")

print(f"\nStub generation complete! ({generated_count} generated, {skipped_count} copied)\n")

except Exception as e:
fmt_error = "\n ".join(str(e).splitlines())
print(f"[WARNING] Could not generate stubs:\n {fmt_error}\n")


ext_modules = []

# OPTIONALLY USE MYPYC COMPILATION
if os.environ.get("XULBUX_USE_MYPYC", "1") == "1":
try:
from mypyc.build import mypycify

print("\nCompiling with mypyc...\n")
source_files = find_python_files("src/xulbux")
ext_modules = mypycify(source_files)
ext_modules = mypycify(source_files, opt_level="3")

generate_stubs_for_package()

except (ImportError, Exception) as e:
fmt_error = "\n ".join(str(e).splitlines())
Expand Down
2 changes: 1 addition & 1 deletion src/xulbux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__package_name__ = "xulbux"
__version__ = "1.9.5"
__version__ = "1.9.6"
__description__ = "A Python library to simplify common programming tasks."
__status__ = "Production/Stable"

Expand Down
49 changes: 35 additions & 14 deletions src/xulbux/base/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,48 +27,48 @@
#
################################################## TypeAlias ##################################################

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

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

IndexIterable: TypeAlias = Union[list, tuple, set, frozenset]
IndexIterable: TypeAlias = Union[list[Any], tuple[Any, ...], set[Any], frozenset[Any]]
"""Union of all iterable types that support indexing operations."""
IndexIterableTypes = (list, tuple, set, frozenset)
"""Tuple of all iterable types that support indexing operations."""

Rgba: TypeAlias = Union[
tuple[Int_0_255, Int_0_255, Int_0_255],
tuple[Int_0_255, Int_0_255, Int_0_255, Float_0_1],
tuple[Int_0_255, Int_0_255, Int_0_255, Optional[Float_0_1]],
list[Int_0_255],
list[Union[Int_0_255, Float_0_1]],
dict[str, Union[int, float]],
list[Union[Int_0_255, Optional[Float_0_1]]],
"RgbaDict",
"rgba",
str,
]
"""Matches all supported RGBA color value formats."""
Hsla: TypeAlias = Union[
tuple[Int_0_360, Int_0_100, Int_0_100],
tuple[Int_0_360, Int_0_100, Int_0_100, Float_0_1],
tuple[Int_0_360, Int_0_100, Int_0_100, Optional[Float_0_1]],
list[Union[Int_0_360, Int_0_100]],
list[Union[Int_0_360, Int_0_100, Float_0_1]],
dict[str, Union[int, float]],
list[Union[Int_0_360, Int_0_100, Optional[Float_0_1]]],
"HslaDict",
"hsla",
str,
]
"""Matches all supported HSLA color value formats."""
Hexa: TypeAlias = Union[str, int, "hexa"]
"""Matches all supported hexadecimal color value formats."""
"""Matches all supported HEXA color value formats."""

AnyRgba: TypeAlias = Any
"""Generic type alias for RGBA color values in any supported format (type checking disabled)."""
"""Generic type alias for RGBA color values in any format (type checking disabled)."""
AnyHsla: TypeAlias = Any
"""Generic type alias for HSLA color values in any supported format (type checking disabled)."""
"""Generic type alias for HSLA color values in any format (type checking disabled)."""
AnyHexa: TypeAlias = Any
"""Generic type alias for hexadecimal color values in any supported format (type checking disabled)."""
"""Generic type alias for HEXA color values in any format (type checking disabled)."""

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


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


class RgbaDict(TypedDict):
"""Dictionary schema for RGBA color components."""
r: Int_0_255
g: Int_0_255
b: Int_0_255
a: Optional[Float_0_1]

class HslaDict(TypedDict):
"""Dictionary schema for HSLA color components."""
h: Int_0_360
s: Int_0_100
l: Int_0_100
a: Optional[Float_0_1]

class HexaDict(TypedDict):
"""Dictionary schema for HEXA color components."""
r: str
g: str
b: str
a: Optional[str]


class MissingLibsMsgs(TypedDict):
"""Configuration schema for custom messages in `System.check_libs()` when checking library dependencies."""
found_missing: str
Expand Down
2 changes: 1 addition & 1 deletion src/xulbux/cli/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,6 @@ def is_latest_version() -> Optional[bool]:


def show_help() -> None:
FormatCodes._config_console()
FormatCodes._config_console() # type: ignore[protected-access]
print(CLI_HELP)
Console.pause_exit(pause=True, prompt=" [dim](Press any key to exit...)\n\n")
7 changes: 4 additions & 3 deletions src/xulbux/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from .regex import Regex
from .data import Data

from typing import Any
import regex as _rx


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

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

return "\n".join(result)

@classmethod
def get_func_calls(cls, code: str) -> list:
def get_func_calls(cls, code: str) -> list[list[Any]]:
"""Will try to get all function calls and return them as a list.\n
-------------------------------------------------------------------
- `code` -⠀the code to analyze"""
nested_func_calls = []
nested_func_calls: list[list[Any]] = []

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