omlish 0.0.0.dev70__py3-none-any.whl → 0.0.0.dev71__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.dev70'
2
- __revision__ = '8d6f6c3ead70cde56b9d8040a05d9f91f6dd90ff'
1
+ __version__ = '0.0.0.dev71'
2
+ __revision__ = '293695b11eeed255848dcf6beee9c4b4383d5c91'
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.26',
40
+ 'trio ~= 0.27',
41
41
  'trio-asyncio ~= 0.15',
42
42
  ],
43
43
 
@@ -17,6 +17,7 @@ POST_INIT_NAME = dc._POST_INIT_NAME # type: ignore # noqa
17
17
 
18
18
  Params = dc._DataclassParams # type: ignore # noqa
19
19
 
20
+
20
21
  """
21
22
  @dc.dataclass(frozen=True)
22
23
  class Params:
@@ -1,6 +1,7 @@
1
1
  """
2
2
  TODO:
3
- - Enum
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, get_params(base).frozen)
49
- elif ck == 'confer':
50
- confer_kwarg(out, 'confer', bmp.confer)
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
- Field:
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
- Params:
18
+ @dc.dataclass(frozen=True)
19
+ class Params_:
18
20
  init: bool = True
19
21
  repr: bool = True
20
22
  eq: bool = True
@@ -1038,11 +1038,13 @@ class ExecDecider:
1038
1038
  if not isinstance(tgt, FileTarget):
1039
1039
  return None
1040
1040
 
1041
- new_file = os.path.abspath(tgt.file)
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=new_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
- af = os.path.abspath(dt.file)
1074
- rp = os.path.relpath(af, self._root_dir).split(os.path.sep)
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/http/__init__.py CHANGED
@@ -1,5 +1,16 @@
1
1
  from . import consts # noqa
2
2
 
3
+ from .client 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/client.py ADDED
@@ -0,0 +1,142 @@
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)
omlish/http/headers.py ADDED
@@ -0,0 +1,146 @@
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
+ ta.Mapping[StrOrBytes, StrOrBytes],
13
+ ta.Mapping[StrOrBytes, ta.Sequence[StrOrBytes]],
14
+ ta.Mapping[StrOrBytes, StrOrBytes | ta.Sequence[StrOrBytes]],
15
+ ta.Sequence[tuple[StrOrBytes, StrOrBytes]],
16
+ ]
17
+
18
+
19
+ class HttpHeaders:
20
+ def __init__(self, src: CanHttpHeaders) -> None:
21
+ super().__init__()
22
+
23
+ if isinstance(src, HttpHeaders):
24
+ check.is_(src, self)
25
+ return
26
+
27
+ # TODO: optimized storage, 'use-whats-given'
28
+ lst: list[tuple[bytes, bytes]] = []
29
+ if isinstance(src, ta.Mapping):
30
+ for k, v in src.items():
31
+ if isinstance(v, (str, bytes)):
32
+ lst.append((self._as_bytes(k), self._as_bytes(v)))
33
+ else:
34
+ for e in v:
35
+ lst.append((self._as_bytes(k), self._as_bytes(e)))
36
+
37
+ elif isinstance(src, (str, bytes)): # type: ignore
38
+ raise TypeError(src)
39
+
40
+ elif isinstance(src, ta.Sequence):
41
+ for k, v in src:
42
+ lst.append((self._as_bytes(k), self._as_bytes(v)))
43
+
44
+ else:
45
+ raise TypeError(src)
46
+
47
+ self._lst = lst
48
+
49
+ def __new__(cls, obj: CanHttpHeaders) -> 'HttpHeaders':
50
+ if isinstance(obj, HttpHeaders):
51
+ return obj
52
+
53
+ return super().__new__(cls)
54
+
55
+ #
56
+
57
+ # https://github.com/pgjones/hypercorn/commit/13f385be7277f407a9a361c958820515e16e217e
58
+ ENCODING: ta.ClassVar[str] = 'latin1'
59
+
60
+ @classmethod
61
+ def _as_bytes(cls, o: StrOrBytes) -> bytes:
62
+ if isinstance(o, bytes):
63
+ return o
64
+ elif isinstance(o, str):
65
+ return o.encode(cls.ENCODING)
66
+ else:
67
+ raise TypeError(o)
68
+
69
+ #
70
+
71
+ @cached.function
72
+ def __repr__(self) -> str:
73
+ return f'{self.__class__.__name__}({{{", ".join(repr(k) for k in self.single_str_dct)}}})'
74
+
75
+ #
76
+
77
+ @cached.property
78
+ def multi_dct(self) -> ta.Mapping[bytes, ta.Sequence[bytes]]:
79
+ return col.multi_map(self._lst)
80
+
81
+ @cached.property
82
+ def single_dct(self) -> ta.Mapping[bytes, bytes]:
83
+ return {k: v[0] for k, v in self.multi_dct.items() if len(v) == 1}
84
+
85
+ @cached.property
86
+ def strict_dct(self) -> ta.Mapping[bytes, bytes]:
87
+ return col.make_map(self._lst, strict=True)
88
+
89
+ #
90
+
91
+ @cached.property
92
+ 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)
94
+
95
+ @cached.property
96
+ def multi_str_dct(self) -> ta.Mapping[str, ta.Sequence[str]]:
97
+ return col.multi_map(self.strs)
98
+
99
+ @cached.property
100
+ def single_str_dct(self) -> ta.Mapping[str, str]:
101
+ return {k: v[0] for k, v in self.multi_str_dct.items() if len(v) == 1}
102
+
103
+ @cached.property
104
+ def strict_str_dct(self) -> ta.Mapping[str, str]:
105
+ return col.make_map(self.strs, strict=True)
106
+
107
+ #
108
+
109
+ def __bool__(self) -> bool:
110
+ return bool(self._lst)
111
+
112
+ def __len__(self) -> int:
113
+ return len(self._lst)
114
+
115
+ def __iter__(self) -> ta.Iterator[tuple[bytes, bytes]]:
116
+ return iter(self._lst)
117
+
118
+ @ta.overload
119
+ def __getitem__(self, item: StrOrBytes) -> ta.Sequence[StrOrBytes]:
120
+ ...
121
+
122
+ @ta.overload
123
+ def __getitem__(self, item: int) -> StrOrBytes:
124
+ ...
125
+
126
+ @ta.overload
127
+ def __getitem__(self, item: slice) -> ta.Sequence[StrOrBytes]:
128
+ ...
129
+
130
+ def __getitem__(self, item):
131
+ if isinstance(item, (int, slice)):
132
+ return self._lst[item]
133
+ elif isinstance(item, (str, bytes)):
134
+ return self.multi_dct[self._as_bytes(item)]
135
+ else:
136
+ raise TypeError(item)
137
+
138
+ def keys(self) -> ta.Iterable[bytes]:
139
+ return self.multi_dct.keys()
140
+
141
+ def items(self) -> ta.Iterable[tuple[bytes, bytes]]:
142
+ return self._lst
143
+
144
+
145
+ def headers(src: CanHttpHeaders) -> HttpHeaders:
146
+ 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()
@@ -0,0 +1,21 @@
1
+ import typing as ta
2
+
3
+ from ... import dataclasses as dc
4
+ from ... import lang
5
+
6
+
7
+ ##
8
+
9
+
10
+ Value: ta.TypeAlias = ta.Any
11
+
12
+
13
+ ##
14
+
15
+
16
+ class Node(dc.Frozen, lang.Abstract, cache_hash=True):
17
+ pass
18
+
19
+
20
+ class Builder(lang.Abstract):
21
+ pass
@@ -0,0 +1,48 @@
1
+ from ... import check
2
+ from ... import lang
3
+ from .base import Node
4
+ from .exprs import CanExpr
5
+ from .exprs import Expr
6
+ from .exprs import ExprBuilder
7
+
8
+
9
+ ##
10
+
11
+
12
+ class BinaryOp(Node, lang.Final):
13
+ name: str
14
+
15
+
16
+ class BinaryOps(lang.Namespace):
17
+ ADD = BinaryOp('add')
18
+ SUB = BinaryOp('sub')
19
+
20
+ EQ = BinaryOp('eq')
21
+ NE = BinaryOp('ne')
22
+
23
+
24
+ class Binary(Expr, lang.Final):
25
+ op: BinaryOp
26
+ l: Expr
27
+ r: Expr
28
+
29
+
30
+ class BinaryBuilder(ExprBuilder):
31
+ def binary(self, op: BinaryOp, *es: CanExpr) -> Expr:
32
+ check.not_empty(es)
33
+ l = self.expr(es[0])
34
+ for r in es[1:]:
35
+ l = Binary(op, l, self.expr(r))
36
+ return l
37
+
38
+ def add(self, *es: CanExpr) -> Expr:
39
+ return self.binary(BinaryOps.ADD, *es)
40
+
41
+ def sub(self, *es: CanExpr) -> Expr:
42
+ return self.binary(BinaryOps.SUB, *es)
43
+
44
+ def eq(self, *es: CanExpr) -> Expr:
45
+ return self.binary(BinaryOps.EQ, *es)
46
+
47
+ def ne(self, *es: CanExpr) -> Expr:
48
+ return self.binary(BinaryOps.NE, *es)
@@ -0,0 +1,54 @@
1
+ import typing as ta
2
+
3
+ from ... import lang
4
+ from .base import Node
5
+ from .base import Value
6
+ from .idents import Ident
7
+ from .names import CanName
8
+ from .names import Name
9
+ from .names import NameBuilder
10
+
11
+
12
+ ##
13
+
14
+
15
+ class Expr(Node, lang.Abstract):
16
+ pass
17
+
18
+
19
+ class Literal(Expr, lang.Final):
20
+ v: Value
21
+
22
+
23
+ class NameExpr(Expr, lang.Final):
24
+ n: Name
25
+
26
+
27
+ CanLiteral: ta.TypeAlias = Literal | Value
28
+ CanExpr: ta.TypeAlias = Expr | CanName | CanLiteral
29
+
30
+
31
+ class ExprBuilder(NameBuilder):
32
+ def literal(self, o: CanLiteral) -> Literal:
33
+ if isinstance(o, Literal):
34
+ return o
35
+ elif isinstance(o, Node):
36
+ raise TypeError(o)
37
+ else:
38
+ return Literal(o)
39
+
40
+ @ta.final
41
+ def l(self, o: CanLiteral) -> Literal: # noqa
42
+ return self.literal(o)
43
+
44
+ def expr(self, o: CanExpr) -> Expr:
45
+ if isinstance(o, Expr):
46
+ return o
47
+ elif isinstance(o, (Name, Ident)):
48
+ return NameExpr(self.name(o))
49
+ else:
50
+ return self.literal(o)
51
+
52
+ @ta.final
53
+ def e(self, o: CanExpr) -> Expr:
54
+ return self.expr(o)
@@ -0,0 +1,29 @@
1
+ import typing as ta
2
+
3
+ from ... import lang
4
+ from .base import Builder
5
+ from .base import Node
6
+
7
+
8
+ ##
9
+
10
+
11
+ class Ident(Node, lang.Final):
12
+ s: str
13
+
14
+
15
+ CanIdent: ta.TypeAlias = Ident | str
16
+
17
+
18
+ class IdentBuilder(Builder):
19
+ def ident(self, o: CanIdent) -> Ident:
20
+ if isinstance(o, Ident):
21
+ return o
22
+ elif isinstance(o, str):
23
+ return Ident(o)
24
+ else:
25
+ raise TypeError(o)
26
+
27
+ @ta.final
28
+ def i(self, o: CanIdent) -> Ident:
29
+ return self.ident(o)