chromatic-python 0.2.3__py3-none-any.whl → 0.3.0__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.
Files changed (36) hide show
  1. chromatic/__init__.py +2 -109
  2. chromatic/__init__.pyi +74 -0
  3. chromatic/_typing.py +50 -55
  4. chromatic/_version.py +2 -2
  5. chromatic/color/__init__.py +2 -5
  6. chromatic/color/__init__.pyi +86 -0
  7. chromatic/color/colorconv.py +57 -60
  8. chromatic/color/core.py +962 -1107
  9. chromatic/color/core.pyi +230 -164
  10. chromatic/color/iterators.py +7 -13
  11. chromatic/color/palette.py +465 -481
  12. chromatic/color/palette.pyi +173 -194
  13. chromatic/data/__init__.py +15 -187
  14. chromatic/data/__init__.pyi +9 -15
  15. chromatic/data/_fetchers.py +55 -0
  16. chromatic/data/registry.json +5 -0
  17. chromatic/data/userfont.py +132 -0
  18. chromatic/data/userfont.pyi +28 -0
  19. chromatic/data/userfont.schema.json +28 -0
  20. chromatic/demo.py +153 -163
  21. chromatic/image/__init__.pyi +70 -0
  22. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.0.dist-info}/METADATA +2 -1
  23. chromatic_python-0.3.0.dist-info/RECORD +35 -0
  24. chromatic/data/images/hotdog.jpg +0 -0
  25. chromatic_python-0.2.3.dist-info/RECORD +0 -28
  26. /chromatic/data/{images/butterfly.jpg → butterfly.jpg} +0 -0
  27. /chromatic/data/{images/escher.png → escher.png} +0 -0
  28. /chromatic/data/fonts/{IBM_VGA_437_8x16.ttf → vga437.ttf} +0 -0
  29. /chromatic/data/{images/goblin_virus.png → goblin_virus.png} +0 -0
  30. /chromatic/{ascii → image}/__init__.py +0 -0
  31. /chromatic/{ascii → image}/_array.py +0 -0
  32. /chromatic/{ascii → image}/_curses.py +0 -0
  33. /chromatic/{ascii → image}/_glyph_proc.py +0 -0
  34. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.0.dist-info}/WHEEL +0 -0
  35. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.0.dist-info}/licenses/LICENSE +0 -0
  36. {chromatic_python-0.2.3.dist-info → chromatic_python-0.3.0.dist-info}/top_level.txt +0 -0
chromatic/__init__.py CHANGED
@@ -3,113 +3,6 @@ try:
3
3
  except ImportError:
4
4
  __version__ = "0.0.0"
5
5
 
6
- from . import ascii, color, data
7
- from .ascii import (
8
- AnsiImage,
9
- ansi2img,
10
- ansify,
11
- ansi_quantize,
12
- ascii2img,
13
- ascii_printable,
14
- contrast_stretch,
15
- cp437_printable,
16
- equalize_white_point,
17
- get_font_key,
18
- get_font_object,
19
- img2ansi,
20
- img2ascii,
21
- read_ans,
22
- render_ans,
23
- reshape_ansi,
24
- to_sgr_array,
25
- )
26
- from .ascii._glyph_proc import get_glyph_masks
27
- from .color import (
28
- Back,
29
- Color,
30
- ColorNamespace,
31
- ColorStr,
32
- Fore,
33
- SgrParameter,
34
- Style,
35
- ansicolor24Bit,
36
- ansicolor4Bit,
37
- ansicolor8Bit,
38
- colorbytes,
39
- named_color,
40
- )
41
- from .data import register_user_font
6
+ import lazy_loader as _lazy
42
7
 
43
- __all__ = []
44
-
45
- try:
46
- import os
47
- import sys
48
- from functools import lru_cache, wraps
49
- from types import ModuleType
50
-
51
- def find_modules(path: str):
52
- from setuptools import find_packages
53
- from pkgutil import iter_modules
54
-
55
- tree: dict[str, dict | ModuleType] = {
56
- __name__: {'__module__': sys.modules[__name__]}
57
- }
58
- children = set()
59
- for pkg in find_packages(path):
60
- children.add(pkg)
61
- pkg_path = path + '/' + pkg.replace('.', '/')
62
- if sys.version_info.major == 2 or (
63
- sys.version_info.major == 3 and sys.version_info.minor < 6
64
- ):
65
- for _, name, ispkg in iter_modules([pkg_path]):
66
- if not ispkg:
67
- children.add(f"{pkg}.{name}")
68
- else:
69
- for info in iter_modules([pkg_path]):
70
- if not info.ispkg:
71
- children.add(f"{pkg}.{info.name}")
72
- for child in children:
73
- name = f"{__name__}.{child}"
74
- depth = tree
75
- for node in name.split('.'):
76
- if node not in depth:
77
- depth[node] = {}
78
- depth = depth[node]
79
- if name in sys.modules:
80
- depth['__module__'] = sys.modules[name]
81
- return tree
82
-
83
- def publicize_modules(modulename: str, tree: dict[str, dict | ModuleType]):
84
- def is_local(obj: object):
85
- if isinstance(obj, ModuleType):
86
- return obj.__spec__.parent == modulename
87
- elif hasattr(obj, '__module__'):
88
- return obj.__module__ == modulename
89
- return obj is not None
90
-
91
- for name, subtree in tree.items():
92
- if name == '__module__' and isinstance(subtree, ModuleType):
93
- submodule = subtree
94
- init_dir = dir(submodule)
95
-
96
- @wraps(submodule.__dir__)
97
- def wrapped():
98
- s = set(
99
- attr
100
- for attr in init_dir
101
- if is_local(getattr(submodule, attr, None))
102
- )
103
- if hasattr(submodule, '__all__'):
104
- s.update(submodule.__all__)
105
- return list(s)
106
-
107
- sys.modules[submodule.__name__].__dir__ = wrapped
108
-
109
- else:
110
- publicize_modules(f"{modulename}.{name}", subtree)
111
-
112
- publicize_modules(*find_modules(os.path.split(__file__)[0]).popitem())
113
-
114
- finally:
115
- del find_modules, publicize_modules
8
+ __getattr__, __dir__, __all__ = _lazy.attach_stub(__name__, __file__)
chromatic/__init__.pyi ADDED
@@ -0,0 +1,74 @@
1
+ __all__ = [
2
+ 'AnsiImage',
3
+ 'Back',
4
+ 'Color',
5
+ 'ColorNamespace',
6
+ 'ColorStr',
7
+ 'Fore',
8
+ 'SgrParameter',
9
+ 'Style',
10
+ '__version__',
11
+ 'ansi2img',
12
+ 'ansi_quantize',
13
+ 'ansicolor24Bit',
14
+ 'ansicolor4Bit',
15
+ 'ansicolor8Bit',
16
+ 'ansify',
17
+ 'image',
18
+ 'ascii2img',
19
+ 'ascii_printable',
20
+ 'color',
21
+ 'colorbytes',
22
+ 'contrast_stretch',
23
+ 'cp437_printable',
24
+ 'data',
25
+ 'equalize_white_point',
26
+ 'get_font_key',
27
+ 'get_font_object',
28
+ 'get_glyph_masks',
29
+ 'img2ansi',
30
+ 'img2ascii',
31
+ 'named_color',
32
+ 'read_ans',
33
+ 'register_userfont',
34
+ 'render_ans',
35
+ 'reshape_ansi',
36
+ 'to_sgr_array',
37
+ ]
38
+ from . import color, data, image
39
+ from ._version import version as __version__
40
+ from .color import (
41
+ Back,
42
+ Color,
43
+ ColorNamespace,
44
+ ColorStr,
45
+ Fore,
46
+ SgrParameter,
47
+ Style,
48
+ ansicolor24Bit,
49
+ ansicolor4Bit,
50
+ ansicolor8Bit,
51
+ colorbytes,
52
+ named_color,
53
+ )
54
+ from .data import register_userfont
55
+ from .image import (
56
+ AnsiImage,
57
+ ansi2img,
58
+ ansi_quantize,
59
+ ansify,
60
+ ascii2img,
61
+ ascii_printable,
62
+ contrast_stretch,
63
+ cp437_printable,
64
+ equalize_white_point,
65
+ get_font_key,
66
+ get_font_object,
67
+ img2ansi,
68
+ img2ascii,
69
+ read_ans,
70
+ render_ans,
71
+ reshape_ansi,
72
+ to_sgr_array,
73
+ )
74
+ from .image._glyph_proc import get_glyph_masks
chromatic/_typing.py CHANGED
@@ -14,12 +14,13 @@ from typing import (
14
14
  Concatenate,
15
15
  Hashable,
16
16
  Iterable,
17
- Literal,
17
+ Literal as L,
18
18
  NamedTuple,
19
19
  Optional,
20
20
  ParamSpec,
21
21
  Protocol,
22
22
  Sequence,
23
+ TYPE_CHECKING,
23
24
  Type,
24
25
  TypeAlias,
25
26
  TypeAliasType,
@@ -40,7 +41,8 @@ from PIL.ImageFont import FreeTypeFont
40
41
  from numpy import dtype, float64, generic, ndarray, number, uint8
41
42
  from numpy._typing import NDArray, _ArrayLike
42
43
 
43
- from chromatic.data import UserFont
44
+ if TYPE_CHECKING:
45
+ from .data import UserFont
44
46
 
45
47
  _P = ParamSpec('_P')
46
48
  _T = TypeVar('_T')
@@ -48,15 +50,11 @@ _T_co = TypeVar('_T_co', covariant=True)
48
50
  _T_contra = TypeVar('_T_contra', contravariant=True)
49
51
  _AnyNumber_co = TypeVar('_AnyNumber_co', number, Number, covariant=True)
50
52
 
51
- type ArrayReducerFunc[_SCT: generic] = Callable[
52
- Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]
53
- ]
54
- type ShapedNDArray[_Shape: tuple[int, ...], _SCT: generic] = ndarray[
55
- _Shape, dtype[_SCT]
56
- ]
53
+ type ArrayReducerFunc[_SCT: generic] = Callable[Concatenate[_ArrayLike[_SCT], _P], NDArray[_SCT]]
54
+ type ShapedNDArray[_Shape: tuple[int, ...], _SCT = generic] = ndarray[_Shape, dtype[_SCT]]
57
55
  type MatrixLike[_SCT: generic] = ShapedNDArray[TupleOf2[int], _SCT]
58
56
  type SquareMatrix[_I: int, _SCT: generic] = ShapedNDArray[TupleOf2[_I], _SCT]
59
- type GlyphArray[_SCT: generic] = SquareMatrix[Literal[24], _SCT]
57
+ type GlyphArray[_SCT: generic] = SquareMatrix[L[24], _SCT]
60
58
  type TupleOf2[_T] = tuple[_T, _T]
61
59
  type TupleOf3[_T] = tuple[_T, _T, _T]
62
60
 
@@ -68,29 +66,41 @@ GlyphBitmask: TypeAlias = GlyphArray[bool]
68
66
  Bitmask: TypeAlias = MatrixLike[bool]
69
67
  GreyscaleGlyphArray: TypeAlias = GlyphArray[float64]
70
68
  GreyscaleArray: TypeAlias = MatrixLike[float64]
71
- RGBArray: TypeAlias = ShapedNDArray[tuple[int, int, Literal[3]], uint8]
72
- RGBPixel: TypeAlias = ShapedNDArray[tuple[Literal[3]], uint8]
69
+ RGBArray: TypeAlias = ShapedNDArray[tuple[int, int, L[3]], uint8]
70
+ RGBPixel: TypeAlias = ShapedNDArray[tuple[L[3]], uint8]
73
71
 
74
72
  RGBImageLike: TypeAlias = Image | RGBArray
75
- RGBVectorLike: TypeAlias = Int3Tuple | IntSequence | RGBPixel
76
- ColorDictKeys = Literal['fg', 'bg']
77
- Ansi4BitAlias = Literal['4b']
78
- Ansi8BitAlias = Literal['8b']
79
- Ansi24BitAlias = Literal['24b']
73
+ RGBVectorLike: TypeAlias = IntSequence | RGBPixel
74
+ ColorDictKeys = L['fg', 'bg']
75
+ Ansi4BitAlias = L['4b']
76
+ Ansi8BitAlias = L['8b']
77
+ Ansi24BitAlias = L['24b']
80
78
  AnsiColorAlias = Ansi4BitAlias | Ansi8BitAlias | Ansi24BitAlias
81
- FontArgType: TypeAlias = FreeTypeFont | UserFont | str | int
79
+ FontArgType: TypeAlias = 'FreeTypeFont | UserFont | str'
80
+
81
+
82
+ def eval_annotation(annotation: str, **kwargs) -> Any:
83
+ globals_ = kwargs.get('globals', {}) | globals()
84
+ locals_ = kwargs.get('locals', {})
85
+ try:
86
+ return subtype(eval(annotation, globals_.copy(), locals_.copy()))
87
+ except NameError as e:
88
+ try:
89
+ import typing
90
+
91
+ globals_[e.name] = getattr(typing, e.name)
92
+ return eval_annotation(annotation, globals=globals_, locals=locals_)
93
+ except AttributeError:
94
+ pass
95
+ raise
82
96
 
83
97
 
84
98
  def type_error_msg(err_obj, *expected, context: str = '', obj_repr=False):
85
99
  n_expected = len(expected)
86
- name_slots = ['{%d.__qualname__!r}' % n for n in range(n_expected)]
87
- if name_slots and n_expected > 1:
100
+ name_slots = ["{%d.__name__!r}" % n for n in range(n_expected)]
101
+ if n_expected > 1:
88
102
  name_slots[-1] = f"or {name_slots[-1]}"
89
- names = (
90
- (', ' if n_expected > 2 else ' ')
91
- .join([context.strip()] + name_slots)
92
- .format(*expected)
93
- )
103
+ names = (', ' if n_expected > 2 else ' ').join([context.strip(), *name_slots]).format(*expected)
94
104
  if not obj_repr:
95
105
  if not isinstance(err_obj, type):
96
106
  err_obj = type(err_obj)
@@ -108,14 +118,11 @@ def is_matching_type(value, typ):
108
118
  origin, args = deconstruct_type(typ)
109
119
  if origin is Union:
110
120
  return any(is_matching_type(value, arg) for arg in args)
111
- elif origin is Literal:
121
+ elif origin is L:
112
122
  return value in args
113
123
  elif isinstance(typ, TypeVar):
114
124
  if typ.__constraints__:
115
- return any(
116
- is_matching_type(value, constraint)
117
- for constraint in typ.__constraints__
118
- )
125
+ return any(is_matching_type(value, constraint) for constraint in typ.__constraints__)
119
126
  else:
120
127
  return True
121
128
  elif origin is type:
@@ -173,9 +180,7 @@ def is_matching_typed_dict(__d: dict, typed_dict: type[dict]) -> tuple[bool, str
173
180
  field = __d.get(name)
174
181
  if field is None or is_matching_type(field, typ):
175
182
  continue
176
- return False, type_error_msg(
177
- field, typ, context=f'keyword argument {name!r} of type'
178
- )
183
+ return False, type_error_msg(field, typ, context=f'keyword argument {name!r} of type')
179
184
  return True, ''
180
185
 
181
186
 
@@ -201,20 +206,20 @@ def unionize(__iterable: Iterable[SupportsUnion[_T_contra, _T_co]]) -> _T_co:
201
206
 
202
207
  _GenericAlias = type(Type[...]) | types.GenericAlias
203
208
  _UnionGenericType = type(Union[..., None])
204
- _LiteralGenericType = type(Literal[''])
209
+ _LiteralGenericType = type(L[''])
205
210
  _CallableGenericType = type(Callable[[], ...]) | type(ABC_Callable[[], ...])
206
211
  _CallableType = type(Callable) | ABC_Callable
207
212
 
208
213
 
209
214
  class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
215
+ """Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
210
216
 
211
217
  def __init__(self, *, maxsize: Optional[int] = 128):
212
- """Bounded OrderedDict, mimics FIFO behavior of `functools.lru_cache`"""
213
218
  super().__init__()
214
219
  if maxsize is not None:
215
220
  maxsize = max(maxsize, 0)
216
221
 
217
- @wraps(self.__setitem__)
222
+ @wraps(_BoundedDict.__setitem__)
218
223
  def _fifo(_, key, value):
219
224
  if maxsize <= len(self):
220
225
  self.popitem(last=True)
@@ -226,9 +231,9 @@ class _BoundedDict[_KT, _VT](OrderedDict[_KT, _VT]):
226
231
 
227
232
 
228
233
  _SUBTYPE_CACHE: _BoundedDict[int, ...] = _BoundedDict()
229
- _ATTR_GETTERS: _BoundedDict[
230
- ..., tuple[Callable[[Iterable], NamedTuple], op.attrgetter]
231
- ] = _BoundedDict()
234
+ _ATTR_GETTERS: _BoundedDict[..., tuple[Callable[[Iterable], NamedTuple], op.attrgetter]] = (
235
+ _BoundedDict()
236
+ )
232
237
 
233
238
 
234
239
  def _unique_attrs(obj) -> Optional['NamedTuple']:
@@ -273,8 +278,7 @@ def _sort_attrs(obj, tp_name, attr_names):
273
278
  try:
274
279
  sig = inspect.signature(type(obj))
275
280
  indices = (
276
- dict.fromkeys(field_names, inf)
277
- | {p: i for i, p in enumerate(sig.parameters)}
281
+ dict.fromkeys(field_names, inf) | {p: i for i, p in enumerate(sig.parameters)}
278
282
  ).values()
279
283
  for names in (attr_names, field_names):
280
284
  names.sort(key=dict(zip(names, indices)).__getitem__)
@@ -296,9 +300,7 @@ def _sort_attrs(obj, tp_name, attr_names):
296
300
  while sig_start not in line:
297
301
  line = next(lines)
298
302
  _, _, params = line.partition(sig_start)
299
- params, _, _ = (
300
- s.translate(no_square_parens) for s in params.partition(')')
301
- )
303
+ params, _, _ = (s.translate(no_square_parens) for s in params.partition(')'))
302
304
  maybe_sigs.add(params)
303
305
  except StopIteration:
304
306
  break
@@ -306,20 +308,15 @@ def _sort_attrs(obj, tp_name, attr_names):
306
308
  if maybe_sigs:
307
309
  if len(maybe_sigs) > 1:
308
310
  sig = max(
309
- maybe_sigs,
310
- key=lambda s: sum(1 for sub in s.split(', ') if sub in field_names),
311
+ maybe_sigs, key=lambda s: sum(1 for sub in s.split(', ') if sub in field_names)
311
312
  )
312
313
  else:
313
314
  sig = maybe_sigs.pop()
314
315
  positions = {x: i for i, x in enumerate(sig.split(', ')) if x}
315
316
  sorted_field_names = sorted(field_names, key=lambda k: positions.get(k, inf))
316
- transitions = {
317
- idx: sorted_field_names.index(x) for idx, x in enumerate(field_names)
318
- }
317
+ transitions = {idx: sorted_field_names.index(x) for idx, x in enumerate(field_names)}
319
318
  field_names = sorted_field_names
320
- attr_names = [
321
- attr_names[k] for k in map(transitions.__getitem__, range(len(attr_names)))
322
- ]
319
+ attr_names = [attr_names[k] for k in map(transitions.__getitem__, range(len(attr_names)))]
323
320
  for Names in (attr_names, field_names):
324
321
  name_attr = next((s for s in Names if s.strip('_') == 'name'), None)
325
322
  if name_attr is not None:
@@ -356,9 +353,7 @@ def subtype[_T](typ: _T) -> _T:
356
353
  args_list = list(args)
357
354
  if (
358
355
  literals := [
359
- idx
360
- for idx, elem in enumerate(args)
361
- if isinstance(elem, _LiteralGenericType)
356
+ idx for idx, elem in enumerate(args) if isinstance(elem, _LiteralGenericType)
362
357
  ]
363
358
  ) and len(literals) > 1:
364
359
 
@@ -368,7 +363,7 @@ def subtype[_T](typ: _T) -> _T:
368
363
  return getattr(value, '__args__', ())
369
364
 
370
365
  start = args[literals.pop(0)]
371
- args_list[args_list.index(start)] = Literal[
366
+ args_list[args_list.index(start)] = L[
372
367
  *start.__args__,
373
368
  *dict.fromkeys(arg for idx in literals for arg in _next_args(idx)),
374
369
  ]
chromatic/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.2.3'
21
- __version_tuple__ = version_tuple = (0, 2, 3)
20
+ __version__ = version = '0.3.0'
21
+ __version_tuple__ = version_tuple = (0, 3, 0)
@@ -1,6 +1,3 @@
1
- from . import core, palette
2
- from .colorconv import *
3
- from .core import *
4
- from .palette import *
1
+ import lazy_loader as _lazy
5
2
 
6
- __all__ = list(set(core.__all__) | set(colorconv.__all__) | set(palette.__all__))
3
+ __getattr__, __dir__, __all__ = _lazy.attach_stub(__name__, __file__)
@@ -0,0 +1,86 @@
1
+ __all__ = [
2
+ 'ANSI_4BIT_RGB',
3
+ 'Back',
4
+ 'CSI',
5
+ 'Color',
6
+ 'ColorNamespace',
7
+ 'ColorStr',
8
+ 'Fore',
9
+ 'SGR_RESET',
10
+ 'SgrParameter',
11
+ 'SgrSequence',
12
+ 'Style',
13
+ 'ansi_4bit_to_rgb',
14
+ 'ansi_8bit_to_rgb',
15
+ 'ansicolor24Bit',
16
+ 'ansicolor4Bit',
17
+ 'ansicolor8Bit',
18
+ 'color_chain',
19
+ 'colorbytes',
20
+ 'get_ansi_type',
21
+ 'int2rgb',
22
+ 'hexstr2rgb',
23
+ 'hsl2rgb',
24
+ 'hsv2rgb',
25
+ 'is_u24',
26
+ 'lab2rgb',
27
+ 'lab2xyz',
28
+ 'named_color',
29
+ 'nearest_ansi_4bit_rgb',
30
+ 'nearest_ansi_8bit_rgb',
31
+ 'randcolor',
32
+ 'rgb2ansi_escape',
33
+ 'rgb2int',
34
+ 'rgb2hexstr',
35
+ 'rgb2hsl',
36
+ 'rgb2hsv',
37
+ 'rgb2lab',
38
+ 'rgb2xyz',
39
+ 'rgb_diff',
40
+ 'rgb_dispatch',
41
+ 'rgb_to_ansi_8bit',
42
+ 'xyz2lab',
43
+ 'xyz2rgb',
44
+ ]
45
+ from . import colorconv, core, iterators, palette
46
+ from .colorconv import (
47
+ ANSI_4BIT_RGB,
48
+ ansi_4bit_to_rgb,
49
+ ansi_8bit_to_rgb,
50
+ int2rgb,
51
+ hexstr2rgb,
52
+ hsl2rgb,
53
+ hsv2rgb,
54
+ is_u24,
55
+ lab2rgb,
56
+ lab2xyz,
57
+ nearest_ansi_4bit_rgb,
58
+ nearest_ansi_8bit_rgb,
59
+ rgb2int,
60
+ rgb2hexstr,
61
+ rgb2hsl,
62
+ rgb2hsv,
63
+ rgb2lab,
64
+ rgb2xyz,
65
+ rgb_diff,
66
+ rgb_to_ansi_8bit,
67
+ xyz2lab,
68
+ xyz2rgb,
69
+ )
70
+ from .core import (
71
+ CSI,
72
+ Color,
73
+ ColorStr,
74
+ SGR_RESET,
75
+ SgrParameter,
76
+ SgrSequence,
77
+ ansicolor24Bit,
78
+ ansicolor4Bit,
79
+ ansicolor8Bit,
80
+ color_chain,
81
+ colorbytes,
82
+ get_ansi_type,
83
+ randcolor,
84
+ rgb2ansi_escape,
85
+ )
86
+ from .palette import Back, ColorNamespace, Fore, Style, named_color, rgb_dispatch