omlish 0.0.0.dev228__py3-none-any.whl → 0.0.0.dev230__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 +2 -2
- omlish/collections/__init__.py +15 -15
- omlish/collections/frozen.py +0 -2
- omlish/collections/identity.py +0 -3
- omlish/collections/indexed.py +0 -2
- omlish/collections/mappings.py +0 -3
- omlish/collections/ordered.py +0 -2
- omlish/collections/persistent/__init__.py +0 -0
- omlish/collections/sorted/__init__.py +0 -0
- omlish/collections/{skiplist.py → sorted/skiplist.py} +0 -1
- omlish/collections/{sorted.py → sorted/sorted.py} +2 -5
- omlish/collections/unmodifiable.py +0 -2
- omlish/dispatch/dispatch.py +4 -1
- omlish/dispatch/methods.py +4 -0
- omlish/formats/json/__init__.py +5 -0
- omlish/io/compress/brotli.py +3 -3
- omlish/io/fdio/__init__.py +3 -0
- omlish/marshal/__init__.py +10 -10
- omlish/marshal/base.py +1 -1
- omlish/marshal/standard.py +2 -2
- omlish/marshal/trivial/__init__.py +0 -0
- omlish/marshal/{forbidden.py → trivial/forbidden.py} +7 -7
- omlish/marshal/{nop.py → trivial/nop.py} +5 -5
- omlish/os/deathpacts/__init__.py +15 -0
- omlish/os/deathpacts/base.py +76 -0
- omlish/os/deathpacts/heartbeatfile.py +85 -0
- omlish/os/{death.py → deathpacts/pipe.py} +25 -93
- omlish/os/forkhooks.py +55 -31
- omlish/os/pidfiles/manager.py +11 -44
- omlish/reflect/__init__.py +1 -0
- omlish/reflect/inspect.py +43 -0
- omlish/sql/queries/__init__.py +4 -4
- omlish/sql/queries/rendering2.py +248 -0
- omlish/text/parts.py +26 -23
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/RECORD +46 -40
- omlish/formats/json/delimted.py +0 -4
- /omlish/collections/{persistent.py → persistent/persistent.py} +0 -0
- /omlish/collections/{treap.py → persistent/treap.py} +0 -0
- /omlish/collections/{treapmap.py → persistent/treapmap.py} +0 -0
- /omlish/marshal/{utils.py → proxy.py} +0 -0
- /omlish/marshal/{singular → trivial}/any.py +0 -0
- /omlish/sql/queries/{building.py → std.py} +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev228.dist-info → omlish-0.0.0.dev230.dist-info}/top_level.txt +0 -0
@@ -1,84 +1,11 @@
|
|
1
|
-
import abc
|
2
1
|
import os
|
3
|
-
import signal
|
4
|
-
import sys
|
5
|
-
import time
|
6
2
|
import typing as ta
|
7
3
|
import weakref
|
8
4
|
|
9
|
-
from
|
10
|
-
from
|
11
|
-
from
|
12
|
-
|
13
|
-
|
14
|
-
##
|
15
|
-
|
16
|
-
|
17
|
-
class Deathpact(abc.ABC):
|
18
|
-
@abc.abstractmethod
|
19
|
-
def poll(self) -> None:
|
20
|
-
raise NotImplementedError
|
21
|
-
|
22
|
-
|
23
|
-
class NopDeathpact(Deathpact):
|
24
|
-
def poll(self) -> None:
|
25
|
-
pass
|
26
|
-
|
27
|
-
|
28
|
-
##
|
29
|
-
|
30
|
-
|
31
|
-
class BaseDeathpact(Deathpact, abc.ABC):
|
32
|
-
def __init__(
|
33
|
-
self,
|
34
|
-
*,
|
35
|
-
interval_s: float = .5,
|
36
|
-
signal: int | None = signal.SIGTERM,
|
37
|
-
output: ta.Literal['stdout', 'stderr'] | None = 'stderr',
|
38
|
-
on_die: ta.Callable[[], None] | None = None,
|
39
|
-
) -> None:
|
40
|
-
super().__init__()
|
41
|
-
|
42
|
-
self._interval_s = interval_s
|
43
|
-
self._signal = signal
|
44
|
-
self._output = output
|
45
|
-
self._on_die = on_die
|
46
|
-
|
47
|
-
self._last_check_t: float | None = None
|
48
|
-
|
49
|
-
def _print(self, msg: str) -> None:
|
50
|
-
match self._output:
|
51
|
-
case 'stdout':
|
52
|
-
f = sys.stdout
|
53
|
-
case 'stderr':
|
54
|
-
f = sys.stderr
|
55
|
-
case _:
|
56
|
-
return
|
57
|
-
print(f'{self} pid={os.getpid()}: {msg}', file=f)
|
58
|
-
|
59
|
-
def die(self) -> None:
|
60
|
-
self._print('Triggered! Process terminating!')
|
61
|
-
|
62
|
-
if self._on_die is not None:
|
63
|
-
self._on_die()
|
64
|
-
|
65
|
-
if self._signal is not None:
|
66
|
-
os.kill(os.getpid(), self._signal)
|
67
|
-
|
68
|
-
sys.exit(1)
|
69
|
-
|
70
|
-
@abc.abstractmethod
|
71
|
-
def should_die(self) -> bool:
|
72
|
-
raise NotImplementedError
|
73
|
-
|
74
|
-
def maybe_die(self) -> None:
|
75
|
-
if self.should_die():
|
76
|
-
self.die()
|
77
|
-
|
78
|
-
def poll(self) -> None:
|
79
|
-
if self._last_check_t is None or (time.monotonic() - self._last_check_t) >= self._interval_s:
|
80
|
-
self.maybe_die()
|
81
|
-
self._last_check_t = time.monotonic()
|
5
|
+
from ... import check
|
6
|
+
from ..forkhooks import ForkHook
|
7
|
+
from ..forkhooks import ProcessOriginTracker
|
8
|
+
from .base import BaseDeathpact
|
82
9
|
|
83
10
|
|
84
11
|
##
|
@@ -92,16 +19,13 @@ class PipeDeathpact(BaseDeathpact):
|
|
92
19
|
handle such cases.
|
93
20
|
"""
|
94
21
|
|
95
|
-
_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
96
|
-
|
97
22
|
def __init__(self, **kwargs: ta.Any) -> None:
|
98
23
|
super().__init__(**kwargs)
|
99
24
|
|
100
25
|
self._rfd: int | None = None
|
101
26
|
self._wfd: int | None = None
|
102
27
|
|
103
|
-
self.
|
104
|
-
self._fork_depth: int | None = get_fork_depth()
|
28
|
+
self._process_origin = ProcessOriginTracker()
|
105
29
|
|
106
30
|
def __repr__(self) -> str:
|
107
31
|
return f'{self.__class__.__name__}(rfd={self._rfd}, wfd={self._wfd})'
|
@@ -111,11 +35,13 @@ class PipeDeathpact(BaseDeathpact):
|
|
111
35
|
return check.not_none(self._rfd)
|
112
36
|
|
113
37
|
def is_parent(self) -> bool:
|
114
|
-
return
|
38
|
+
return self._process_origin.is_in_origin_process()
|
115
39
|
|
116
40
|
#
|
117
41
|
|
118
42
|
def __enter__(self) -> ta.Self:
|
43
|
+
check.state(self.is_parent())
|
44
|
+
|
119
45
|
check.none(self._rfd)
|
120
46
|
check.none(self._wfd)
|
121
47
|
|
@@ -125,28 +51,34 @@ class PipeDeathpact(BaseDeathpact):
|
|
125
51
|
|
126
52
|
return self
|
127
53
|
|
128
|
-
def
|
54
|
+
def close(self) -> None:
|
55
|
+
if self._rfd is not None:
|
56
|
+
os.close(self._rfd)
|
57
|
+
self._rfd = None
|
58
|
+
|
129
59
|
if self._wfd is not None:
|
130
|
-
if
|
60
|
+
if self.is_parent():
|
131
61
|
os.close(check.not_none(self._wfd))
|
132
62
|
self._wfd = None
|
133
63
|
|
134
64
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
135
|
-
|
136
|
-
os.close(self._rfd)
|
137
|
-
self._rfd = None
|
65
|
+
self.close()
|
138
66
|
|
139
|
-
|
67
|
+
def _close_wfd_if_not_parent(self) -> None:
|
68
|
+
if self._wfd is not None:
|
69
|
+
if not self.is_parent():
|
70
|
+
os.close(check.not_none(self._wfd))
|
71
|
+
self._wfd = None
|
140
72
|
|
141
73
|
#
|
142
74
|
|
143
75
|
def __getstate__(self):
|
144
|
-
return
|
76
|
+
return {
|
145
77
|
**self.__dict__,
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
78
|
+
**dict(
|
79
|
+
_wfd=None,
|
80
|
+
),
|
81
|
+
}
|
150
82
|
|
151
83
|
def __setstate__(self, state):
|
152
84
|
self.__dict__.update(state)
|
omlish/os/forkhooks.py
CHANGED
@@ -1,9 +1,5 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
# @omlish-lite
|
3
|
-
"""
|
4
|
-
TODO:
|
5
|
-
- ForkHook base class? all classmethods? prevents pickling
|
6
|
-
"""
|
7
3
|
import abc
|
8
4
|
import os
|
9
5
|
import threading
|
@@ -29,31 +25,6 @@ class _ForkHookManager:
|
|
29
25
|
|
30
26
|
#
|
31
27
|
|
32
|
-
_installed: ta.ClassVar[bool] = False
|
33
|
-
|
34
|
-
@classmethod
|
35
|
-
def _install(cls) -> bool:
|
36
|
-
if cls._installed:
|
37
|
-
return False
|
38
|
-
|
39
|
-
check.state(not cls._installed)
|
40
|
-
|
41
|
-
os.register_at_fork(
|
42
|
-
before=cls._before_fork,
|
43
|
-
after_in_parent=cls._after_fork_in_parent,
|
44
|
-
after_in_child=cls._after_fork_in_child,
|
45
|
-
)
|
46
|
-
|
47
|
-
cls._installed = True
|
48
|
-
return True
|
49
|
-
|
50
|
-
@classmethod
|
51
|
-
def install(cls) -> bool:
|
52
|
-
with cls._lock:
|
53
|
-
return cls._install()
|
54
|
-
|
55
|
-
#
|
56
|
-
|
57
28
|
class Hook(ta.NamedTuple):
|
58
29
|
key: ta.Any
|
59
30
|
priority: int
|
@@ -74,8 +45,8 @@ class _ForkHookManager:
|
|
74
45
|
def _rebuild_hook_collections(cls) -> None:
|
75
46
|
cls._hook_keys = frozenset(cls._hooks_by_key)
|
76
47
|
|
77
|
-
# Uses
|
78
|
-
# depended upon for usecase correctness.
|
48
|
+
# Uses dict order preservation to retain insertion-order of hooks of equal priority (although that shouldn't be
|
49
|
+
# depended upon for usecase correctness).
|
79
50
|
cls._priority_ordered_hooks = sorted(cls._hooks_by_key.values(), key=lambda h: h.priority)
|
80
51
|
|
81
52
|
#
|
@@ -121,6 +92,31 @@ class _ForkHookManager:
|
|
121
92
|
|
122
93
|
#
|
123
94
|
|
95
|
+
_installed: ta.ClassVar[bool] = False
|
96
|
+
|
97
|
+
@classmethod
|
98
|
+
def _install(cls) -> bool:
|
99
|
+
if cls._installed:
|
100
|
+
return False
|
101
|
+
|
102
|
+
check.state(not cls._installed)
|
103
|
+
|
104
|
+
os.register_at_fork(
|
105
|
+
before=cls._before_fork,
|
106
|
+
after_in_parent=cls._after_fork_in_parent,
|
107
|
+
after_in_child=cls._after_fork_in_child,
|
108
|
+
)
|
109
|
+
|
110
|
+
cls._installed = True
|
111
|
+
return True
|
112
|
+
|
113
|
+
@classmethod
|
114
|
+
def install(cls) -> bool:
|
115
|
+
with cls._lock:
|
116
|
+
return cls._install()
|
117
|
+
|
118
|
+
#
|
119
|
+
|
124
120
|
@classmethod
|
125
121
|
def _before_fork(cls) -> None:
|
126
122
|
cls._lock.acquire()
|
@@ -213,3 +209,31 @@ class _ForkDepthTracker(ForkHook):
|
|
213
209
|
|
214
210
|
def get_fork_depth() -> int:
|
215
211
|
return _ForkDepthTracker.get_fork_depth()
|
212
|
+
|
213
|
+
|
214
|
+
##
|
215
|
+
|
216
|
+
|
217
|
+
class ProcessOriginTracker:
|
218
|
+
_PROCESS_COOKIE: ta.ClassVar[bytes] = os.urandom(16)
|
219
|
+
|
220
|
+
def __init__(self, **kwargs: ta.Any) -> None:
|
221
|
+
super().__init__(**kwargs)
|
222
|
+
|
223
|
+
self._process_cookie: bytes | None = self._PROCESS_COOKIE
|
224
|
+
self._fork_depth: int | None = get_fork_depth()
|
225
|
+
|
226
|
+
def is_in_origin_process(self) -> bool:
|
227
|
+
return (self._PROCESS_COOKIE, get_fork_depth()) == (self._process_cookie, self._fork_depth)
|
228
|
+
|
229
|
+
def __getstate__(self):
|
230
|
+
return {
|
231
|
+
**self.__dict__,
|
232
|
+
**dict(
|
233
|
+
_cookie=None,
|
234
|
+
_fork_depth=None,
|
235
|
+
),
|
236
|
+
}
|
237
|
+
|
238
|
+
def __setstate__(self, state):
|
239
|
+
self.__dict__.update(state)
|
omlish/os/pidfiles/manager.py
CHANGED
@@ -7,71 +7,38 @@ import typing as ta
|
|
7
7
|
import weakref
|
8
8
|
|
9
9
|
from ...lite.check import check
|
10
|
+
from ..forkhooks import ForkHook
|
10
11
|
from .pidfile import Pidfile
|
11
12
|
|
12
13
|
|
13
14
|
##
|
14
15
|
|
15
16
|
|
16
|
-
class _PidfileManager:
|
17
|
+
class _PidfileManager(ForkHook):
|
17
18
|
"""
|
18
19
|
Manager for controlled inheritance of Pidfiles across forks in the presence of multiple threads. There is of course
|
19
20
|
no safe or correct way to mix the use of fork and multiple active threads, and one should never write code which
|
20
21
|
does so, but in the Real World one may still find oneself in such a situation outside of their control (such as when
|
21
22
|
running under Pycharm's debugger which forces the use of forked multiprocessing).
|
22
|
-
|
23
|
-
Not implemented as an instantiated class as there is no way to unregister at_fork listeners, and because Pidfiles
|
24
|
-
may be pickled and there must be no possibility of accidentally unpickling and instantiating a new instance of the
|
25
|
-
manager.
|
26
23
|
"""
|
27
24
|
|
28
|
-
def __new__(cls, *args, **kwargs): # noqa
|
29
|
-
raise TypeError
|
30
|
-
|
31
25
|
_lock: ta.ClassVar[threading.Lock] = threading.Lock()
|
32
|
-
_installed: ta.ClassVar[bool] = False
|
33
26
|
_pidfile_threads: ta.ClassVar[ta.MutableMapping[Pidfile, threading.Thread]] = weakref.WeakKeyDictionary()
|
27
|
+
_num_kills: ta.ClassVar[int] = 0
|
34
28
|
|
35
29
|
@classmethod
|
36
|
-
def
|
37
|
-
cls.
|
38
|
-
|
39
|
-
@classmethod
|
40
|
-
def _after_fork_in_parent(cls) -> None:
|
41
|
-
cls._lock.release()
|
30
|
+
def num_kills(cls) -> int:
|
31
|
+
return cls._num_kills
|
42
32
|
|
43
33
|
@classmethod
|
44
34
|
def _after_fork_in_child(cls) -> None:
|
45
|
-
th = threading.current_thread()
|
46
|
-
for pf, pf_th in list(cls._pidfile_threads.items()):
|
47
|
-
if pf_th is not th:
|
48
|
-
pf.close()
|
49
|
-
del cls._pidfile_threads[pf]
|
50
|
-
|
51
|
-
cls._lock.release()
|
52
|
-
|
53
|
-
#
|
54
|
-
|
55
|
-
@classmethod
|
56
|
-
def _install(cls) -> None:
|
57
|
-
check.state(not cls._installed)
|
58
|
-
|
59
|
-
os.register_at_fork(
|
60
|
-
before=cls._before_fork,
|
61
|
-
after_in_parent=cls._after_fork_in_parent,
|
62
|
-
after_in_child=cls._after_fork_in_child,
|
63
|
-
)
|
64
|
-
|
65
|
-
cls._installed = True
|
66
|
-
|
67
|
-
@classmethod
|
68
|
-
def install(cls) -> bool:
|
69
35
|
with cls._lock:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
36
|
+
th = threading.current_thread()
|
37
|
+
for pf, pf_th in list(cls._pidfile_threads.items()):
|
38
|
+
if pf_th is not th:
|
39
|
+
pf.close()
|
40
|
+
del cls._pidfile_threads[pf]
|
41
|
+
cls._num_kills += 1
|
75
42
|
|
76
43
|
@classmethod
|
77
44
|
@contextlib.contextmanager
|
omlish/reflect/__init__.py
CHANGED
omlish/reflect/inspect.py
CHANGED
@@ -4,6 +4,7 @@ TODO:
|
|
4
4
|
"""
|
5
5
|
import typing as ta
|
6
6
|
|
7
|
+
from .. import check
|
7
8
|
from .. import lang
|
8
9
|
|
9
10
|
|
@@ -16,8 +17,50 @@ else:
|
|
16
17
|
annotationlib = lang.lazy_import('annotationlib', optional=True, cache_failure=True)
|
17
18
|
|
18
19
|
|
20
|
+
##
|
21
|
+
|
22
|
+
|
19
23
|
def get_annotations(obj: ta.Any) -> ta.Mapping[str, ta.Any]:
|
20
24
|
if (al := annotationlib()) is not None:
|
21
25
|
return al.get_annotations(obj, format=al.Format.FORWARDREF) # noqa
|
22
26
|
else:
|
23
27
|
return inspect.get_annotations(obj)
|
28
|
+
|
29
|
+
|
30
|
+
##
|
31
|
+
|
32
|
+
|
33
|
+
class _TypeHintsTarget:
|
34
|
+
def __init__(self, obj, annotations):
|
35
|
+
super().__init__()
|
36
|
+
|
37
|
+
self.obj = obj
|
38
|
+
self.__annotations__ = annotations
|
39
|
+
self.__globals__ = obj.__globals__
|
40
|
+
self.__type_params__ = obj.__type_params__
|
41
|
+
|
42
|
+
|
43
|
+
def get_filtered_type_hints(
|
44
|
+
obj: ta.Any,
|
45
|
+
*,
|
46
|
+
include: ta.Container[str] | None = None,
|
47
|
+
exclude: ta.Container[str] | None = None,
|
48
|
+
) -> ta.Mapping[str, ta.Any]:
|
49
|
+
"""
|
50
|
+
Gross hack: get_type_hints doesn't support recursive types, but it's nice to support functions with, at least,
|
51
|
+
recursive return types.
|
52
|
+
|
53
|
+
This is consciously constrained in what it supports - basically just functions.
|
54
|
+
"""
|
55
|
+
|
56
|
+
check.not_isinstance(include, str)
|
57
|
+
check.not_isinstance(exclude, str)
|
58
|
+
|
59
|
+
anns = {
|
60
|
+
k: v
|
61
|
+
for k, v in obj.__annotations__.items()
|
62
|
+
if (include is None or k in include)
|
63
|
+
and not (exclude is not None and k in exclude)
|
64
|
+
}
|
65
|
+
|
66
|
+
return ta.get_type_hints(_TypeHintsTarget(obj, anns))
|
omlish/sql/queries/__init__.py
CHANGED
@@ -11,10 +11,6 @@ from .binary import ( # noqa
|
|
11
11
|
BinaryOps,
|
12
12
|
)
|
13
13
|
|
14
|
-
from .building import ( # noqa
|
15
|
-
StdBuilder,
|
16
|
-
)
|
17
|
-
|
18
14
|
from .exprs import ( # noqa
|
19
15
|
CanExpr,
|
20
16
|
CanLiteral,
|
@@ -76,6 +72,10 @@ from .selects import ( # noqa
|
|
76
72
|
SelectItem,
|
77
73
|
)
|
78
74
|
|
75
|
+
from .std import ( # noqa
|
76
|
+
StdBuilder,
|
77
|
+
)
|
78
|
+
|
79
79
|
from .stmts import ( # noqa
|
80
80
|
CanExpr,
|
81
81
|
ExprStmt,
|
@@ -0,0 +1,248 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- minimal parens
|
4
|
+
- text.parts
|
5
|
+
- QuoteStyle
|
6
|
+
- ParamStyle
|
7
|
+
|
8
|
+
==
|
9
|
+
|
10
|
+
def needs_parens(self, e: Expr) -> bool:
|
11
|
+
if isinstance(e, (Literal, Ident, Name)):
|
12
|
+
return True
|
13
|
+
elif isinstance(e, Expr):
|
14
|
+
return False
|
15
|
+
else:
|
16
|
+
raise TypeError(e)
|
17
|
+
"""
|
18
|
+
import dataclasses as dc
|
19
|
+
import typing as ta
|
20
|
+
|
21
|
+
from ... import dispatch
|
22
|
+
from ... import lang
|
23
|
+
from ...text import parts as tp
|
24
|
+
from ..params import ParamStyle
|
25
|
+
from ..params import make_params_preparer
|
26
|
+
from .base import Node
|
27
|
+
from .binary import Binary
|
28
|
+
from .binary import BinaryOp
|
29
|
+
from .binary import BinaryOps
|
30
|
+
from .exprs import Literal
|
31
|
+
from .exprs import NameExpr
|
32
|
+
from .idents import Ident
|
33
|
+
from .inserts import Insert
|
34
|
+
from .inserts import Values
|
35
|
+
from .multi import Multi
|
36
|
+
from .multi import MultiKind
|
37
|
+
from .names import Name
|
38
|
+
from .params import Param
|
39
|
+
from .relations import Join
|
40
|
+
from .relations import JoinKind
|
41
|
+
from .relations import Table
|
42
|
+
from .selects import Select
|
43
|
+
from .selects import SelectItem
|
44
|
+
from .unary import Unary
|
45
|
+
from .unary import UnaryOp
|
46
|
+
from .unary import UnaryOps
|
47
|
+
|
48
|
+
|
49
|
+
@dc.dataclass(frozen=True)
|
50
|
+
class RenderedQuery(lang.Final):
|
51
|
+
t: tp.Part
|
52
|
+
args: lang.Args
|
53
|
+
|
54
|
+
|
55
|
+
class Renderer(lang.Abstract):
|
56
|
+
def __init__(
|
57
|
+
self,
|
58
|
+
*,
|
59
|
+
param_style: ParamStyle | None = None,
|
60
|
+
) -> None:
|
61
|
+
super().__init__()
|
62
|
+
|
63
|
+
self._param_style = param_style if param_style is not None else self.default_param_style
|
64
|
+
|
65
|
+
self._params_preparer = make_params_preparer(self._param_style)
|
66
|
+
|
67
|
+
default_param_style: ta.ClassVar[ParamStyle] = ParamStyle.PYFORMAT
|
68
|
+
|
69
|
+
def args(self) -> lang.Args:
|
70
|
+
return self._params_preparer.prepare()
|
71
|
+
|
72
|
+
@dispatch.method
|
73
|
+
def render(self, o: ta.Any) -> tp.Part:
|
74
|
+
raise TypeError(o)
|
75
|
+
|
76
|
+
|
77
|
+
class StdRenderer(Renderer):
|
78
|
+
# parens
|
79
|
+
|
80
|
+
NEEDS_PAREN_TYPES: ta.AbstractSet[type[Node]] = {
|
81
|
+
Binary,
|
82
|
+
# IsNull,
|
83
|
+
# SelectExpr,
|
84
|
+
}
|
85
|
+
|
86
|
+
def needs_paren(self, node: Node) -> bool:
|
87
|
+
return type(node) in self.NEEDS_PAREN_TYPES
|
88
|
+
|
89
|
+
def paren(self, node: Node) -> tp.Part:
|
90
|
+
return tp.Wrap(self.render(node)) if self.needs_paren(node) else self.render(node)
|
91
|
+
|
92
|
+
# binary
|
93
|
+
|
94
|
+
BINARY_OP_TO_STR: ta.ClassVar[ta.Mapping[BinaryOp, str]] = {
|
95
|
+
BinaryOps.EQ: '=',
|
96
|
+
BinaryOps.NE: '!=',
|
97
|
+
BinaryOps.LT: '<',
|
98
|
+
BinaryOps.LE: '<=',
|
99
|
+
BinaryOps.GT: '>',
|
100
|
+
BinaryOps.GE: '>=',
|
101
|
+
|
102
|
+
BinaryOps.ADD: '+',
|
103
|
+
BinaryOps.SUB: '-',
|
104
|
+
BinaryOps.MUL: '*',
|
105
|
+
BinaryOps.DIV: '/',
|
106
|
+
BinaryOps.MOD: '%',
|
107
|
+
|
108
|
+
BinaryOps.CONCAT: '||',
|
109
|
+
}
|
110
|
+
|
111
|
+
@Renderer.render.register
|
112
|
+
def render_binary(self, o: Binary) -> tp.Part:
|
113
|
+
return [
|
114
|
+
self.paren(o.l),
|
115
|
+
self.BINARY_OP_TO_STR[o.op],
|
116
|
+
self.paren(o.r),
|
117
|
+
]
|
118
|
+
|
119
|
+
# exprs
|
120
|
+
|
121
|
+
@Renderer.render.register
|
122
|
+
def render_literal(self, o: Literal) -> tp.Part:
|
123
|
+
return repr(o.v)
|
124
|
+
|
125
|
+
@Renderer.render.register
|
126
|
+
def render_name_expr(self, o: NameExpr) -> tp.Part:
|
127
|
+
return self.render(o.n)
|
128
|
+
|
129
|
+
@Renderer.render.register
|
130
|
+
def render_param(self, o: Param) -> tp.Part:
|
131
|
+
return self._params_preparer.add(o.n if o.n is not None else id(o))
|
132
|
+
|
133
|
+
# idents
|
134
|
+
|
135
|
+
@Renderer.render.register
|
136
|
+
def render_ident(self, o: Ident) -> tp.Part:
|
137
|
+
return f'"{o.s}"'
|
138
|
+
|
139
|
+
# inserts
|
140
|
+
|
141
|
+
@Renderer.render.register
|
142
|
+
def render_values(self, o: Values) -> tp.Part:
|
143
|
+
return [
|
144
|
+
'values',
|
145
|
+
tp.Wrap(tp.List([self.render(v) for v in o.vs])),
|
146
|
+
]
|
147
|
+
|
148
|
+
@Renderer.render.register
|
149
|
+
def render_insert(self, o: Insert) -> tp.Part:
|
150
|
+
return [
|
151
|
+
'insert into',
|
152
|
+
self.render(o.into),
|
153
|
+
tp.Wrap(tp.List([self.render(c) for c in o.columns])),
|
154
|
+
]
|
155
|
+
|
156
|
+
# multis
|
157
|
+
|
158
|
+
MULTI_KIND_TO_STR: ta.ClassVar[ta.Mapping[MultiKind, str]] = {
|
159
|
+
MultiKind.AND: 'and',
|
160
|
+
MultiKind.OR: 'or',
|
161
|
+
}
|
162
|
+
|
163
|
+
@Renderer.render.register
|
164
|
+
def render_multi(self, o: Multi) -> tp.Part:
|
165
|
+
return tp.Wrap(tp.List(
|
166
|
+
[self.render(e) for e in o.es],
|
167
|
+
delimiter=' ' + self.MULTI_KIND_TO_STR[o.k], # FIXME: Part
|
168
|
+
))
|
169
|
+
|
170
|
+
# names
|
171
|
+
|
172
|
+
@Renderer.render.register
|
173
|
+
def render_name(self, o: Name) -> tp.Part:
|
174
|
+
out: list[tp.Part] = []
|
175
|
+
for n, i in enumerate(o.ps):
|
176
|
+
if n:
|
177
|
+
out.append('.')
|
178
|
+
out.append(self.render(i))
|
179
|
+
return tp.Concat(out)
|
180
|
+
|
181
|
+
# relations
|
182
|
+
|
183
|
+
@Renderer.render.register
|
184
|
+
def render_table(self, o: Table) -> tp.Part:
|
185
|
+
return [
|
186
|
+
self.render(o.n),
|
187
|
+
*(['as', self.render(o.a)] if o.a is not None else []),
|
188
|
+
]
|
189
|
+
|
190
|
+
JOIN_KIND_TO_STR: ta.ClassVar[ta.Mapping[JoinKind, str]] = {
|
191
|
+
JoinKind.DEFAULT: 'join',
|
192
|
+
JoinKind.INNER: 'inner join',
|
193
|
+
JoinKind.LEFT: 'left join',
|
194
|
+
JoinKind.LEFT_OUTER: 'left outer join',
|
195
|
+
JoinKind.RIGHT: 'right join',
|
196
|
+
JoinKind.RIGHT_OUTER: 'right outer join',
|
197
|
+
JoinKind.FULL: 'full join',
|
198
|
+
JoinKind.FULL_OUTER: 'full outer join',
|
199
|
+
JoinKind.CROSS: 'cross join',
|
200
|
+
JoinKind.NATURAL: 'natural join',
|
201
|
+
}
|
202
|
+
|
203
|
+
@Renderer.render.register
|
204
|
+
def render_join(self, o: Join) -> tp.Part:
|
205
|
+
return [
|
206
|
+
self.render(o.l),
|
207
|
+
self.JOIN_KIND_TO_STR[o.k],
|
208
|
+
self.render(o.r),
|
209
|
+
*(['on', self.render(o.c)] if o.c is not None else []),
|
210
|
+
]
|
211
|
+
|
212
|
+
# selects
|
213
|
+
|
214
|
+
@Renderer.render.register
|
215
|
+
def render_select_item(self, o: SelectItem) -> tp.Part:
|
216
|
+
return [
|
217
|
+
self.render(o.v),
|
218
|
+
*(['as', self.render(o.a)] if o.a is not None else []),
|
219
|
+
]
|
220
|
+
|
221
|
+
@Renderer.render.register
|
222
|
+
def render_select(self, o: Select) -> tp.Part:
|
223
|
+
return [
|
224
|
+
'select',
|
225
|
+
tp.List([self.render(i) for i in o.items]),
|
226
|
+
*(['from', self.render(o.from_)] if o.from_ is not None else []),
|
227
|
+
*(['where', self.render(o.where)] if o.where is not None else []),
|
228
|
+
]
|
229
|
+
|
230
|
+
# unary
|
231
|
+
|
232
|
+
UNARY_OP_TO_STR: ta.ClassVar[ta.Mapping[UnaryOp, tuple[str, str]]] = {
|
233
|
+
UnaryOps.NOT: ('not ', ''),
|
234
|
+
UnaryOps.IS_NULL: ('', ' is null'),
|
235
|
+
UnaryOps.IS_NOT_NULL: ('', ' is not null'),
|
236
|
+
|
237
|
+
UnaryOps.POS: ('+', ''),
|
238
|
+
UnaryOps.NEG: ('-', ''),
|
239
|
+
}
|
240
|
+
|
241
|
+
@Renderer.render.register
|
242
|
+
def render_unary(self, o: Unary) -> tp.Part:
|
243
|
+
pfx, sfx = self.UNARY_OP_TO_STR[o.op]
|
244
|
+
return tp.Concat([
|
245
|
+
pfx,
|
246
|
+
self.render(o.v),
|
247
|
+
sfx,
|
248
|
+
])
|