omlish 0.0.0.dev315__py3-none-any.whl → 0.0.0.dev317__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.dev315'
2
- __revision__ = '9f4bb119f0561532694288dd8bb808ce67ebb43d'
1
+ __version__ = '0.0.0.dev317'
2
+ __revision__ = 'fde65227a86537b0442352985307b74b7376b98e'
3
3
 
4
4
 
5
5
  #
@@ -58,13 +58,33 @@ def unique(
58
58
  ##
59
59
 
60
60
 
61
+ @ta.overload
62
+ def make_map(
63
+ kvs: ta.Iterable[tuple[K, V]],
64
+ *,
65
+ identity: ta.Literal[False] = False,
66
+ strict: bool = False,
67
+ ) -> dict[K, V]:
68
+ ...
69
+
70
+
71
+ @ta.overload
61
72
  def make_map(
62
73
  kvs: ta.Iterable[tuple[K, V]],
63
74
  *,
64
75
  identity: bool = False,
65
76
  strict: bool = False,
66
77
  ) -> ta.MutableMapping[K, V]:
67
- d: ta.MutableMapping[K, V] = IdentityKeyDict() if identity else {}
78
+ ...
79
+
80
+
81
+ def make_map(
82
+ kvs,
83
+ *,
84
+ identity=False,
85
+ strict=False,
86
+ ):
87
+ d: ta.MutableMapping = IdentityKeyDict() if identity else {}
68
88
  for k, v in kvs:
69
89
  if k in d:
70
90
  if strict:
@@ -74,6 +94,21 @@ def make_map(
74
94
  return d
75
95
 
76
96
 
97
+ #
98
+
99
+
100
+ @ta.overload
101
+ def make_map_by(
102
+ fn: ta.Callable[[V], K],
103
+ vs: ta.Iterable[V],
104
+ *,
105
+ identity: ta.Literal[False] = False,
106
+ strict: bool = False,
107
+ ) -> dict[K, V]:
108
+ ...
109
+
110
+
111
+ @ta.overload
77
112
  def make_map_by(
78
113
  fn: ta.Callable[[V], K],
79
114
  vs: ta.Iterable[V],
@@ -81,6 +116,16 @@ def make_map_by(
81
116
  identity: bool = False,
82
117
  strict: bool = False,
83
118
  ) -> ta.MutableMapping[K, V]:
119
+ ...
120
+
121
+
122
+ def make_map_by(
123
+ fn,
124
+ vs,
125
+ *,
126
+ identity=False,
127
+ strict=False,
128
+ ):
84
129
  return make_map(
85
130
  ((fn(v), v) for v in vs),
86
131
  identity=identity,
@@ -91,9 +136,30 @@ def make_map_by(
91
136
  ##
92
137
 
93
138
 
94
- def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]:
95
- d: ta.MutableMapping[K, list[V]] = IdentityKeyDict() if identity else {}
96
- l: list[V]
139
+ @ta.overload
140
+ def multi_map(
141
+ kvs: ta.Iterable[tuple[K, V]],
142
+ *,
143
+ identity: ta.Literal[False] = False,
144
+ ) -> dict[K, list[V]]:
145
+ ...
146
+
147
+
148
+ @ta.overload
149
+ def multi_map(
150
+ kvs: ta.Iterable[tuple[K, V]],
151
+ *,
152
+ identity: bool = False,
153
+ ) -> ta.MutableMapping[K, list[V]]:
154
+ ...
155
+
156
+
157
+ def multi_map(
158
+ kvs,
159
+ *,
160
+ identity=False,
161
+ ):
162
+ d: ta.MutableMapping = IdentityKeyDict() if identity else {}
97
163
  for k, v in kvs:
98
164
  try:
99
165
  l = d[k]
@@ -103,7 +169,35 @@ def multi_map(kvs: ta.Iterable[tuple[K, V]], *, identity: bool = False) -> ta.Mu
103
169
  return d
104
170
 
105
171
 
106
- def multi_map_by(fn: ta.Callable[[V], K], vs: ta.Iterable[V], *, identity: bool = False) -> ta.MutableMapping[K, list[V]]: # noqa
172
+ #
173
+
174
+
175
+ @ta.overload
176
+ def multi_map_by(
177
+ fn: ta.Callable[[V], K],
178
+ vs: ta.Iterable[V],
179
+ *,
180
+ identity: ta.Literal[False] = False,
181
+ ) -> dict[K, list[V]]: # noqa
182
+ ...
183
+
184
+
185
+ @ta.overload
186
+ def multi_map_by(
187
+ fn: ta.Callable[[V], K],
188
+ vs: ta.Iterable[V],
189
+ *,
190
+ identity: bool = False,
191
+ ) -> ta.MutableMapping[K, list[V]]: # noqa
192
+ ...
193
+
194
+
195
+ def multi_map_by(
196
+ fn,
197
+ vs,
198
+ *,
199
+ identity=False,
200
+ ):
107
201
  return multi_map(((fn(v), v) for v in vs), identity=identity)
108
202
 
109
203
 
@@ -1,6 +1,9 @@
1
1
  import json
2
2
 
3
3
 
4
+ ##
5
+
6
+
4
7
  detect_encoding = json.detect_encoding
5
8
 
6
9
 
@@ -15,6 +15,7 @@
15
15
  # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
16
16
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17
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/encoder.py
18
19
  # https://github.com/simplejson/simplejson/blob/6932004966ab70ef47250a2b3152acd8c904e6b5/simplejson/scanner.py
19
20
  import json
20
21
  import re
@@ -25,6 +26,91 @@ import typing as ta
25
26
  ##
26
27
 
27
28
 
29
+ _ESCAPE_PAT = re.compile(r'[\x00-\x1f\\"]')
30
+ _ESCAPE_ASCII_PAT = re.compile(r'([\\"]|[^\ -~])')
31
+
32
+ ESCAPE_MAP: ta.Mapping[str, str] = {
33
+ **{
34
+ chr(i): f'\\u{i:04x}'
35
+ for i in range(0x20)
36
+ },
37
+ '\\': '\\\\',
38
+ '"': '\\"',
39
+ '\b': '\\b',
40
+ '\f': '\\f',
41
+ '\n': '\\n',
42
+ '\r': '\\r',
43
+ '\t': '\\t',
44
+ }
45
+
46
+
47
+ def _convert_to_string(s: str | bytes) -> str:
48
+ if isinstance(s, bytes):
49
+ return str(s, 'utf-8')
50
+
51
+ elif type(s) is not str:
52
+ # Convert a str subclass instance to exact str. Raise a TypeError otherwise.
53
+ return str.__str__(s)
54
+
55
+ else:
56
+ return s
57
+
58
+
59
+ def encode_string(
60
+ s: str | bytes,
61
+ q: str = '"',
62
+ *,
63
+ escape_map: ta.Mapping[str, str] | None = None,
64
+ ) -> str:
65
+ """Return a JSON representation of a Python string."""
66
+
67
+ if escape_map is None:
68
+ escape_map = ESCAPE_MAP
69
+
70
+ s = _convert_to_string(s)
71
+
72
+ def replace(m):
73
+ return escape_map[m.group(0)]
74
+
75
+ return q + _ESCAPE_PAT.sub(replace, s) + q
76
+
77
+
78
+ def encode_string_ascii(
79
+ s: str | bytes,
80
+ q: str = '"',
81
+ *,
82
+ escape_map: ta.Mapping[str, str] | None = None,
83
+ ) -> str:
84
+ """Return an ASCII-only JSON representation of a Python string."""
85
+
86
+ if escape_map is None:
87
+ escape_map = ESCAPE_MAP
88
+
89
+ s = _convert_to_string(s)
90
+
91
+ def replace(m):
92
+ s = m.group(0)
93
+
94
+ try:
95
+ return escape_map[s]
96
+
97
+ except KeyError:
98
+ n = ord(s)
99
+ if n < 0x10000:
100
+ return f'\\u{n:04x}'
101
+
102
+ # surrogate pair
103
+ n -= 0x10000
104
+ s1 = 0xD800 | ((n >> 10) & 0x3FF)
105
+ s2 = 0xDC00 | (n & 0x3FF)
106
+ return f'\\u{s1:04x}\\u{s2:04x}'
107
+
108
+ return q + str(_ESCAPE_ASCII_PAT.sub(replace, s)) + q
109
+
110
+
111
+ ##
112
+
113
+
28
114
  _FOUR_DIGIT_HEX_PAT = re.compile(r'^[0-9a-fA-F]{4}$')
29
115
 
30
116
 
@@ -32,7 +118,7 @@ def _scan_four_digit_hex(
32
118
  s: str,
33
119
  end: int,
34
120
  ):
35
- """Scan a four digit hex number from s[end:end + 4]"""
121
+ """Scan a four digit hex number from s[end:end + 4]."""
36
122
 
37
123
  msg = 'Invalid \\uXXXX escape sequence'
38
124
  esc = s[end: end + 4]
@@ -47,7 +133,7 @@ def _scan_four_digit_hex(
47
133
 
48
134
  _STRING_CHUNK_PAT = re.compile(r'(.*?)(["\\\x00-\x1f])', re.VERBOSE | re.MULTILINE | re.DOTALL)
49
135
 
50
- _BACKSLASH_MAP = {
136
+ BACKSLASH_MAP: ta.Mapping[str, str] = {
51
137
  '"': '"',
52
138
  '\\': '\\',
53
139
  '/': '/',
@@ -86,7 +172,7 @@ def parse_string(
86
172
  prev_end = end
87
173
  end = chunk.end()
88
174
  content, terminator = chunk.groups()
89
- # Content is contains zero or more unescaped string characters
175
+ # Content contains zero or more unescaped string characters
90
176
  if content:
91
177
  chunks.append(content)
92
178
 
@@ -110,7 +196,7 @@ def parse_string(
110
196
  # If not a unicode escape sequence, must be in the lookup table
111
197
  if esc != 'u':
112
198
  try:
113
- char = _BACKSLASH_MAP[esc]
199
+ char = BACKSLASH_MAP[esc]
114
200
  except KeyError:
115
201
  msg = 'Invalid \\X escape sequence %r'
116
202
  raise json.JSONDecodeError(msg, s, end) from None
@@ -12,12 +12,13 @@ from .types import Scalar
12
12
 
13
13
  I = ta.TypeVar('I')
14
14
 
15
- MULTILINE_SEPARATORS = consts.Separators(',', ': ')
16
-
17
15
 
18
16
  ##
19
17
 
20
18
 
19
+ MULTILINE_SEPARATORS = consts.Separators(',', ': ')
20
+
21
+
21
22
  class AbstractJsonRenderer(lang.Abstract, ta.Generic[I]):
22
23
  class State(enum.Enum):
23
24
  VALUE = enum.auto()
@@ -25,6 +26,7 @@ class AbstractJsonRenderer(lang.Abstract, ta.Generic[I]):
25
26
 
26
27
  def __init__(
27
28
  self,
29
+ *,
28
30
  indent: int | str | None = None,
29
31
  separators: tuple[str, str] | None = None,
30
32
  sort_keys: bool = False,
@@ -0,0 +1,44 @@
1
+ import typing as ta
2
+
3
+ from ..json import Scalar
4
+ from ..json.literals import ESCAPE_MAP
5
+ from ..json.literals import encode_string
6
+ from ..json.literals import encode_string_ascii
7
+ from ..json.rendering import JsonRenderer
8
+ from ..json.rendering import JsonRendererOut
9
+
10
+
11
+ ##
12
+
13
+
14
+ MULTILINE_STRINGS_ESCAPE_MAP = {
15
+ **ESCAPE_MAP,
16
+ '\n': '\\n\\\n',
17
+ }
18
+
19
+
20
+ class Json5Renderer(JsonRenderer):
21
+ def __init__(
22
+ self,
23
+ out: JsonRendererOut,
24
+ *,
25
+ multiline_strings: bool = False,
26
+ **kwargs: ta.Any,
27
+ ) -> None:
28
+ super().__init__(out, **kwargs)
29
+
30
+ self._multiline_strings = multiline_strings
31
+
32
+ def _format_scalar(self, o: Scalar) -> str:
33
+ if (
34
+ self._multiline_strings and
35
+ isinstance(o, str) and
36
+ '\n' in o
37
+ ):
38
+ if self._ensure_ascii:
39
+ return encode_string_ascii(o, escape_map=MULTILINE_STRINGS_ESCAPE_MAP)
40
+ else:
41
+ return encode_string(o, escape_map=MULTILINE_STRINGS_ESCAPE_MAP)
42
+
43
+ else:
44
+ return super()._format_scalar(o)
@@ -56,8 +56,7 @@ import abc
56
56
  import dataclasses as dc
57
57
  import email.utils
58
58
  import html
59
- import http.client
60
- import http.server
59
+ import http
61
60
  import io
62
61
  import textwrap
63
62
  import time
omlish/lang/__init__.py CHANGED
@@ -225,13 +225,6 @@ from .iterables import ( # noqa
225
225
  take,
226
226
  )
227
227
 
228
- from .maybes import ( # noqa
229
- Maybe,
230
- empty,
231
- just,
232
- maybe,
233
- )
234
-
235
228
  from .objects import ( # noqa
236
229
  Identity,
237
230
  SimpleProxy,
@@ -341,6 +334,13 @@ from ..lite.imports import ( # noqa
341
334
  import_module_attr,
342
335
  )
343
336
 
337
+ from ..lite.maybes import ( # noqa
338
+ Maybe,
339
+ )
340
+
341
+ empty = Maybe.empty
342
+ just = Maybe.just
343
+
344
344
  from ..lite.reprs import ( # noqa
345
345
  AttrRepr,
346
346
  attr_repr,
@@ -22,6 +22,7 @@ TODO:
22
22
  import dataclasses as dc
23
23
  import functools
24
24
  import inspect
25
+ import types
25
26
  import typing as ta
26
27
 
27
28
  from ..classes.abstract import Abstract
@@ -153,6 +154,10 @@ def _make_cache_key_maker(
153
154
  ##
154
155
 
155
156
 
157
+ class _CachedException(ta.NamedTuple):
158
+ ex: BaseException
159
+
160
+
156
161
  class _CachedFunction(ta.Generic[T], Abstract):
157
162
  @dc.dataclass(frozen=True, kw_only=True)
158
163
  class Opts:
@@ -160,6 +165,19 @@ class _CachedFunction(ta.Generic[T], Abstract):
160
165
  lock: DefaultLockable = None
161
166
  transient: bool = False
162
167
  no_wrapper_update: bool = False
168
+ cache_exceptions: type[BaseException] | tuple[type[BaseException], ...] | None = None
169
+
170
+ def __post_init__(self) -> None:
171
+ if (ce := self.cache_exceptions) is not None:
172
+ if isinstance(ce, type):
173
+ if not issubclass(ce, BaseException):
174
+ raise TypeError(ce)
175
+ elif isinstance(ce, tuple):
176
+ for e in ce:
177
+ if not issubclass(e, BaseException):
178
+ raise TypeError(e)
179
+ else:
180
+ raise TypeError(ce)
163
181
 
164
182
  def __init__(
165
183
  self,
@@ -199,13 +217,33 @@ class _CachedFunction(ta.Generic[T], Abstract):
199
217
  def __call__(self, *args, **kwargs) -> T:
200
218
  k = self._key_maker(*args, **kwargs)
201
219
 
202
- try:
203
- return self._values[k]
204
- except KeyError:
205
- pass
220
+ if (ce := self._opts.cache_exceptions) is None:
221
+ try:
222
+ return self._values[k]
223
+ except KeyError:
224
+ pass
206
225
 
207
- def call_value_fn():
208
- return self._value_fn(*args, **kwargs)
226
+ else:
227
+ try:
228
+ hit = self._values[k]
229
+ except KeyError:
230
+ pass
231
+ else:
232
+ if isinstance(hit, _CachedException):
233
+ raise hit.ex
234
+ else:
235
+ return hit
236
+
237
+ if ce is None:
238
+ def call_value_fn():
239
+ return self._value_fn(*args, **kwargs)
240
+
241
+ else:
242
+ def call_value_fn():
243
+ try:
244
+ return self._value_fn(*args, **kwargs)
245
+ except ce as ex: # type: ignore[misc]
246
+ return _CachedException(ex)
209
247
 
210
248
  if self._lock is not None:
211
249
  with self._lock:
@@ -220,7 +258,11 @@ class _CachedFunction(ta.Generic[T], Abstract):
220
258
  value = call_value_fn()
221
259
 
222
260
  self._values[k] = value
223
- return value
261
+
262
+ if isinstance(value, _CachedException):
263
+ raise value.ex
264
+ else:
265
+ return value
224
266
 
225
267
 
226
268
  #
@@ -369,12 +411,27 @@ def cached_function(fn=None, **kwargs): # noqa
369
411
 
370
412
  opts = _CachedFunction.Opts(**kwargs)
371
413
 
414
+ if isinstance(fn, types.MethodType):
415
+ return _FreeCachedFunction(
416
+ fn,
417
+ opts=opts,
418
+ key_maker=_make_cache_key_maker(fn, bound=True),
419
+ )
420
+
372
421
  if isinstance(fn, staticmethod):
373
- return _FreeCachedFunction(fn, opts=opts, value_fn=unwrap_func(fn))
422
+ return _FreeCachedFunction(
423
+ fn,
424
+ opts=opts,
425
+ value_fn=unwrap_func(fn),
426
+ )
374
427
 
375
428
  scope = classmethod if isinstance(fn, classmethod) else None
376
429
 
377
- return _DescriptorCachedFunction(fn, scope, opts=opts)
430
+ return _DescriptorCachedFunction(
431
+ fn,
432
+ scope,
433
+ opts=opts,
434
+ )
378
435
 
379
436
 
380
437
  def static_init(fn: CallableT) -> CallableT:
omlish/lang/generators.py CHANGED
@@ -2,10 +2,8 @@ import abc
2
2
  import functools
3
3
  import typing as ta
4
4
 
5
+ from ..lite.maybes import Maybe
5
6
  from .classes.restrict import Abstract
6
- from .maybes import Maybe
7
- from .maybes import empty
8
- from .maybes import just
9
7
 
10
8
 
11
9
  T = ta.TypeVar('T')
@@ -203,7 +201,7 @@ class GeneratorMappedIterator(ta.Generic[O, I, R]):
203
201
 
204
202
  self._g = g
205
203
  self._it = it
206
- self._value: Maybe[R] = empty()
204
+ self._value: Maybe[R] = Maybe.empty()
207
205
 
208
206
  @property
209
207
  def g(self) -> ta.Generator[O, I, R]:
@@ -225,7 +223,7 @@ class GeneratorMappedIterator(ta.Generic[O, I, R]):
225
223
  try:
226
224
  o = self._g.send(i)
227
225
  except StopIteration as e:
228
- self._value = just(e.value)
226
+ self._value = Maybe.just(e.value)
229
227
  raise StopIteration from e
230
228
  return o
231
229
 
omlish/lang/params.py CHANGED
@@ -7,12 +7,10 @@ import enum
7
7
  import inspect
8
8
  import typing as ta
9
9
 
10
+ from ..lite.maybes import Maybe
10
11
  from .classes.abstract import Abstract
11
12
  from .classes.restrict import Final
12
13
  from .classes.restrict import Sealed
13
- from .maybes import Maybe
14
- from .maybes import empty
15
- from .maybes import just
16
14
 
17
15
 
18
16
  T = ta.TypeVar('T')
@@ -25,7 +23,7 @@ T = ta.TypeVar('T')
25
23
  class Param(Sealed, Abstract):
26
24
  name: str
27
25
 
28
- annotation: Maybe = empty()
26
+ annotation: Maybe = Maybe.empty()
29
27
 
30
28
  prefix: ta.ClassVar[str] = ''
31
29
 
@@ -57,7 +55,7 @@ class KwargsParam(VarParam, Final):
57
55
 
58
56
  @dc.dataclass(frozen=True, unsafe_hash=True)
59
57
  class ValParam(Param):
60
- default: Maybe = empty()
58
+ default: Maybe = Maybe.empty()
61
59
 
62
60
 
63
61
  @dc.dataclass(frozen=True, unsafe_hash=True)
@@ -83,9 +81,9 @@ class ParamSeparator(enum.Enum):
83
81
 
84
82
  def _inspect_empty_to_maybe(o: T) -> Maybe[T]:
85
83
  if o is inspect.Parameter.empty:
86
- return empty()
84
+ return Maybe.empty()
87
85
  else:
88
- return just(o)
86
+ return Maybe.just(o)
89
87
 
90
88
 
91
89
  class ParamSpec(ta.Sequence[Param], Final):
@@ -119,8 +117,8 @@ class ParamSpec(ta.Sequence[Param], Final):
119
117
  if i < offset:
120
118
  continue
121
119
 
122
- ann = _inspect_empty_to_maybe(ip.annotation) if not strip_annotations else empty()
123
- dfl = _inspect_empty_to_maybe(ip.default) if not strip_defaults else empty()
120
+ ann = _inspect_empty_to_maybe(ip.annotation) if not strip_annotations else Maybe.empty()
121
+ dfl = _inspect_empty_to_maybe(ip.default) if not strip_defaults else Maybe.empty()
124
122
 
125
123
  if ip.kind == inspect.Parameter.POSITIONAL_ONLY:
126
124
  ps.append(PosOnlyParam(ip.name, ann, dfl))