omlish 0.0.0.dev268__py3-none-any.whl → 0.0.0.dev269__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.
@@ -5,6 +5,9 @@ import typing as ta
5
5
  T = ta.TypeVar('T')
6
6
 
7
7
 
8
+ ##
9
+
10
+
8
11
  _DISABLE_CHECKS = False
9
12
 
10
13
  _ABSTRACT_METHODS_ATTR = '__abstractmethods__'
omlish/lang/comparison.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import typing as ta
2
2
 
3
3
 
4
+ ##
5
+
6
+
4
7
  def cmp(l: ta.Any, r: ta.Any) -> int:
5
8
  return int(l > r) - int(l < r)
6
9
 
omlish/lang/datetimes.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import datetime
2
2
 
3
3
 
4
+ ##
5
+
6
+
4
7
  def utcnow() -> datetime.datetime:
5
8
  return datetime.datetime.now(tz=datetime.UTC)
6
9
 
omlish/lang/generators.py CHANGED
@@ -203,8 +203,8 @@ class GeneratorMappedIterator(ta.Generic[O, I, R]):
203
203
  Like a `map` iterator but takes a generator instead of a function. Provided generator *must* yield outputs 1:1 with
204
204
  inputs.
205
205
 
206
- Generator return value will be captured on `value` property - if present generator stopped, it absent iterator
207
- stopped.
206
+ Generator return value will be captured on `value` property - present if the generator stopped, absent if the
207
+ iterator stopped.
208
208
  """
209
209
 
210
210
  def __init__(self, g: ta.Generator[O, I, R], it: ta.Iterator[I]) -> None:
omlish/lang/maybes.py CHANGED
@@ -6,6 +6,9 @@ T = ta.TypeVar('T')
6
6
  U = ta.TypeVar('U')
7
7
 
8
8
 
9
+ ##
10
+
11
+
9
12
  class ValueNotPresentException(BaseException):
10
13
  pass
11
14
 
omlish/lang/resolving.py CHANGED
@@ -3,6 +3,9 @@ import string
3
3
  import typing as ta
4
4
 
5
5
 
6
+ ##
7
+
8
+
6
9
  class ResolvableClassNameError(NameError):
7
10
  pass
8
11
 
omlish/lang/sys.py CHANGED
@@ -1,6 +1,9 @@
1
1
  import sys
2
2
 
3
3
 
4
+ ##
5
+
6
+
4
7
  REQUIRED_PYTHON_VERSION = (3, 12)
5
8
 
6
9
 
@@ -66,7 +66,7 @@ def get_dataclass_field_infos(
66
66
  type_hints = ta.get_type_hints(ty)
67
67
 
68
68
  ret: list[FieldInfo] = []
69
- for field in dc_rf.fields.values():
69
+ for field in dc_rf.instance_fields:
70
70
  if (f_naming := field.metadata.get(Naming, dc_naming)) is not None:
71
71
  um_name = translate_name(field.name, f_naming)
72
72
  else:
@@ -109,12 +109,11 @@ def polymorphism_from_subclasses(
109
109
  ty: type,
110
110
  *,
111
111
  naming: Naming | None = None,
112
- strip_suffix: bool = False,
112
+ strip_suffix: bool | ta.Literal['auto'] = False,
113
113
  ) -> Polymorphism:
114
- dct: dict[str, Impl] = {}
115
-
116
114
  seen: set[type] = set()
117
115
  todo: list[type] = [ty]
116
+ impls: set[type] = set()
118
117
  while todo:
119
118
  cur = todo.pop()
120
119
  seen.add(cur)
@@ -124,6 +123,13 @@ def polymorphism_from_subclasses(
124
123
  if lang.is_abstract_class(cur):
125
124
  continue
126
125
 
126
+ impls.add(cur)
127
+
128
+ if strip_suffix == 'auto':
129
+ strip_suffix = all(c.__name__.endswith(ty.__name__) for c in impls)
130
+
131
+ dct: dict[str, Impl] = {}
132
+ for cur in impls:
127
133
  name = cur.__name__
128
134
  if strip_suffix:
129
135
  name = lang.strip_suffix(name, ty.__name__)
@@ -1,3 +1,34 @@
1
+ # ruff: noqa: I001
2
+ import typing as _ta
3
+
4
+ from ... import lang as _lang
5
+
6
+
7
+ if _ta.TYPE_CHECKING:
8
+ from .apiadapter import ( # noqa
9
+ SqlalchemyApiWrapper,
10
+
11
+ SqlalchemyApiRows,
12
+ SqlalchemyApiConn,
13
+ SqlalchemyApiDb,
14
+ SqlalchemyApiAdapter,
15
+
16
+ api_adapt,
17
+ )
18
+
19
+ else:
20
+ _lang.proxy_init(globals(), '.apiadapter', [
21
+ 'SqlalchemyApiWrapper',
22
+
23
+ 'SqlalchemyApiRows',
24
+ 'SqlalchemyApiConn',
25
+ 'SqlalchemyApiDb',
26
+ 'SqlalchemyApiAdapter',
27
+
28
+ 'api_adapt',
29
+ ])
30
+
31
+
1
32
  from .asyncs import ( # noqa
2
33
  AsyncConnection,
3
34
  AsyncConnectionLike,
@@ -0,0 +1,121 @@
1
+ import typing as ta
2
+
3
+ import sqlalchemy as sa
4
+
5
+ from ... import check
6
+ from .. import api
7
+ from ..api.dbapi import build_dbapi_columns
8
+
9
+
10
+ T = ta.TypeVar('T')
11
+
12
+
13
+ ##
14
+
15
+
16
+ class SqlalchemyApiWrapper(api.ContextCloser, ta.Generic[T]):
17
+ def __init__(
18
+ self,
19
+ u: T,
20
+ *,
21
+ auto_close: bool = False,
22
+ ) -> None:
23
+ super().__init__()
24
+
25
+ self._u = u
26
+ self._auto_close = auto_close
27
+
28
+ def close(self) -> None:
29
+ if self._auto_close and hasattr(self._u, 'close'):
30
+ self._u.close()
31
+ super().close()
32
+
33
+
34
+ ##
35
+
36
+
37
+ class SqlalchemyApiRows(api.Rows):
38
+ def __init__(self, columns: api.Columns, rows: ta.Sequence[api.Row]) -> None:
39
+ super().__init__()
40
+
41
+ self._columns = columns
42
+ self._rows = rows
43
+
44
+ self._it = iter(self._rows)
45
+
46
+ @property
47
+ def columns(self) -> api.Columns:
48
+ return self._columns
49
+
50
+ def __next__(self) -> api.Row:
51
+ return next(self._it)
52
+
53
+
54
+ class SqlalchemyApiConn(SqlalchemyApiWrapper[sa.engine.Connection], api.Conn):
55
+ @property
56
+ def adapter(self) -> api.Adapter:
57
+ raise NotImplementedError
58
+
59
+ def query(self, query: api.Query) -> api.Rows:
60
+ check.empty(query.args)
61
+ result: sa.engine.cursor.CursorResult
62
+ with self._u.execute(sa.text(query.text)) as result:
63
+ cols = build_dbapi_columns(result.cursor.description)
64
+ sa_rows = result.fetchall()
65
+ rows = [
66
+ api.Row(cols, tuple(sa_row))
67
+ for sa_row in sa_rows
68
+ ]
69
+ return SqlalchemyApiRows(cols, rows)
70
+
71
+
72
+ class SqlalchemyApiDb(SqlalchemyApiWrapper[sa.engine.Engine], api.Db):
73
+ def connect(self) -> api.Conn:
74
+ return SqlalchemyApiConn(self._u.connect(), auto_close=True)
75
+
76
+ @property
77
+ def adapter(self) -> api.Adapter:
78
+ raise NotImplementedError
79
+
80
+
81
+ class SqlalchemyApiAdapter(api.Adapter):
82
+ def scan_type(self, c: api.Column) -> type:
83
+ raise NotImplementedError
84
+
85
+
86
+ ##
87
+
88
+
89
+ @ta.overload
90
+ def api_adapt(o: sa.engine.Connection) -> SqlalchemyApiConn:
91
+ ...
92
+
93
+
94
+ @ta.overload
95
+ def api_adapt(o: api.Conn) -> api.Conn:
96
+ ...
97
+
98
+
99
+ @ta.overload
100
+ def api_adapt(o: sa.engine.Engine) -> SqlalchemyApiDb:
101
+ ...
102
+
103
+
104
+ @ta.overload
105
+ def api_adapt(o: api.Db) -> api.Db:
106
+ ...
107
+
108
+
109
+ def api_adapt(o):
110
+ if isinstance(o, sa.engine.Connection):
111
+ return SqlalchemyApiConn(o)
112
+ elif isinstance(o, api.Conn):
113
+ return o
114
+
115
+ elif isinstance(o, sa.engine.Engine):
116
+ return SqlalchemyApiDb(o)
117
+ elif isinstance(o, api.Db):
118
+ return o
119
+
120
+ else:
121
+ raise TypeError(o)
@@ -0,0 +1,39 @@
1
+ from .base import ( # noqa
2
+ Closer,
3
+ ContextCloser,
4
+
5
+ Querier,
6
+ Rows,
7
+ Conn,
8
+ Db,
9
+ Adapter,
10
+ )
11
+
12
+ from .columns import ( # noqa
13
+ Column,
14
+ Columns,
15
+ )
16
+
17
+ from .errors import ( # noqa
18
+ Error,
19
+
20
+ ColumnError,
21
+ DuplicateColumnNameError,
22
+ MismatchedColumnCountError,
23
+
24
+ QueryError,
25
+ )
26
+
27
+ from .funcs import ( # noqa
28
+ query,
29
+ exec, # noqa
30
+ )
31
+
32
+ from .queries import ( # noqa
33
+ QueryMode,
34
+ Query,
35
+ )
36
+
37
+ from .rows import ( # noqa
38
+ Row,
39
+ )
omlish/sql/api/base.py CHANGED
@@ -24,6 +24,7 @@ class ContextCloser(Closer):
24
24
  def __enter__(self) -> ta.Self:
25
25
  return self
26
26
 
27
+ @ta.final
27
28
  def __exit__(self, exc_type, exc_val, exc_tb):
28
29
  self.close()
29
30
 
@@ -33,7 +33,7 @@ class _ParseVisitor(MinisqlVisitor):
33
33
  def visitExprSelectItem(self, ctx: MinisqlParser.ExprSelectItemContext):
34
34
  value = self.visit(ctx.expr())
35
35
  label = self.visit(ctx.ident()) if ctx.ident() is not None else None
36
- return no.SelectItem(value, label)
36
+ return no.ExprSelectItem(value, label)
37
37
 
38
38
  def visitIntegerNumber(self, ctx: MinisqlParser.IntegerNumberContext):
39
39
  return no.Literal(int(ctx.INTEGER_VALUE().getText()))
@@ -1,6 +1,7 @@
1
1
  from .base import ( # noqa
2
2
  Builder,
3
3
  Node,
4
+ NodeComparisonTypeError,
4
5
  Value,
5
6
  )
6
7
 
@@ -18,6 +19,7 @@ from .exprs import ( # noqa
18
19
  ExprBuilder,
19
20
  Literal,
20
21
  NameExpr,
22
+ ParamExpr,
21
23
  )
22
24
 
23
25
  from .idents import ( # noqa
@@ -66,7 +68,9 @@ from .rendering import ( # noqa
66
68
  )
67
69
 
68
70
  from .selects import ( # noqa
71
+ AllSelectItem,
69
72
  CanRelation,
73
+ ExprSelectItem,
70
74
  Select,
71
75
  SelectBuilder,
72
76
  SelectItem,
@@ -1,3 +1,4 @@
1
+ import types
1
2
  import typing as ta
2
3
 
3
4
  from ... import dataclasses as dc
@@ -13,8 +14,118 @@ Value: ta.TypeAlias = ta.Any
13
14
  ##
14
15
 
15
16
 
16
- class Node(dc.Frozen, lang.Abstract, cache_hash=True):
17
- pass
17
+ class NodeComparisonTypeError(TypeError):
18
+ def __init__(self, cls: type['Node'], *args: ta.Any) -> None:
19
+ super().__init__(
20
+ 'Query node types are not comparable or hashable - this would be a common source of confusion when '
21
+ 'constructing query filters',
22
+ cls,
23
+ *args,
24
+ )
25
+
26
+
27
+ class Node(
28
+ dc.Frozen,
29
+ lang.Abstract,
30
+ eq=False,
31
+ confer=frozenset([
32
+ *dc.get_metaclass_params(dc.Frozen).confer,
33
+ 'eq',
34
+ ]),
35
+ ):
36
+ @ta.final
37
+ def __hash__(self) -> ta.NoReturn:
38
+ raise NodeComparisonTypeError(type(self))
39
+
40
+ @ta.final
41
+ def __eq__(self, other) -> ta.NoReturn:
42
+ raise NodeComparisonTypeError(type(self))
43
+
44
+ @ta.final
45
+ def __ne__(self, other) -> ta.NoReturn:
46
+ raise NodeComparisonTypeError(type(self))
47
+
48
+ #
49
+
50
+ @dc.dataclass(frozen=True)
51
+ class _Fields:
52
+ cmp_fields: ta.Sequence[str]
53
+ hash_fields: ta.Sequence[str]
54
+
55
+ __node_fields__: ta.ClassVar[tuple[str, ...]]
56
+
57
+ @classmethod
58
+ def _fields(cls) -> _Fields:
59
+ try:
60
+ return cls.__dict__['__node_fields__']
61
+ except KeyError:
62
+ pass
63
+
64
+ dc_info = dc.reflect(cls)
65
+ fields = Node._Fields(
66
+ cmp_fields=tuple(f.name for f in dc_info.instance_fields if f.compare),
67
+ hash_fields=tuple(f.name for f in dc_info.instance_fields if (f.compare if f.hash is None else f.hash)),
68
+ )
69
+
70
+ setattr(cls, '__node_fields__', fields)
71
+ return fields
72
+
73
+ _hash: ta.ClassVar[int]
74
+
75
+ def hash(self) -> int:
76
+ try:
77
+ return self._hash
78
+ except AttributeError:
79
+ pass
80
+
81
+ h = hash(tuple(getattr(self, f) for f in self._fields().hash_fields))
82
+ object.__setattr__(self, '_hash', h)
83
+ return h
84
+
85
+ def eq(self, other: 'Node') -> bool | types.NotImplementedType:
86
+ if self is other:
87
+ return True
88
+
89
+ def rec(l, r):
90
+ if l.__class__ is not r.__class__:
91
+ return NotImplemented
92
+
93
+ if isinstance(l, lang.BUILTIN_SCALAR_ITERABLE_TYPES):
94
+ return l == r
95
+
96
+ elif isinstance(l, ta.Mapping):
97
+ ks = set(l)
98
+ if set(r) != ks:
99
+ return False
100
+ for k, lv in l.items():
101
+ rv = r[k]
102
+ if (ret := rec(lv, rv)) is not True:
103
+ return ret
104
+ return True
105
+
106
+ elif isinstance(l, ta.Sequence):
107
+ if len(l) != len(r):
108
+ return False
109
+ for le, re in zip(l, r):
110
+ if (ret := rec(le, re)) is not True:
111
+ return ret
112
+ return True
113
+
114
+ elif isinstance(l, Node):
115
+ return l.eq(r)
116
+
117
+ else:
118
+ return l == r
119
+
120
+ for f in self._fields().cmp_fields:
121
+ lf = getattr(self, f)
122
+ rf = getattr(other, f)
123
+ if (ret := rec(lf, rf)) is not True:
124
+ return ret
125
+ return True
126
+
127
+
128
+ ##
18
129
 
19
130
 
20
131
  class Builder(lang.Abstract):
@@ -2,6 +2,7 @@
2
2
  TODO:
3
3
  - case
4
4
  - cast / ::
5
+ - explicit null? already wrapped in non-null Literal node
5
6
  """
6
7
  import typing as ta
7
8
 
@@ -13,6 +14,9 @@ from .names import CanName
13
14
  from .names import Name
14
15
  from .names import NameBuilder
15
16
  from .names import NameLike
17
+ from .params import CanParam
18
+ from .params import Param
19
+ from .params import ParamBuilder
16
20
 
17
21
 
18
22
  ##
@@ -36,14 +40,21 @@ class NameExpr(Expr, lang.Final):
36
40
  n: Name
37
41
 
38
42
 
43
+ #
44
+
45
+
46
+ class ParamExpr(Expr, lang.Final):
47
+ p: Param
48
+
49
+
39
50
  ##
40
51
 
41
52
 
42
53
  CanLiteral: ta.TypeAlias = Literal | Value
43
- CanExpr: ta.TypeAlias = Expr | CanName | CanLiteral
54
+ CanExpr: ta.TypeAlias = Expr | CanParam | CanName | CanLiteral
44
55
 
45
56
 
46
- class ExprBuilder(NameBuilder):
57
+ class ExprBuilder(ParamBuilder, NameBuilder):
47
58
  def literal(self, o: CanLiteral) -> Literal:
48
59
  if isinstance(o, Literal):
49
60
  return o
@@ -61,6 +72,8 @@ class ExprBuilder(NameBuilder):
61
72
  def expr(self, o: CanExpr) -> Expr:
62
73
  if isinstance(o, Expr):
63
74
  return o
75
+ elif isinstance(o, Param):
76
+ return ParamExpr(o)
64
77
  elif isinstance(o, (NameLike, IdentLike)):
65
78
  return NameExpr(self.name(o))
66
79
  else:
@@ -2,6 +2,7 @@ import typing as ta
2
2
 
3
3
  from ... import dataclasses as dc
4
4
  from ... import lang
5
+ from .base import Node
5
6
  from .exprs import CanExpr
6
7
  from .exprs import Expr
7
8
  from .exprs import ExprBuilder
@@ -17,7 +18,7 @@ from .stmts import Stmt
17
18
  ##
18
19
 
19
20
 
20
- class Values(dc.Frozen, lang.Final):
21
+ class Values(Node, lang.Final):
21
22
  vs: ta.Sequence[Expr] = dc.xfield(coerce=tuple)
22
23
 
23
24
 
@@ -20,6 +20,7 @@ from .multi import MultiKind
20
20
  from .relations import JoinKind
21
21
  from .relations import Relation
22
22
  from .selects import Select
23
+ from .selects import SelectItem
23
24
  from .stmts import Stmt
24
25
  from .unary import UnaryOp
25
26
  from .unary import UnaryOps
@@ -78,9 +79,14 @@ def _install_standard_marshalling() -> None:
78
79
  Expr,
79
80
  Node,
80
81
  Relation,
82
+ SelectItem,
81
83
  Stmt,
82
84
  ]:
83
- p = msh.polymorphism_from_subclasses(cls, naming=msh.Naming.SNAKE)
85
+ p = msh.polymorphism_from_subclasses(
86
+ cls,
87
+ naming=msh.Naming.SNAKE,
88
+ strip_suffix='auto',
89
+ )
84
90
  msh.STANDARD_MARSHALER_FACTORIES[0:0] = [msh.PolymorphismMarshalerFactory(p)]
85
91
  msh.STANDARD_UNMARSHALER_FACTORIES[0:0] = [msh.PolymorphismUnmarshalerFactory(p)]
86
92
 
@@ -1,14 +1,15 @@
1
1
  import typing as ta
2
2
 
3
+ from ... import dataclasses as dc
3
4
  from ... import lang
4
5
  from .base import Builder
5
- from .exprs import Expr
6
6
 
7
7
 
8
8
  ##
9
9
 
10
10
 
11
- class Param(Expr, lang.Final):
11
+ @dc.dataclass(frozen=True)
12
+ class Param(lang.Final):
12
13
  n: str | None = None
13
14
 
14
15
  def __repr__(self) -> str:
@@ -29,6 +29,7 @@ from .binary import BinaryOp
29
29
  from .binary import BinaryOps
30
30
  from .exprs import Literal
31
31
  from .exprs import NameExpr
32
+ from .exprs import ParamExpr
32
33
  from .idents import Ident
33
34
  from .inserts import Insert
34
35
  from .inserts import Values
@@ -39,8 +40,9 @@ from .params import Param
39
40
  from .relations import Join
40
41
  from .relations import JoinKind
41
42
  from .relations import Table
43
+ from .selects import AllSelectItem
44
+ from .selects import ExprSelectItem
42
45
  from .selects import Select
43
- from .selects import SelectItem
44
46
  from .unary import Unary
45
47
  from .unary import UnaryOp
46
48
  from .unary import UnaryOps
@@ -149,8 +151,8 @@ class StdRenderer(Renderer):
149
151
  return self.render(o.n)
150
152
 
151
153
  @Renderer.render.register
152
- def render_param(self, o: Param) -> tp.Part:
153
- return self._params_preparer.add(o.n if o.n is not None else id(o))
154
+ def render_param_expr(self, o: ParamExpr) -> tp.Part:
155
+ return self.render(o.p)
154
156
 
155
157
  # idents
156
158
 
@@ -201,6 +203,12 @@ class StdRenderer(Renderer):
201
203
  out.append(self.render(i))
202
204
  return tp.Concat(out)
203
205
 
206
+ # params
207
+
208
+ @Renderer.render.register
209
+ def render_param(self, o: Param) -> tp.Part:
210
+ return self._params_preparer.add(o.n if o.n is not None else id(o))
211
+
204
212
  # relations
205
213
 
206
214
  @Renderer.render.register
@@ -235,7 +243,11 @@ class StdRenderer(Renderer):
235
243
  # selects
236
244
 
237
245
  @Renderer.render.register
238
- def render_select_item(self, o: SelectItem) -> tp.Part:
246
+ def render_all_select_item(self, o: AllSelectItem) -> tp.Part:
247
+ return '*'
248
+
249
+ @Renderer.render.register
250
+ def render_expr_select_item(self, o: ExprSelectItem) -> tp.Part:
239
251
  return [
240
252
  self.render(o.v),
241
253
  *(['as', self.render(o.a)] if o.a is not None else []),
@@ -16,11 +16,22 @@ from .stmts import Stmt
16
16
  ##
17
17
 
18
18
 
19
- class SelectItem(Node, lang.Final):
19
+ class SelectItem(Node, lang.Abstract):
20
+ pass
21
+
22
+
23
+ class AllSelectItem(SelectItem, lang.Final):
24
+ pass
25
+
26
+
27
+ class ExprSelectItem(SelectItem, lang.Final):
20
28
  v: Expr
21
29
  a: Ident | None = dc.xfield(None, repr_fn=dc.opt_repr)
22
30
 
23
31
 
32
+ ##
33
+
34
+
24
35
  class Select(Stmt, lang.Final):
25
36
  items: ta.Sequence[SelectItem] = dc.xfield(coerce=tuple)
26
37
  from_: Relation | None = dc.xfield(None, repr_fn=dc.opt_repr)
@@ -31,11 +42,15 @@ CanSelectItem: ta.TypeAlias = SelectItem | CanExpr
31
42
 
32
43
 
33
44
  class SelectBuilder(RelationBuilder, ExprBuilder):
45
+ @property
46
+ def star(self) -> AllSelectItem:
47
+ return AllSelectItem()
48
+
34
49
  def select_item(self, o: CanSelectItem) -> SelectItem:
35
50
  if isinstance(o, SelectItem):
36
51
  return o
37
52
  else:
38
- return SelectItem(self.expr(o))
53
+ return ExprSelectItem(self.expr(o))
39
54
 
40
55
  def select(
41
56
  self,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omlish
3
- Version: 0.0.0.dev268
3
+ Version: 0.0.0.dev269
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause