Skip to content
Merged
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
21 changes: 21 additions & 0 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
# THIS WORKFLOW WILL BUILD WHEELS FOR ALL MAJOR PLATFORMS AND UPLOAD THEM TO PYPI

# TO BUILD AND INSTALL LOCALLY FOR TESTING, RUN THE FOLLOWING COMMAND:
# pip install "/path/to/PythonLibraryXulbuX" --no-deps --no-cache-dir --force-reinstall --no-build-isolation

# TO CREATE A NEW RELEASE, TAG A COMMIT WITH THE FOLLOWING FORMAT:
# git tag v1.X.Y
# git push origin v1.X.Y
# IF THE TAG v1.X.Y ALREADY EXISTS, RUN THE FOLLOWING COMMANDS FIRST:
# git tag -d v1.X.Y
# git push origin :refs/tags/v1.X.Y

name: Build and Publish
permissions:
Expand Down Expand Up @@ -29,6 +36,19 @@ jobs:
env:
CIBW_BUILD: cp310-* cp311-* cp312-* cp313-* cp314-*
CIBW_SKIP: "*-musllinux_*"
CIBW_BEFORE_BUILD: pip install setuptools>=80.0.0 wheel>=0.45.0 mypy>=1.19.0 mypy-extensions>=1.1.0 types-regex types-keyboard prompt_toolkit>=3.0.41
CIBW_BUILD_FRONTEND: "pip; args: --no-build-isolation"
CIBW_ENVIRONMENT: XULBUX_USE_MYPYC=1

- name: Verify wheels were built
run: |
ls -la ./wheelhouse/
if [ -z "$(ls -A ./wheelhouse/)" ]; then
echo "[ERROR] No wheels were built!"
exit 1
fi
echo "[SUCCESS] Built $(ls ./wheelhouse/*.whl | wc -l) wheels."
shell: bash

- uses: actions/upload-artifact@v6
with:
Expand All @@ -50,6 +70,7 @@ jobs:
path: dist/*.tar.gz

upload_pypi:
name: Upload to PyPI
needs: [build_wheels, build_sdist]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,25 @@
# <br><b>Changelog</b><br>


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

## 06.01.2026 `v1.9.4`

* Added a new base module `base.decorators` which contains custom decorators used throughout the library.
* Made `mypy_extensions` an optional dependency by wrapping all uses of `mypy_extensions.mypyc_attr` in a custom decorator that acts as a no-op if `mypy_extensions` is not installed.
* The methods from the `env_path` module that modify the PATH environment variable, no longer sort all paths alphabetically, but keep the original order, to not mess with the user's intended PATH order.
* Added a new TypeAlias `PathsList` to the `base.types` module, which matches a list of paths as strings or `pathlib.Path` objects.

**BREAKING CHANGES:**
* Renamed the module `path` to `file_sys` and its main class `Path` to `FileSys`, so you can better use it alongside the built-in `pathlib.Path` class without always needing to import one of them under an alias.
* Renamed most `FileSys` methods to better describe their functionality:
- `Path.extend()` is now `FileSys.extend_path()`
- `Path.extend_or_make()` is now `FileSys.extend_or_make_path()`
* Renamed the param `use_closest_match` in `FileSys.extend_path()` and `FileSys.extend_or_make_path()` to `fuzzy_match`, since that name is more commonly used for that functionality.
* Updated all library methods that work with paths to accept `pathlib.Path` objects additionally to strings, as path inputs.
* Also, all library methods that return paths now return `pathlib.Path` objects instead of strings.


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

## 01.01.2026 `v1.9.3` Big Update 🚀
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ from xulbux.color import rgba, hsla, hexa
<td><a href="https://github.com/XulbuX/PythonLibraryXulbuX/wiki/env_path"><img src="https://img.shields.io/badge/env__path-B272FC?style=for-the-badge" alt="env_path"></a></td>
<td><code>EnvPath</code> class, which includes methods to work with the PATH environment variable.</td>
</tr>
<tr>
<td><a href="https://github.com/XulbuX/PythonLibraryXulbuX/wiki/path"><img src="https://img.shields.io/badge/file__sys-B272FC?style=for-the-badge" alt="path"></a></td>
<td><code>FileSys</code> class, which includes methods to work with the file system and directories.</td>
</tr>
<tr>
<td><a href="https://github.com/XulbuX/PythonLibraryXulbuX/wiki/file"><img src="https://img.shields.io/badge/file-B272FC?style=for-the-badge" alt="file"></a></td>
<td><code>File</code> class, which includes methods to work with files and file paths.</td>
Expand All @@ -135,10 +139,6 @@ from xulbux.color import rgba, hsla, hexa
<td><code>Json</code> class, which includes methods to read, create and update JSON files,<br>
with support for comments inside the JSON data.</td>
</tr>
<tr>
<td><a href="https://github.com/XulbuX/PythonLibraryXulbuX/wiki/path"><img src="https://img.shields.io/badge/path-B272FC?style=for-the-badge" alt="path"></a></td>
<td><code>Path</code> class, which includes methods to work with file and directory paths.</td>
</tr>
<tr>
<td><a href="https://github.com/XulbuX/PythonLibraryXulbuX/wiki/regex"><img src="https://img.shields.io/badge/regex-B272FC?style=for-the-badge" alt="regex"></a></td>
<td><code>Regex</code> class, which includes methods to dynamically generate complex regex patterns<br>
Expand Down
42 changes: 7 additions & 35 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[build-system]
# SAME BUILD-DEPS ALSO NEED TO BE SPECIFIED IN CIBW_BEFORE_BUILD IN .github/workflows/build-and-publish.yml
requires = [
"setuptools>=80.0.0",
"wheel>=0.45.0",
Expand All @@ -13,26 +14,24 @@ build-backend = "setuptools.build_meta"

[project]
name = "xulbux"
version = "1.9.3"
authors = [{ name = "XulbuX", email = "xulbux.real@gmail.com" }]
maintainers = [{ name = "XulbuX", email = "xulbux.real@gmail.com" }]
version = "1.9.4"
description = "A Python library to simplify common programming tasks."
readme = "README.md"
authors = [{ name = "XulbuX", email = "xulbux.real@gmail.com" }]
maintainers = [{ name = "XulbuX", email = "xulbux.real@gmail.com" }]
license = "MIT"
license-files = ["LICENSE"]
requires-python = ">=3.10.0"
dependencies = [
"keyboard>=0.13.5",
"mypy-extensions>=1.1.0",
"prompt_toolkit>=3.0.41",
"regex>=2023.10.3",
]
optional-dependencies = { dev = [
"black>=23.7.0",
"flake8-pyproject>=1.2.3",
"flake8>=6.1.0",
"isort>=5.12.0",
"pytest>=7.4.2",
"toml>=0.10.2",
] }
classifiers = [
"Development Status :: 5 - Production/Stable",
Expand Down Expand Up @@ -118,33 +117,6 @@ keywords = [
[project.scripts]
xulbux-help = "xulbux.cli.help:show_help"

[tool.black]
line-length = 127
target-version = ['py310', 'py311', 'py312', 'py313', 'py314']
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
)/
'''

[tool.isort]
profile = "black"
line_length = 127
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true

[tool.flake8]
max-complexity = 12
max-line-length = 127
Expand All @@ -169,12 +141,12 @@ testpaths = [
"tests/test_console.py",
"tests/test_data.py",
"tests/test_env_path.py",
"tests/test_file_sys.py",
"tests/test_file.py",
"tests/test_format_codes.py",
"tests/test_json.py",
"tests/test_path.py",
"tests/test_metadata_consistency.py",
"tests/test_regex.py",
"tests/test_string.py",
"tests/test_system.py",
"tests/test_version_consistency.py",
]
26 changes: 19 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
from mypyc.build import mypycify
from setuptools import setup
from pathlib import Path
import os


def find_python_files(directory: str) -> list[str]:
python_files: list[str] = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(".py"):
python_files.append(os.path.join(root, file))
for file in Path(directory).rglob("*.py"):
python_files.append(str(file))
return python_files


source_files = find_python_files("src/xulbux")
# OPTIONALLY USE MYPYC COMPILATION
ext_modules = []
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)

except (ImportError, Exception) as e:
fmt_error = "\n ".join(str(e).splitlines())
print(
f"\n[WARNING] mypyc compilation disabled (not available or failed):\n {fmt_error}\n"
"\nInstalling as pure Python package...\n"
)

setup(
name="xulbux",
ext_modules=mypycify(source_files),
ext_modules=ext_modules,
)
20 changes: 15 additions & 5 deletions src/xulbux/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
__version__ = "1.9.3"
__package_name__ = "xulbux"
__version__ = "1.9.4"
__description__ = "A Python library to simplify common programming tasks."
__status__ = "Production/Stable"

__url__ = "https://github.com/XulbuX/PythonLibraryXulbuX"

__author__ = "XulbuX"
__email__ = "xulbux.real@gmail.com"
__license__ = "MIT"
__copyright__ = "Copyright (c) 2024 XulbuX"
__url__ = "https://github.com/XulbuX/PythonLibraryXulbuX"
__description__ = "A Python library to simplify common programming tasks."

__requires_python__ = ">=3.10.0"
__dependencies__ = [
"keyboard>=0.13.5",
"prompt_toolkit>=3.0.41",
"regex>=2023.10.3",
]

__all__ = [
"Code",
Expand All @@ -14,9 +24,9 @@
"Data",
"EnvPath",
"File",
"FileSys",
"FormatCodes",
"Json",
"Path",
"Regex",
"String",
"System",
Expand All @@ -28,9 +38,9 @@
from .data import Data
from .env_path import EnvPath
from .file import File
from .file_sys import FileSys
from .format_codes import FormatCodes
from .json import Json
from .path import Path
from .regex import Regex
from .string import String
from .system import System
28 changes: 28 additions & 0 deletions src/xulbux/base/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""
This module contains custom decorators used throughout the library.
"""

from typing import Callable, TypeVar, Any


T = TypeVar("T")


def _noop_decorator(obj: T) -> T:
"""No-op decorator that returns the object unchanged."""
return obj


def mypyc_attr(**kwargs: Any) -> Callable[[T], T]:
"""A custom decorator that wraps `mypy_extensions.mypyc_attr` when available,<br>
or acts as a no-op decorator when `mypy_extensions` is not installed.\n
This allows the use of `mypyc` compilation hints for compiling without making
`mypy_extensions` a required dependency.\n
-----------------------------------------------------------------------------------------
- `**kwargs` -⠀keyword arguments to pass to `mypy_extensions.mypyc_attr` if available"""
try:
from mypy_extensions import mypyc_attr as _mypyc_attr
return _mypyc_attr(**kwargs)
except ImportError:
# IF 'mypy_extensions' IS NOT INSTALLED, JUST RETURN A NO-OP DECORATOR
return _noop_decorator
2 changes: 1 addition & 1 deletion src/xulbux/base/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
This module contains all custom exception classes used throughout the library.
"""

from mypy_extensions import mypyc_attr
from .decorators import mypyc_attr

#
################################################## FILE ##################################################
Expand Down
4 changes: 4 additions & 0 deletions src/xulbux/base/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

from typing import TYPE_CHECKING, Annotated, TypeAlias, TypedDict, Optional, Protocol, Union, Any
from pathlib import Path

# PREVENT CIRCULAR IMPORTS
if TYPE_CHECKING:
Expand All @@ -26,6 +27,9 @@
#
################################################## TypeAlias ##################################################

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

DataStructure: TypeAlias = Union[list, tuple, set, frozenset, dict]
"""Union of supported data structures used in the `data` module."""
DataStructureTypes = (list, tuple, set, frozenset, dict)
Expand Down
2 changes: 1 addition & 1 deletion src/xulbux/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def is_js(cls, code: str, funcs: set[str] = {"__", "$t", "$lang"}) -> bool:
return True

js_score = 0.0
funcs_pattern = r"(" + "|".join(_rx.escape(f) for f in funcs) + r")" + Regex.brackets("()")
funcs_pattern = r"(" + "|".join(_rx.escape(func) for func in funcs) + r")" + Regex.brackets("()")
js_indicators: list[tuple[str, float]] = [
(r"\b(var|let|const)\s+[\w_$]+", 2.0), # JS VARIABLE DECLARATIONS
(r"\$[\w_$]+\s*=", 2.0), # jQuery-STYLE VARIABLES
Expand Down
2 changes: 1 addition & 1 deletion src/xulbux/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from .base.types import ArgConfigWithDefault, ArgResultRegular, ArgResultPositional, ProgressUpdater, AllTextChars, Rgba, Hexa
from .base.decorators import mypyc_attr
from .base.consts import COLOR, CHARS, ANSI

from .format_codes import _PATTERNS as _FC_PATTERNS, FormatCodes
Expand All @@ -16,7 +17,6 @@
from prompt_toolkit.validation import ValidationError, Validator
from prompt_toolkit.styles import Style
from prompt_toolkit.keys import Keys
from mypy_extensions import mypyc_attr
from contextlib import contextmanager
from io import StringIO
import prompt_toolkit as _pt
Expand Down
4 changes: 2 additions & 2 deletions src/xulbux/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,13 +318,13 @@ def get_value_by_path_id(cls, data: DataStructure, path_id: str, get_key: bool =
elif isinstance(current_data, IndexIterableTypes):
if i == len(path) - 1 and get_key:
if parent is None or not isinstance(parent, dict):
raise ValueError(f"Cannot get key from a non-dict parent at path '{path[:i+1]}'")
raise ValueError(f"Cannot get key from a non-dict parent at path '{path[:i + 1]}'")
return next(key for key, value in parent.items() if value is current_data)
parent = current_data
current_data = list(current_data)[path_idx] # CONVERT TO LIST FOR INDEXING

else:
raise TypeError(f"Unsupported type '{type(current_data)}' at path '{path[:i+1]}'")
raise TypeError(f"Unsupported type '{type(current_data)}' at path '{path[:i + 1]}'")

return current_data

Expand Down
Loading