omlish 0.0.0.dev71__py3-none-any.whl → 0.0.0.dev73__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.dev71'
2
- __revision__ = '293695b11eeed255848dcf6beee9c4b4383d5c91'
1
+ __version__ = '0.0.0.dev73'
2
+ __revision__ = '1fa2fcb99d4ea871fe3f5aa5c094ae37d0b1090c'
3
3
 
4
4
 
5
5
  #
omlish/check.py CHANGED
@@ -96,7 +96,7 @@ def _default_exception_factory(exc_cls: type[Exception], *args, **kwargs) -> Exc
96
96
  _EXCEPTION_FACTORY = _default_exception_factory
97
97
 
98
98
 
99
- class _Args:
99
+ class _ArgsKwargs:
100
100
  def __init__(self, *args, **kwargs):
101
101
  self.args = args
102
102
  self.kwargs = kwargs
@@ -106,7 +106,7 @@ def _raise(
106
106
  exception_type: type[Exception],
107
107
  default_message: str,
108
108
  message: Message,
109
- ak: _Args = _Args(),
109
+ ak: _ArgsKwargs = _ArgsKwargs(),
110
110
  *,
111
111
  render_fmt: str | None = None,
112
112
  ) -> ta.NoReturn:
@@ -159,7 +159,7 @@ def isinstance(v: ta.Any, spec: type[T] | tuple, msg: Message = None) -> T: # n
159
159
  TypeError,
160
160
  'Must be instance',
161
161
  msg,
162
- _Args(v, spec),
162
+ _ArgsKwargs(v, spec),
163
163
  render_fmt='not isinstance(%s, %s)',
164
164
  )
165
165
 
@@ -179,7 +179,7 @@ def cast(v: ta.Any, cls: type[T], msg: Message = None) -> T: # noqa
179
179
  TypeError,
180
180
  'Must be instance',
181
181
  msg,
182
- _Args(v, cls),
182
+ _ArgsKwargs(v, cls),
183
183
  )
184
184
 
185
185
  return v
@@ -198,7 +198,7 @@ def not_isinstance(v: T, spec: ta.Any, msg: Message = None) -> T: # noqa
198
198
  TypeError,
199
199
  'Must not be instance',
200
200
  msg,
201
- _Args(v, spec),
201
+ _ArgsKwargs(v, spec),
202
202
  render_fmt='isinstance(%s, %s)',
203
203
  )
204
204
 
@@ -221,7 +221,7 @@ def issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: # noq
221
221
  TypeError,
222
222
  'Must be subclass',
223
223
  msg,
224
- _Args(v, spec),
224
+ _ArgsKwargs(v, spec),
225
225
  render_fmt='not issubclass(%s, %s)',
226
226
  )
227
227
 
@@ -234,7 +234,7 @@ def not_issubclass(v: type[T], spec: ta.Any, msg: Message = None) -> type[T]: #
234
234
  TypeError,
235
235
  'Must not be subclass',
236
236
  msg,
237
- _Args(v, spec),
237
+ _ArgsKwargs(v, spec),
238
238
  render_fmt='issubclass(%s, %s)',
239
239
  )
240
240
 
@@ -250,7 +250,7 @@ def in_(v: T, c: ta.Container[T], msg: Message = None) -> T:
250
250
  ValueError,
251
251
  'Must be in',
252
252
  msg,
253
- _Args(v, c),
253
+ _ArgsKwargs(v, c),
254
254
  render_fmt='%s not in %s',
255
255
  )
256
256
 
@@ -263,7 +263,7 @@ def not_in(v: T, c: ta.Container[T], msg: Message = None) -> T:
263
263
  ValueError,
264
264
  'Must not be in',
265
265
  msg,
266
- _Args(v, c),
266
+ _ArgsKwargs(v, c),
267
267
  render_fmt='%s in %s',
268
268
  )
269
269
 
@@ -276,7 +276,7 @@ def empty(v: SizedT, msg: Message = None) -> SizedT:
276
276
  ValueError,
277
277
  'Must be empty',
278
278
  msg,
279
- _Args(v),
279
+ _ArgsKwargs(v),
280
280
  render_fmt='%s',
281
281
  )
282
282
 
@@ -294,7 +294,7 @@ def iterempty(v: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
294
294
  ValueError,
295
295
  'Must be empty',
296
296
  msg,
297
- _Args(v),
297
+ _ArgsKwargs(v),
298
298
  render_fmt='%s',
299
299
  )
300
300
 
@@ -307,7 +307,7 @@ def not_empty(v: SizedT, msg: Message = None) -> SizedT:
307
307
  ValueError,
308
308
  'Must not be empty',
309
309
  msg,
310
- _Args(v),
310
+ _ArgsKwargs(v),
311
311
  render_fmt='%s',
312
312
  )
313
313
 
@@ -321,7 +321,7 @@ def unique(it: ta.Iterable[T], msg: Message = None) -> ta.Iterable[T]:
321
321
  ValueError,
322
322
  'Must be unique',
323
323
  msg,
324
- _Args(it, dupes),
324
+ _ArgsKwargs(it, dupes),
325
325
  )
326
326
 
327
327
  return it
@@ -335,7 +335,7 @@ def single(obj: ta.Iterable[T], message: Message = None) -> T:
335
335
  ValueError,
336
336
  'Must be single',
337
337
  message,
338
- _Args(obj),
338
+ _ArgsKwargs(obj),
339
339
  render_fmt='%s',
340
340
  )
341
341
 
@@ -358,7 +358,7 @@ def opt_single(obj: ta.Iterable[T], message: Message = None) -> T | None:
358
358
  ValueError,
359
359
  'Must be empty or single',
360
360
  message,
361
- _Args(obj),
361
+ _ArgsKwargs(obj),
362
362
  render_fmt='%s',
363
363
  )
364
364
 
@@ -372,7 +372,7 @@ def none(v: ta.Any, msg: Message = None) -> None:
372
372
  ValueError,
373
373
  'Must be None',
374
374
  msg,
375
- _Args(v),
375
+ _ArgsKwargs(v),
376
376
  render_fmt='%s',
377
377
  )
378
378
 
@@ -383,7 +383,7 @@ def not_none(v: T | None, msg: Message = None) -> T:
383
383
  ValueError,
384
384
  'Must not be None',
385
385
  msg,
386
- _Args(v),
386
+ _ArgsKwargs(v),
387
387
  render_fmt='%s',
388
388
  )
389
389
 
@@ -399,7 +399,7 @@ def equal(v: T, o: ta.Any, msg: Message = None) -> T:
399
399
  ValueError,
400
400
  'Must be equal',
401
401
  msg,
402
- _Args(v, o),
402
+ _ArgsKwargs(v, o),
403
403
  render_fmt='%s != %s',
404
404
  )
405
405
 
@@ -412,7 +412,7 @@ def is_(v: T, o: ta.Any, msg: Message = None) -> T:
412
412
  ValueError,
413
413
  'Must be the same',
414
414
  msg,
415
- _Args(v, o),
415
+ _ArgsKwargs(v, o),
416
416
  render_fmt='%s is not %s',
417
417
  )
418
418
 
@@ -425,7 +425,7 @@ def is_not(v: T, o: ta.Any, msg: Message = None) -> T:
425
425
  ValueError,
426
426
  'Must not be the same',
427
427
  msg,
428
- _Args(v, o),
428
+ _ArgsKwargs(v, o),
429
429
  render_fmt='%s is %s',
430
430
  )
431
431
 
@@ -438,7 +438,7 @@ def callable(v: T, msg: Message = None) -> T: # noqa
438
438
  TypeError,
439
439
  'Must be callable',
440
440
  msg,
441
- _Args(v),
441
+ _ArgsKwargs(v),
442
442
  render_fmt='%s',
443
443
  )
444
444
 
@@ -451,7 +451,7 @@ def non_empty_str(v: str | None, msg: Message = None) -> str:
451
451
  ValueError,
452
452
  'Must be non-empty str',
453
453
  msg,
454
- _Args(v),
454
+ _ArgsKwargs(v),
455
455
  render_fmt='%s',
456
456
  )
457
457
 
@@ -464,7 +464,7 @@ def replacing(expected: ta.Any, old: ta.Any, new: T, msg: Message = None) -> T:
464
464
  ValueError,
465
465
  'Must be replacing',
466
466
  msg,
467
- _Args(expected, old, new),
467
+ _ArgsKwargs(expected, old, new),
468
468
  render_fmt='%s -> %s -> %s',
469
469
  )
470
470
 
@@ -477,7 +477,7 @@ def replacing_none(old: ta.Any, new: T, msg: Message = None) -> T:
477
477
  ValueError,
478
478
  'Must be replacing None',
479
479
  msg,
480
- _Args(old, new),
480
+ _ArgsKwargs(old, new),
481
481
  render_fmt='%s -> %s',
482
482
  )
483
483
 
@@ -493,7 +493,7 @@ def arg(v: bool, msg: Message = None) -> None:
493
493
  RuntimeError,
494
494
  'Argument condition not met',
495
495
  msg,
496
- _Args(v),
496
+ _ArgsKwargs(v),
497
497
  render_fmt='%s',
498
498
  )
499
499
 
@@ -504,6 +504,6 @@ def state(v: bool, msg: Message = None) -> None:
504
504
  RuntimeError,
505
505
  'State condition not met',
506
506
  msg,
507
- _Args(v),
507
+ _ArgsKwargs(v),
508
508
  render_fmt='%s',
509
509
  )
@@ -13,6 +13,7 @@ from .render import JsonRenderer
13
13
 
14
14
 
15
15
  if ta.TYPE_CHECKING:
16
+ import ast
16
17
  import tomllib
17
18
 
18
19
  import yaml
@@ -21,6 +22,7 @@ if ta.TYPE_CHECKING:
21
22
  from .. import props
22
23
 
23
24
  else:
25
+ ast = lang.proxy_import('ast')
24
26
  tomllib = lang.proxy_import('tomllib')
25
27
 
26
28
  yaml = lang.proxy_import('yaml')
@@ -50,6 +52,7 @@ class Formats(enum.Enum):
50
52
  TOML = Format(['toml'], lambda f: tomllib.loads(f.read()))
51
53
  ENV = Format(['env', 'dotenv'], lambda f: dotenv.dotenv_values(stream=f))
52
54
  PROPS = Format(['properties', 'props'], lambda f: dict(props.Properties().load(f.read())))
55
+ PY = Format(['py', 'python', 'repr'], lambda f: ast.literal_eval(f.read()))
53
56
 
54
57
 
55
58
  FORMATS_BY_NAME: ta.Mapping[str, Format] = {
omlish/http/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from . import consts # noqa
2
2
 
3
- from .client import ( # noqa
3
+ from .clients import ( # noqa
4
4
  HttpClient,
5
5
  HttpClientError,
6
6
  HttpRequest,
omlish/http/clients.py ADDED
@@ -0,0 +1,239 @@
1
+ """
2
+ TODO:
3
+ - check=False
4
+ - return non-200 HttpResponses
5
+ - async
6
+ - stream
7
+ """
8
+ import abc
9
+ import http.client
10
+ import typing as ta
11
+ import urllib.error
12
+ import urllib.request
13
+
14
+ from .. import cached
15
+ from .. import dataclasses as dc
16
+ from .. import lang
17
+ from .headers import CanHttpHeaders
18
+ from .headers import HttpHeaders
19
+
20
+
21
+ if ta.TYPE_CHECKING:
22
+ import httpx
23
+ else:
24
+ httpx = lang.proxy_import('httpx')
25
+
26
+
27
+ ##
28
+
29
+
30
+ DEFAULT_ENCODING = 'utf-8'
31
+
32
+
33
+ def is_success_status(status: int) -> bool:
34
+ return 200 <= status < 300
35
+
36
+
37
+ ##
38
+
39
+
40
+ @dc.dataclass(frozen=True)
41
+ class HttpRequest(lang.Final):
42
+ url: str
43
+ method: str | None = None # noqa
44
+
45
+ _: dc.KW_ONLY
46
+
47
+ headers: CanHttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
48
+ data: bytes | str | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
49
+
50
+ timeout_s: float | None = None
51
+
52
+ #
53
+
54
+ @property
55
+ def method_or_default(self) -> str:
56
+ if self.method is not None:
57
+ return self.method
58
+ if self.data is not None:
59
+ return 'POST'
60
+ return 'GET'
61
+
62
+ @cached.property
63
+ def headers_(self) -> HttpHeaders | None:
64
+ return HttpHeaders(self.headers) if self.headers is not None else None
65
+
66
+
67
+ @dc.dataclass(frozen=True, kw_only=True)
68
+ class HttpResponse(lang.Final):
69
+ status: int
70
+
71
+ headers: HttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
72
+ data: bytes | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
73
+
74
+ request: HttpRequest
75
+ underlying: ta.Any = dc.field(default=None, repr=False)
76
+
77
+ #
78
+
79
+ @property
80
+ def is_success(self) -> bool:
81
+ return is_success_status(self.status)
82
+
83
+
84
+ class HttpClientError(Exception):
85
+ @property
86
+ def cause(self) -> BaseException | None:
87
+ return self.__cause__
88
+
89
+
90
+ @dc.dataclass(frozen=True)
91
+ class HttpStatusError(HttpClientError):
92
+ response: HttpResponse
93
+
94
+
95
+ class HttpClient(lang.Abstract):
96
+ def __enter__(self) -> ta.Self:
97
+ return self
98
+
99
+ def __exit__(self, exc_type, exc_val, exc_tb):
100
+ pass
101
+
102
+ def request(
103
+ self,
104
+ req: HttpRequest,
105
+ *,
106
+ check: bool = False,
107
+ ) -> HttpResponse:
108
+ resp = self._request(req)
109
+
110
+ if check and not resp.is_success:
111
+ if isinstance(resp.underlying, Exception):
112
+ cause = resp.underlying
113
+ else:
114
+ cause = None
115
+ raise HttpStatusError(resp) from cause
116
+
117
+ return resp
118
+
119
+ @abc.abstractmethod
120
+ def _request(self, req: HttpRequest) -> HttpResponse:
121
+ raise NotImplementedError
122
+
123
+
124
+ ##
125
+
126
+
127
+ class UrllibHttpClient(HttpClient):
128
+ def _request(self, req: HttpRequest) -> HttpResponse:
129
+ d: ta.Any
130
+ if (d := req.data) is not None:
131
+ if isinstance(d, str):
132
+ d = d.encode(DEFAULT_ENCODING)
133
+
134
+ # urllib headers are dumb dicts [1], and keys *must* be strings or it will automatically add problematic default
135
+ # headers because it doesn't see string keys in its header dict [2]. frustratingly it has no problem accepting
136
+ # bytes values though [3].
137
+ # [1]: https://github.com/python/cpython/blob/232b303e4ca47892f544294bf42e31dc34f0ec72/Lib/urllib/request.py#L319-L325 # noqa
138
+ # [2]: https://github.com/python/cpython/blob/232b303e4ca47892f544294bf42e31dc34f0ec72/Lib/urllib/request.py#L1276-L1279 # noqa
139
+ # [3]: https://github.com/python/cpython/blob/232b303e4ca47892f544294bf42e31dc34f0ec72/Lib/http/client.py#L1300-L1301 # noqa
140
+ h: dict[str, str] = {}
141
+ if hs := req.headers_:
142
+ for k, v in hs.strict_dct.items():
143
+ h[k.decode('ascii')] = v.decode('ascii')
144
+
145
+ try:
146
+ with urllib.request.urlopen( # noqa
147
+ urllib.request.Request( # noqa
148
+ req.url,
149
+ method=req.method_or_default,
150
+ headers=h,
151
+ data=d,
152
+ ),
153
+ timeout=req.timeout_s,
154
+ ) as resp:
155
+ return HttpResponse(
156
+ status=resp.status,
157
+ headers=HttpHeaders(resp.headers.items()),
158
+ data=resp.read(),
159
+ request=req,
160
+ underlying=resp,
161
+ )
162
+
163
+ except urllib.error.HTTPError as e:
164
+ return HttpResponse(
165
+ status=e.code,
166
+ headers=HttpHeaders(e.headers.items()),
167
+ data=e.read(),
168
+ request=req,
169
+ underlying=e,
170
+ )
171
+
172
+ except (urllib.error.URLError, http.client.HTTPException) as e:
173
+ raise HttpClientError from e
174
+
175
+
176
+ ##
177
+
178
+
179
+ class HttpxHttpClient(HttpClient):
180
+ def _request(self, req: HttpRequest) -> HttpResponse:
181
+ try:
182
+ response = httpx.request(
183
+ method=req.method_or_default,
184
+ url=req.url,
185
+ headers=req.headers_ or None, # type: ignore
186
+ content=req.data,
187
+ timeout=req.timeout_s,
188
+ )
189
+
190
+ return HttpResponse(
191
+ status=response.status_code,
192
+ headers=HttpHeaders(response.headers.raw),
193
+ data=response.content,
194
+ request=req,
195
+ underlying=response,
196
+ )
197
+
198
+ except httpx.HTTPError as e:
199
+ raise HttpClientError from e
200
+
201
+
202
+ ##
203
+
204
+
205
+ def client() -> HttpClient:
206
+ return UrllibHttpClient()
207
+
208
+
209
+ def request(
210
+ url: str,
211
+ method: str | None = None,
212
+ *,
213
+ headers: CanHttpHeaders | None = None,
214
+ data: bytes | str | None = None,
215
+
216
+ timeout_s: float | None = None,
217
+
218
+ check: bool = False,
219
+
220
+ **kwargs: ta.Any,
221
+ ) -> HttpResponse:
222
+ req = HttpRequest(
223
+ url,
224
+ method=method,
225
+
226
+ headers=headers,
227
+ data=data,
228
+
229
+ timeout_s=timeout_s,
230
+
231
+ **kwargs,
232
+ )
233
+
234
+ with client() as cli:
235
+ return cli.request(
236
+ req,
237
+
238
+ check=check,
239
+ )
omlish/http/consts.py CHANGED
@@ -64,5 +64,9 @@ BEARER_AUTH_HEADER_PREFIX = b'Bearer '
64
64
  BASIC_AUTH_HEADER_PREFIX = b'Basic '
65
65
 
66
66
 
67
+ def format_bearer_auth_header(token: str | bytes) -> bytes:
68
+ return BEARER_AUTH_HEADER_PREFIX + (token.encode('ascii') if isinstance(token, str) else token)
69
+
70
+
67
71
  def format_basic_auth_header(username: str, password: str) -> bytes:
68
72
  return BASIC_AUTH_HEADER_PREFIX + base64.b64encode(':'.join([username, password]).encode())
omlish/http/headers.py CHANGED
@@ -9,9 +9,21 @@ StrOrBytes: ta.TypeAlias = str | bytes
9
9
 
10
10
  CanHttpHeaders: ta.TypeAlias = ta.Union[
11
11
  'HttpHeaders',
12
+
13
+ ta.Mapping[str, str],
14
+ ta.Mapping[str, ta.Sequence[str]],
15
+ ta.Mapping[str, str | ta.Sequence[str]],
16
+
17
+ ta.Mapping[bytes, bytes],
18
+ ta.Mapping[bytes, ta.Sequence[bytes]],
19
+ ta.Mapping[bytes, bytes | ta.Sequence[bytes]],
20
+
12
21
  ta.Mapping[StrOrBytes, StrOrBytes],
13
22
  ta.Mapping[StrOrBytes, ta.Sequence[StrOrBytes]],
14
23
  ta.Mapping[StrOrBytes, StrOrBytes | ta.Sequence[StrOrBytes]],
24
+
25
+ ta.Sequence[tuple[str, str]],
26
+ ta.Sequence[tuple[bytes, bytes]],
15
27
  ta.Sequence[tuple[StrOrBytes, StrOrBytes]],
16
28
  ]
17
29
 
@@ -38,7 +50,11 @@ class HttpHeaders:
38
50
  raise TypeError(src)
39
51
 
40
52
  elif isinstance(src, ta.Sequence):
41
- for k, v in src:
53
+ for t in src:
54
+ if isinstance(t, (str, bytes)):
55
+ raise TypeError(t)
56
+
57
+ k, v = t
42
58
  lst.append((self._as_bytes(k), self._as_bytes(v)))
43
59
 
44
60
  else:
@@ -74,9 +90,23 @@ class HttpHeaders:
74
90
 
75
91
  #
76
92
 
93
+ @property
94
+ def raw(self) -> ta.Sequence[tuple[bytes, bytes]]:
95
+ return self._lst
96
+
97
+ @classmethod
98
+ def _as_key(cls, o: StrOrBytes) -> bytes:
99
+ return cls._as_bytes(o).lower()
100
+
101
+ @cached.property
102
+ def normalized(self) -> ta.Sequence[tuple[bytes, bytes]]:
103
+ return [(self._as_key(k), v) for k, v in self._lst]
104
+
105
+ #
106
+
77
107
  @cached.property
78
108
  def multi_dct(self) -> ta.Mapping[bytes, ta.Sequence[bytes]]:
79
- return col.multi_map(self._lst)
109
+ return col.multi_map(self.normalized)
80
110
 
81
111
  @cached.property
82
112
  def single_dct(self) -> ta.Mapping[bytes, bytes]:
@@ -84,13 +114,13 @@ class HttpHeaders:
84
114
 
85
115
  @cached.property
86
116
  def strict_dct(self) -> ta.Mapping[bytes, bytes]:
87
- return col.make_map(self._lst, strict=True)
117
+ return col.make_map(self.normalized, strict=True)
88
118
 
89
119
  #
90
120
 
91
121
  @cached.property
92
122
  def strs(self) -> ta.Sequence[tuple[str, str]]:
93
- return tuple((k.decode(self.ENCODING), v.decode(self.ENCODING)) for k, v in self._lst)
123
+ return tuple((k.decode(self.ENCODING), v.decode(self.ENCODING)) for k, v in self.normalized)
94
124
 
95
125
  @cached.property
96
126
  def multi_str_dct(self) -> ta.Mapping[str, ta.Sequence[str]]:
@@ -116,22 +146,28 @@ class HttpHeaders:
116
146
  return iter(self._lst)
117
147
 
118
148
  @ta.overload
119
- def __getitem__(self, item: StrOrBytes) -> ta.Sequence[StrOrBytes]:
149
+ def __getitem__(self, item: str) -> ta.Sequence[str]:
150
+ ...
151
+
152
+ @ta.overload
153
+ def __getitem__(self, item: bytes) -> ta.Sequence[bytes]:
120
154
  ...
121
155
 
122
156
  @ta.overload
123
- def __getitem__(self, item: int) -> StrOrBytes:
157
+ def __getitem__(self, item: int) -> tuple[StrOrBytes, StrOrBytes]:
124
158
  ...
125
159
 
126
160
  @ta.overload
127
- def __getitem__(self, item: slice) -> ta.Sequence[StrOrBytes]:
161
+ def __getitem__(self, item: slice) -> ta.Sequence[tuple[StrOrBytes, StrOrBytes]]:
128
162
  ...
129
163
 
130
164
  def __getitem__(self, item):
131
165
  if isinstance(item, (int, slice)):
132
166
  return self._lst[item]
133
- elif isinstance(item, (str, bytes)):
134
- return self.multi_dct[self._as_bytes(item)]
167
+ elif isinstance(item, str):
168
+ return self.multi_str_dct[item.lower()]
169
+ elif isinstance(item, bytes):
170
+ return self.multi_dct[self._as_key(item)]
135
171
  else:
136
172
  raise TypeError(item)
137
173
 
@@ -1,6 +1,8 @@
1
+ import enum
2
+
1
3
  from ... import check
4
+ from ... import dataclasses as dc
2
5
  from ... import lang
3
- from .base import Node
4
6
  from .exprs import CanExpr
5
7
  from .exprs import Expr
6
8
  from .exprs import ExprBuilder
@@ -9,16 +11,23 @@ from .exprs import ExprBuilder
9
11
  ##
10
12
 
11
13
 
12
- class BinaryOp(Node, lang.Final):
14
+ class BinaryOpKind(enum.Enum):
15
+ ARITH = enum.auto()
16
+ BIT = enum.auto()
17
+ CMP = enum.auto()
18
+
19
+
20
+ class BinaryOp(dc.Frozen, lang.Final, eq=False):
13
21
  name: str
22
+ kind: BinaryOpKind
14
23
 
15
24
 
16
25
  class BinaryOps(lang.Namespace):
17
- ADD = BinaryOp('add')
18
- SUB = BinaryOp('sub')
26
+ ADD = BinaryOp('add', BinaryOpKind.ARITH)
27
+ SUB = BinaryOp('sub', BinaryOpKind.ARITH)
19
28
 
20
- EQ = BinaryOp('eq')
21
- NE = BinaryOp('ne')
29
+ EQ = BinaryOp('eq', BinaryOpKind.CMP)
30
+ NE = BinaryOp('ne', BinaryOpKind.CMP)
22
31
 
23
32
 
24
33
  class Binary(Expr, lang.Final):
@@ -3,7 +3,6 @@ import typing as ta
3
3
  from ... import check
4
4
  from ... import dataclasses as dc
5
5
  from ... import lang
6
- from .base import Node
7
6
  from .exprs import CanExpr
8
7
  from .exprs import Expr
9
8
  from .exprs import ExprBuilder
@@ -12,7 +11,7 @@ from .exprs import ExprBuilder
12
11
  ##
13
12
 
14
13
 
15
- class MultiOp(Node, lang.Final):
14
+ class MultiOp(dc.Frozen, lang.Final, eq=False):
16
15
  name: str
17
16
 
18
17
 
@@ -22,9 +22,9 @@ class SelectItem(Node, lang.Final):
22
22
 
23
23
 
24
24
  class Select(Stmt, lang.Final):
25
- its: ta.Sequence[SelectItem] = dc.xfield(coerce=tuple)
26
- fr: Relation | None = dc.xfield(None, repr_fn=dc.opt_repr)
27
- wh: Expr | None = dc.xfield(None, repr_fn=dc.opt_repr)
25
+ items: ta.Sequence[SelectItem] = dc.xfield(coerce=tuple)
26
+ from_: Relation | None = dc.xfield(None, repr_fn=dc.opt_repr)
27
+ where: Expr | None = dc.xfield(None, repr_fn=dc.opt_repr)
28
28
 
29
29
 
30
30
  CanSelectItem: ta.TypeAlias = SelectItem | CanExpr
@@ -39,12 +39,12 @@ class SelectBuilder(ExprBuilder, RelationBuilder):
39
39
 
40
40
  def select(
41
41
  self,
42
- its: ta.Sequence[CanSelectItem],
43
- fr: CanRelation | None = None,
44
- wh: CanExpr | None = None,
42
+ items: ta.Sequence[CanSelectItem],
43
+ from_: CanRelation | None = None,
44
+ where: CanExpr | None = None,
45
45
  ) -> Select:
46
46
  return Select(
47
- [self.select_item(i) for i in its],
48
- fr=self.relation(fr) if fr is not None else None,
49
- wh=self.expr(wh) if wh is not None else None,
47
+ [self.select_item(i) for i in items],
48
+ from_=self.relation(from_) if from_ is not None else None,
49
+ where=self.expr(where) if where is not None else None,
50
50
  )
@@ -1,5 +1,5 @@
1
+ from ... import dataclasses as dc
1
2
  from ... import lang
2
- from .base import Node
3
3
  from .exprs import CanExpr
4
4
  from .exprs import Expr
5
5
  from .exprs import ExprBuilder
@@ -8,7 +8,7 @@ from .exprs import ExprBuilder
8
8
  ##
9
9
 
10
10
 
11
- class UnaryOp(Node, lang.Final):
11
+ class UnaryOp(dc.Frozen, lang.Final, eq=False):
12
12
  name: str
13
13
 
14
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev71
3
+ Version: 0.0.0.dev73
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,10 +1,10 @@
1
1
  omlish/.manifests.json,sha256=TXvFdkAU0Zr2FKdo7fyvt9nr3UjCtrnAZ0diZXSAteE,1430
2
- omlish/__about__.py,sha256=UA4amXjbtRmwpmcjop8hOCA8ytGC55dHDjgDm1toVmM,3420
2
+ omlish/__about__.py,sha256=yPixWBOcI8ebqG0dPVHJ2dG1M4Ec4rBKr8ehmN0d7qQ,3420
3
3
  omlish/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  omlish/argparse.py,sha256=Dc73G8lyoQBLvXhMYUbzQUh4SJu_OTvKUXjSUxq_ang,7499
5
5
  omlish/c3.py,sha256=4vogWgwPb8TbNS2KkZxpoWbwjj7MuHG2lQG-hdtkvjI,8062
6
6
  omlish/cached.py,sha256=UAizxlH4eMWHPzQtmItmyE6FEpFEUFzIkxaO2BHWZ5s,196
7
- omlish/check.py,sha256=fgWiBoHvqZlE8tjaxK7OMW4J8z3_rEGRENTka3EohbI,10378
7
+ omlish/check.py,sha256=rZFEn6IHiMq4KGCYxlUGcUCJP12pPPUs_-AG0aouPM8,10540
8
8
  omlish/datetimes.py,sha256=HajeM1kBvwlTa-uR1TTZHmZ3zTPnnUr1uGGQhiO1XQ0,2152
9
9
  omlish/defs.py,sha256=T3bq_7h_tO3nDB5RAFBn7DkdeQgqheXzkFColbOHZko,4890
10
10
  omlish/dynamic.py,sha256=35C_cCX_Vq2HrHzGk5T-zbrMvmUdiIiwDzDNixczoDo,6541
@@ -184,7 +184,7 @@ omlish/formats/props.py,sha256=JwFJbKblqzqnzXf7YKFzQSDfcAXzkKsfoYvad6FPy98,18945
184
184
  omlish/formats/yaml.py,sha256=DSJXUq9yanfxdS6ufNTyBHMtIZO57LRnJj4w9fLY1aM,6852
185
185
  omlish/formats/json/__init__.py,sha256=moSR67Qkju2eYb_qVDtaivepe44mxAnYuC8OCSbtETg,298
186
186
  omlish/formats/json/__main__.py,sha256=1wxxKZVkj_u7HCcewwMIbGuZj_Wph95yrUbm474Op9M,188
187
- omlish/formats/json/cli.py,sha256=4zftNijlIOnGUHYn5J1s4yRDRM1K4udBzS3Kh8R2vNc,3374
187
+ omlish/formats/json/cli.py,sha256=pHFvYji6h_kMUyTgHCuDFofeDVY_5Em0wBqqVOJzDmI,3504
188
188
  omlish/formats/json/json.py,sha256=y8d8WWgzGZDTjzYc_xe9v4T0foXHI-UP7gjCwnHzUIA,828
189
189
  omlish/formats/json/render.py,sha256=6edhSrxXWW3nzRfokp5qaldT0_YAj-HVEan_rErf-vo,3208
190
190
  omlish/formats/json/backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -200,15 +200,15 @@ omlish/graphs/dot/items.py,sha256=OWPf0-hjBgS1uyy2QgAEn4IgFHJcEg7sHVWeTx1ghZc,40
200
200
  omlish/graphs/dot/make.py,sha256=RN30gHfJPiXx5Q51kbDdhVJYf59Fr84Lz9J-mXRt9sI,360
201
201
  omlish/graphs/dot/rendering.py,sha256=2UgXvMRN4Z9cfIqLlC7Iu_8bWbwUDEL4opHHkFfSqTw,3630
202
202
  omlish/graphs/dot/utils.py,sha256=_FMwn77WfiiAfLsRTOKWm4IYbNv5kQN22YJ5psw6CWg,801
203
- omlish/http/__init__.py,sha256=zv9D9PPGS49W8fKpU2k12AmUL5vE-vYxStwkz_3yxZ4,624
203
+ omlish/http/__init__.py,sha256=-ENDALr8ehHvivRD6cxIbEC94t0RHhrakf6CQRDTc8o,625
204
204
  omlish/http/asgi.py,sha256=wXhBZ21bEl32Kv9yBrRwUR_7pHEgVtHP8ZZwbasQ6-4,3307
205
- omlish/http/client.py,sha256=Zr5QzjTEo_iILQ0Ypkjr2RZQGPWa6q57BDqkpeT3qsQ,3587
205
+ omlish/http/clients.py,sha256=eOY4bmbGdXuOOabt9NLAcTO7G49u85-HoAFW28mCXS4,6004
206
206
  omlish/http/collections.py,sha256=s8w5s4Gewgxxhe2Ai0R45PgJYYifrLgTbU3VXVflHj4,260
207
- omlish/http/consts.py,sha256=-O0F6qiVWGGT18j8TMP7UNfHCECg1MmByx05oc7Ae9Q,1985
207
+ omlish/http/consts.py,sha256=FTolezLknKU6WJjk_x2T3a5LEMlnZSqv7gzTq55lxcU,2147
208
208
  omlish/http/cookies.py,sha256=uuOYlHR6e2SC3GM41V0aozK10nef9tYg83Scqpn5-HM,6351
209
209
  omlish/http/dates.py,sha256=Otgp8wRxPgNGyzx8LFowu1vC4EKJYARCiAwLFncpfHM,2875
210
210
  omlish/http/encodings.py,sha256=w2WoKajpaZnQH8j-IBvk5ZFL2O2pAU_iBvZnkocaTlw,164
211
- omlish/http/headers.py,sha256=_1d4cdRoh9rWP8QpMCVLyo5sGEDqLInzJ7GNSlsXY5s,4045
211
+ omlish/http/headers.py,sha256=S66wiXezBHybrnjAM15E9x4GJvPRvFQHeKaXdJ799fw,5028
212
212
  omlish/http/json.py,sha256=9XwAsl4966Mxrv-1ytyCqhcE6lbBJw-0_tFZzGszgHE,7440
213
213
  omlish/http/sessions.py,sha256=VZ_WS5uiQG5y7i3u8oKuQMqf8dPKUOjFm_qk_0OvI8c,4793
214
214
  omlish/http/wsgi.py,sha256=czZsVUX-l2YTlMrUjKN49wRoP4rVpS0qpeBn4O5BoMY,948
@@ -390,16 +390,16 @@ omlish/sql/alchemy/secrets.py,sha256=EMfy4EfTbEvrlv_41oOhn8qsoF-eTkY7HciPenIE6rI
390
390
  omlish/sql/alchemy/sqlean.py,sha256=RbkuOuFIfM4fowwKk8-sQ6Dxk-tTUwxS94nY5Kxt52s,403
391
391
  omlish/sql/queries/__init__.py,sha256=TEl0iNWVxPURJIRJSaKpy5EJkyVXw2Zej8jlKmeggAo,963
392
392
  omlish/sql/queries/base.py,sha256=_8O3MbH_OEjBnhp2oIJUZ3ClaQ8l4Sj9BdPdsP0Ie-g,224
393
- omlish/sql/queries/binary.py,sha256=7K8pX2ki4PsXo3RP-81LD-sl8A4nGAN7hbgJExwoQPw,1003
393
+ omlish/sql/queries/binary.py,sha256=qqlsyW7PM4DS2MkESKV81GMue22OnftOc8VD5KFpZI8,1242
394
394
  omlish/sql/queries/exprs.py,sha256=tJ5gK21NKYbxKnJmrAUDkzaPqzg7hg6-z2GS4-MG05U,1092
395
395
  omlish/sql/queries/idents.py,sha256=erW6fE9UapuvW1ZeLfGFz7yuW6zzktWIWmOuAHeF8_g,496
396
- omlish/sql/queries/multi.py,sha256=7qFq3-n3g6MmYHM_lyhru9DcabiZRAQUVaOyR3gXvXU,865
396
+ omlish/sql/queries/multi.py,sha256=qlcci2wv_gDfOPuPpwFS-aV6f11T8ftmIDzx2dbI82k,857
397
397
  omlish/sql/queries/names.py,sha256=YiIyS6ehYMYrdLlUxMawV_Xf2zdi7RwVO9Qsxr_W4_4,772
398
398
  omlish/sql/queries/relations.py,sha256=zAOCJWopA3MxAE_J4wxRtuvGEOcQgOnAQaF_wT4z930,804
399
- omlish/sql/queries/selects.py,sha256=yuFg_OZ0Nzn5Jgy3-gkkDV3JhtVrzR3Ov3asAzCgkQU,1328
399
+ omlish/sql/queries/selects.py,sha256=EcHlyKl5kGSY1d3GVxnImhGCTB6WvwQnlSA9eZanBqU,1364
400
400
  omlish/sql/queries/std.py,sha256=pddD8-WDDGwX97OW7MLahaZTLsO_0NLxxIIAXXq1N40,537
401
401
  omlish/sql/queries/stmts.py,sha256=pBqwD7dRlqMu6uh6vR3xaWOEgbZCcFWbOQ9ryYd17T4,441
402
- omlish/sql/queries/unary.py,sha256=B4PA__3GUB1L7bqG4e3fmUcS3Wypx8Ryy6P0rLiPBHQ,514
402
+ omlish/sql/queries/unary.py,sha256=lbVfsfS2zcJFchZ4sqoGMFzNEgc_Tnfj5_P1Yjs9lqs,540
403
403
  omlish/sql/tabledefs/__init__.py,sha256=TvtQsp-jJu6_ZahyCOFAaElSSBcRftNyJpdiDPGYCDk,190
404
404
  omlish/sql/tabledefs/alchemy.py,sha256=MiNfVSgX_Ka6PmVTgAcCmS3ULK5mfVRXD_VC2-9TidU,485
405
405
  omlish/sql/tabledefs/dtypes.py,sha256=egZDi-A17MC-4R_ZKR_3uQf1a6mGm91Gmh0b72O85bQ,362
@@ -433,9 +433,9 @@ omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
433
433
  omlish/text/glyphsplit.py,sha256=Ug-dPRO7x-OrNNr8g1y6DotSZ2KH0S-VcOmUobwa4B0,3296
434
434
  omlish/text/indent.py,sha256=6Jj6TFY9unaPa4xPzrnZemJ-fHsV53IamP93XGjSUHs,1274
435
435
  omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
436
- omlish-0.0.0.dev71.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
437
- omlish-0.0.0.dev71.dist-info/METADATA,sha256=UWQoK0TuRSj5SbCN-2CeBmXPBL5rCJUeBJg_qLHv6Q0,4167
438
- omlish-0.0.0.dev71.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
439
- omlish-0.0.0.dev71.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
440
- omlish-0.0.0.dev71.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
441
- omlish-0.0.0.dev71.dist-info/RECORD,,
436
+ omlish-0.0.0.dev73.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
437
+ omlish-0.0.0.dev73.dist-info/METADATA,sha256=8-3oSBXhJcYztmni0BLIGOG4G_Wme3JfJKM25ag5sLI,4167
438
+ omlish-0.0.0.dev73.dist-info/WHEEL,sha256=OVMc5UfuAQiSplgO0_WdW7vXVGAt9Hdd6qtN4HotdyA,91
439
+ omlish-0.0.0.dev73.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
440
+ omlish-0.0.0.dev73.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
441
+ omlish-0.0.0.dev73.dist-info/RECORD,,
omlish/http/client.py DELETED
@@ -1,142 +0,0 @@
1
- """
2
- TODO:
3
- - return non-200 HttpResponses
4
- - async
5
- - stream
6
- """
7
- import abc
8
- import http.client
9
- import typing as ta
10
- import urllib.error
11
- import urllib.request
12
-
13
- from .. import cached
14
- from .. import dataclasses as dc
15
- from .. import lang
16
- from .headers import CanHttpHeaders
17
- from .headers import HttpHeaders
18
-
19
-
20
- if ta.TYPE_CHECKING:
21
- import httpx
22
- else:
23
- httpx = lang.proxy_import('httpx')
24
-
25
-
26
- @dc.dataclass(frozen=True)
27
- class HttpRequest(lang.Final):
28
- url: str
29
- method: str = 'GET' # noqa
30
-
31
- _: dc.KW_ONLY
32
-
33
- headers: CanHttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
34
- data: bytes | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
35
-
36
- timeout_s: float | None = None
37
-
38
- @cached.property
39
- def headers_(self) -> HttpHeaders | None:
40
- return HttpHeaders(self.headers) if self.headers is not None else None
41
-
42
-
43
- @dc.dataclass(frozen=True, kw_only=True)
44
- class HttpResponse(lang.Final):
45
- code: int
46
-
47
- headers: HttpHeaders | None = dc.xfield(None, repr=dc.truthy_repr)
48
- data: bytes | None = dc.xfield(None, repr_fn=lambda v: '...' if v is not None else None)
49
-
50
- request: HttpRequest
51
- underlying: ta.Any = dc.field(default=None, repr=False)
52
-
53
-
54
- class HttpClientError(Exception):
55
- pass
56
-
57
-
58
- class HttpClient(lang.Abstract):
59
- def __enter__(self) -> ta.Self:
60
- return self
61
-
62
- def __exit__(self, exc_type, exc_val, exc_tb):
63
- pass
64
-
65
- @abc.abstractmethod
66
- def request(self, req: HttpRequest) -> HttpResponse:
67
- raise NotImplementedError
68
-
69
-
70
- class UrllibHttpClient(HttpClient):
71
- def request(self, req: HttpRequest) -> HttpResponse:
72
- try:
73
- with urllib.request.urlopen( # noqa
74
- urllib.request.Request( # noqa
75
- req.url,
76
- method=req.method,
77
- headers=req.headers_ or {}, # type: ignore
78
- data=req.data,
79
- ),
80
- timeout=req.timeout_s,
81
- ) as resp:
82
- return HttpResponse(
83
- code=resp.status,
84
- headers=HttpHeaders(resp.headers.items()),
85
- data=resp.read(),
86
- request=req,
87
- underlying=resp,
88
- )
89
- except (urllib.error.URLError, http.client.HTTPException) as e:
90
- raise HttpClientError from e
91
-
92
-
93
- class HttpxHttpClient(HttpClient):
94
- def request(self, req: HttpRequest) -> HttpResponse:
95
- try:
96
- response = httpx.request(
97
- method=req.method,
98
- url=req.url,
99
- headers=req.headers_ or None, # type: ignore
100
- content=req.data,
101
- timeout=req.timeout_s,
102
- )
103
- return HttpResponse(
104
- code=response.status_code,
105
- headers=HttpHeaders(response.headers.raw),
106
- data=response.content,
107
- request=req,
108
- underlying=response,
109
- )
110
- except httpx.HTTPError as e:
111
- raise HttpClientError from e
112
-
113
-
114
- def client() -> HttpClient:
115
- return UrllibHttpClient()
116
-
117
-
118
- def request(
119
- url: str,
120
- method: str = 'GET',
121
- *,
122
- headers: CanHttpHeaders | None = None,
123
- data: bytes | None = None,
124
-
125
- timeout_s: float | None = None,
126
-
127
- **kwargs: ta.Any,
128
- ) -> HttpResponse:
129
- req = HttpRequest(
130
- url,
131
- method=method,
132
-
133
- headers=headers,
134
- data=data,
135
-
136
- timeout_s=timeout_s,
137
-
138
- **kwargs,
139
- )
140
-
141
- with client() as cli:
142
- return cli.request(req)