Define shared context variables for Jinja templates and Python models
The pyconfigs/context.py file allows you to define context variables that are shared across all your data models. These variables are computed after parameter selections are made and are available to both Jinja SQL templates and Python data models.
The main function in context.py runs every time an API request is made, after parameter selections are processed but before data models are executed. This makes it the ideal place to transform parameter selections into reusable context variables.
The context.py file must define a main function with the following signature:
pyconfigs/context.py
Copy
from typing import Anyfrom squirrels import arguments as argsdef main(ctx: dict[str, Any], sqrl: args.ContextArgs) -> None: """ Define context variables by adding entries to the "ctx" dictionary. These context variables can then be used in your data models. """ pass
A dictionary to populate with your context variables. The keys are variable names (strings) that you’ll reference in your data models, and the values can be of any type (strings, numbers, lists, dictionaries, functions, etc.).Variables added to this dictionary are available in:
Checks whether a parameter exists and is enabled (has available options).
Copy
def param_exists(self, param_name: str) -> bool
Returns True if the parameter exists and is enabled, False otherwise. A parameter becomes disabled when it has no available options (e.g., due to parent-child cascading or user attribute filtering).
Sets a placeholder value for use in SQL queries. This is useful for dynamic SQL generation where you want to use placeholders instead of direct string interpolation.
To access parameter selections in context.py, use sqrl.param_exists() to check if a parameter exists, then access it from sqrl.prms. Each parameter type has specific methods to get selected values:
pyconfigs/context.py
Copy
from typing import Anyfrom squirrels import arguments as args, parameters as pdef main(ctx: dict[str, Any], sqrl: args.ContextArgs) -> None: # Check if parameter exists before accessing if sqrl.param_exists("region"): region_param = sqrl.prms["region"] assert isinstance(region_param, p.SingleSelectParameter) # Get the selected option ID ctx["region_id"] = region_param.get_selected_id()
It’s generally better to transform parameter selections into context variables in context.py rather than accessing parameters directly in data models. This provides:
If your parameter options have custom fields, you can access them using get_selected():
pyconfigs/context.py
Copy
from typing import Anyfrom squirrels import arguments as args, parameters as pdef main(ctx: dict[str, Any], sqrl: args.ContextArgs) -> None: if sqrl.param_exists("group_by"): group_by_param = sqrl.prms["group_by"] assert isinstance(group_by_param, p.SingleSelectParameter) # Get custom fields from the selected option columns = group_by_param.get_selected("columns") aliases = group_by_param.get_selected("aliases", default_field="columns") # Create a mapping dictionary ctx["column_to_alias_mapping"] = { col: alias for col, alias in zip(columns, aliases) if not alias.startswith("_") }
Access user information to create user-specific context variables:
pyconfigs/context.py
Copy
from typing import Any, castfrom squirrels import arguments as args, parameters as pfrom pyconfigs.user import CustomUserFieldsdef main(ctx: dict[str, Any], sqrl: args.ContextArgs) -> None: custom_fields = cast(CustomUserFields, sqrl.user.custom_fields) # Create a masking function based on user role if custom_fields.role == "manager": ctx["mask_column"] = lambda x: x else: ctx["mask_column"] = lambda x: "***MASKED***" # Or for SQL templates, return SQL string if custom_fields.role == "manager": ctx["mask_column_sql"] = lambda x: x else: ctx["mask_column_sql"] = lambda x: "'***MASKED***'"
You can store functions in the context dictionary and use them in Python models:
pyconfigs/context.py
Copy
def main(ctx: dict[str, Any], sqrl: args.ContextArgs) -> None: # Store a function for masking sensitive data ctx["mask_column_function"] = lambda x: x if sqrl.user.custom_fields.role == "manager" else "***MASKED***"
models/federates/fed_model.py
Copy
def main(sqrl: args.ModelArgs) -> pl.LazyFrame: df = sqrl.ref("source_table") # Apply masking function if it exists mask_func = sqrl.ctx.get("mask_column_function") if mask_func and "description" in df.columns: df = df.with_columns( pl.col("description").map_elements(mask_func, return_dtype=pl.String) ) return df
Placeholders are a way to set values that can be referenced in SQL queries using parameterized queries (which helps prevent SQL injection). Use sqrl.set_placeholder() to set placeholder values:
pyconfigs/context.py
Copy
from typing import Anyfrom squirrels import arguments as args, parameters as pdef main(ctx: dict[str, Any], sqrl: args.ContextArgs) -> None: if sqrl.param_exists("region"): region_param = sqrl.prms["region"] assert isinstance(region_param, p.TextParameter) region = region_param.get_entered_text() # Set a placeholder for use in SQL queries sqrl.set_placeholder("region", region.apply_percent_wrap())
In SQL templates, you can then reference placeholders using :placeholder_name (for SQLAlchemy) or $placeholder_name (for DuckDB):
models/federates/fed_model.sql
Copy
-- For SQLAlchemy connectionsSELECT * FROM sales WHERE region LIKE :region-- For DuckDB connectionsSELECT * FROM sales WHERE region LIKE $region
When using placeholders, ensure your connection type supports them. SQLAlchemy connections support :param_name syntax, while DuckDB connections support $param_name syntax. ConnectorX and ADBC connections do not support placeholders.