sqlakit 0.22.2__tar.gz → 0.22.4__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.
- {sqlakit-0.22.2 → sqlakit-0.22.4}/PKG-INFO +1 -1
- {sqlakit-0.22.2 → sqlakit-0.22.4}/pyproject.toml +2 -1
- {sqlakit-0.22.2 → sqlakit-0.22.4}/pyproject.toml.orig +2 -1
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_cli.py +208 -15
- sqlakit-0.22.4/sqlakit/_linters.py +375 -0
- sqlakit-0.22.4/sqlakit/_render.py +382 -0
- sqlakit-0.22.4/sqlakit/_sql_formatter.py +91 -0
- sqlakit-0.22.2/sqlakit/_sqruff.py +0 -168
- {sqlakit-0.22.2 → sqlakit-0.22.4}/LICENSE +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/README.md +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/__init__.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_base.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_db.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_discovery.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_model.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_project.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_pycharm.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_query.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_recording.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_registry.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_routing.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_sql.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/_static.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/asyncio/__init__.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/asyncio/_db.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/asyncio/_registry.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/asyncio/orm.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/asyncio/sql.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/exceptions.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/orm.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/py.typed +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/pytest_plugin.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/sql.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/testing.py +0 -0
- {sqlakit-0.22.2 → sqlakit-0.22.4}/sqlakit/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlakit"
|
|
3
|
-
version = "0.22.
|
|
3
|
+
version = "0.22.4"
|
|
4
4
|
description = "A toolkit for SQLAlchemy applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -201,6 +201,7 @@ dev = [
|
|
|
201
201
|
"dirty-equals>=0.11",
|
|
202
202
|
"codespell>=2.4.3",
|
|
203
203
|
"sqruff>=0.40.0",
|
|
204
|
+
"sqlfluff>=4.3.0",
|
|
204
205
|
]
|
|
205
206
|
docs = [
|
|
206
207
|
"mkdocs>=1.6.1,<2",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sqlakit"
|
|
3
|
-
version = "0.22.
|
|
3
|
+
version = "0.22.4"
|
|
4
4
|
description = "A toolkit for SQLAlchemy applications."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -77,6 +77,7 @@ dev = [
|
|
|
77
77
|
"dirty-equals>=0.11",
|
|
78
78
|
"codespell>=2.4.3",
|
|
79
79
|
"sqruff>=0.40.0",
|
|
80
|
+
"sqlfluff>=4.3.0",
|
|
80
81
|
]
|
|
81
82
|
docs = [
|
|
82
83
|
"mkdocs>=1.6.1,<2",
|
|
@@ -5,17 +5,20 @@ from __future__ import annotations
|
|
|
5
5
|
import argparse
|
|
6
6
|
import json
|
|
7
7
|
import os
|
|
8
|
+
import shutil
|
|
8
9
|
import sys
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
|
|
12
|
+
from . import _sql_formatter
|
|
13
|
+
from ._linters import LINTERS, dialect_of, settings, stale
|
|
11
14
|
from ._project import Problem, Project, load_project
|
|
12
15
|
from ._pycharm import DIALECTS, ddl, dialects
|
|
16
|
+
from ._render import import_macros, rendered, unparsed
|
|
13
17
|
from ._sql import NAMESPACE, registered, signature_of
|
|
14
|
-
from ._sqruff import settings, stale
|
|
15
18
|
from .exceptions import MacroDefinitionError, ProjectConfigError, UnknownImportPathError
|
|
16
19
|
|
|
17
20
|
|
|
18
|
-
def main(argv: list[str] | None = None) -> int:
|
|
21
|
+
def main(argv: list[str] | None = None) -> int: # noqa: PLR0911 - one return a command
|
|
19
22
|
"""Run a command, and return what the shell should make of it."""
|
|
20
23
|
parser = argparse.ArgumentParser(prog="sqlakit", description="SQLAKit.")
|
|
21
24
|
commands = parser.add_subparsers(dest="command", required=True)
|
|
@@ -51,11 +54,36 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
51
54
|
help="a directory in the project, the current one by default",
|
|
52
55
|
)
|
|
53
56
|
check.add_argument("--format", choices=("text", "json"), default="text")
|
|
57
|
+
check.add_argument(
|
|
58
|
+
"--lint",
|
|
59
|
+
choices=tuple(LINTERS),
|
|
60
|
+
help="also render every template, running the project's macros, and "
|
|
61
|
+
"fail where the linter cannot parse the SQL",
|
|
62
|
+
)
|
|
63
|
+
check.add_argument(
|
|
64
|
+
"--dialect", help="the dialect to render for, in place of the project's"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
render = commands.add_parser(
|
|
68
|
+
"render",
|
|
69
|
+
help="write every template as the SQL it renders, for a linter to read",
|
|
70
|
+
)
|
|
71
|
+
render.add_argument(
|
|
72
|
+
"--out", required=True, help="the directory to write the rendered SQL into"
|
|
73
|
+
)
|
|
74
|
+
render.add_argument(
|
|
75
|
+
"--project",
|
|
76
|
+
default=".",
|
|
77
|
+
help="a directory in the project, the current one by default",
|
|
78
|
+
)
|
|
79
|
+
render.add_argument(
|
|
80
|
+
"--dialect", help="the dialect to render for, in place of the project's"
|
|
81
|
+
)
|
|
54
82
|
|
|
55
83
|
export = commands.add_parser(
|
|
56
84
|
"export", help="write what another tool needs to read the templates"
|
|
57
85
|
)
|
|
58
|
-
export.add_argument("tool", choices=("
|
|
86
|
+
export.add_argument("tool", choices=(*LINTERS, "sql-formatter", "pycharm"))
|
|
59
87
|
export.add_argument(
|
|
60
88
|
"--project",
|
|
61
89
|
default=".",
|
|
@@ -72,11 +100,34 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
72
100
|
|
|
73
101
|
arguments = parser.parse_args(argv)
|
|
74
102
|
if arguments.command == "check":
|
|
75
|
-
return _check(
|
|
103
|
+
return _check(
|
|
104
|
+
Path(arguments.project),
|
|
105
|
+
json_output=arguments.format == "json",
|
|
106
|
+
lint=arguments.lint,
|
|
107
|
+
dialect=arguments.dialect,
|
|
108
|
+
)
|
|
109
|
+
if arguments.command == "render":
|
|
110
|
+
return _render(
|
|
111
|
+
Path(arguments.project), Path(arguments.out), dialect=arguments.dialect
|
|
112
|
+
)
|
|
76
113
|
if arguments.command == "export":
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
114
|
+
if arguments.tool == "sql-formatter":
|
|
115
|
+
return _export_sql_formatter(
|
|
116
|
+
Path(arguments.project),
|
|
117
|
+
dialect=arguments.dialect,
|
|
118
|
+
check=arguments.check,
|
|
119
|
+
)
|
|
120
|
+
if arguments.tool == "pycharm":
|
|
121
|
+
return _export_pycharm(
|
|
122
|
+
Path(arguments.project),
|
|
123
|
+
dialect=arguments.dialect,
|
|
124
|
+
check=arguments.check,
|
|
125
|
+
)
|
|
126
|
+
return _export_linter(
|
|
127
|
+
arguments.tool,
|
|
128
|
+
Path(arguments.project),
|
|
129
|
+
dialect=arguments.dialect,
|
|
130
|
+
check=arguments.check,
|
|
80
131
|
)
|
|
81
132
|
if arguments.command == "macros":
|
|
82
133
|
return _macros(
|
|
@@ -133,11 +184,14 @@ def _load(directory: Path, *, json_output: bool = False) -> Project | None:
|
|
|
133
184
|
return None
|
|
134
185
|
|
|
135
186
|
|
|
136
|
-
def _check(
|
|
187
|
+
def _check(
|
|
188
|
+
directory: Path, *, json_output: bool, lint: str | None, dialect: str | None
|
|
189
|
+
) -> int:
|
|
137
190
|
"""Print what is wrong with the project's templates, and fail if anything is.
|
|
138
191
|
|
|
139
|
-
|
|
140
|
-
|
|
192
|
+
With a linter, every template is rendered too, and what it cannot parse is
|
|
193
|
+
a problem. The exit is 1 for problems in the templates, and 2 for a project
|
|
194
|
+
that cannot be read, holds no template to check, or cannot be linted.
|
|
141
195
|
"""
|
|
142
196
|
project = _load(directory, json_output=json_output)
|
|
143
197
|
if project is None:
|
|
@@ -148,6 +202,12 @@ def _check(directory: Path, *, json_output: bool) -> int:
|
|
|
148
202
|
_say(json.dumps({"error": message}) if json_output else message)
|
|
149
203
|
return 2
|
|
150
204
|
problems = list(project.problems())
|
|
205
|
+
if lint is not None:
|
|
206
|
+
try:
|
|
207
|
+
problems.extend(_lint(project, lint, dialect))
|
|
208
|
+
except _CannotRenderError as error:
|
|
209
|
+
_say(json.dumps({"error": str(error)}) if json_output else str(error))
|
|
210
|
+
return 2
|
|
151
211
|
if json_output:
|
|
152
212
|
_say(json.dumps([_as_json(problem) for problem in problems], indent=2))
|
|
153
213
|
else:
|
|
@@ -165,21 +225,132 @@ def _check(directory: Path, *, json_output: bool) -> int:
|
|
|
165
225
|
return 1 if problems else 0
|
|
166
226
|
|
|
167
227
|
|
|
168
|
-
|
|
169
|
-
|
|
228
|
+
_WRITTEN = {"given": "every parameter given", "not_given": "no parameter given"}
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class _CannotRenderError(Exception):
|
|
232
|
+
"""Why the templates cannot be rendered, or the rendering linted."""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _lint(project: Project, name: str, given: str | None) -> list[Problem]:
|
|
236
|
+
"""Return where the linter cannot parse what the templates render.
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
_CannotRenderError: if the linter cannot read the templates as the
|
|
240
|
+
project is set up.
|
|
241
|
+
|
|
242
|
+
"""
|
|
243
|
+
linter = LINTERS[name]
|
|
244
|
+
if shutil.which(name) is None:
|
|
245
|
+
problem = f"`{name}` is not installed, so the rendered SQL cannot be linted"
|
|
246
|
+
raise _CannotRenderError(problem)
|
|
247
|
+
pyproject = project.root / "pyproject.toml"
|
|
248
|
+
text = pyproject.read_text(encoding="utf-8") if pyproject.exists() else ""
|
|
249
|
+
if stale(linter, project, text):
|
|
250
|
+
problem = (
|
|
251
|
+
f"the `{name}` settings do not read the templates: run "
|
|
252
|
+
f"`sqlakit export {name}` first"
|
|
253
|
+
)
|
|
254
|
+
raise _CannotRenderError(problem)
|
|
255
|
+
configured = dialect_of(linter, text)
|
|
256
|
+
chosen = _dialect_to_render(project, given, configured)
|
|
257
|
+
if configured is not None and configured != chosen:
|
|
258
|
+
problem = (
|
|
259
|
+
f"the templates render on {chosen}, and `{name}` reads {configured}: "
|
|
260
|
+
f"pass --dialect {configured}, or set the dialect in `[tool.{name}.core]`"
|
|
261
|
+
)
|
|
262
|
+
raise _CannotRenderError(problem)
|
|
263
|
+
try:
|
|
264
|
+
import_macros(project)
|
|
265
|
+
except Exception as error:
|
|
266
|
+
problem = (
|
|
267
|
+
f"the project's macros cannot be imported: {type(error).__name__}: {error}"
|
|
268
|
+
)
|
|
269
|
+
raise _CannotRenderError(problem) from error
|
|
270
|
+
return unparsed(project, linter, chosen)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _dialect_to_render(
|
|
274
|
+
project: Project, given: str | None, configured: str | None = None
|
|
275
|
+
) -> str:
|
|
276
|
+
"""Return the dialect to render the templates on.
|
|
277
|
+
|
|
278
|
+
`--dialect` wins, then the one the project's database URL names, then the
|
|
279
|
+
one a linter's settings read in. A macro writes by `ctx.dialect`, so a
|
|
280
|
+
guess would render SQL the database never gets.
|
|
281
|
+
|
|
282
|
+
Raises:
|
|
283
|
+
_CannotRenderError: if nothing names one.
|
|
284
|
+
|
|
285
|
+
"""
|
|
286
|
+
chosen = given or project.dialect or configured
|
|
287
|
+
if chosen is None:
|
|
288
|
+
problem = (
|
|
289
|
+
"the code names no database URL to take the dialect from: pass --dialect"
|
|
290
|
+
)
|
|
291
|
+
raise _CannotRenderError(problem)
|
|
292
|
+
return chosen.lower()
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _render(directory: Path, out: Path, *, dialect: str | None) -> int:
|
|
296
|
+
"""Write each template twice, rendered with its parameters given and not.
|
|
297
|
+
|
|
298
|
+
The project's Python macros are imported and run, so what they write is
|
|
299
|
+
in the SQL. Exit 1 when a template cannot be rendered.
|
|
300
|
+
"""
|
|
301
|
+
project = _load(directory)
|
|
302
|
+
if project is None:
|
|
303
|
+
return 2
|
|
304
|
+
try:
|
|
305
|
+
import_macros(project)
|
|
306
|
+
except Exception as error: # noqa: BLE001 - the project's code raises anything
|
|
307
|
+
_say(
|
|
308
|
+
f"the project's macros cannot be imported: {type(error).__name__}: {error}"
|
|
309
|
+
)
|
|
310
|
+
return 2
|
|
311
|
+
try:
|
|
312
|
+
chosen = _dialect_to_render(project, dialect)
|
|
313
|
+
except _CannotRenderError as error:
|
|
314
|
+
_say(str(error))
|
|
315
|
+
return 2
|
|
316
|
+
problems = 0
|
|
317
|
+
count = 0
|
|
318
|
+
for one in rendered(project, chosen):
|
|
319
|
+
if one.sql is None:
|
|
320
|
+
problems += 1
|
|
321
|
+
_say(f"{one.name} ({one.variant}): {one.problem}")
|
|
322
|
+
continue
|
|
323
|
+
target = out / f"{one.name.removesuffix('.sql')}.{one.variant}.sql"
|
|
324
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
325
|
+
header = [f"-- {one.name} on {chosen}, {_WRITTEN[one.variant]}"]
|
|
326
|
+
if one.kept:
|
|
327
|
+
header.append(
|
|
328
|
+
f"-- {', '.join(one.kept)} could not be made, and stays a call"
|
|
329
|
+
)
|
|
330
|
+
target.write_text("\n".join(header) + "\n" + one.sql, encoding="utf-8")
|
|
331
|
+
count += 1
|
|
332
|
+
_say(f"wrote {count} files to {_relative(out)}")
|
|
333
|
+
return 1 if problems else 0
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _export_linter(
|
|
337
|
+
name: str, directory: Path, *, dialect: str | None, check: bool
|
|
338
|
+
) -> int:
|
|
339
|
+
"""Write the settings of `sqruff` or `sqlfluff` that read the templates."""
|
|
340
|
+
linter = LINTERS[name]
|
|
170
341
|
project = _load(directory)
|
|
171
342
|
if project is None:
|
|
172
343
|
return 2
|
|
173
344
|
pyproject = project.root / "pyproject.toml"
|
|
174
345
|
text = pyproject.read_text(encoding="utf-8") if pyproject.exists() else ""
|
|
175
346
|
if check:
|
|
176
|
-
tables = stale(project, text)
|
|
347
|
+
tables = stale(linter, project, text)
|
|
177
348
|
if tables:
|
|
178
|
-
_say(f"{', '.join(tables)} out of date: run `sqlakit export
|
|
349
|
+
_say(f"{', '.join(tables)} out of date: run `sqlakit export {name}`")
|
|
179
350
|
return 1
|
|
180
351
|
return 0
|
|
181
352
|
try:
|
|
182
|
-
written = settings(project, text, dialect)
|
|
353
|
+
written = settings(linter, project, text, dialect)
|
|
183
354
|
except ProjectConfigError as error:
|
|
184
355
|
_say(str(error))
|
|
185
356
|
return 2
|
|
@@ -188,6 +359,28 @@ def _export_sqruff(directory: Path, *, dialect: str | None, check: bool) -> int:
|
|
|
188
359
|
return 0
|
|
189
360
|
|
|
190
361
|
|
|
362
|
+
def _export_sql_formatter(directory: Path, *, dialect: str | None, check: bool) -> int:
|
|
363
|
+
"""Write the `.sql-formatter.json` that reads `:name` as a parameter."""
|
|
364
|
+
project = _load(directory)
|
|
365
|
+
if project is None:
|
|
366
|
+
return 2
|
|
367
|
+
path = project.root / _sql_formatter.FILE
|
|
368
|
+
written = path.read_text(encoding="utf-8") if path.exists() else None
|
|
369
|
+
if check:
|
|
370
|
+
if _sql_formatter.stale(written):
|
|
371
|
+
_say(f"{_relative(path)} out of date: run `sqlakit export sql-formatter`")
|
|
372
|
+
return 1
|
|
373
|
+
return 0
|
|
374
|
+
try:
|
|
375
|
+
text = _sql_formatter.config(project, written, dialect)
|
|
376
|
+
except ProjectConfigError as error:
|
|
377
|
+
_say(str(error))
|
|
378
|
+
return 2
|
|
379
|
+
path.write_text(text, encoding="utf-8")
|
|
380
|
+
_say(f"wrote {_relative(path)}")
|
|
381
|
+
return 0
|
|
382
|
+
|
|
383
|
+
|
|
191
384
|
def _export_pycharm(directory: Path, *, dialect: str | None, check: bool) -> int:
|
|
192
385
|
"""Write what PyCharm needs into `.idea/`: the macros as DDL, and the dialect."""
|
|
193
386
|
project = _load(directory)
|