sqlakit 0.22.7__tar.gz → 0.22.9__tar.gz

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.
Files changed (34) hide show
  1. {sqlakit-0.22.7 → sqlakit-0.22.9}/PKG-INFO +1 -1
  2. {sqlakit-0.22.7 → sqlakit-0.22.9}/pyproject.toml +1 -1
  3. {sqlakit-0.22.7 → sqlakit-0.22.9}/pyproject.toml.orig +1 -1
  4. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_sql.py +72 -3
  5. {sqlakit-0.22.7 → sqlakit-0.22.9}/LICENSE +0 -0
  6. {sqlakit-0.22.7 → sqlakit-0.22.9}/README.md +0 -0
  7. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/__init__.py +0 -0
  8. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_base.py +0 -0
  9. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_cli.py +0 -0
  10. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_db.py +0 -0
  11. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_discovery.py +0 -0
  12. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_linters.py +0 -0
  13. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_model.py +0 -0
  14. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_project.py +0 -0
  15. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_pycharm.py +0 -0
  16. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_query.py +0 -0
  17. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_recording.py +0 -0
  18. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_registry.py +0 -0
  19. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_render.py +0 -0
  20. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_routing.py +0 -0
  21. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_sql_formatter.py +0 -0
  22. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/_static.py +0 -0
  23. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/__init__.py +0 -0
  24. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/_db.py +0 -0
  25. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/_registry.py +0 -0
  26. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/orm.py +0 -0
  27. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/asyncio/sql.py +0 -0
  28. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/exceptions.py +0 -0
  29. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/orm.py +0 -0
  30. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/py.typed +0 -0
  31. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/pytest_plugin.py +0 -0
  32. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/sql.py +0 -0
  33. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/testing.py +0 -0
  34. {sqlakit-0.22.7 → sqlakit-0.22.9}/sqlakit/types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlakit
3
- Version: 0.22.7
3
+ Version: 0.22.9
4
4
  Summary: A toolkit for SQLAlchemy applications.
5
5
  Keywords: sqlalchemy,database,orm,sql,asyncio
6
6
  Author: Anton Ruhlov
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlakit"
3
- version = "0.22.7"
3
+ version = "0.22.9"
4
4
  description = "A toolkit for SQLAlchemy applications."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlakit"
3
- version = "0.22.7"
3
+ version = "0.22.9"
4
4
  description = "A toolkit for SQLAlchemy applications."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -10,7 +10,7 @@ from collections.abc import Mapping, Sized
10
10
  from contextlib import contextmanager
11
11
  from contextvars import ContextVar
12
12
  from dataclasses import dataclass, is_dataclass
13
- from functools import cache, cached_property, lru_cache
13
+ from functools import cache, cached_property, lru_cache, partial
14
14
  from inspect import iscoroutinefunction
15
15
  from pathlib import Path
16
16
  from types import SimpleNamespace
@@ -613,6 +613,9 @@ def sql_macros(path: Path | str, source: str | None = None) -> list[SqlMacro]:
613
613
  problem = f"its arguments in {path.name}:{line} name one twice: {params}"
614
614
  raise MacroDefinitionError(name, problem, line=line)
615
615
  named = offset + statement.start("name")
616
+ # The body may start below its `SELECT`, where its lines count from.
617
+ body = statement.group("body")
618
+ body_at = offset + statement.start("body") + len(body) - len(body.lstrip())
616
619
  found.append(
617
620
  SqlMacro(
618
621
  name,
@@ -621,7 +624,7 @@ def sql_macros(path: Path | str, source: str | None = None) -> list[SqlMacro]:
621
624
  doc=" ".join(one for one in doc if one),
622
625
  path=path,
623
626
  line=line,
624
- body_line=line,
627
+ body_line=source.count("\n", 0, body_at) + 1,
625
628
  name_at=(
626
629
  source.count("\n", 0, named) + 1,
627
630
  named - (source.rfind("\n", 0, named) + 1),
@@ -2293,6 +2296,69 @@ def array_contains_all(ctx: Context, array: Sql, other: Sql) -> str:
2293
2296
  return f"({array} @> {other})"
2294
2297
 
2295
2298
 
2299
+ @sql_macro(optional=True)
2300
+ def any_of(ctx: Context, rows: Param, row: Sql, condition: Sql) -> str:
2301
+ """Whether the condition holds for any of the rows, `row.field` reading a field.
2302
+
2303
+ ```sql
2304
+ WHERE tpl.any_of(
2305
+ :cities, city,
2306
+ ST_DISTANCE(p.location, ST_POINT(city.lon, city.lat)) <= city.radius
2307
+ )
2308
+ ```
2309
+
2310
+ The condition is written once for each row, its `city.field` bound to the
2311
+ row's field, and the conditions joined with `OR`. A row is an object or a
2312
+ mapping. No rows, or none passed, is `FALSE`: wrap the call in
2313
+ `tpl.if_set(:cities, ...)` for a filter that is off when nothing is passed.
2314
+
2315
+ The SQL grows with the rows, so this is for a few dozen. A condition made
2316
+ of equalities only is better as `EXISTS (SELECT 1 FROM tpl.values(:rows) AS
2317
+ v WHERE t.a = v.column1)`, which the database joins. Snowflake evaluates no
2318
+ subquery tied to the outer row by anything else, a distance, a range or a
2319
+ `LIKE`, and this macro covers those.
2320
+
2321
+ Raises:
2322
+ MacroArgumentError: if ``row`` is not a name, or a row has no field the
2323
+ condition reads.
2324
+
2325
+ """
2326
+ name = str(row).strip()
2327
+ if not _IDENTIFIER.fullmatch(name):
2328
+ problem = f"`{name}` is not a name to read the fields of a row under"
2329
+ raise MacroArgumentError(any_of.name, problem)
2330
+ field = re.compile(
2331
+ rf"{_LITERAL}|(?<![\w.]){re.escape(name)}\.(?P<field>[A-Za-z_]\w*)\b",
2332
+ re.DOTALL,
2333
+ )
2334
+ prefix = _named(rows)
2335
+
2336
+ def written(item: Any, found: re.Match[str]) -> str: # noqa: ANN401
2337
+ key = found.group("field")
2338
+ if key is None:
2339
+ return found.group()
2340
+ try:
2341
+ value = item[key] if isinstance(item, Mapping) else getattr(item, key)
2342
+ except (KeyError, AttributeError):
2343
+ problem = f"a row of `{_as_written(rows)}` has no field `{key}`"
2344
+ raise MacroArgumentError(any_of.name, problem) from None
2345
+ return ctx.bind(value, f"{prefix}_{key}" if prefix else key)
2346
+
2347
+ text = str(condition)
2348
+ # A `--` comment at the end would take the `)` and the `OR` after it along.
2349
+ if any(
2350
+ found.group().startswith("--") and found.end() == len(text.rstrip())
2351
+ for found in re.finditer(_LITERAL, text, re.DOTALL)
2352
+ ):
2353
+ text = text.rstrip() + "\n"
2354
+ conditions = [
2355
+ _grouped(field.sub(partial(written, item), text)) for item in rows.value or ()
2356
+ ]
2357
+ if not conditions:
2358
+ return "FALSE"
2359
+ return _grouped(" OR ".join(conditions))
2360
+
2361
+
2296
2362
  @sql_macro(name="values")
2297
2363
  def values_table(ctx: Context, rows: Param) -> str:
2298
2364
  """Write a small table out in the query, one parameter per value.
@@ -2310,7 +2376,9 @@ def values_table(ctx: Context, rows: Param) -> str:
2310
2376
  and PostgreSQL wants one type down a column: cast where the rows mix them.
2311
2377
 
2312
2378
  For a few dozen rows. Hundreds are better sent as one JSON value and
2313
- unpacked in the database, with `FLATTEN` or `json_array_elements`.
2379
+ unpacked in the database, with `FLATTEN` or `json_array_elements`. In an
2380
+ `EXISTS` tied to the outer row, compare by equalities only: Snowflake
2381
+ refuses any other tie, and `any_of` writes that condition for each row.
2314
2382
 
2315
2383
  Raises:
2316
2384
  MacroArgumentError: if there are no rows, or they differ in length.
@@ -2595,6 +2663,7 @@ BUILTIN_MACROS: Mapping[str, Macro] = {
2595
2663
  arrays_overlap,
2596
2664
  array_contains_all,
2597
2665
  values_table,
2666
+ any_of,
2598
2667
  )
2599
2668
  }
2600
2669
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes