Skip to content

Commit b55368e

Browse files
Merge branch 'main' into main
2 parents 5f83b92 + 1fb40ee commit b55368e

11 files changed

Lines changed: 442 additions & 172 deletions

File tree

docs/cloud/features/scheduler/scheduler.md

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,77 @@ Tobiko Cloud automatically manages Python dependencies of your Python macros and
167167

168168
SQLMesh automatically infers which Python libraries are used by statically analyzing the code of your models and macros.
169169

170-
For fine-grained control, dependencies can be specified, pinned, or excluded using the `sqlmesh-requirements.lock` file. See the [Python library dependencies](../../../guides/configuration.md#python-library-dependencies) section in the SQLMesh configuration guide for more information.
170+
For fine-grained control, dependencies can be specified, pinned, or excluded using the `sqlmesh-requirements.lock` file. See the [Python library dependencies](../../../guides/configuration.md#python-library-dependencies) section in the SQLMesh configuration guide for more information.
171+
172+
## Secret Manager
173+
174+
Tobiko Cloud provides a secrets manager where you can define environment variables for your project's Python models.
175+
176+
These variables are most commonly used to provide sensitive information to Python models, such as API keys or other credentials.
177+
178+
Secret values are encrypted at rest and only available in the environment of your running Python models.
179+
180+
!!! note "Cloud Scheduler Only"
181+
182+
Secrets from the secret manager do not load into hybrid executors. They are only used for cloud scheduler executors.
183+
184+
Secret names have two restrictions - they must:
185+
186+
- Start with a letter or an underscore
187+
- Only include letters, numbers, and underscores (no spaces or other symbols)
188+
189+
Secret values have no limits or restrictions. We recommend base64 encoding any secrets that contain binary data.
190+
191+
### Defining secrets
192+
193+
Define a secret on the Secrets page, accessible via the Settings section in Tobiko Cloud's left side navigation bar.
194+
195+
The Secrets page has a single panel you use to create a new secret, edit the value of an existing secret, or remove an existing secret. You cannot view the value of any existing secret.
196+
197+
In this example, only one secret has been defined: `MY_SECRET`. Update its value by entering a new value in the Secret field and clicking the `Update` button, or delete it by clicking the `Remove` button.
198+
199+
![secrets_panel](./scheduler/secrets.png)
200+
201+
202+
### Python Model Example
203+
204+
This Python model demonstrates how to read the `MY_SECRET` secret from an environment variable.
205+
206+
!!! danger "Protecting Secrets"
207+
208+
Only read environment variables from inside a Python model's `execute` function definition (not in the global scope).
209+
210+
If the variable is read in the global scope, SQLMesh will load the value from *your local system* when it renders the Python model instead of loading it at runtime on our executors.
211+
212+
This could expose sensitive information or embed an incorrect local value in the rendered model.
213+
214+
```python linenums="1"
215+
import os
216+
import pandas as pd
217+
import typing as t
218+
from datetime import datetime
219+
220+
from sqlmesh import ExecutionContext, model
221+
222+
# DO NOT read environment variables here.
223+
# Only inside the `execute` function definition!
224+
225+
@model(
226+
"my_model.name",
227+
columns={
228+
"column_name": "int",
229+
},
230+
)
231+
def execute(
232+
context: ExecutionContext,
233+
start: datetime,
234+
end: datetime,
235+
execution_time: datetime,
236+
**kwargs: t.Any,
237+
) -> pd.DataFrame:
238+
239+
# Read a secret from the MY_SECRET environment variable
240+
my_secret = os.environ["MY_SECRET"]
241+
242+
...
243+
```
57.2 KB
Loading

sqlmesh/dbt/loader.py

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
from sqlmesh.utils.errors import ConfigError
2929
from sqlmesh.utils.jinja import (
3030
JinjaMacroRegistry,
31-
extract_macro_references_and_variables,
3231
make_jinja_registry,
3332
)
3433

@@ -240,11 +239,11 @@ def _load_requirements(self) -> t.Tuple[t.Dict[str, str], t.Set[str]]:
240239
def _load_environment_statements(self, macros: MacroRegistry) -> t.List[EnvironmentStatements]:
241240
"""Loads dbt's on_run_start, on_run_end hooks into sqlmesh's before_all, after_all statements respectively."""
242241

243-
environment_statements: t.List[EnvironmentStatements] = []
242+
hooks_by_package_name: t.Dict[str, EnvironmentStatements] = {}
243+
project_names: t.Set[str] = set()
244244
dialect = self.config.dialect
245245
for project in self._load_projects():
246246
context = project.context
247-
hooks_by_package_name: t.Dict[str, EnvironmentStatements] = {}
248247
for package_name, package in project.packages.items():
249248
context.set_and_render_variables(package.variables, package_name)
250249
on_run_start: t.List[str] = [
@@ -256,18 +255,14 @@ def _load_environment_statements(self, macros: MacroRegistry) -> t.List[Environm
256255
for on_run_hook in sorted(package.on_run_end.values(), key=lambda h: h.index)
257256
]
258257

259-
if statements := on_run_start + on_run_end:
260-
jinja_references, used_variables = extract_macro_references_and_variables(
261-
*statements
262-
)
258+
if on_run_start or on_run_end:
259+
dependencies = Dependencies()
260+
for hook in [*package.on_run_start.values(), *package.on_run_end.values()]:
261+
dependencies = dependencies.union(hook.dependencies)
263262

264-
statements_context = context.context_for_dependencies(
265-
Dependencies(
266-
variables=used_variables,
267-
)
268-
)
263+
statements_context = context.context_for_dependencies(dependencies)
269264
jinja_registry = make_jinja_registry(
270-
statements_context.jinja_macros, package_name, jinja_references
265+
statements_context.jinja_macros, package_name, set(dependencies.macros)
271266
)
272267
jinja_registry.add_globals(statements_context.jinja_globals)
273268

@@ -283,15 +278,15 @@ def _load_environment_statements(self, macros: MacroRegistry) -> t.List[Environm
283278
python_env={},
284279
jinja_macros=jinja_registry,
285280
)
286-
# Project hooks should be executed first and then rest of the packages
287-
environment_statements = [
288-
statements
289-
for _, statements in sorted(
290-
hooks_by_package_name.items(),
291-
key=lambda item: 0 if item[0] == context.project_name else 1,
292-
)
293-
]
294-
return environment_statements
281+
project_names.add(package_name)
282+
283+
return [
284+
statements
285+
for _, statements in sorted(
286+
hooks_by_package_name.items(),
287+
key=lambda item: 0 if item[0] in project_names else 1,
288+
)
289+
]
295290

296291
def _compute_yaml_max_mtime_per_subfolder(self, root: Path) -> t.Dict[Path, float]:
297292
if not root.is_dir():

sqlmesh/dbt/manifest.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,13 +292,24 @@ def _load_on_run_start_end(self) -> None:
292292
sql = node.raw_code if DBT_VERSION >= (1, 3) else node.raw_sql # type: ignore
293293
node_name = node.name
294294
node_path = Path(node.original_file_path)
295+
296+
dependencies = Dependencies(
297+
macros=_macro_references(self._manifest, node),
298+
refs=_refs(node),
299+
sources=_sources(node),
300+
)
301+
dependencies = dependencies.union(self._extra_dependencies(sql, node.package_name))
302+
dependencies = dependencies.union(
303+
self._flatten_dependencies_from_macros(dependencies.macros, node.package_name)
304+
)
305+
295306
if "on-run-start" in node.tags:
296307
self._on_run_start_per_package[node.package_name][node_name] = HookConfig(
297-
sql=sql, index=node.index or 0, path=node_path
308+
sql=sql, index=node.index or 0, path=node_path, dependencies=dependencies
298309
)
299310
else:
300311
self._on_run_end_per_package[node.package_name][node_name] = HookConfig(
301-
sql=sql, index=node.index or 0, path=node_path
312+
sql=sql, index=node.index or 0, path=node_path, dependencies=dependencies
302313
)
303314

304315
@property

sqlmesh/dbt/package.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class HookConfig(PydanticModel):
3434
sql: str
3535
index: int
3636
path: Path
37+
dependencies: Dependencies
3738

3839

3940
class Package(PydanticModel):

0 commit comments

Comments
 (0)