xulbux 1.6.9__py3-none-any.whl → 1.7.1__py3-none-any.whl

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.

Potentially problematic release.


This version of xulbux might be problematic. Click here for more details.

xulbux/__init__.py CHANGED
@@ -1,8 +1,8 @@
1
- __version__ = "1.6.9"
1
+ __version__ = "1.7.1"
2
2
  __author__ = "XulbuX"
3
3
  __email__ = "xulbux.real@gmail.com"
4
4
  __license__ = "MIT"
5
- __copyright__ = "Copyright (c) 2025 XulbuX"
5
+ __copyright__ = "Copyright (c) 2024 XulbuX"
6
6
  __url__ = "https://github.com/XulbuX/PythonLibraryXulbuX"
7
7
  __description__ = "A library which includes a lot of really helpful functions."
8
8
  __all__ = [
xulbux/_cli_.py CHANGED
@@ -7,11 +7,13 @@ from .xx_console import Console
7
7
  def help_command():
8
8
  """Show some info about the library, with a brief explanation of how to use it."""
9
9
  color = {
10
- "lib": COLOR.ice,
11
- "import": COLOR.red,
12
- "class": COLOR.lavender,
13
- "types": COLOR.lightblue,
10
+ "class": COLOR.tangerine,
11
+ "const": COLOR.red,
12
+ "func": COLOR.cyan,
13
+ "import": COLOR.neongreen,
14
+ "lib": COLOR.orange,
14
15
  "punctuators": COLOR.darkgray,
16
+ "code_border": COLOR.gray,
15
17
  }
16
18
  FormatCodes.print(
17
19
  rf""" [_|b|#7075FF] __ __
@@ -22,29 +24,20 @@ def help_command():
22
24
 
23
25
  [i|{COLOR.coral}]A TON OF COOL FUNCTIONS, YOU NEED![*]
24
26
 
25
- [b|#75A2FF]Usage:[*]
26
- [{color['punctuators']}]# GENERAL LIBRARY[*]
27
- [{color['import']}]import [{color['lib']}]xulbux [{color['import']}]as [{color['lib']}]xx[*]
28
- [{color['punctuators']}]# CUSTOM TYPES[*]
29
- [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['lib']}]rgba[{color['punctuators']}], [{color['lib']}]hsla[{color['punctuators']}], [{color['lib']}]hexa[*]
30
-
31
- [b|#75A2FF]Includes:[*]
32
- [dim]() CUSTOM TYPES:
33
- [dim](•) [{color['class']}]rgba[{color['punctuators']}]/([i|{color['types']}]int[_|{color['punctuators']}],[i|{color['types']}]int[_|{color['punctuators']}],[i|{color['types']}]int[_|{color['punctuators']}],[i|{color['types']}]float[_|{color['punctuators']}])[*]
34
- [dim](•) [{color['class']}]hsla[{color['punctuators']}]/([i|{color['types']}]int[_|{color['punctuators']}],[i|{color['types']}]int[_|{color['punctuators']}],[i|{color['types']}]int[_|{color['punctuators']}],[i|{color['types']}]float[_|{color['punctuators']}])[*]
35
- [dim](•) [{color['class']}]hexa[{color['punctuators']}]/([i|{color['types']}]str[_|{color['punctuators']}]|[i|{color['types']}]int[_|{color['punctuators']}])[*]
36
- [dim](•) CODE STRING OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Code[*]
37
- [dim](•) WORKING WITH COLORS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Color[*]
38
- [dim](•) CONSOLE LOG AND ACTIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Console[*]
39
- [dim](•) PATH OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Path[*]
40
- [dim](•) FILE OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]File[*]
41
- [dim](•) JSON FILE OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Json[*]
42
- [dim](•) SYSTEM ACTIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]System[*]
43
- [dim](•) MANAGE THE ENV PATH VAR [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]EnvPath[*]
44
- [dim](•) EASY PRETTY PRINTING [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]FormatCodes[*]
45
- [dim](•) DATA OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Data[*]
46
- [dim](•) STRING OPERATIONS [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]String[*]
47
- [dim](•) REGEX PATTERN TEMPLATES [{color['lib']}]xx[{color['punctuators']}].[{color['class']}]Regex[*]
27
+ [b|#FCFCFF]Usage:[*]
28
+ [dim|{color['code_border']}](╭────────────────────────────────────────────────────╮)
29
+ [dim|{color['code_border']}](│) [{color['punctuators']}]# CONSTANTS[*] [dim|{color['code_border']}](│)
30
+ [dim|{color['code_border']}](│) [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['const']}]COLOR[{color['punctuators']}], [{color['const']}]CHARS[{color['punctuators']}], [{color['const']}]ANSI[*] [dim|{color['code_border']}](│)
31
+ [dim|{color['code_border']}](│) [{color['punctuators']}]# Classes[*] [dim|{color['code_border']}](│)
32
+ [dim|{color['code_border']}](│) [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['class']}]Code[{color['punctuators']}], [{color['class']}]Color[{color['punctuators']}], [{color['class']}]Console[{color['punctuators']}], ...[*] [dim|{color['code_border']}](│)
33
+ [dim|{color['code_border']}](│) [{color['punctuators']}]# types[*] [dim|{color['code_border']}](│)
34
+ [dim|{color['code_border']}]() [{color['import']}]from [{color['lib']}]xulbux [{color['import']}]import [{color['func']}]rgba[{color['punctuators']}], [{color['func']}]hsla[{color['punctuators']}], [{color['func']}]hexa[*] [dim|{color['code_border']}](│)
35
+ [dim|{color['code_border']}](╰────────────────────────────────────────────────────╯)
36
+ [b|#FCFCFF]Documentation:[*]
37
+ [dim|{color['code_border']}](╭────────────────────────────────────────────────────╮)
38
+ [dim|{color['code_border']}](│) [#DADADD]For more information see the GitHub page. [dim|{color['code_border']}](│)
39
+ [dim|{color['code_border']}](│) [u|#8085FF](https://github.com/XulbuX/PythonLibraryXulbuX/wiki) [dim|{color['code_border']}](│)
40
+ [dim|{color['code_border']}](╰────────────────────────────────────────────────────╯)
48
41
  [_]
49
42
  [dim](Press any key to exit...)
50
43
  """,
xulbux/_consts_.py CHANGED
@@ -26,6 +26,7 @@ class COLOR:
26
26
  yellow = "#FFD260"
27
27
  lime = "#C9F16E"
28
28
  green = "#7EE787"
29
+ neongreen = "#4CFF85"
29
30
  teal = "#50EAAF"
30
31
  cyan = "#3EDEE6"
31
32
  ice = "#77DBEF"
xulbux/xx_code.py CHANGED
@@ -50,7 +50,7 @@ class Code:
50
50
  nested_calls = _rx.findall(r"(?i)" + Regex.func_call(), func_attrs)
51
51
  if nested_calls:
52
52
  nested_func_calls.extend(nested_calls)
53
- return Data.remove_duplicates(funcs + nested_func_calls)
53
+ return list(Data.remove_duplicates(funcs + nested_func_calls))
54
54
 
55
55
  @staticmethod
56
56
  def is_js(code: str, funcs: list = ["__", "$t", "$lang"]) -> bool:
xulbux/xx_color.py CHANGED
@@ -33,10 +33,40 @@ The `Color` class, which contains all sorts of different color-related methods:
33
33
 
34
34
  from .xx_regex import Regex
35
35
 
36
- from typing import Optional
36
+ from typing import Annotated, TypeAlias, Iterator, Optional, Literal, Union, Any, cast
37
37
  import re as _re
38
38
 
39
39
 
40
+ Int_0_100 = Annotated[int, "An integer value between 0 and 100, inclusive."]
41
+ Int_0_255 = Annotated[int, "An integer value between 0 and 255, inclusive."]
42
+ Int_0_360 = Annotated[int, "An integer value between 0 and 360, inclusive."]
43
+ Float_0_1 = Annotated[float, "A float value between 0.0 and 1.0, inclusive."]
44
+
45
+ AnyRgba: TypeAlias = Any
46
+ AnyHsla: TypeAlias = Any
47
+ AnyHexa: TypeAlias = Any
48
+
49
+ Rgba: TypeAlias = Union[
50
+ tuple[Int_0_255, Int_0_255, Int_0_255],
51
+ tuple[Int_0_255, Int_0_255, Int_0_255, Float_0_1],
52
+ list[Int_0_255],
53
+ list[Union[Int_0_255, Float_0_1]],
54
+ dict[str, Union[int, float]],
55
+ "rgba",
56
+ str,
57
+ ]
58
+ Hsla: TypeAlias = Union[
59
+ tuple[Int_0_360, Int_0_100, Int_0_100],
60
+ tuple[Int_0_360, Int_0_100, Int_0_100, Float_0_1],
61
+ list[Union[Int_0_360, Int_0_100]],
62
+ list[Union[Int_0_360, Int_0_100, Float_0_1]],
63
+ dict[str, Union[int, float]],
64
+ "hsla",
65
+ str,
66
+ ]
67
+ Hexa: TypeAlias = Union[str, int, "hexa"]
68
+
69
+
40
70
  class rgba:
41
71
  """An RGB/RGBA color: is a tuple of 3 integers, representing the red (`0`-`255`), green (`0`-`255`), and blue (`0`-`255`).\n
42
72
  Also includes an optional 4th param, which is a float, that represents the alpha channel (`0.0`-`1.0`).\n
@@ -60,7 +90,11 @@ class rgba:
60
90
  - `with_alpha(alpha)` to create a new color with different alpha
61
91
  - `complementary()` to get the complementary color"""
62
92
 
63
- def __init__(self, r: int, g: int, b: int, a: float = None, _validate: bool = True):
93
+ def __init__(self, r: int, g: int, b: int, a: Optional[float] = None, _validate: bool = True):
94
+ self.r: int
95
+ self.g: int
96
+ self.b: int
97
+ self.a: Optional[float]
64
98
  if not _validate:
65
99
  self.r, self.g, self.b, self.a = r, g, b, a
66
100
  return
@@ -79,13 +113,10 @@ class rgba:
79
113
  def __len__(self) -> int:
80
114
  return 3 if self.a is None else 4
81
115
 
82
- def __iter__(self) -> iter:
116
+ def __iter__(self) -> Iterator:
83
117
  return iter((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))
84
118
 
85
- def __dict__(self) -> dict:
86
- return self.dict()
87
-
88
- def __getitem__(self, index: int) -> int:
119
+ def __getitem__(self, index: int) -> int | float:
89
120
  return ((self.r, self.g, self.b) + (() if self.a is None else (self.a, )))[index]
90
121
 
91
122
  def __repr__(self) -> str:
@@ -94,7 +125,7 @@ class rgba:
94
125
  def __str__(self) -> str:
95
126
  return f'({self.r}, {self.g}, {self.b}{"" if self.a is None else f", {self.a}"})'
96
127
 
97
- def __eq__(self, other: "rgba") -> bool:
128
+ def __eq__(self, other: "rgba") -> bool: # type: ignore[override]
98
129
  if not isinstance(other, rgba):
99
130
  return False
100
131
  return (self.r, self.g, self.b, self.a) == (other.r, other.g, other.b, other.a)
@@ -109,7 +140,7 @@ class rgba:
109
140
 
110
141
  def to_hsla(self) -> "hsla":
111
142
  """Returns the color as a `hsla()` color"""
112
- return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False)
143
+ return hsla(*self._rgb_to_hsl(self.r, self.g, self.b), self.a, _validate=False) # type: ignore[positional-arguments]
113
144
 
114
145
  def to_hexa(self) -> "hexa":
115
146
  """Returns the color as a `hexa()` color"""
@@ -147,11 +178,11 @@ class rgba:
147
178
  def invert(self, invert_alpha: bool = False) -> "rgba":
148
179
  """Inverts the color by rotating hue by 180 degrees and inverting lightness"""
149
180
  self.r, self.g, self.b = 255 - self.r, 255 - self.g, 255 - self.b
150
- if invert_alpha:
181
+ if invert_alpha and self.a is not None:
151
182
  self.a = 1 - self.a
152
183
  return rgba(self.r, self.g, self.b, self.a, _validate=False)
153
184
 
154
- def grayscale(self, method: str = "wcag2") -> "rgba":
185
+ def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "rgba":
155
186
  """Converts the color to grayscale using the luminance formula.\n
156
187
  ------------------------------------------------------------------
157
188
  The `method` is the luminance calculation method to use:
@@ -159,10 +190,10 @@ class rgba:
159
190
  - `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
160
191
  - `"simple"` Simple arithmetic mean (less accurate)
161
192
  - `"bt601"` ITU-R BT.601 standard (older TV standard)"""
162
- self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b, method=method)
193
+ self.r = self.g = self.b = int(Color.luminance(self.r, self.g, self.b, method=method))
163
194
  return rgba(self.r, self.g, self.b, self.a, _validate=False)
164
195
 
165
- def blend(self, other: "rgba", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
196
+ def blend(self, other: Rgba, ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
166
197
  """Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
167
198
  - if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
168
199
  - if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
@@ -171,7 +202,7 @@ class rgba:
171
202
  raise ValueError("'ratio' must be a float/int in [0.0, 1.0]")
172
203
  elif not isinstance(other, rgba):
173
204
  if Color.is_valid_rgba(other):
174
- other = rgba(*other, _validate=False)
205
+ other = Color.to_rgba(other)
175
206
  else:
176
207
  raise TypeError("'other' must be a valid RGBA color")
177
208
  ratio *= 2
@@ -217,20 +248,20 @@ class rgba:
217
248
  return self.to_hsla().complementary().to_rgba()
218
249
 
219
250
  def _rgb_to_hsl(self, r: int, g: int, b: int) -> tuple:
220
- r, g, b = r / 255.0, g / 255.0, b / 255.0
221
- max_c, min_c = max(r, g, b), min(r, g, b)
251
+ _r, _g, _b = r / 255.0, g / 255.0, b / 255.0
252
+ max_c, min_c = max(_r, _g, _b), min(_r, _g, _b)
222
253
  l = (max_c + min_c) / 2
223
254
  if max_c == min_c:
224
255
  h = s = 0
225
256
  else:
226
257
  delta = max_c - min_c
227
258
  s = delta / (1 - abs(2 * l - 1))
228
- if max_c == r:
229
- h = ((g - b) / delta) % 6
230
- elif max_c == g:
231
- h = ((b - r) / delta) + 2
259
+ if max_c == _r:
260
+ h = ((_g - _b) / delta) % 6
261
+ elif max_c == _g:
262
+ h = ((_b - _r) / delta) + 2
232
263
  else:
233
- h = ((r - g) / delta) + 4
264
+ h = ((_r - _g) / delta) + 4
234
265
  h /= 6
235
266
  return int(round(h * 360)), int(round(s * 100)), int(round(l * 100))
236
267
 
@@ -258,7 +289,11 @@ class hsla:
258
289
  - `with_alpha(alpha)` to create a new color with different alpha
259
290
  - `complementary()` to get the complementary color"""
260
291
 
261
- def __init__(self, h: int, s: int, l: int, a: float = None, _validate: bool = True):
292
+ def __init__(self, h: int, s: int, l: int, a: Optional[float] = None, _validate: bool = True):
293
+ self.h: int
294
+ self.s: int
295
+ self.l: int
296
+ self.a: Optional[float]
262
297
  if not _validate:
263
298
  self.h, self.s, self.l, self.a = h, s, l, a
264
299
  return
@@ -277,13 +312,10 @@ class hsla:
277
312
  def __len__(self) -> int:
278
313
  return 3 if self.a is None else 4
279
314
 
280
- def __iter__(self) -> iter:
315
+ def __iter__(self) -> Iterator:
281
316
  return iter((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))
282
317
 
283
- def __dict__(self) -> dict:
284
- return self.dict()
285
-
286
- def __getitem__(self, index: int) -> int:
318
+ def __getitem__(self, index: int) -> int | float:
287
319
  return ((self.h, self.s, self.l) + (() if self.a is None else (self.a, )))[index]
288
320
 
289
321
  def __repr__(self) -> str:
@@ -292,7 +324,7 @@ class hsla:
292
324
  def __str__(self) -> str:
293
325
  return f'({self.h}°, {self.s}%, {self.l}%{"" if self.a is None else f", {self.a}"})'
294
326
 
295
- def __eq__(self, other: "hsla") -> bool:
327
+ def __eq__(self, other: "hsla") -> bool: # type: ignore[override]
296
328
  if not isinstance(other, hsla):
297
329
  return False
298
330
  return (self.h, self.s, self.l, self.a) == (other.h, other.s, other.l, other.a)
@@ -307,7 +339,7 @@ class hsla:
307
339
 
308
340
  def to_rgba(self) -> "rgba":
309
341
  """Returns the color as a `rgba()` color"""
310
- return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False)
342
+ return rgba(*self._hsl_to_rgb(self.h, self.s, self.l), self.a, _validate=False) # type: ignore[positional-arguments]
311
343
 
312
344
  def to_hexa(self) -> "hexa":
313
345
  """Returns the color as a `hexa()` color"""
@@ -355,11 +387,11 @@ class hsla:
355
387
  """Inverts the color by rotating hue by 180 degrees and inverting lightness"""
356
388
  self.h = (self.h + 180) % 360
357
389
  self.l = 100 - self.l
358
- if invert_alpha:
390
+ if invert_alpha and self.a is not None:
359
391
  self.a = 1 - self.a
360
392
  return hsla(self.h, self.s, self.l, self.a, _validate=False)
361
393
 
362
- def grayscale(self, method: str = "wcag2") -> "hsla":
394
+ def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "hsla":
363
395
  """Converts the color to grayscale using the luminance formula.\n
364
396
  ------------------------------------------------------------------
365
397
  The `method` is the luminance calculation method to use:
@@ -367,11 +399,11 @@ class hsla:
367
399
  - `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
368
400
  - `"simple"` Simple arithmetic mean (less accurate)
369
401
  - `"bt601"` ITU-R BT.601 standard (older TV standard)"""
370
- l = Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l), method=method)
402
+ l = int(Color.luminance(*self._hsl_to_rgb(self.h, self.s, self.l), method=method))
371
403
  self.h, self.s, self.l, _ = rgba(l, l, l, _validate=False).to_hsla().values()
372
404
  return hsla(self.h, self.s, self.l, self.a, _validate=False)
373
405
 
374
- def blend(self, other: "hsla", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
406
+ def blend(self, other: Hsla, ratio: float = 0.5, additive_alpha: bool = False) -> "hsla":
375
407
  """Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
376
408
  - if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
377
409
  - if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
@@ -406,9 +438,9 @@ class hsla:
406
438
  return hsla((self.h + 180) % 360, self.s, self.l, self.a, _validate=False)
407
439
 
408
440
  def _hsl_to_rgb(self, h: int, s: int, l: int) -> tuple:
409
- h, s, l = h / 360, s / 100, l / 100
410
- if s == 0:
411
- r = g = b = int(l * 255)
441
+ _h, _s, _l = h / 360, s / 100, l / 100
442
+ if _s == 0:
443
+ r = g = b = int(_l * 255)
412
444
  else:
413
445
 
414
446
  def hue_to_rgb(p, q, t):
@@ -424,11 +456,11 @@ class hsla:
424
456
  return p + (q - p) * (2 / 3 - t) * 6
425
457
  return p
426
458
 
427
- q = l * (1 + s) if l < 0.5 else l + s - l * s
428
- p = 2 * l - q
429
- r = int(round(hue_to_rgb(p, q, h + 1 / 3) * 255))
430
- g = int(round(hue_to_rgb(p, q, h) * 255))
431
- b = int(round(hue_to_rgb(p, q, h - 1 / 3) * 255))
459
+ q = _l * (1 + _s) if _l < 0.5 else _l + _s - _l * _s
460
+ p = 2 * _l - q
461
+ r = int(round(hue_to_rgb(p, q, _h + 1 / 3) * 255))
462
+ g = int(round(hue_to_rgb(p, q, _h) * 255))
463
+ b = int(round(hue_to_rgb(p, q, _h - 1 / 3) * 255))
432
464
  return r, g, b
433
465
 
434
466
 
@@ -455,9 +487,20 @@ class hexa:
455
487
  - `with_alpha(alpha)` to create a new color with different alpha
456
488
  - `complementary()` to get the complementary color"""
457
489
 
458
- def __init__(self, color: str | int, _r: int = None, _g: int = None, _b: int = None, _a: float = None):
490
+ def __init__(
491
+ self,
492
+ color: str | int,
493
+ _r: Optional[int] = None,
494
+ _g: Optional[int] = None,
495
+ _b: Optional[int] = None,
496
+ _a: Optional[float] = None,
497
+ ):
498
+ self.r: int
499
+ self.g: int
500
+ self.b: int
501
+ self.a: Optional[float]
459
502
  if all(x is not None for x in (_r, _g, _b)):
460
- self.r, self.g, self.b, self.a = _r, _g, _b, _a
503
+ self.r, self.g, self.b, self.a = cast(int, _r), cast(int, _g), cast(int, _b), _a
461
504
  return
462
505
  if isinstance(color, hexa):
463
506
  raise ValueError("Color is already a hexa() color")
@@ -504,14 +547,11 @@ class hexa:
504
547
  def __len__(self) -> int:
505
548
  return 3 if self.a is None else 4
506
549
 
507
- def __iter__(self) -> iter:
550
+ def __iter__(self) -> Iterator:
508
551
  return iter((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}")
509
552
  + (() if self.a is None else (f"{int(self.a * 255):02X}", )))
510
553
 
511
- def __dict__(self) -> dict:
512
- return self.dict()
513
-
514
- def __getitem__(self, index: int) -> int:
554
+ def __getitem__(self, index: int) -> str | int:
515
555
  return ((f"{self.r:02X}", f"{self.g:02X}", f"{self.b:02X}") + (() if self.a is None else
516
556
  (f"{int(self.a * 255):02X}", )))[index]
517
557
 
@@ -521,7 +561,7 @@ class hexa:
521
561
  def __str__(self) -> str:
522
562
  return f'#{self.r:02X}{self.g:02X}{self.b:02X}{"" if self.a is None else f"{int(self.a * 255):02X}"}'
523
563
 
524
- def __eq__(self, other: "hexa") -> bool:
564
+ def __eq__(self, other: "hexa") -> bool: # type: ignore[override]
525
565
  """Returns whether the other color is equal to this one."""
526
566
  if not isinstance(other, hexa):
527
567
  return False
@@ -588,11 +628,11 @@ class hexa:
588
628
  def invert(self, invert_alpha: bool = False) -> "hexa":
589
629
  """Inverts the color by rotating hue by 180 degrees and inverting lightness"""
590
630
  self.r, self.g, self.b, self.a = self.to_rgba(False).invert().values()
591
- if invert_alpha:
631
+ if invert_alpha and self.a is not None:
592
632
  self.a = 1 - self.a
593
633
  return hexa("", self.r, self.g, self.b, self.a)
594
634
 
595
- def grayscale(self, method: str = "wcag2") -> "hexa":
635
+ def grayscale(self, method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2") -> "hexa":
596
636
  """Converts the color to grayscale using the luminance formula.\n
597
637
  ------------------------------------------------------------------
598
638
  The `method` is the luminance calculation method to use:
@@ -600,10 +640,10 @@ class hexa:
600
640
  - `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
601
641
  - `"simple"` Simple arithmetic mean (less accurate)
602
642
  - `"bt601"` ITU-R BT.601 standard (older TV standard)"""
603
- self.r = self.g = self.b = Color.luminance(self.r, self.g, self.b, method=method)
643
+ self.r = self.g = self.b = int(Color.luminance(self.r, self.g, self.b, method=method))
604
644
  return hexa("", self.r, self.g, self.b, self.a)
605
645
 
606
- def blend(self, other: "hexa", ratio: float = 0.5, additive_alpha: bool = False) -> "rgba":
646
+ def blend(self, other: Hexa, ratio: float = 0.5, additive_alpha: bool = False) -> "hexa":
607
647
  """Blends the current color with another color using the specified ratio (`0.0`-`1.0`):
608
648
  - if `ratio` is `0.0` it means 100% of the current color and 0% of the `other` color (2:0 mixture)
609
649
  - if `ratio` is `0.5` it means 50% of both colors (1:1 mixture)
@@ -641,7 +681,7 @@ class hexa:
641
681
  class Color:
642
682
 
643
683
  @staticmethod
644
- def is_valid_rgba(color: str | list | tuple | dict, allow_alpha: bool = True) -> bool:
684
+ def is_valid_rgba(color: AnyRgba, allow_alpha: bool = True) -> bool:
645
685
  try:
646
686
  if isinstance(color, rgba):
647
687
  return True
@@ -672,7 +712,7 @@ class Color:
672
712
  return False
673
713
 
674
714
  @staticmethod
675
- def is_valid_hsla(color: str | list | tuple | dict, allow_alpha: bool = True) -> bool:
715
+ def is_valid_hsla(color: AnyHsla, allow_alpha: bool = True) -> bool:
676
716
  try:
677
717
  if isinstance(color, hsla):
678
718
  return True
@@ -698,11 +738,16 @@ class Color:
698
738
  return False
699
739
  elif isinstance(color, str):
700
740
  return bool(_re.fullmatch(Regex.hsla_str(allow_alpha=allow_alpha), color))
741
+ return False
701
742
  except Exception:
702
743
  return False
703
744
 
704
745
  @staticmethod
705
- def is_valid_hexa(color: str | int, allow_alpha: bool = True, get_prefix: bool = False) -> bool | tuple[bool, str]:
746
+ def is_valid_hexa(
747
+ color: AnyHexa,
748
+ allow_alpha: bool = True,
749
+ get_prefix: bool = False,
750
+ ) -> bool | tuple[bool, Optional[Literal['#', '0x']]]:
706
751
  try:
707
752
  if isinstance(color, hexa):
708
753
  return (True, "#") if get_prefix else True
@@ -714,18 +759,19 @@ class Color:
714
759
  (color[2:], "0x") if color.startswith("0x") else (color, None))
715
760
  return ((bool(_re.fullmatch(Regex.hexa_str(allow_alpha=allow_alpha), color)),
716
761
  prefix) if get_prefix else bool(_re.fullmatch(Regex.hexa_str(allow_alpha=allow_alpha), color)))
762
+ return False
717
763
  except Exception:
718
764
  return (False, None) if get_prefix else False
719
765
 
720
766
  @staticmethod
721
- def is_valid(color: str | list | tuple | dict, allow_alpha: bool = True) -> bool:
767
+ def is_valid(color: AnyRgba | AnyHsla | AnyHexa, allow_alpha: bool = True) -> bool:
722
768
  return bool(
723
769
  Color.is_valid_rgba(color, allow_alpha) or Color.is_valid_hsla(color, allow_alpha)
724
770
  or Color.is_valid_hexa(color, allow_alpha)
725
771
  )
726
772
 
727
773
  @staticmethod
728
- def has_alpha(color: rgba | hsla | hexa) -> bool:
774
+ def has_alpha(color: Rgba | Hsla | Hexa) -> bool:
729
775
  """Check if the given color has an alpha channel.\n
730
776
  ---------------------------------------------------------------------------
731
777
  Input a RGBA, HSLA or HEXA color as `color`.
@@ -747,42 +793,42 @@ class Color:
747
793
  return False
748
794
 
749
795
  @staticmethod
750
- def to_rgba(color: hsla | hexa) -> rgba:
796
+ def to_rgba(color: Rgba | Hsla | Hexa) -> rgba:
751
797
  """Will try to convert any color type to a color of type RGBA."""
752
798
  if isinstance(color, (hsla, hexa)):
753
799
  return color.to_rgba()
754
800
  elif Color.is_valid_hsla(color):
755
- return hsla(*color, _validate=False).to_rgba()
801
+ return hsla(*color, _validate=False).to_rgba() # type: ignore[not-iterable]
756
802
  elif Color.is_valid_hexa(color):
757
- return hexa(color).to_rgba()
803
+ return hexa(cast(str | int, color)).to_rgba()
758
804
  elif Color.is_valid_rgba(color):
759
- return color if isinstance(color, rgba) else (rgba(*color, _validate=False))
805
+ return color if isinstance(color, rgba) else (rgba(*color, _validate=False)) # type: ignore[not-iterable]
760
806
  raise ValueError(f"Invalid color format '{color}'")
761
807
 
762
808
  @staticmethod
763
- def to_hsla(color: rgba | hexa) -> hsla:
809
+ def to_hsla(color: Rgba | Hsla | Hexa) -> hsla:
764
810
  """Will try to convert any color type to a color of type HSLA."""
765
811
  if isinstance(color, (rgba, hexa)):
766
812
  return color.to_hsla()
767
813
  elif Color.is_valid_rgba(color):
768
- return rgba(*color, _validate=False).to_hsla()
814
+ return rgba(*color, _validate=False).to_hsla() # type: ignore[not-iterable]
769
815
  elif Color.is_valid_hexa(color):
770
- return hexa(color).to_hsla()
816
+ return hexa(cast(str | int, color)).to_hsla()
771
817
  elif Color.is_valid_hsla(color):
772
- return color if isinstance(color, hsla) else (hsla(*color, _validate=False))
818
+ return color if isinstance(color, hsla) else (hsla(*color, _validate=False)) # type: ignore[not-iterable]
773
819
  raise ValueError(f"Invalid color format '{color}'")
774
820
 
775
821
  @staticmethod
776
- def to_hexa(color: rgba | hsla) -> hexa:
822
+ def to_hexa(color: Rgba | Hsla | Hexa) -> hexa:
777
823
  """Will try to convert any color type to a color of type HEXA."""
778
824
  if isinstance(color, (rgba, hsla)):
779
825
  return color.to_hexa()
780
826
  elif Color.is_valid_rgba(color):
781
- return rgba(*color, _validate=False).to_hexa()
827
+ return rgba(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
782
828
  elif Color.is_valid_hsla(color):
783
- return hsla(*color, _validate=False).to_hexa()
829
+ return hsla(*color, _validate=False).to_hexa() # type: ignore[not-iterable]
784
830
  elif Color.is_valid_hexa(color):
785
- return color if isinstance(color, hexa) else hexa(color)
831
+ return color if isinstance(color, hexa) else hexa(cast(str | int, color))
786
832
  raise ValueError(f"Invalid color format '{color}'")
787
833
 
788
834
  @staticmethod
@@ -821,7 +867,7 @@ class Color:
821
867
  r: int,
822
868
  g: int,
823
869
  b: int,
824
- a: float = None,
870
+ a: Optional[float] = None,
825
871
  preserve_original: bool = False,
826
872
  ) -> int:
827
873
  """Convert RGBA channels to a HEXA integer (alpha is optional).\n
@@ -879,7 +925,13 @@ class Color:
879
925
  raise ValueError(f"Invalid HEX integer '0x{hex_str}': expected in range [0x000000, 0xFFFFFF]")
880
926
 
881
927
  @staticmethod
882
- def luminance(r: int, g: int, b: int, output_type: type = None, method: str = "wcag2") -> int | float:
928
+ def luminance(
929
+ r: int,
930
+ g: int,
931
+ b: int,
932
+ output_type: Optional[type] = None,
933
+ method: Literal["wcag2", "wcag3", "simple", "bt601"] = "wcag2",
934
+ ) -> int | float:
883
935
  """Calculates the relative luminance of a color according to various standards.\n
884
936
  ----------------------------------------------------------------------------------
885
937
  The `output_type` controls the range of the returned luminance value:
@@ -891,21 +943,21 @@ class Color:
891
943
  - `"wcag3"` Draft WCAG 3.0 standard with improved coefficients
892
944
  - `"simple"` Simple arithmetic mean (less accurate)
893
945
  - `"bt601"` ITU-R BT.601 standard (older TV standard)"""
894
- r, g, b = r / 255.0, g / 255.0, b / 255.0
946
+ _r, _g, _b = r / 255.0, g / 255.0, b / 255.0
895
947
  if method == "simple":
896
- luminance = (r + g + b) / 3
948
+ luminance = (_r + _g + _b) / 3
897
949
  elif method == "bt601":
898
- luminance = 0.299 * r + 0.587 * g + 0.114 * b
950
+ luminance = 0.299 * _r + 0.587 * _g + 0.114 * _b
899
951
  elif method == "wcag3":
900
- r = Color._linearize_srgb(r)
901
- g = Color._linearize_srgb(g)
902
- b = Color._linearize_srgb(b)
903
- luminance = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b
952
+ _r = Color._linearize_srgb(_r)
953
+ _g = Color._linearize_srgb(_g)
954
+ _b = Color._linearize_srgb(_b)
955
+ luminance = 0.2126729 * _r + 0.7151522 * _g + 0.0721750 * _b
904
956
  else:
905
- r = Color._linearize_srgb(r)
906
- g = Color._linearize_srgb(g)
907
- b = Color._linearize_srgb(b)
908
- luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
957
+ _r = Color._linearize_srgb(_r)
958
+ _g = Color._linearize_srgb(_g)
959
+ _b = Color._linearize_srgb(_b)
960
+ luminance = 0.2126 * _r + 0.7152 * _g + 0.0722 * _b
909
961
  if output_type == int:
910
962
  return round(luminance * 100)
911
963
  elif output_type == float:
@@ -922,15 +974,16 @@ class Color:
922
974
  return ((c + 0.055) / 1.055)**2.4
923
975
 
924
976
  @staticmethod
925
- def text_color_for_on_bg(text_bg_color: rgba | hexa) -> rgba | hexa:
977
+ def text_color_for_on_bg(text_bg_color: Rgba | Hexa) -> rgba | hexa | int:
926
978
  was_hexa, was_int = Color.is_valid_hexa(text_bg_color), isinstance(text_bg_color, int)
927
979
  text_bg_color = Color.to_rgba(text_bg_color)
928
980
  brightness = 0.2126 * text_bg_color[0] + 0.7152 * text_bg_color[1] + 0.0722 * text_bg_color[2]
929
- return ((hexa("", 255, 255, 255) if was_hexa else rgba(255, 255, 255, _validate=False)) if brightness < 128 else
981
+ return (((0xFFFFFF if was_int else hexa("", 255, 255, 255)) if was_hexa else rgba(255, 255, 255, _validate=False))
982
+ if brightness < 128 else
930
983
  ((0x000 if was_int else hexa("", 0, 0, 0)) if was_hexa else rgba(0, 0, 0, _validate=False)))
931
984
 
932
985
  @staticmethod
933
- def adjust_lightness(color: rgba | hexa, lightness_change: float) -> rgba | hexa:
986
+ def adjust_lightness(color: Rgba | Hexa, lightness_change: float) -> rgba | hexa:
934
987
  """In- or decrease the lightness of the input color.\n
935
988
  -----------------------------------------------------------------------------------------------------
936
989
  - color (rgba|hexa): HEX or RGBA color
@@ -938,18 +991,13 @@ class Color:
938
991
  -----------------------------------------------------------------------------------------------------
939
992
  returns (rgba|hexa): the adjusted color in the format of the input color"""
940
993
  was_hexa = Color.is_valid_hexa(color)
941
- color = Color.to_hsla(color)
942
- h, s, l, a = (
943
- color[0],
944
- color[1],
945
- color[2],
946
- color[3] if Color.has_alpha(color) else None,
947
- )
994
+ _color: hsla = Color.to_hsla(color)
995
+ h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
948
996
  l = int(max(0, min(100, l + lightness_change * 100)))
949
997
  return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()
950
998
 
951
999
  @staticmethod
952
- def adjust_saturation(color: rgba | hexa, saturation_change: float) -> rgba | hexa:
1000
+ def adjust_saturation(color: Rgba | Hexa, saturation_change: float) -> rgba | hexa:
953
1001
  """In- or decrease the saturation of the input color.\n
954
1002
  -----------------------------------------------------------------------------------------------------------
955
1003
  - color (rgba|hexa): HEX or RGBA color
@@ -957,12 +1005,7 @@ class Color:
957
1005
  -----------------------------------------------------------------------------------------------------------
958
1006
  returns (rgba|hexa): the adjusted color in the format of the input color"""
959
1007
  was_hexa = Color.is_valid_hexa(color)
960
- color = Color.to_hsla(color)
961
- h, s, l, a = (
962
- color[0],
963
- color[1],
964
- color[2],
965
- color[3] if Color.has_alpha(color) else None,
966
- )
1008
+ _color: hsla = Color.to_hsla(color)
1009
+ h, s, l, a = (int(_color[0]), int(_color[1]), int(_color[2]), _color[3] if Color.has_alpha(_color) else None)
967
1010
  s = int(max(0, min(100, s + saturation_change * 100)))
968
1011
  return hsla(h, s, l, a, _validate=False).to_hexa() if was_hexa else hsla(h, s, l, a, _validate=False).to_rgba()