omlish 0.0.0.dev117__py3-none-any.whl → 0.0.0.dev118__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.
omlish/__about__.py CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev117'
2
- __revision__ = '53d3fb51abb6f481bd836efe8a32c8da91113468'
1
+ __version__ = '0.0.0.dev118'
2
+ __revision__ = '3d4642bb79ba8bc7d7c6a1009237095185ac3c86'
3
3
 
4
4
 
5
5
  #
@@ -27,21 +27,11 @@ class HashEq(lang.Abstract, ta.Generic[K]):
27
27
  raise NotImplementedError
28
28
 
29
29
 
30
- @lang.unabstract_class([
31
- ('hash', '_hash'),
32
- ('eq', '_eq'),
33
- ])
34
30
  @dc.dataclass(frozen=True)
35
31
  class HashEq_(ta.Generic[K]): # noqa
36
32
  hash: ta.Callable[[K], int]
37
33
  eq: ta.Callable[[K, K], bool]
38
34
 
39
- def _hash(self, k: K) -> int:
40
- return self.hash(k)
41
-
42
- def _eq(self, l: K, r: K) -> bool:
43
- return self.eq(l, r)
44
-
45
35
 
46
36
  class hash_eq(HashEq[K], lang.NotInstantiable, lang.Final): # noqa
47
37
  """Workaround for PEP 695 support."""
omlish/fnpairs.py CHANGED
@@ -94,21 +94,12 @@ class FnPair(ta.Generic[F, T], abc.ABC):
94
94
  ##
95
95
 
96
96
 
97
- @lang.unabstract_class([
98
- ('forward', '_forward'),
99
- ('backward', 'backward'),
100
- ])
97
+ @lang.unabstract_class(['forward', 'backward'])
101
98
  @dc.dataclass(frozen=True)
102
99
  class Simple(FnPair[F, T]):
103
100
  forward: ta.Callable[[F], T] # type: ignore
104
101
  backward: ta.Callable[[T], F] # type: ignore
105
102
 
106
- def _forward(self, f: F) -> T:
107
- return self.forward(f)
108
-
109
- def _backward(self, t: T) -> F:
110
- return self.backward(t)
111
-
112
103
 
113
104
  of = Simple
114
105
 
@@ -37,3 +37,8 @@ else:
37
37
  _lang.proxy_init(globals(), '.render', [
38
38
  'JsonRenderer',
39
39
  ])
40
+
41
+ from .types import ( # noqa
42
+ SCALAR_TYPES,
43
+ Scalar,
44
+ )
@@ -0,0 +1,179 @@
1
+ # MIT License
2
+ # ===========
3
+ #
4
+ # Copyright (c) 2006 Bob Ippolito
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
7
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
8
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
9
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
12
+ # Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
15
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
+ # https://github.com/simplejson/simplejson/blob/6932004966ab70ef47250a2b3152acd8c904e6b5/simplejson/scanner.py
19
+ import json
20
+ import re
21
+ import sys
22
+ import typing as ta
23
+
24
+
25
+ ##
26
+
27
+
28
+ _FOUR_DIGIT_HEX_PAT = re.compile(r'^[0-9a-fA-F]{4}$')
29
+
30
+
31
+ def _scan_four_digit_hex(
32
+ s: str,
33
+ end: int,
34
+ ):
35
+ """Scan a four digit hex number from s[end:end + 4]"""
36
+
37
+ msg = 'Invalid \\uXXXX escape sequence'
38
+ esc = s[end: end + 4]
39
+ if not _FOUR_DIGIT_HEX_PAT.match(esc):
40
+ raise json.JSONDecodeError(msg, s, end - 2)
41
+
42
+ try:
43
+ return int(esc, 16), end + 4
44
+ except ValueError:
45
+ raise json.JSONDecodeError(msg, s, end - 2) from None
46
+
47
+
48
+ _STRING_CHUNK_PAT = re.compile(r'(.*?)(["\\\x00-\x1f])', re.VERBOSE | re.MULTILINE | re.DOTALL)
49
+
50
+ _BACKSLASH_MAP = {
51
+ '"': '"',
52
+ '\\': '\\',
53
+ '/': '/',
54
+ 'b': '\b',
55
+ 'f': '\f',
56
+ 'n': '\n',
57
+ 'r': '\r',
58
+ 't': '\t',
59
+ }
60
+
61
+
62
+ def parse_string(
63
+ s: str,
64
+ idx: int = 0,
65
+ *,
66
+ strict: bool = True,
67
+ ) -> tuple[str, int]:
68
+ """
69
+ Scan the string s for a JSON string. Idx is the index of the quote that starts the JSON string. Unescapes all valid
70
+ JSON string escape sequences and raises ValueError on attempt to decode an invalid string. If strict is False then
71
+ literal control characters are allowed in the string.
72
+
73
+ Returns a tuple of the decoded string and the index of the character in s after the end quote.
74
+ """
75
+
76
+ if s[idx] != '"':
77
+ raise json.JSONDecodeError('No opening string quotes at', s, idx)
78
+
79
+ chunks: list[str] = []
80
+ end = idx + 1
81
+ while True:
82
+ chunk = _STRING_CHUNK_PAT.match(s, end)
83
+ if chunk is None:
84
+ raise json.JSONDecodeError('Unterminated string starting at', s, idx)
85
+
86
+ prev_end = end
87
+ end = chunk.end()
88
+ content, terminator = chunk.groups()
89
+ # Content is contains zero or more unescaped string characters
90
+ if content:
91
+ chunks.append(content)
92
+
93
+ # Terminator is the end of string, a literal control character, or a backslash denoting that an escape sequence
94
+ # follows
95
+ if terminator == '"':
96
+ break
97
+ elif terminator != '\\':
98
+ if strict:
99
+ msg = 'Invalid control character %r at'
100
+ raise json.JSONDecodeError(msg, s, prev_end)
101
+ else:
102
+ chunks.append(terminator)
103
+ continue
104
+
105
+ try:
106
+ esc = s[end]
107
+ except IndexError:
108
+ raise json.JSONDecodeError('Unterminated string starting at', s, idx) from None
109
+
110
+ # If not a unicode escape sequence, must be in the lookup table
111
+ if esc != 'u':
112
+ try:
113
+ char = _BACKSLASH_MAP[esc]
114
+ except KeyError:
115
+ msg = 'Invalid \\X escape sequence %r'
116
+ raise json.JSONDecodeError(msg, s, end) from None
117
+ end += 1
118
+
119
+ else:
120
+ # Unicode escape sequence
121
+ uni, end = _scan_four_digit_hex(s, end + 1)
122
+
123
+ # Check for surrogate pair on UCS-4 systems Note that this will join high/low surrogate pairs but will also
124
+ # pass unpaired surrogates through
125
+ if (
126
+ sys.maxunicode > 65535 and
127
+ uni & 0xFC00 == 0xD800 and
128
+ s[end: end + 2] == '\\u'
129
+ ):
130
+ uni2, end2 = _scan_four_digit_hex(s, end + 2)
131
+ if uni2 & 0xFC00 == 0xDC00:
132
+ uni = 0x10000 + (((uni - 0xD800) << 10) | (uni2 - 0xDC00))
133
+ end = end2
134
+
135
+ char = chr(uni)
136
+
137
+ # Append the unescaped character
138
+ chunks.append(char)
139
+
140
+ return ''.join(chunks), end
141
+
142
+
143
+ def try_parse_string(
144
+ s: str,
145
+ idx: int = 0,
146
+ *,
147
+ strict: bool = True,
148
+ ) -> tuple[str, int] | None:
149
+ if not s or s[idx] != '"':
150
+ return None
151
+
152
+ return parse_string(s, idx, strict=strict)
153
+
154
+
155
+ ##
156
+
157
+
158
+ _NUMBER_PAT = re.compile(
159
+ r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?',
160
+ (re.VERBOSE | re.MULTILINE | re.DOTALL),
161
+ )
162
+
163
+
164
+ def try_parse_number(
165
+ s: str,
166
+ idx: int = 0,
167
+ *,
168
+ parse_float: ta.Callable[[str], float] = float,
169
+ parse_int: ta.Callable[[str], int] = int,
170
+ ) -> tuple[int | float, int] | None:
171
+ if (m := _NUMBER_PAT.match(s, idx)) is None:
172
+ return None
173
+
174
+ integer, frac, exp = m.groups()
175
+ if frac or exp:
176
+ res = parse_float(integer + (frac or '') + (exp or ''))
177
+ else:
178
+ res = parse_int(integer)
179
+ return res, m.end()
@@ -6,12 +6,11 @@ import typing as ta
6
6
 
7
7
  from ... import lang
8
8
  from . import consts
9
+ from .types import SCALAR_TYPES
10
+ from .types import Scalar
9
11
 
10
12
 
11
13
  I = ta.TypeVar('I')
12
- Scalar: ta.TypeAlias = bool | int | float | str | None
13
-
14
- SCALAR_TYPES: tuple[type, ...] = (bool, int, float, str, type(None))
15
14
 
16
15
  MULTILINE_SEPARATORS = consts.Separators(',', ': ')
17
16
 
@@ -1,8 +1,8 @@
1
1
  import io
2
2
  import typing as ta
3
3
 
4
- from ..render import SCALAR_TYPES
5
4
  from ..render import AbstractJsonRenderer
5
+ from ..types import SCALAR_TYPES
6
6
  from .parse import BeginArray
7
7
  from .parse import BeginObject
8
8
  from .parse import EndArray
@@ -0,0 +1,6 @@
1
+ import typing as ta
2
+
3
+
4
+ Scalar: ta.TypeAlias = bool | int | float | str | None
5
+
6
+ SCALAR_TYPES: tuple[type, ...] = (bool, int, float, str, type(None))
@@ -7,6 +7,10 @@ T = ta.TypeVar('T')
7
7
 
8
8
  _DISABLE_CHECKS = False
9
9
 
10
+ _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
11
+ _IS_ABSTRACT_METHOD_ATTR = '__isabstractmethod__'
12
+ _FORCE_ABSTRACT_ATTR = '__forceabstract__'
13
+
10
14
 
11
15
  def make_abstract(obj: T) -> T:
12
16
  if callable(obj):
@@ -29,13 +33,13 @@ class Abstract(abc.ABC): # noqa
29
33
  def __forceabstract__(self):
30
34
  raise TypeError
31
35
 
32
- setattr(__forceabstract__, '__isabstractmethod__', True)
36
+ setattr(__forceabstract__, _IS_ABSTRACT_METHOD_ATTR, True)
33
37
 
34
38
  def __init_subclass__(cls, **kwargs: ta.Any) -> None:
35
39
  if Abstract in cls.__bases__:
36
- cls.__forceabstract__ = Abstract.__forceabstract__ # type: ignore
40
+ setattr(cls, _FORCE_ABSTRACT_ATTR, getattr(Abstract, _FORCE_ABSTRACT_ATTR))
37
41
  else:
38
- cls.__forceabstract__ = False # type: ignore
42
+ setattr(cls, _FORCE_ABSTRACT_ATTR, False)
39
43
 
40
44
  super().__init_subclass__(**kwargs)
41
45
 
@@ -43,7 +47,7 @@ class Abstract(abc.ABC): # noqa
43
47
  ams = {a for a, o in cls.__dict__.items() if is_abstract_method(o)}
44
48
  seen = set(cls.__dict__)
45
49
  for b in cls.__bases__:
46
- ams.update(set(getattr(b, '__abstractmethods__', [])) - seen)
50
+ ams.update(set(getattr(b, _ABSTRACT_METHODS_ATTR, [])) - seen)
47
51
  seen.update(dir(b))
48
52
  if ams:
49
53
  raise TypeError(
@@ -53,18 +57,18 @@ class Abstract(abc.ABC): # noqa
53
57
 
54
58
 
55
59
  def is_abstract_method(obj: ta.Any) -> bool:
56
- return bool(getattr(obj, '__isabstractmethod__', False))
60
+ return bool(getattr(obj, _IS_ABSTRACT_METHOD_ATTR, False))
57
61
 
58
62
 
59
63
  def is_abstract_class(obj: ta.Any) -> bool:
60
- if bool(getattr(obj, '__abstractmethods__', [])):
64
+ if bool(getattr(obj, _ABSTRACT_METHODS_ATTR, [])):
61
65
  return True
62
66
  if isinstance(obj, type):
63
67
  if Abstract in obj.__bases__:
64
68
  return True
65
69
  if (
66
70
  Abstract in obj.__mro__
67
- and getattr(obj.__dict__.get('__forceabstract__', None), '__isabstractmethod__', False)
71
+ and getattr(obj.__dict__.get(_FORCE_ABSTRACT_ATTR, None), _IS_ABSTRACT_METHOD_ATTR, False)
68
72
  ):
69
73
  return True
70
74
  return False
@@ -75,13 +79,32 @@ def is_abstract(obj: ta.Any) -> bool:
75
79
 
76
80
 
77
81
  def unabstract_class(
78
- impls: ta.Iterable[tuple[str, ta.Any]],
79
- ) -> ta.Callable[[type[T]], type[T]]:
82
+ members: ta.Iterable[str | tuple[str, ta.Any]],
83
+ ): # -> ta.Callable[[type[T]], type[T]]:
80
84
  def inner(cls):
81
- for name, impl in impls:
82
- if isinstance(impl, str):
83
- impl = getattr(cls, impl)
84
- setattr(cls, name, impl)
85
- setattr(cls, '__abstractmethods__', frozenset())
85
+ if isinstance(members, str):
86
+ raise TypeError(members)
87
+
88
+ ams = getattr(cls, _ABSTRACT_METHODS_ATTR)
89
+
90
+ names: set[str] = set()
91
+ for m in members:
92
+ if isinstance(m, str):
93
+ if m not in ams:
94
+ raise NameError(m)
95
+ getattr(cls, m)
96
+ names.add(m)
97
+
98
+ elif isinstance(m, tuple):
99
+ name, impl = m
100
+ if name not in ams:
101
+ raise NameError(name)
102
+ if isinstance(impl, str):
103
+ impl = getattr(cls, impl)
104
+ setattr(cls, name, impl)
105
+ names.add(name)
106
+
107
+ setattr(cls, _ABSTRACT_METHODS_ATTR, ams - names)
86
108
  return cls
109
+
87
110
  return inner
omlish/lang/imports.py CHANGED
@@ -312,7 +312,7 @@ class _ProxyInit:
312
312
  try:
313
313
  mod = self._mods_by_pkgs[pkg]
314
314
  except KeyError:
315
- mod = importlib.import_module(pkg, package=self.name_package.package)
315
+ mod = importlib.import_module(pkg, package=self._name_package.package)
316
316
 
317
317
  val = getattr(mod, attr)
318
318
 
omlish/lang/maybes.py CHANGED
@@ -50,15 +50,15 @@ class Maybe(abc.ABC, ta.Generic[T]):
50
50
  raise NotImplementedError
51
51
 
52
52
  @abc.abstractmethod
53
- def or_else(self, other: T) -> 'Maybe[T]':
53
+ def or_else(self, other: T) -> T:
54
54
  raise NotImplementedError
55
55
 
56
56
  @abc.abstractmethod
57
- def or_else_get(self, supplier: ta.Callable[[], T]) -> 'Maybe[T]':
57
+ def or_else_get(self, supplier: ta.Callable[[], T]) -> T:
58
58
  raise NotImplementedError
59
59
 
60
60
  @abc.abstractmethod
61
- def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> 'Maybe[T]':
61
+ def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
62
62
  raise NotImplementedError
63
63
 
64
64
 
@@ -98,9 +98,7 @@ class _Maybe(Maybe[T], tuple):
98
98
 
99
99
  def map(self, mapper: ta.Callable[[T], U]) -> Maybe[U]:
100
100
  if self:
101
- value = mapper(self[0])
102
- if value is not None:
103
- return just(value)
101
+ return just(mapper(self[0]))
104
102
  return _empty # noqa
105
103
 
106
104
  def flat_map(self, mapper: ta.Callable[[T], Maybe[U]]) -> Maybe[U]:
@@ -111,15 +109,15 @@ class _Maybe(Maybe[T], tuple):
111
109
  return value
112
110
  return _empty # noqa
113
111
 
114
- def or_else(self, other: T) -> Maybe[T]:
115
- return self if self else just(other)
112
+ def or_else(self, other: T) -> T:
113
+ return self.must() if self else other
116
114
 
117
- def or_else_get(self, supplier: ta.Callable[[], T]) -> Maybe[T]:
118
- return self if self else just(supplier())
115
+ def or_else_get(self, supplier: ta.Callable[[], T]) -> T:
116
+ return self.must() if self else supplier()
119
117
 
120
- def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> Maybe[T]:
118
+ def or_else_raise(self, exception_supplier: ta.Callable[[], Exception]) -> T:
121
119
  if self:
122
- return self
120
+ return self.must()
123
121
  raise exception_supplier()
124
122
 
125
123