vericto 0.2.0__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.
vericto/__init__.py ADDED
@@ -0,0 +1,55 @@
1
+ """Vericto: check every SQL statement with Vericto before it runs.
2
+
3
+ The core is driver-agnostic:
4
+
5
+ * :class:`Vericto` (the guard) applies a verdict: raise on block, run the masked
6
+ rewrite, warn on flag, and the fail mode when Vericto cannot answer.
7
+ * :class:`Evaluator` is how a verdict is obtained; :class:`RuntimeApiEvaluator`
8
+ asks the Vericto Runtime API.
9
+
10
+ Driver wrappers live in submodules so that importing ``vericto`` never imports a
11
+ database driver: :mod:`vericto.snowflake`, :mod:`vericto.sqlite`,
12
+ :mod:`vericto.sqlalchemy` and the generic :mod:`vericto.dbapi`.
13
+ """
14
+
15
+ from ._version import __version__
16
+ from .dbapi import wrap
17
+ from .dialects import KNOWN_DIALECTS, normalize_dialect
18
+ from .errors import (
19
+ VerictoBlocked,
20
+ VerictoConfigError,
21
+ VerictoError,
22
+ VerictoFailOpenWarning,
23
+ VerictoFlaggedWarning,
24
+ VerictoQuotaExceeded,
25
+ VerictoUnavailable,
26
+ VerictoUnsupportedDialect,
27
+ )
28
+ from .evaluator import Evaluator
29
+ from .guard import Vericto
30
+ from .placeholders import PlaceholderMappingError
31
+ from .runtime_api import DEFAULT_API_URL, RuntimeApiEvaluator
32
+ from .verdict import Decision, Finding, Verdict
33
+
34
+ __all__ = [
35
+ "DEFAULT_API_URL",
36
+ "KNOWN_DIALECTS",
37
+ "Decision",
38
+ "Evaluator",
39
+ "Finding",
40
+ "PlaceholderMappingError",
41
+ "RuntimeApiEvaluator",
42
+ "Verdict",
43
+ "Vericto",
44
+ "VerictoBlocked",
45
+ "VerictoConfigError",
46
+ "VerictoError",
47
+ "VerictoFailOpenWarning",
48
+ "VerictoFlaggedWarning",
49
+ "VerictoQuotaExceeded",
50
+ "VerictoUnavailable",
51
+ "VerictoUnsupportedDialect",
52
+ "__version__",
53
+ "normalize_dialect",
54
+ "wrap",
55
+ ]
vericto/__main__.py ADDED
@@ -0,0 +1,18 @@
1
+ """``python -m vericto dbt check ...``: the same command as ``vericto-dbt``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+
7
+
8
+ def main(argv: list[str]) -> int:
9
+ if argv and argv[0] == "dbt":
10
+ from .dbt import main as dbt_main
11
+
12
+ return dbt_main(argv[1:])
13
+ sys.stderr.write("usage: python -m vericto dbt check [options] (see: python -m vericto dbt --help)\n")
14
+ return 2
15
+
16
+
17
+ if __name__ == "__main__":
18
+ sys.exit(main(sys.argv[1:]))
vericto/_agent.py ADDED
@@ -0,0 +1,153 @@
1
+ """Shared pieces of the LLM-framework integrations (LangChain, LlamaIndex).
2
+
3
+ * :func:`block_message`: the text an agent receives instead of rows when Vericto
4
+ blocks its query, written so the model can correct itself.
5
+ * :func:`describe_verdict`: a one-paragraph verdict for check tools.
6
+ * :class:`EngineGate`: checks the SQL an integration sends through a SQLAlchemy
7
+ engine, and only that SQL (not schema reflection, not the framework's own
8
+ ``SET search_path``), so a query costs one evaluation.
9
+
10
+ Nothing here imports LangChain or LlamaIndex.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import contextvars
16
+ from contextlib import contextmanager
17
+ from typing import Any, Collection, Iterator, Optional
18
+
19
+ from .errors import VerictoBlocked, VerictoConfigError
20
+ from .guard import Vericto
21
+ from .verdict import Decision, Verdict
22
+
23
+ ON_BLOCK = ("return", "raise")
24
+
25
+
26
+ def check_on_block(value: str) -> str:
27
+ mode = str(value).strip().lower()
28
+ if mode not in ON_BLOCK:
29
+ raise VerictoConfigError(f"on_block must be 'return' or 'raise', got {value!r}.")
30
+ return mode
31
+
32
+
33
+ def _rule_label(verdict: Verdict) -> str:
34
+ rule = verdict.rule_code or "a Vericto rule"
35
+ return f"{rule}, severity {verdict.severity}" if verdict.severity else rule
36
+
37
+
38
+ def _denied(verdict: Verdict) -> Optional[str]:
39
+ if not verdict.access_denied:
40
+ return None
41
+ parts = []
42
+ for item in verdict.access_denied:
43
+ if isinstance(item, dict):
44
+ name = ".".join(str(item[k]) for k in ("schema", "table", "column") if item.get(k))
45
+ parts.append(name or str(item))
46
+ else:
47
+ parts.append(str(item))
48
+ return ", ".join(parts)
49
+
50
+
51
+ def block_message(blocked: VerictoBlocked) -> str:
52
+ """What the agent reads when its query is blocked. Never contains row data."""
53
+ verdict = blocked.verdict
54
+ lines = [f"Error: Vericto blocked this query ({_rule_label(verdict)}). It was NOT executed."]
55
+ if verdict.message:
56
+ lines.append(f"Reason: {verdict.message}")
57
+ others = [f for f in verdict.findings if f.rule_code != verdict.rule_code]
58
+ if others:
59
+ lines.append("Other findings: " + ", ".join(f.rule_code for f in others) + ".")
60
+ denied = _denied(verdict)
61
+ if denied:
62
+ lines.append(f"Not allowed for this agent: {denied}.")
63
+ if verdict.suggested_fix:
64
+ lines.append(f"Suggested fix: {verdict.suggested_fix}")
65
+ lines.append("Rewrite the query following the suggested fix and try again.")
66
+ else:
67
+ lines.append("Rewrite the query so that it complies with this rule, or tell the user it cannot be run.")
68
+ if verdict.event_id:
69
+ lines.append(f"(Vericto event {verdict.event_id})")
70
+ return "\n".join(lines)
71
+
72
+
73
+ def describe_verdict(verdict: Verdict) -> str:
74
+ """A readable verdict for a check-only tool."""
75
+ if verdict.blocked:
76
+ return block_message(VerictoBlocked(verdict, verdict.sql))
77
+ if verdict.decision is Decision.UNEVALUATED:
78
+ return f"UNEVALUATED: Vericto could not evaluate the query ({verdict.message or 'no verdict'})."
79
+ if verdict.decision is Decision.ALLOWED and not verdict.masked:
80
+ text = "ALLOWED: Vericto found no problem with this query."
81
+ elif verdict.decision is Decision.PARSE_ERROR:
82
+ text = "PARSE_ERROR: Vericto could not parse the query; the workspace policy lets it run."
83
+ if verdict.parse_error:
84
+ text += f" Parser: {verdict.parse_error}"
85
+ else:
86
+ text = f"{verdict.decision.value}: allowed to run ({_rule_label(verdict)})."
87
+ if verdict.message:
88
+ text += f" {verdict.message}"
89
+ if verdict.masked:
90
+ text += f" Sensitive columns are masked: the query that will run is: {verdict.rewritten_query}"
91
+ return text
92
+
93
+
94
+ class EngineGate:
95
+ """A ``before_cursor_execute`` listener that is active only inside :meth:`active`.
96
+
97
+ The integrations wrap their query entry points in ``with gate.active():``; the
98
+ statements SQLAlchemy sends to the driver meanwhile are enforced (blocked ones
99
+ raise :class:`~vericto.VerictoBlocked`, masked ones run as the rewrite).
100
+ Statements in ``internal`` (fixed SQL the framework issues itself, such as
101
+ ``SET search_path``) are let through unevaluated.
102
+
103
+ When ``passthrough`` is true the engine is already checked by
104
+ :func:`vericto.sqlalchemy.instrument`: no listener is added, so a statement is
105
+ evaluated once, and blocks still raise from that listener.
106
+ """
107
+
108
+ def __init__(
109
+ self,
110
+ engine: Any,
111
+ guard: Vericto,
112
+ dialect: str,
113
+ *,
114
+ internal: Collection[str] = (),
115
+ passthrough: bool = False,
116
+ ) -> None:
117
+ from sqlalchemy import event
118
+
119
+ self.guard = guard
120
+ self.dialect = dialect
121
+ self.internal = frozenset(internal)
122
+ self._active: contextvars.ContextVar[bool] = contextvars.ContextVar(f"vericto_gate_{id(self)}", default=False)
123
+ self._target = getattr(engine, "sync_engine", engine)
124
+ self._paramstyle: Optional[str] = getattr(self._target.dialect, "paramstyle", None)
125
+ self._listening = False
126
+ if not passthrough:
127
+ event.listen(self._target, "before_cursor_execute", self._before_cursor_execute, retval=True)
128
+ self._listening = True
129
+
130
+ def _before_cursor_execute(
131
+ self, conn: Any, cursor: Any, statement: str, parameters: Any, context: Any, executemany: bool
132
+ ) -> tuple[str, Any]:
133
+ if not self._active.get() or statement in self.internal:
134
+ return statement, parameters
135
+ has_params = executemany or bool(parameters)
136
+ final = self.guard.prepare(statement, self.dialect, paramstyle=self._paramstyle if has_params else None)
137
+ return final, parameters
138
+
139
+ @contextmanager
140
+ def active(self) -> Iterator[None]:
141
+ token = self._active.set(True)
142
+ try:
143
+ yield
144
+ finally:
145
+ self._active.reset(token)
146
+
147
+ def remove(self) -> None:
148
+ """Detach the listener (idempotent)."""
149
+ from sqlalchemy import event
150
+
151
+ if self._listening and event.contains(self._target, "before_cursor_execute", self._before_cursor_execute):
152
+ event.remove(self._target, "before_cursor_execute", self._before_cursor_execute)
153
+ self._listening = False
vericto/_version.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.2.0"
@@ -0,0 +1,221 @@
1
+ """Vericto for Apache Airflow (2.9+ and 3.x), on the common-sql provider.
2
+
3
+ Two ways in, sharing the same check:
4
+
5
+ * :class:`VerictoSQLExecuteQueryOperator`: a drop-in ``SQLExecuteQueryOperator``
6
+ that checks every statement before any of them runs::
7
+
8
+ from vericto.airflow import VerictoSQLExecuteQueryOperator
9
+
10
+ VerictoSQLExecuteQueryOperator(task_id="cleanup", conn_id="warehouse", sql="sql/cleanup.sql")
11
+
12
+ * :func:`vericto.airflow.policy.guard_sql_tasks`: a cluster policy for
13
+ ``airflow_local_settings.task_policy`` that guards existing SQL operators
14
+ without DAG changes.
15
+
16
+ The SQL checked is the **rendered** SQL (templates are rendered before the check
17
+ runs). A script is split with the hook's own ``split_sql_string`` and every
18
+ statement is evaluated before the first one runs:
19
+
20
+ * ``BLOCKED``: the task fails with :class:`VerictoTaskBlocked` (an
21
+ ``AirflowFailException``, so it is not retried: the same SQL would be blocked
22
+ again) and nothing runs.
23
+ * masked columns: the masked rewrite runs instead.
24
+ * ``FLAGGED`` / ``MONITORED``: runs; logged on the ``vericto`` logger and passed to
25
+ the guard's ``on_verdict``.
26
+ * Vericto unavailable: ``fail_mode="closed"`` (default) fails the task with
27
+ :class:`~vericto.VerictoUnavailable`, which is retried like any other error.
28
+
29
+ The dialect comes from the connection type (``postgres``, ``redshift`` (as
30
+ postgres), ``mysql``, ``mssql``, ``oracle``, ``snowflake``, ``sqlite``), or from
31
+ ``vericto_dialect`` in the connection's extra, or from the operator argument.
32
+
33
+ Requires the ``airflow`` extra: ``pip install "vericto[airflow]"``. The guard is
34
+ built from the ``VERICTO_*`` environment variables of the worker the first time a
35
+ task needs it (DAG parsing never calls Vericto).
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import logging
41
+ import threading
42
+ from typing import Any, List, Mapping, Optional
43
+
44
+ from airflow.providers.common.sql.operators.sql import SQLExecuteQueryOperator
45
+
46
+ from ..dialects import normalize_dialect
47
+ from ..errors import VerictoBlocked, VerictoConfigError
48
+ from ..guard import Vericto
49
+ from ..verdict import Verdict
50
+
51
+ try: # Airflow 3
52
+ from airflow.sdk.exceptions import AirflowFailException
53
+ except ImportError: # Airflow 2
54
+ from airflow.exceptions import AirflowFailException # type: ignore[no-redef,attr-defined,unused-ignore]
55
+
56
+ logger = logging.getLogger("vericto")
57
+
58
+ #: Airflow connection type -> Vericto dialect.
59
+ CONN_TYPE_DIALECTS: Mapping[str, str] = {
60
+ "postgres": "postgres",
61
+ "postgresql": "postgres",
62
+ "redshift": "postgres",
63
+ "mysql": "mysql",
64
+ "mariadb": "mysql",
65
+ "mssql": "mssql",
66
+ "oracle": "oracle",
67
+ "snowflake": "snowflake",
68
+ "sqlite": "sqlite",
69
+ }
70
+ ON_UNSUPPORTED = ("fail", "skip")
71
+ EXTRA_KEY = "vericto_dialect"
72
+
73
+
74
+ class VerictoTaskBlocked(VerictoBlocked, AirflowFailException):
75
+ """Vericto blocked a statement of the task; nothing ran. Not retried."""
76
+
77
+
78
+ _shared: Optional[Vericto] = None
79
+ _shared_lock = threading.Lock()
80
+
81
+
82
+ def shared_guard() -> Vericto:
83
+ """The process-wide guard, built from ``VERICTO_*`` on first use."""
84
+ global _shared
85
+ with _shared_lock:
86
+ if _shared is None:
87
+ _shared = Vericto()
88
+ return _shared
89
+
90
+
91
+ def _check_on_unsupported(value: str) -> str:
92
+ if value not in ON_UNSUPPORTED:
93
+ raise VerictoConfigError(f"on_unsupported must be 'fail' or 'skip', got {value!r}.")
94
+ return value
95
+
96
+
97
+ def dialect_for_hook(hook: Any, override: Optional[str] = None) -> Optional[str]:
98
+ """The Vericto dialect for a DB hook, or ``None`` when its connection type is not mapped."""
99
+ if override:
100
+ return normalize_dialect(override)
101
+ conn = None
102
+ try:
103
+ conn = hook.connection
104
+ except Exception: # no connection property on this hook, or it cannot be read
105
+ logger.debug("vericto: could not read the hook's connection", exc_info=True)
106
+ if conn is not None:
107
+ extra = getattr(conn, "extra_dejson", None) or {}
108
+ if isinstance(extra, Mapping) and extra.get(EXTRA_KEY):
109
+ return normalize_dialect(str(extra[EXTRA_KEY]))
110
+ conn_type = (getattr(conn, "conn_type", None) or getattr(hook, "conn_type", None) or "").lower()
111
+ return CONN_TYPE_DIALECTS.get(conn_type)
112
+
113
+
114
+ def _paramstyle(hook: Any, sql: str, parameters: Any) -> Optional[str]:
115
+ if not parameters:
116
+ return None
117
+ if isinstance(parameters, Mapping) and "%(" in sql:
118
+ return "pyformat"
119
+ placeholder = getattr(hook, "placeholder", "%s")
120
+ return "format" if placeholder == "%s" else None
121
+
122
+
123
+ def _statements(hook: Any, sql: Any) -> List[str]:
124
+ if isinstance(sql, str):
125
+ split = getattr(hook, "split_sql_string", None)
126
+ parts = split(sql) if callable(split) else [sql]
127
+ return [p for p in parts if p and p.strip()]
128
+ if isinstance(sql, (list, tuple)):
129
+ return [s for s in sql if isinstance(s, str) and s.strip()]
130
+ raise TypeError(f"vericto: expected the task's sql as str or list of str, got {type(sql).__name__}.")
131
+
132
+
133
+ def check_task_sql(
134
+ task: Any,
135
+ *,
136
+ guard: Optional[Vericto] = None,
137
+ dialect: Optional[str] = None,
138
+ on_unsupported: str = "fail",
139
+ ) -> List[Verdict]:
140
+ """Check a SQL task's rendered ``sql`` and apply masks to it. Call before it runs.
141
+
142
+ Every statement is evaluated before any runs. Raises :class:`VerictoTaskBlocked`
143
+ on a block, :class:`~vericto.VerictoUnavailable` (fail-closed) or
144
+ :class:`~vericto.VerictoConfigError`. When a statement is masked, ``task.sql``
145
+ is replaced with the statements to run.
146
+ """
147
+ hook = task.get_db_hook()
148
+ name = dialect_for_hook(hook, dialect)
149
+ if name is None:
150
+ conn_type = getattr(getattr(hook, "connection", None), "conn_type", None) or getattr(hook, "conn_type", "?")
151
+ message = (
152
+ f"Vericto has no dialect for the connection type '{conn_type}' of task {task.task_id}. "
153
+ f"Set '{EXTRA_KEY}' in the connection extra or vericto_dialect= on the operator "
154
+ f"(one of: postgres, mysql, oracle, mssql, snowflake)."
155
+ )
156
+ if on_unsupported == "skip":
157
+ logger.warning("vericto: %s The task runs unchecked (on_unsupported='skip').", message)
158
+ return []
159
+ raise VerictoConfigError(message + " Nothing was executed.")
160
+
161
+ g = guard if guard is not None else shared_guard()
162
+ g.require_dialect(name)
163
+ sql = task.sql
164
+ statements = _statements(hook, sql)
165
+ parameters = getattr(task, "parameters", None)
166
+ verdicts: List[Verdict] = []
167
+ for statement in statements:
168
+ try:
169
+ verdicts.append(g.enforce(statement, name, paramstyle=_paramstyle(hook, statement, parameters)))
170
+ except VerictoBlocked as exc:
171
+ raise VerictoTaskBlocked(exc.verdict, exc.sql) from None
172
+
173
+ if any(v.masked for v in verdicts):
174
+ rewritten = [v.sql_to_execute for v in verdicts]
175
+ task.sql = rewritten[0] if isinstance(sql, str) and len(rewritten) == 1 else rewritten
176
+ logger.info("vericto: task %s runs the masked rewrite of %d statement(s)", task.task_id, len(rewritten))
177
+ return verdicts
178
+
179
+
180
+ class VerictoSQLExecuteQueryOperator(SQLExecuteQueryOperator):
181
+ """``SQLExecuteQueryOperator`` that checks the rendered SQL with Vericto first.
182
+
183
+ Args:
184
+ vericto_guard: the :class:`~vericto.Vericto` to use; the process-wide guard
185
+ from ``VERICTO_*`` when omitted.
186
+ vericto_dialect: override the dialect read from the connection.
187
+ vericto_on_unsupported: ``"fail"`` (default): a connection type Vericto has
188
+ no dialect for fails the task before anything runs. ``"skip"``: run it
189
+ unchecked, with a warning.
190
+
191
+ Every other argument is ``SQLExecuteQueryOperator``'s.
192
+ """
193
+
194
+ def __init__(
195
+ self,
196
+ *,
197
+ vericto_guard: Optional[Vericto] = None,
198
+ vericto_dialect: Optional[str] = None,
199
+ vericto_on_unsupported: str = "fail",
200
+ **kwargs: Any,
201
+ ) -> None:
202
+ super().__init__(**kwargs)
203
+ self.vericto_guard = vericto_guard
204
+ self.vericto_dialect = vericto_dialect
205
+ self.vericto_on_unsupported = _check_on_unsupported(vericto_on_unsupported)
206
+
207
+ def execute(self, context: Any) -> Any:
208
+ check_task_sql(
209
+ self, guard=self.vericto_guard, dialect=self.vericto_dialect, on_unsupported=self.vericto_on_unsupported
210
+ )
211
+ return super().execute(context) # type: ignore[no-untyped-call,unused-ignore] # untyped upstream
212
+
213
+
214
+ __all__ = [
215
+ "CONN_TYPE_DIALECTS",
216
+ "VerictoSQLExecuteQueryOperator",
217
+ "VerictoTaskBlocked",
218
+ "check_task_sql",
219
+ "dialect_for_hook",
220
+ "shared_guard",
221
+ ]
@@ -0,0 +1,85 @@
1
+ """A cluster policy that guards existing SQL tasks with Vericto, without DAG changes.
2
+
3
+ In ``airflow_local_settings.py`` (on ``PYTHONPATH`` or in ``$AIRFLOW_HOME/config``)
4
+ of the DAG processor **and the workers**::
5
+
6
+ from vericto.airflow.policy import guard_sql_tasks
7
+
8
+ def task_policy(task):
9
+ guard_sql_tasks(task)
10
+
11
+ Every common-sql operator (``SQLExecuteQueryOperator`` and its subclasses, the
12
+ ``SQL*Check`` operators, provider operators built on them) then checks its
13
+ rendered SQL with Vericto right before it runs, exactly as
14
+ :class:`~vericto.airflow.VerictoSQLExecuteQueryOperator` does. The check is
15
+ installed as the task's ``pre_execute`` hook (an existing hook still runs, first),
16
+ so the operator class, its serialization and the UI are unchanged.
17
+
18
+ Airflow applies ``task_policy`` whenever a DAG file is parsed, including on the
19
+ worker that runs the task, so the guard is in place where the SQL executes.
20
+ Parsing never calls Vericto; the guard is built from the worker's ``VERICTO_*``
21
+ environment on the first checked task.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Any, Callable, Optional, Tuple, Type
27
+
28
+ from airflow.providers.common.sql.operators.sql import BaseSQLOperator
29
+
30
+ from ..guard import Vericto
31
+ from . import VerictoSQLExecuteQueryOperator, _check_on_unsupported, check_task_sql
32
+
33
+ MARKER = "_vericto_guarded"
34
+
35
+
36
+ def guard_sql_tasks(
37
+ task: Any,
38
+ *,
39
+ guard: Optional[Vericto] = None,
40
+ dialect: Optional[str] = None,
41
+ on_unsupported: str = "fail",
42
+ operators: Tuple[Type[Any], ...] = (BaseSQLOperator,),
43
+ when: Optional[Callable[[Any], bool]] = None,
44
+ ) -> bool:
45
+ """Guard ``task`` if it is a SQL operator. Returns whether it is now guarded.
46
+
47
+ Args:
48
+ task: the task ``task_policy`` receives.
49
+ guard: the :class:`~vericto.Vericto` to use; the process-wide one from
50
+ ``VERICTO_*`` when omitted.
51
+ dialect: force a dialect instead of reading it from the connection.
52
+ on_unsupported: ``"fail"`` (default) fails a task whose connection type has
53
+ no Vericto dialect, before anything runs; ``"skip"`` runs it unchecked
54
+ with a warning. Set ``vericto_dialect`` in a connection's extra to map
55
+ ODBC/JDBC connections.
56
+ operators: the operator classes to guard (default: every common-sql operator).
57
+ when: an optional predicate on the task (e.g. by ``conn_id`` or DAG tags).
58
+
59
+ Idempotent: a task is guarded once, and
60
+ :class:`~vericto.airflow.VerictoSQLExecuteQueryOperator` tasks are left alone
61
+ (they check themselves).
62
+ """
63
+ mode = _check_on_unsupported(on_unsupported)
64
+ if not isinstance(task, operators) or isinstance(task, VerictoSQLExecuteQueryOperator):
65
+ return False
66
+ if getattr(task, MARKER, False):
67
+ return True
68
+ if when is not None and not when(task):
69
+ return False
70
+
71
+ previous = getattr(task, "_pre_execute_hook", None)
72
+
73
+ def vericto_pre_execute(context: Any) -> None:
74
+ if previous is not None:
75
+ previous(context)
76
+ # The task being executed: the rendered copy Airflow made for this run.
77
+ running = context.get("task", task) if hasattr(context, "get") else task
78
+ check_task_sql(running, guard=guard, dialect=dialect, on_unsupported=mode)
79
+
80
+ task._pre_execute_hook = vericto_pre_execute
81
+ setattr(task, MARKER, True)
82
+ return True
83
+
84
+
85
+ __all__ = ["guard_sql_tasks"]