omlish 0.0.0.dev70__py3-none-any.whl → 0.0.0.dev72__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 +3 -3
- omlish/dataclasses/impl/internals.py +1 -0
- omlish/dataclasses/impl/metaclass.py +22 -5
- omlish/dataclasses/impl/params.py +4 -2
- omlish/diag/_pycharm/runhack.py +9 -4
- omlish/formats/json/cli.py +3 -0
- omlish/http/__init__.py +17 -0
- omlish/http/clients.py +239 -0
- omlish/http/consts.py +4 -0
- omlish/http/headers.py +181 -0
- omlish/sql/queries/__init__.py +79 -0
- omlish/sql/queries/base.py +21 -0
- omlish/sql/queries/binary.py +48 -0
- omlish/sql/queries/exprs.py +54 -0
- omlish/sql/queries/idents.py +29 -0
- omlish/sql/queries/multi.py +41 -0
- omlish/sql/queries/names.py +34 -0
- omlish/sql/queries/relations.py +39 -0
- omlish/sql/queries/selects.py +50 -0
- omlish/sql/queries/std.py +29 -0
- omlish/sql/queries/stmts.py +28 -0
- omlish/sql/queries/unary.py +29 -0
- omlish/sql/tabledefs/__init__.py +11 -0
- omlish/sql/tabledefs/alchemy.py +26 -0
- omlish/sql/tabledefs/dtypes.py +22 -0
- omlish/sql/tabledefs/elements.py +88 -0
- omlish/sql/tabledefs/lower.py +49 -0
- omlish/sql/tabledefs/marshal.py +19 -0
- omlish/sql/tabledefs/tabledefs.py +52 -0
- {omlish-0.0.0.dev70.dist-info → omlish-0.0.0.dev72.dist-info}/METADATA +3 -3
- {omlish-0.0.0.dev70.dist-info → omlish-0.0.0.dev72.dist-info}/RECORD +35 -14
- {omlish-0.0.0.dev70.dist-info → omlish-0.0.0.dev72.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev70.dist-info → omlish-0.0.0.dev72.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev70.dist-info → omlish-0.0.0.dev72.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev70.dist-info → omlish-0.0.0.dev72.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
__version__ = '0.0.0.
|
2
|
-
__revision__ = '
|
1
|
+
__version__ = '0.0.0.dev72'
|
2
|
+
__revision__ = '4448e03bbb77cb149e46eeefb0e9e61faed2a494'
|
3
3
|
|
4
4
|
|
5
5
|
#
|
@@ -37,7 +37,7 @@ class Project(ProjectBase):
|
|
37
37
|
|
38
38
|
'greenlet ~= 3.1',
|
39
39
|
|
40
|
-
'trio ~= 0.
|
40
|
+
'trio ~= 0.27',
|
41
41
|
'trio-asyncio ~= 0.15',
|
42
42
|
],
|
43
43
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
"""
|
2
2
|
TODO:
|
3
|
-
-
|
3
|
+
- Rewrite lol
|
4
|
+
- Enum - enforce Abstract or Final
|
4
5
|
"""
|
5
6
|
import abc
|
6
7
|
import collections
|
@@ -13,6 +14,7 @@ from .api import field # noqa
|
|
13
14
|
from .params import MetaclassParams
|
14
15
|
from .params import get_metaclass_params
|
15
16
|
from .params import get_params
|
17
|
+
from .params import get_params_extras
|
16
18
|
|
17
19
|
|
18
20
|
T = ta.TypeVar('T')
|
@@ -34,22 +36,35 @@ def confer_kwargs(
|
|
34
36
|
for base in bases:
|
35
37
|
if not dc.is_dataclass(base):
|
36
38
|
continue
|
39
|
+
|
37
40
|
if not (bmp := get_metaclass_params(base)).confer:
|
38
41
|
continue
|
42
|
+
|
39
43
|
for ck in bmp.confer:
|
40
44
|
if ck in kwargs:
|
41
45
|
continue
|
46
|
+
|
42
47
|
if ck in (
|
43
48
|
'frozen',
|
44
|
-
'generic_init',
|
45
49
|
'kw_only',
|
50
|
+
):
|
51
|
+
confer_kwarg(out, ck, getattr(get_params(base), ck))
|
52
|
+
|
53
|
+
elif ck in (
|
54
|
+
'cache_hash',
|
55
|
+
'generic_init',
|
46
56
|
'reorder',
|
47
57
|
):
|
48
|
-
confer_kwarg(out, ck,
|
49
|
-
|
50
|
-
|
58
|
+
confer_kwarg(out, ck, getattr(get_params_extras(base), ck))
|
59
|
+
|
60
|
+
elif ck in (
|
61
|
+
'confer',
|
62
|
+
):
|
63
|
+
confer_kwarg(out, ck, getattr(bmp, ck))
|
64
|
+
|
51
65
|
else:
|
52
66
|
raise KeyError(ck)
|
67
|
+
|
53
68
|
return out
|
54
69
|
|
55
70
|
|
@@ -112,6 +127,7 @@ class Frozen(
|
|
112
127
|
frozen=True,
|
113
128
|
confer=frozenset([
|
114
129
|
'frozen',
|
130
|
+
'cache_hash',
|
115
131
|
'confer',
|
116
132
|
]),
|
117
133
|
):
|
@@ -124,6 +140,7 @@ class Box(
|
|
124
140
|
generic_init=True,
|
125
141
|
confer=frozenset([
|
126
142
|
'frozen',
|
143
|
+
'cache_hash',
|
127
144
|
'generic_init',
|
128
145
|
'confer',
|
129
146
|
]),
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""
|
2
|
-
|
2
|
+
@dc.dataclass(frozen=True)
|
3
|
+
class Field_:
|
3
4
|
name: str | None = None
|
4
5
|
type: Any = None
|
5
6
|
default: Any | MISSING = MISSING
|
@@ -14,7 +15,8 @@ Field:
|
|
14
15
|
_field_type: Any = None
|
15
16
|
|
16
17
|
|
17
|
-
|
18
|
+
@dc.dataclass(frozen=True)
|
19
|
+
class Params_:
|
18
20
|
init: bool = True
|
19
21
|
repr: bool = True
|
20
22
|
eq: bool = True
|
omlish/diag/_pycharm/runhack.py
CHANGED
@@ -1038,11 +1038,13 @@ class ExecDecider:
|
|
1038
1038
|
if not isinstance(tgt, FileTarget):
|
1039
1039
|
return None
|
1040
1040
|
|
1041
|
-
|
1041
|
+
abs_file = os.path.abspath(tgt.file)
|
1042
|
+
if os.path.commonpath([abs_file, self._root_dir]) != self._root_dir:
|
1043
|
+
return None
|
1042
1044
|
|
1043
1045
|
return ExecDecision(
|
1044
1046
|
tgt.replace(
|
1045
|
-
file=
|
1047
|
+
file=abs_file,
|
1046
1048
|
),
|
1047
1049
|
cwd=self._root_dir,
|
1048
1050
|
)
|
@@ -1070,8 +1072,11 @@ class ExecDecider:
|
|
1070
1072
|
if not (isinstance(dt, FileTarget) and dt.file.endswith('.py')):
|
1071
1073
|
return None
|
1072
1074
|
|
1073
|
-
|
1074
|
-
|
1075
|
+
abs_file = os.path.abspath(dt.file)
|
1076
|
+
if os.path.commonpath([abs_file, self._root_dir]) != self._root_dir:
|
1077
|
+
return None
|
1078
|
+
|
1079
|
+
rp = os.path.relpath(abs_file, self._root_dir).split(os.path.sep)
|
1075
1080
|
mod = '.'.join([*rp[:-1], rp[-1][:-3]])
|
1076
1081
|
new_dt = ModuleTarget(
|
1077
1082
|
mod,
|
omlish/formats/json/cli.py
CHANGED
@@ -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,5 +1,16 @@
|
|
1
1
|
from . import consts # noqa
|
2
2
|
|
3
|
+
from .clients import ( # noqa
|
4
|
+
HttpClient,
|
5
|
+
HttpClientError,
|
6
|
+
HttpRequest,
|
7
|
+
HttpResponse,
|
8
|
+
HttpxHttpClient,
|
9
|
+
UrllibHttpClient,
|
10
|
+
client,
|
11
|
+
request,
|
12
|
+
)
|
13
|
+
|
3
14
|
from .cookies import ( # noqa
|
4
15
|
CookieTooBigError,
|
5
16
|
dump_cookie,
|
@@ -16,6 +27,12 @@ from .encodings import ( # noqa
|
|
16
27
|
latin1_encode,
|
17
28
|
)
|
18
29
|
|
30
|
+
from .headers import ( # noqa
|
31
|
+
CanHttpHeaders,
|
32
|
+
HttpHeaders,
|
33
|
+
headers,
|
34
|
+
)
|
35
|
+
|
19
36
|
from .json import ( # noqa
|
20
37
|
JSON_TAGGER,
|
21
38
|
JsonTag,
|
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 keys 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
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
import typing as ta
|
2
|
+
|
3
|
+
from .. import cached
|
4
|
+
from .. import check
|
5
|
+
from .. import collections as col
|
6
|
+
|
7
|
+
|
8
|
+
StrOrBytes: ta.TypeAlias = str | bytes
|
9
|
+
|
10
|
+
CanHttpHeaders: ta.TypeAlias = ta.Union[
|
11
|
+
'HttpHeaders',
|
12
|
+
|
13
|
+
ta.Mapping[str, str],
|
14
|
+
ta.Mapping[str, ta.Sequence[str]],
|
15
|
+
|
16
|
+
ta.Mapping[bytes, bytes],
|
17
|
+
ta.Mapping[bytes, ta.Sequence[bytes]],
|
18
|
+
|
19
|
+
ta.Mapping[StrOrBytes, StrOrBytes],
|
20
|
+
ta.Mapping[StrOrBytes, ta.Sequence[StrOrBytes]],
|
21
|
+
|
22
|
+
ta.Mapping[StrOrBytes, StrOrBytes | ta.Sequence[StrOrBytes]],
|
23
|
+
|
24
|
+
ta.Sequence[tuple[str, str]],
|
25
|
+
ta.Sequence[tuple[bytes, bytes]],
|
26
|
+
ta.Sequence[tuple[StrOrBytes, StrOrBytes]],
|
27
|
+
]
|
28
|
+
|
29
|
+
|
30
|
+
class HttpHeaders:
|
31
|
+
def __init__(self, src: CanHttpHeaders) -> None:
|
32
|
+
super().__init__()
|
33
|
+
|
34
|
+
if isinstance(src, HttpHeaders):
|
35
|
+
check.is_(src, self)
|
36
|
+
return
|
37
|
+
|
38
|
+
# TODO: optimized storage, 'use-whats-given'
|
39
|
+
lst: list[tuple[bytes, bytes]] = []
|
40
|
+
if isinstance(src, ta.Mapping):
|
41
|
+
for k, v in src.items():
|
42
|
+
if isinstance(v, (str, bytes)):
|
43
|
+
lst.append((self._as_bytes(k), self._as_bytes(v)))
|
44
|
+
else:
|
45
|
+
for e in v:
|
46
|
+
lst.append((self._as_bytes(k), self._as_bytes(e)))
|
47
|
+
|
48
|
+
elif isinstance(src, (str, bytes)): # type: ignore
|
49
|
+
raise TypeError(src)
|
50
|
+
|
51
|
+
elif isinstance(src, ta.Sequence):
|
52
|
+
for t in src:
|
53
|
+
if isinstance(t, (str, bytes)):
|
54
|
+
raise TypeError(t)
|
55
|
+
|
56
|
+
k, v = t
|
57
|
+
lst.append((self._as_bytes(k), self._as_bytes(v)))
|
58
|
+
|
59
|
+
else:
|
60
|
+
raise TypeError(src)
|
61
|
+
|
62
|
+
self._lst = lst
|
63
|
+
|
64
|
+
def __new__(cls, obj: CanHttpHeaders) -> 'HttpHeaders':
|
65
|
+
if isinstance(obj, HttpHeaders):
|
66
|
+
return obj
|
67
|
+
|
68
|
+
return super().__new__(cls)
|
69
|
+
|
70
|
+
#
|
71
|
+
|
72
|
+
# https://github.com/pgjones/hypercorn/commit/13f385be7277f407a9a361c958820515e16e217e
|
73
|
+
ENCODING: ta.ClassVar[str] = 'latin1'
|
74
|
+
|
75
|
+
@classmethod
|
76
|
+
def _as_bytes(cls, o: StrOrBytes) -> bytes:
|
77
|
+
if isinstance(o, bytes):
|
78
|
+
return o
|
79
|
+
elif isinstance(o, str):
|
80
|
+
return o.encode(cls.ENCODING)
|
81
|
+
else:
|
82
|
+
raise TypeError(o)
|
83
|
+
|
84
|
+
#
|
85
|
+
|
86
|
+
@cached.function
|
87
|
+
def __repr__(self) -> str:
|
88
|
+
return f'{self.__class__.__name__}({{{", ".join(repr(k) for k in self.single_str_dct)}}})'
|
89
|
+
|
90
|
+
#
|
91
|
+
|
92
|
+
@property
|
93
|
+
def raw(self) -> ta.Sequence[tuple[bytes, bytes]]:
|
94
|
+
return self._lst
|
95
|
+
|
96
|
+
@classmethod
|
97
|
+
def _as_key(cls, o: StrOrBytes) -> bytes:
|
98
|
+
return cls._as_bytes(o).lower()
|
99
|
+
|
100
|
+
@cached.property
|
101
|
+
def normalized(self) -> ta.Sequence[tuple[bytes, bytes]]:
|
102
|
+
return [(self._as_key(k), v) for k, v in self._lst]
|
103
|
+
|
104
|
+
#
|
105
|
+
|
106
|
+
@cached.property
|
107
|
+
def multi_dct(self) -> ta.Mapping[bytes, ta.Sequence[bytes]]:
|
108
|
+
return col.multi_map(self.normalized)
|
109
|
+
|
110
|
+
@cached.property
|
111
|
+
def single_dct(self) -> ta.Mapping[bytes, bytes]:
|
112
|
+
return {k: v[0] for k, v in self.multi_dct.items() if len(v) == 1}
|
113
|
+
|
114
|
+
@cached.property
|
115
|
+
def strict_dct(self) -> ta.Mapping[bytes, bytes]:
|
116
|
+
return col.make_map(self.normalized, strict=True)
|
117
|
+
|
118
|
+
#
|
119
|
+
|
120
|
+
@cached.property
|
121
|
+
def strs(self) -> ta.Sequence[tuple[str, str]]:
|
122
|
+
return tuple((k.decode(self.ENCODING), v.decode(self.ENCODING)) for k, v in self.normalized)
|
123
|
+
|
124
|
+
@cached.property
|
125
|
+
def multi_str_dct(self) -> ta.Mapping[str, ta.Sequence[str]]:
|
126
|
+
return col.multi_map(self.strs)
|
127
|
+
|
128
|
+
@cached.property
|
129
|
+
def single_str_dct(self) -> ta.Mapping[str, str]:
|
130
|
+
return {k: v[0] for k, v in self.multi_str_dct.items() if len(v) == 1}
|
131
|
+
|
132
|
+
@cached.property
|
133
|
+
def strict_str_dct(self) -> ta.Mapping[str, str]:
|
134
|
+
return col.make_map(self.strs, strict=True)
|
135
|
+
|
136
|
+
#
|
137
|
+
|
138
|
+
def __bool__(self) -> bool:
|
139
|
+
return bool(self._lst)
|
140
|
+
|
141
|
+
def __len__(self) -> int:
|
142
|
+
return len(self._lst)
|
143
|
+
|
144
|
+
def __iter__(self) -> ta.Iterator[tuple[bytes, bytes]]:
|
145
|
+
return iter(self._lst)
|
146
|
+
|
147
|
+
@ta.overload
|
148
|
+
def __getitem__(self, item: str) -> ta.Sequence[str]:
|
149
|
+
...
|
150
|
+
|
151
|
+
@ta.overload
|
152
|
+
def __getitem__(self, item: bytes) -> ta.Sequence[bytes]:
|
153
|
+
...
|
154
|
+
|
155
|
+
@ta.overload
|
156
|
+
def __getitem__(self, item: int) -> StrOrBytes:
|
157
|
+
...
|
158
|
+
|
159
|
+
@ta.overload
|
160
|
+
def __getitem__(self, item: slice) -> ta.Sequence[StrOrBytes]:
|
161
|
+
...
|
162
|
+
|
163
|
+
def __getitem__(self, item):
|
164
|
+
if isinstance(item, (int, slice)):
|
165
|
+
return self._lst[item]
|
166
|
+
elif isinstance(item, str):
|
167
|
+
return self.multi_str_dct[item.lower()]
|
168
|
+
elif isinstance(item, bytes):
|
169
|
+
return self.multi_dct[self._as_key(item)]
|
170
|
+
else:
|
171
|
+
raise TypeError(item)
|
172
|
+
|
173
|
+
def keys(self) -> ta.Iterable[bytes]:
|
174
|
+
return self.multi_dct.keys()
|
175
|
+
|
176
|
+
def items(self) -> ta.Iterable[tuple[bytes, bytes]]:
|
177
|
+
return self._lst
|
178
|
+
|
179
|
+
|
180
|
+
def headers(src: CanHttpHeaders) -> HttpHeaders:
|
181
|
+
return HttpHeaders(src)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
from .base import ( # noqa
|
2
|
+
Builder,
|
3
|
+
Node,
|
4
|
+
Value,
|
5
|
+
)
|
6
|
+
|
7
|
+
from .binary import ( # noqa
|
8
|
+
Binary,
|
9
|
+
BinaryBuilder,
|
10
|
+
BinaryOp,
|
11
|
+
BinaryOps,
|
12
|
+
)
|
13
|
+
|
14
|
+
from .exprs import ( # noqa
|
15
|
+
CanExpr,
|
16
|
+
CanLiteral,
|
17
|
+
Expr,
|
18
|
+
ExprBuilder,
|
19
|
+
Literal,
|
20
|
+
NameExpr,
|
21
|
+
)
|
22
|
+
|
23
|
+
from .idents import ( # noqa
|
24
|
+
CanIdent,
|
25
|
+
Ident,
|
26
|
+
IdentBuilder,
|
27
|
+
)
|
28
|
+
|
29
|
+
from .multi import ( # noqa
|
30
|
+
Multi,
|
31
|
+
MultiBuilder,
|
32
|
+
MultiOp,
|
33
|
+
MultiOps,
|
34
|
+
)
|
35
|
+
|
36
|
+
from .names import ( # noqa
|
37
|
+
CanName,
|
38
|
+
Name,
|
39
|
+
NameBuilder,
|
40
|
+
)
|
41
|
+
|
42
|
+
from .relations import ( # noqa
|
43
|
+
CanRelation,
|
44
|
+
CanTable,
|
45
|
+
Relation,
|
46
|
+
RelationBuilder,
|
47
|
+
Table,
|
48
|
+
)
|
49
|
+
|
50
|
+
from .selects import ( # noqa
|
51
|
+
CanRelation,
|
52
|
+
Select,
|
53
|
+
SelectBuilder,
|
54
|
+
SelectItem,
|
55
|
+
)
|
56
|
+
|
57
|
+
from .std import ( # noqa
|
58
|
+
StdBuilder,
|
59
|
+
)
|
60
|
+
|
61
|
+
from .stmts import ( # noqa
|
62
|
+
CanExpr,
|
63
|
+
ExprStmt,
|
64
|
+
Stmt,
|
65
|
+
StmtBuilder,
|
66
|
+
)
|
67
|
+
|
68
|
+
from .unary import ( # noqa
|
69
|
+
Unary,
|
70
|
+
UnaryBuilder,
|
71
|
+
UnaryOp,
|
72
|
+
UnaryOps,
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
##
|
77
|
+
|
78
|
+
|
79
|
+
Q = StdBuilder()
|