squirrels 0.1.0__py3-none-any.whl → 0.6.0.post0__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.
- dateutils/__init__.py +6 -0
- dateutils/_enums.py +25 -0
- squirrels/dateutils.py → dateutils/_implementation.py +409 -380
- dateutils/types.py +6 -0
- squirrels/__init__.py +21 -18
- squirrels/_api_routes/__init__.py +5 -0
- squirrels/_api_routes/auth.py +337 -0
- squirrels/_api_routes/base.py +196 -0
- squirrels/_api_routes/dashboards.py +156 -0
- squirrels/_api_routes/data_management.py +148 -0
- squirrels/_api_routes/datasets.py +220 -0
- squirrels/_api_routes/project.py +289 -0
- squirrels/_api_server.py +552 -134
- squirrels/_arguments/__init__.py +0 -0
- squirrels/_arguments/init_time_args.py +83 -0
- squirrels/_arguments/run_time_args.py +111 -0
- squirrels/_auth.py +777 -0
- squirrels/_command_line.py +239 -107
- squirrels/_compile_prompts.py +147 -0
- squirrels/_connection_set.py +94 -0
- squirrels/_constants.py +141 -64
- squirrels/_dashboards.py +179 -0
- squirrels/_data_sources.py +570 -0
- squirrels/_dataset_types.py +91 -0
- squirrels/_env_vars.py +209 -0
- squirrels/_exceptions.py +29 -0
- squirrels/_http_error_responses.py +52 -0
- squirrels/_initializer.py +319 -110
- squirrels/_logging.py +121 -0
- squirrels/_manifest.py +357 -187
- squirrels/_mcp_server.py +578 -0
- squirrels/_model_builder.py +69 -0
- squirrels/_model_configs.py +74 -0
- squirrels/_model_queries.py +52 -0
- squirrels/_models.py +1201 -0
- squirrels/_package_data/base_project/.env +7 -0
- squirrels/_package_data/base_project/.env.example +44 -0
- squirrels/_package_data/base_project/connections.yml +16 -0
- squirrels/_package_data/base_project/dashboards/dashboard_example.py +40 -0
- squirrels/_package_data/base_project/dashboards/dashboard_example.yml +22 -0
- squirrels/_package_data/base_project/docker/.dockerignore +16 -0
- squirrels/_package_data/base_project/docker/Dockerfile +16 -0
- squirrels/_package_data/base_project/docker/compose.yml +7 -0
- squirrels/_package_data/base_project/duckdb_init.sql +10 -0
- squirrels/_package_data/base_project/gitignore +13 -0
- squirrels/_package_data/base_project/macros/macros_example.sql +17 -0
- squirrels/_package_data/base_project/models/builds/build_example.py +26 -0
- squirrels/_package_data/base_project/models/builds/build_example.sql +16 -0
- squirrels/_package_data/base_project/models/builds/build_example.yml +57 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +17 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +32 -0
- squirrels/_package_data/base_project/models/federates/federate_example.py +51 -0
- squirrels/_package_data/base_project/models/federates/federate_example.sql +21 -0
- squirrels/_package_data/base_project/models/federates/federate_example.yml +65 -0
- squirrels/_package_data/base_project/models/sources.yml +38 -0
- squirrels/_package_data/base_project/parameters.yml +142 -0
- squirrels/_package_data/base_project/pyconfigs/connections.py +19 -0
- squirrels/_package_data/base_project/pyconfigs/context.py +96 -0
- squirrels/_package_data/base_project/pyconfigs/parameters.py +141 -0
- squirrels/_package_data/base_project/pyconfigs/user.py +56 -0
- squirrels/_package_data/base_project/resources/expenses.db +0 -0
- squirrels/_package_data/base_project/resources/public/.gitkeep +0 -0
- squirrels/_package_data/base_project/resources/weather.db +0 -0
- squirrels/_package_data/base_project/seeds/seed_categories.csv +6 -0
- squirrels/_package_data/base_project/seeds/seed_categories.yml +15 -0
- squirrels/_package_data/base_project/seeds/seed_subcategories.csv +15 -0
- squirrels/_package_data/base_project/seeds/seed_subcategories.yml +21 -0
- squirrels/_package_data/base_project/squirrels.yml.j2 +61 -0
- squirrels/_package_data/base_project/tmp/.gitignore +2 -0
- squirrels/_package_data/templates/login_successful.html +53 -0
- squirrels/_package_data/templates/squirrels_studio.html +22 -0
- squirrels/_package_loader.py +29 -0
- squirrels/_parameter_configs.py +592 -0
- squirrels/_parameter_options.py +348 -0
- squirrels/_parameter_sets.py +207 -0
- squirrels/_parameters.py +1703 -0
- squirrels/_project.py +796 -0
- squirrels/_py_module.py +122 -0
- squirrels/_request_context.py +33 -0
- squirrels/_schemas/__init__.py +0 -0
- squirrels/_schemas/auth_models.py +83 -0
- squirrels/_schemas/query_param_models.py +70 -0
- squirrels/_schemas/request_models.py +26 -0
- squirrels/_schemas/response_models.py +286 -0
- squirrels/_seeds.py +97 -0
- squirrels/_sources.py +112 -0
- squirrels/_utils.py +540 -149
- squirrels/_version.py +1 -3
- squirrels/arguments.py +7 -0
- squirrels/auth.py +4 -0
- squirrels/connections.py +3 -0
- squirrels/dashboards.py +3 -0
- squirrels/data_sources.py +14 -282
- squirrels/parameter_options.py +13 -189
- squirrels/parameters.py +14 -801
- squirrels/types.py +18 -0
- squirrels-0.6.0.post0.dist-info/METADATA +148 -0
- squirrels-0.6.0.post0.dist-info/RECORD +101 -0
- {squirrels-0.1.0.dist-info → squirrels-0.6.0.post0.dist-info}/WHEEL +1 -2
- {squirrels-0.1.0.dist-info → squirrels-0.6.0.post0.dist-info}/entry_points.txt +1 -0
- squirrels-0.6.0.post0.dist-info/licenses/LICENSE +201 -0
- squirrels/_credentials_manager.py +0 -87
- squirrels/_module_loader.py +0 -37
- squirrels/_parameter_set.py +0 -151
- squirrels/_renderer.py +0 -286
- squirrels/_timed_imports.py +0 -37
- squirrels/connection_set.py +0 -126
- squirrels/package_data/base_project/.gitignore +0 -4
- squirrels/package_data/base_project/connections.py +0 -21
- squirrels/package_data/base_project/database/sample_database.db +0 -0
- squirrels/package_data/base_project/database/seattle_weather.db +0 -0
- squirrels/package_data/base_project/datasets/sample_dataset/context.py +0 -8
- squirrels/package_data/base_project/datasets/sample_dataset/database_view1.py +0 -23
- squirrels/package_data/base_project/datasets/sample_dataset/database_view1.sql.j2 +0 -7
- squirrels/package_data/base_project/datasets/sample_dataset/final_view.py +0 -10
- squirrels/package_data/base_project/datasets/sample_dataset/final_view.sql.j2 +0 -2
- squirrels/package_data/base_project/datasets/sample_dataset/parameters.py +0 -30
- squirrels/package_data/base_project/datasets/sample_dataset/selections.cfg +0 -6
- squirrels/package_data/base_project/squirrels.yaml +0 -26
- squirrels/package_data/static/favicon.ico +0 -0
- squirrels/package_data/static/script.js +0 -234
- squirrels/package_data/static/style.css +0 -110
- squirrels/package_data/templates/index.html +0 -32
- squirrels-0.1.0.dist-info/LICENSE +0 -22
- squirrels-0.1.0.dist-info/METADATA +0 -67
- squirrels-0.1.0.dist-info/RECORD +0 -40
- squirrels-0.1.0.dist-info/top_level.txt +0 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
parameters:
|
|
2
|
+
- type: SingleSelectParameter
|
|
3
|
+
factory: CreateWithOptions ## one of 'CreateSimple', 'CreateWithOptions', or 'CreateFromSource'
|
|
4
|
+
arguments: ## arguments to specify depend on values for 'type' and 'factory'
|
|
5
|
+
name: group_by
|
|
6
|
+
label: Group By
|
|
7
|
+
description: Dimension(s) to aggregate by ## optional, default is empty string
|
|
8
|
+
user_attribute: access_level ## optional, default is null
|
|
9
|
+
all_options:
|
|
10
|
+
- id: trans
|
|
11
|
+
label: Transaction
|
|
12
|
+
columns: ["id", "date", "category", "subcategory", "description"] ## custom field
|
|
13
|
+
aliases: ["_id", "date", "category", "subcategory", "description"] ## custom field (any alias starting with "_" will not be selected - see context.py for implementation)
|
|
14
|
+
is_default: false ## optional, shown is default - exists for SingleSelect or MultiSelect options only
|
|
15
|
+
user_groups: ["admin"] ## optional, default is empty list
|
|
16
|
+
parent_option_ids: [] ## optional, shown is default - exists for all parameter options
|
|
17
|
+
- id: day
|
|
18
|
+
label: Day
|
|
19
|
+
columns: [date]
|
|
20
|
+
aliases: [day]
|
|
21
|
+
user_groups: ["admin", "member"]
|
|
22
|
+
- id: month
|
|
23
|
+
label: Month
|
|
24
|
+
columns: [month]
|
|
25
|
+
user_groups: ["admin", "member", "guest"]
|
|
26
|
+
- id: cat
|
|
27
|
+
label: Category
|
|
28
|
+
columns: [category]
|
|
29
|
+
user_groups: ["admin", "member", "guest"]
|
|
30
|
+
- id: subcat
|
|
31
|
+
label: Subcategory
|
|
32
|
+
columns: [category, subcategory]
|
|
33
|
+
user_groups: ["admin", "member", "guest"]
|
|
34
|
+
parent_name: null ## optional, shown is default - exists for all parameter types
|
|
35
|
+
|
|
36
|
+
- type: DateParameter
|
|
37
|
+
factory: CreateFromSource
|
|
38
|
+
arguments:
|
|
39
|
+
name: start_date
|
|
40
|
+
label: Start Date
|
|
41
|
+
description: Start date to filter transactions by
|
|
42
|
+
data_source:
|
|
43
|
+
table_or_query: SELECT min(date) AS min_date, max(date) AS max_date FROM expenses
|
|
44
|
+
default_date_col: min_date
|
|
45
|
+
min_date_col: min_date
|
|
46
|
+
max_date_col: max_date
|
|
47
|
+
|
|
48
|
+
- type: DateParameter
|
|
49
|
+
factory: CreateWithOptions
|
|
50
|
+
arguments:
|
|
51
|
+
name: end_date
|
|
52
|
+
label: End Date
|
|
53
|
+
description: End date to filter transactions by
|
|
54
|
+
all_options:
|
|
55
|
+
- default_date: 2024-12-31
|
|
56
|
+
min_date: 2024-01-01
|
|
57
|
+
max_date: 2024-12-31
|
|
58
|
+
|
|
59
|
+
- type: DateRangeParameter
|
|
60
|
+
factory: CreateWithOptions
|
|
61
|
+
arguments:
|
|
62
|
+
name: date_range
|
|
63
|
+
label: Date Range
|
|
64
|
+
description: Date range to filter transactions by
|
|
65
|
+
all_options:
|
|
66
|
+
- default_start_date: 2024-01-01
|
|
67
|
+
default_end_date: 2024-12-31
|
|
68
|
+
min_date: 2024-01-01
|
|
69
|
+
max_date: 2024-12-31
|
|
70
|
+
|
|
71
|
+
- type: MultiSelectParameter
|
|
72
|
+
factory: CreateFromSource
|
|
73
|
+
arguments:
|
|
74
|
+
name: category
|
|
75
|
+
label: Category Filter
|
|
76
|
+
description: The expense categories to filter transactions by
|
|
77
|
+
data_source:
|
|
78
|
+
table_or_query: seed_categories
|
|
79
|
+
id_col: category_id
|
|
80
|
+
options_col: category
|
|
81
|
+
source: seeds ## optional, default is "connection" - must be one of "connection", "seeds", or "vdl" - exists for data_source of any parameters
|
|
82
|
+
order_by_col: null ## optional, shown is default - exists for data_source of SingleSelect and MultiSelect
|
|
83
|
+
is_default_col: null ## optional, shown is default - exists for data_source of SingleSelect and MultiSelect
|
|
84
|
+
custom_cols: {} ## optional, shown is default - exists for data_source of SingleSelect and MultiSelect
|
|
85
|
+
include_all: true ## optional, shown is default - exists for data_source of MultiSelect only
|
|
86
|
+
order_matters: false ## optional, shown is default - exists for data_source of MultiSelect only
|
|
87
|
+
user_group_col: null ## optional, shown is default - exists for data_source of any parameters
|
|
88
|
+
connection_name: default ## optional, shown is default - exists for data_source of any parameters
|
|
89
|
+
|
|
90
|
+
- type: MultiSelectParameter
|
|
91
|
+
factory: CreateFromSource
|
|
92
|
+
arguments:
|
|
93
|
+
name: subcategory
|
|
94
|
+
label: Subcategory Filter
|
|
95
|
+
description: The expense subcategories to filter transactions by (available options are based on selected value(s) of 'Category Filter')
|
|
96
|
+
parent_name: category
|
|
97
|
+
data_source:
|
|
98
|
+
table_or_query: seed_subcategories
|
|
99
|
+
id_col: subcategory_id
|
|
100
|
+
options_col: subcategory
|
|
101
|
+
source: seeds
|
|
102
|
+
parent_id_col: category_id ## optional, default is null - exists for all parameter types
|
|
103
|
+
|
|
104
|
+
- type: NumberParameter
|
|
105
|
+
factory: CreateWithOptions
|
|
106
|
+
arguments:
|
|
107
|
+
name: min_filter
|
|
108
|
+
label: Amounts Greater Than
|
|
109
|
+
description: Number to filter on transactions with an amount greater than this value
|
|
110
|
+
all_options:
|
|
111
|
+
- min_value: 0
|
|
112
|
+
max_value: 300
|
|
113
|
+
increment: 10 ## optional, default is 1 - exists for Number and NumberRange options
|
|
114
|
+
default_value: null ## optional, shown is default - exists for Number options only
|
|
115
|
+
|
|
116
|
+
- type: NumberParameter
|
|
117
|
+
factory: CreateFromSource
|
|
118
|
+
arguments:
|
|
119
|
+
name: max_filter
|
|
120
|
+
label: Amounts Less Than
|
|
121
|
+
description: Number to filter on transactions with an amount less than this value
|
|
122
|
+
data_source:
|
|
123
|
+
table_or_query: "SELECT 0 as min_value, 300 as max_value, 10 as increment"
|
|
124
|
+
min_value_col: min_value
|
|
125
|
+
max_value_col: max_value
|
|
126
|
+
increment_col: increment ## optional, default is null
|
|
127
|
+
default_value_col: max_value ## optional, default is null
|
|
128
|
+
id_col: null ## optional, shown is default - required for SingleSelect and MultiSelect, optional for others
|
|
129
|
+
|
|
130
|
+
- type: NumberRangeParameter
|
|
131
|
+
factory: CreateWithOptions
|
|
132
|
+
arguments:
|
|
133
|
+
name: between_filter
|
|
134
|
+
label: Amounts Between
|
|
135
|
+
description: Number range to filter on transactions with an amount within this range
|
|
136
|
+
all_options:
|
|
137
|
+
- min_value: 0
|
|
138
|
+
max_value: 300
|
|
139
|
+
default_lower_value: 0 ## optional, default is null (or min_value) - exists for NumberRange options only
|
|
140
|
+
default_upper_value: 300 ## optional, default is null (or max_value) - exists for NumberRange options only
|
|
141
|
+
|
|
142
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from squirrels.arguments import ConnectionsArgs
|
|
3
|
+
from squirrels.connections import ConnectionProperties, ConnectionTypeEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def main(connections: dict[str, ConnectionProperties | Any], sqrl: ConnectionsArgs) -> None:
|
|
7
|
+
"""
|
|
8
|
+
Define sqlalchemy engines by adding them to the "connections" dictionary
|
|
9
|
+
"""
|
|
10
|
+
## SQLAlchemy URL for a connection engine
|
|
11
|
+
conn_str: str = sqrl.env_vars["SQLITE_URI"].format(project_path=sqrl.project_path)
|
|
12
|
+
|
|
13
|
+
## Assigning names to connection engines
|
|
14
|
+
connections["default"] = ConnectionProperties(
|
|
15
|
+
label="SQLite Expenses Database",
|
|
16
|
+
type=ConnectionTypeEnum.SQLALCHEMY,
|
|
17
|
+
uri=conn_str
|
|
18
|
+
)
|
|
19
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import cast, Any
|
|
2
|
+
from squirrels.arguments import ContextArgs
|
|
3
|
+
from squirrels import parameters as p
|
|
4
|
+
|
|
5
|
+
from pyconfigs.user import CustomUserFields
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def main(ctx: dict[str, Any], sqrl: ContextArgs) -> None:
|
|
9
|
+
"""
|
|
10
|
+
Define context variables AFTER parameter selections are made by adding entries to the dictionary "ctx".
|
|
11
|
+
These context variables can then be used in the models.
|
|
12
|
+
|
|
13
|
+
Note that the code here is used by all datasets, regardless of the parameters they use. You can use
|
|
14
|
+
sqrl.param_exists to determine the conditions to execute certain blocks of code.
|
|
15
|
+
"""
|
|
16
|
+
custom_fields = cast(CustomUserFields, sqrl.user.custom_fields)
|
|
17
|
+
|
|
18
|
+
if sqrl.param_exists("group_by"):
|
|
19
|
+
group_by_param = sqrl.prms["group_by"]
|
|
20
|
+
assert isinstance(group_by_param, p.SingleSelectParameter)
|
|
21
|
+
|
|
22
|
+
selected_id = group_by_param.get_selected_id()
|
|
23
|
+
columns = group_by_param.get_selected("columns")
|
|
24
|
+
aliases = group_by_param.get_selected("aliases", default_field="columns")
|
|
25
|
+
assert isinstance(columns, list) and isinstance(aliases, list) and len(columns) == len(aliases)
|
|
26
|
+
|
|
27
|
+
column_to_alias_mapping = {x: y for x, y in zip(columns, aliases) if not y.startswith("_")}
|
|
28
|
+
order_by_cols = list(column_to_alias_mapping.values())
|
|
29
|
+
|
|
30
|
+
ctx["column_to_alias_mapping"] = column_to_alias_mapping
|
|
31
|
+
ctx["group_by_cols"] = order_by_cols if selected_id != "trans" else None
|
|
32
|
+
|
|
33
|
+
# Only used if federate_example is a Python model
|
|
34
|
+
mask_column = lambda x: x if custom_fields.role == "manager" else "***MASKED***"
|
|
35
|
+
ctx["order_by_cols"] = order_by_cols
|
|
36
|
+
ctx["mask_column_function"] = mask_column
|
|
37
|
+
|
|
38
|
+
# Only used if federate_example is a SQL model
|
|
39
|
+
mask_column = lambda x: x if custom_fields.role == "manager" else "'***MASKED***'"
|
|
40
|
+
x_as_y = lambda x, y: (mask_column(x) if x in ["description"] else x)+" as "+y
|
|
41
|
+
ctx["select_dim_cols"] = list(x_as_y(x, y) for x, y in column_to_alias_mapping.items())
|
|
42
|
+
ctx["aggregator"] = "SUM" if selected_id != "trans" else ""
|
|
43
|
+
ctx["order_by_cols_desc"] = list(y+" DESC" for y in order_by_cols)
|
|
44
|
+
|
|
45
|
+
if sqrl.param_exists("start_date"):
|
|
46
|
+
start_date_param = sqrl.prms["start_date"]
|
|
47
|
+
assert isinstance(start_date_param, p.DateParameter)
|
|
48
|
+
|
|
49
|
+
ctx["start_date"] = start_date_param.get_selected_date()
|
|
50
|
+
|
|
51
|
+
if sqrl.param_exists("end_date"):
|
|
52
|
+
end_date_param = sqrl.prms["end_date"]
|
|
53
|
+
assert isinstance(end_date_param, p.DateParameter)
|
|
54
|
+
|
|
55
|
+
ctx["end_date"] = end_date_param.get_selected_date()
|
|
56
|
+
|
|
57
|
+
if sqrl.param_exists("date_range"):
|
|
58
|
+
date_range_param = sqrl.prms["date_range"]
|
|
59
|
+
assert isinstance(date_range_param, p.DateRangeParameter)
|
|
60
|
+
|
|
61
|
+
ctx["start_date_from_range"] = date_range_param.get_selected_start_date()
|
|
62
|
+
ctx["end_date_from_range"] = date_range_param.get_selected_end_date()
|
|
63
|
+
|
|
64
|
+
if sqrl.param_exists("category"):
|
|
65
|
+
category_param = sqrl.prms["category"]
|
|
66
|
+
assert isinstance(category_param, p.MultiSelectParameter)
|
|
67
|
+
|
|
68
|
+
ctx["has_categories"] = category_param.has_non_empty_selection()
|
|
69
|
+
ctx["categories"] = category_param.get_selected_ids_as_list()
|
|
70
|
+
|
|
71
|
+
if sqrl.param_exists("subcategory"):
|
|
72
|
+
subcategory_param = sqrl.prms["subcategory"]
|
|
73
|
+
assert isinstance(subcategory_param, p.MultiSelectParameter)
|
|
74
|
+
|
|
75
|
+
ctx["has_subcategories"] = subcategory_param.has_non_empty_selection()
|
|
76
|
+
ctx["subcategories"] = subcategory_param.get_selected_ids_as_list()
|
|
77
|
+
|
|
78
|
+
if sqrl.param_exists("min_filter"):
|
|
79
|
+
min_amount_filter = sqrl.prms["min_filter"]
|
|
80
|
+
assert isinstance(min_amount_filter, p.NumberParameter)
|
|
81
|
+
|
|
82
|
+
ctx["min_amount"] = min_amount_filter.get_selected_value()
|
|
83
|
+
|
|
84
|
+
if sqrl.param_exists("max_filter"):
|
|
85
|
+
max_amount_filter = sqrl.prms["max_filter"]
|
|
86
|
+
assert isinstance(max_amount_filter, p.NumberParameter)
|
|
87
|
+
|
|
88
|
+
ctx["max_amount"] = max_amount_filter.get_selected_value()
|
|
89
|
+
|
|
90
|
+
if sqrl.param_exists("between_filter"):
|
|
91
|
+
between_filter = sqrl.prms["between_filter"]
|
|
92
|
+
assert isinstance(between_filter, p.NumberRangeParameter)
|
|
93
|
+
|
|
94
|
+
ctx["min_amount_from_range"] = between_filter.get_selected_lower_value()
|
|
95
|
+
ctx["max_amount_from_range"] = between_filter.get_selected_upper_value()
|
|
96
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from squirrels import parameters as p, parameter_options as po, data_sources as ds
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
## Example of creating SingleSelectParameter and specifying each option by code
|
|
5
|
+
@p.SingleSelectParameter.create_with_options(
|
|
6
|
+
name="group_by", label="Group By",
|
|
7
|
+
description="Dimension(s) to aggregate by",
|
|
8
|
+
user_attribute="access_level"
|
|
9
|
+
)
|
|
10
|
+
def group_by_options():
|
|
11
|
+
return [
|
|
12
|
+
po.SelectParameterOption(
|
|
13
|
+
id="trans", label="Transaction",
|
|
14
|
+
columns=["id","date","category","subcategory","description"],
|
|
15
|
+
aliases=["_id","date","category","subcategory","description"], # in context.py, any alias starting with "_" will not be selected
|
|
16
|
+
user_groups=["admin"]
|
|
17
|
+
),
|
|
18
|
+
po.SelectParameterOption(
|
|
19
|
+
id="day", label="Day",
|
|
20
|
+
columns=["date"],
|
|
21
|
+
aliases=["day"],
|
|
22
|
+
user_groups=["admin","member"]
|
|
23
|
+
),
|
|
24
|
+
po.SelectParameterOption(
|
|
25
|
+
id="month", label="Month",
|
|
26
|
+
columns=["month"],
|
|
27
|
+
user_groups=["admin","member","guest"]
|
|
28
|
+
),
|
|
29
|
+
po.SelectParameterOption(
|
|
30
|
+
id="cat", label="Category",
|
|
31
|
+
columns=["category"],
|
|
32
|
+
user_groups=["admin","member","guest"]
|
|
33
|
+
),
|
|
34
|
+
po.SelectParameterOption(
|
|
35
|
+
id="subcat", label="Subcategory",
|
|
36
|
+
columns=["category","subcategory"],
|
|
37
|
+
user_groups=["admin","member","guest"]
|
|
38
|
+
),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## Example of creating DateParameter
|
|
43
|
+
@p.DateParameter.create_from_source(
|
|
44
|
+
name="start_date", label="Start Date",
|
|
45
|
+
description="Start date to filter transactions by"
|
|
46
|
+
)
|
|
47
|
+
def start_date_source():
|
|
48
|
+
return ds.DateDataSource(
|
|
49
|
+
table_or_query="SELECT min(date) AS min_date, max(date) AS max_date FROM expenses",
|
|
50
|
+
default_date_col="min_date",
|
|
51
|
+
min_date_col="min_date", max_date_col="max_date",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## Example of creating DateParameter from list of DateParameterOption's
|
|
56
|
+
@p.DateParameter.create_with_options(
|
|
57
|
+
name="end_date", label="End Date",
|
|
58
|
+
description="End date to filter transactions by"
|
|
59
|
+
)
|
|
60
|
+
def end_date_options():
|
|
61
|
+
return [
|
|
62
|
+
po.DateParameterOption(
|
|
63
|
+
default_date="2024-12-31", min_date="2024-01-01", max_date="2024-12-31"
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
## Example of creating DateRangeParameter
|
|
69
|
+
@p.DateRangeParameter.create_simple(
|
|
70
|
+
name="date_range", label="Date Range",
|
|
71
|
+
default_start_date="2024-01-01", default_end_date="2024-12-31",
|
|
72
|
+
min_date="2024-01-01", max_date="2024-12-31",
|
|
73
|
+
description="Date range to filter transactions by"
|
|
74
|
+
)
|
|
75
|
+
def date_range_options():
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Example of creating MultiSelectParameter from lookup query/table
|
|
80
|
+
@p.MultiSelectParameter.create_from_source(
|
|
81
|
+
name="category", label="Category Filter",
|
|
82
|
+
description="The expense categories to filter transactions by"
|
|
83
|
+
)
|
|
84
|
+
def category_source():
|
|
85
|
+
return ds.SelectDataSource(
|
|
86
|
+
table_or_query="seed_categories",
|
|
87
|
+
id_col="category_id",
|
|
88
|
+
options_col="category",
|
|
89
|
+
source=ds.SourceEnum.SEEDS
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
## Example of creating MultiSelectParameter with parent from lookup query/table
|
|
94
|
+
@p.MultiSelectParameter.create_from_source(
|
|
95
|
+
name="subcategory", label="Subcategory Filter",
|
|
96
|
+
description="The expense subcategories to filter transactions by (available options are based on selected value(s) of 'Category Filter')",
|
|
97
|
+
parent_name="category"
|
|
98
|
+
)
|
|
99
|
+
def subcategory_source():
|
|
100
|
+
return ds.SelectDataSource(
|
|
101
|
+
table_or_query="seed_subcategories",
|
|
102
|
+
id_col="subcategory_id",
|
|
103
|
+
options_col="subcategory",
|
|
104
|
+
source=ds.SourceEnum.SEEDS,
|
|
105
|
+
parent_id_col="category_id"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
## Example of creating NumberParameter
|
|
110
|
+
@p.NumberParameter.create_simple(
|
|
111
|
+
name="min_filter", label="Amounts Greater Than",
|
|
112
|
+
min_value=0, max_value=300, increment=10,
|
|
113
|
+
description="Number to filter on transactions with an amount greater than this value"
|
|
114
|
+
)
|
|
115
|
+
def min_filter_options():
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
## Example of creating NumberParameter from lookup query/table
|
|
120
|
+
@p.NumberParameter.create_from_source(
|
|
121
|
+
name="max_filter", label="Amounts Less Than",
|
|
122
|
+
description="Number to filter on transactions with an amount less than this value"
|
|
123
|
+
)
|
|
124
|
+
def max_filter_source():
|
|
125
|
+
return ds.NumberDataSource(
|
|
126
|
+
table_or_query="SELECT 0 as min_value, 300 as max_value, 10 as increment",
|
|
127
|
+
min_value_col="min_value", max_value_col="max_value",
|
|
128
|
+
increment_col="increment",
|
|
129
|
+
default_value_col="max_value"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## Example of creating NumberRangeParameter
|
|
134
|
+
@p.NumberRangeParameter.create_simple(
|
|
135
|
+
name="between_filter", label="Amounts Between",
|
|
136
|
+
min_value=0, max_value=300,
|
|
137
|
+
default_lower_value=0, default_upper_value=300,
|
|
138
|
+
description="Number range to filter on transactions with an amount within this range"
|
|
139
|
+
)
|
|
140
|
+
def between_filter_options():
|
|
141
|
+
pass
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from typing import Literal, Any
|
|
2
|
+
from squirrels.auth import CustomUserFields as BaseCustomUserFields, ProviderConfigs, RegisteredUser, provider
|
|
3
|
+
from squirrels.arguments import AuthProviderArgs
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CustomUserFields(BaseCustomUserFields):
|
|
7
|
+
"""
|
|
8
|
+
Extend the CustomUserFields class to add custom user attributes.
|
|
9
|
+
- Only the following types are supported: [str, int, float, bool, typing.Literal]
|
|
10
|
+
- Add "| None" after the type to make it nullable.
|
|
11
|
+
- Always set a default value for the field (use None if default is null).
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
organization: str | None = None
|
|
15
|
+
"""
|
|
16
|
+
role: Literal["manager", "staff", "customer"] = "staff"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# @provider(
|
|
20
|
+
# name="google", label="Google",
|
|
21
|
+
# icon="https://www.google.com/favicon.ico"
|
|
22
|
+
# # ^ Note: You also can save the google favicon as an image file in "resources/public/logos/google.ico"
|
|
23
|
+
# # and use icon="/public/logos/google.ico" instead
|
|
24
|
+
# )
|
|
25
|
+
def google_auth_provider(sqrl: AuthProviderArgs) -> ProviderConfigs:
|
|
26
|
+
"""
|
|
27
|
+
Example provider configs for authenticating a user using Google credentials.
|
|
28
|
+
|
|
29
|
+
See the following page for setting up the CLIENT_ID and CLIENT_SECRET for Google specifically:
|
|
30
|
+
- https://support.google.com/googleapi/answer/6158849?hl=en
|
|
31
|
+
|
|
32
|
+
IMPORTANT: Avoid using Google OAuth if you set auth_strategy to 'external'.
|
|
33
|
+
- If auth_strategy is 'external', MCP clients would require Dynamic Client Registration (DCR) to
|
|
34
|
+
authenticate with the associated OAuth provider used by the Squirrels MCP server.
|
|
35
|
+
- Unfortunately, Google OAuth (and many other OAuth providers) do not support DCR. If auth_strategy
|
|
36
|
+
is 'external', consider using an alternative that supports DCR instead (such as WorkOS or Keycloak).
|
|
37
|
+
"""
|
|
38
|
+
def get_sqrl_user(claims: dict[str, Any]) -> RegisteredUser:
|
|
39
|
+
custom_fields = CustomUserFields(role="customer")
|
|
40
|
+
return RegisteredUser(
|
|
41
|
+
username=claims["email"],
|
|
42
|
+
access_level="member", # or "admin"
|
|
43
|
+
custom_fields=custom_fields
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# TODO: Add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to the .env file
|
|
47
|
+
# Then, uncomment the @provider decorator above and set the client_id and client_secret below
|
|
48
|
+
provider_configs = ProviderConfigs(
|
|
49
|
+
client_id="", # sqrl.env_vars["GOOGLE_CLIENT_ID"],
|
|
50
|
+
client_secret="", # sqrl.env_vars["GOOGLE_CLIENT_SECRET"],
|
|
51
|
+
server_url="https://accounts.google.com",
|
|
52
|
+
client_kwargs={"scope": "openid email profile"},
|
|
53
|
+
get_user=get_sqrl_user
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return provider_configs
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
description: |
|
|
2
|
+
Lookup table for the category IDs and names of transactions.
|
|
3
|
+
|
|
4
|
+
cast_column_types: true # optional, default is false - if set to true, then SQRL_SEEDS__INFER_SCHEMA is ignored for this seed
|
|
5
|
+
|
|
6
|
+
columns:
|
|
7
|
+
- name: category_id
|
|
8
|
+
type: string
|
|
9
|
+
description: The category ID
|
|
10
|
+
category: dimension
|
|
11
|
+
|
|
12
|
+
- name: category
|
|
13
|
+
type: string
|
|
14
|
+
description: The human-readable category name
|
|
15
|
+
category: dimension
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"subcategory_id","subcategory","category_id"
|
|
2
|
+
"0","Dining Out","0"
|
|
3
|
+
"1","Groceries","0"
|
|
4
|
+
"2","Utilities","1"
|
|
5
|
+
"3","Electronics","2"
|
|
6
|
+
"4","Ride Sharing","3"
|
|
7
|
+
"5","Mobile","1"
|
|
8
|
+
"6","Home Decor","2"
|
|
9
|
+
"7","Internet","1"
|
|
10
|
+
"8","Theater","4"
|
|
11
|
+
"9","Movies","4"
|
|
12
|
+
"10","Sports","2"
|
|
13
|
+
"11","Public Transit","3"
|
|
14
|
+
"12","Clothing","2"
|
|
15
|
+
"13","Concerts","4"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
description: |
|
|
2
|
+
Lookup table for the subcategory IDs and names of transactions.
|
|
3
|
+
|
|
4
|
+
cast_column_types: true # optional, default is false - if set to true, then SQRL_SEEDS__INFER_SCHEMA is ignored for this seed
|
|
5
|
+
|
|
6
|
+
columns:
|
|
7
|
+
- name: subcategory_id
|
|
8
|
+
type: string
|
|
9
|
+
description: The subcategory ID
|
|
10
|
+
category: dimension
|
|
11
|
+
|
|
12
|
+
- name: subcategory
|
|
13
|
+
type: string
|
|
14
|
+
description: The human-readable subcategory name
|
|
15
|
+
category: dimension
|
|
16
|
+
|
|
17
|
+
- name: category_id
|
|
18
|
+
type: string
|
|
19
|
+
description: The category ID that the subcategory belongs to
|
|
20
|
+
category: dimension
|
|
21
|
+
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
project_variables:
|
|
2
|
+
name: expenses
|
|
3
|
+
label: "Sample Expenses"
|
|
4
|
+
description: This is a sample Squirrels project for analyzing expense transactions
|
|
5
|
+
major_version: 1
|
|
6
|
+
auth_type: optional ## one of 'optional' (default) or 'required'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
packages: []
|
|
10
|
+
|
|
11
|
+
## Example for packages section:
|
|
12
|
+
# packages:
|
|
13
|
+
# - git: https://.../myrepo.git
|
|
14
|
+
# revision: v0.1.0
|
|
15
|
+
# directory: custom_name ## optional
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
{{ connections -}}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
{{ parameters -}}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
datasets:
|
|
25
|
+
- name: expense_transactions
|
|
26
|
+
label: Expense Transactions - DBView Example
|
|
27
|
+
description: All expense transactions
|
|
28
|
+
model: dbview_example ## optional - if not specified, model name uses the dataset name
|
|
29
|
+
scope: private ## optional - one of 'public', 'protected', or 'private'. Default is 'public' (if auth_type is 'optional') or 'protected' (if auth_type is 'required').
|
|
30
|
+
parameters: ## optional - if not specified, then all parameters are used
|
|
31
|
+
- start_date
|
|
32
|
+
- end_date
|
|
33
|
+
- min_filter
|
|
34
|
+
- max_filter
|
|
35
|
+
|
|
36
|
+
- name: grouped_expenses
|
|
37
|
+
label: Grouped Expenses - Federate Example
|
|
38
|
+
description: Aggregated expense transactions by custom dimension using federate_example model
|
|
39
|
+
model: federate_example
|
|
40
|
+
parameters:
|
|
41
|
+
- group_by
|
|
42
|
+
- date_range
|
|
43
|
+
- category
|
|
44
|
+
- subcategory
|
|
45
|
+
- between_filter
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
selection_test_sets:
|
|
49
|
+
- name: set_start_date
|
|
50
|
+
parameters: ## optional section - if not provided, then default value is used for all parameters
|
|
51
|
+
start_date: 2024-07-01 ## this parameter is only used by model "dbview_example"
|
|
52
|
+
|
|
53
|
+
- name: set_date_range
|
|
54
|
+
parameters:
|
|
55
|
+
date_range: [2024-02-01,2024-11-30] ## this parameter is only used by model "federate_example"
|
|
56
|
+
|
|
57
|
+
- name: use_admin_privileged_group_by
|
|
58
|
+
user:
|
|
59
|
+
access_level: admin
|
|
60
|
+
parameters:
|
|
61
|
+
group_by: trans
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Signed In</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: -apple-system, system-ui, sans-serif;
|
|
10
|
+
display: flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
justify-content: center;
|
|
13
|
+
height: 100vh;
|
|
14
|
+
margin: 0;
|
|
15
|
+
background-color: #f9fafb;
|
|
16
|
+
color: #111827;
|
|
17
|
+
}
|
|
18
|
+
.card {
|
|
19
|
+
text-align: center;
|
|
20
|
+
padding: 24px;
|
|
21
|
+
}
|
|
22
|
+
.icon {
|
|
23
|
+
color: #059669;
|
|
24
|
+
font-size: 48px;
|
|
25
|
+
margin-bottom: 16px;
|
|
26
|
+
}
|
|
27
|
+
h1 {
|
|
28
|
+
font-size: 24px;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
margin: 0 0 8px;
|
|
31
|
+
}
|
|
32
|
+
p {
|
|
33
|
+
font-size: 16px;
|
|
34
|
+
color: #6b7280;
|
|
35
|
+
margin: 0;
|
|
36
|
+
}
|
|
37
|
+
</style>
|
|
38
|
+
</head>
|
|
39
|
+
<body>
|
|
40
|
+
<div class="card">
|
|
41
|
+
<div class="icon">✓</div>
|
|
42
|
+
<h1>Signed in successfully</h1>
|
|
43
|
+
<p>You can close this window now.</p>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
if (window.opener) {
|
|
48
|
+
window.opener.postMessage({ type: "squirrels:auth:login-successful" }, "*");
|
|
49
|
+
setTimeout(function() { window.close(); }, 1000);
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link id="favicon" rel="icon" type="image/svg+xml" href="{{ sqrl_studio_base_url }}/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Squirrels Studio</title>
|
|
8
|
+
<script>
|
|
9
|
+
// Optional: Set global defaults for project parameters
|
|
10
|
+
window.DEFAULT_MOUNTPATH = '{{ mount_path }}';
|
|
11
|
+
</script>
|
|
12
|
+
<!--
|
|
13
|
+
GitHub Pages serves static files with a Cache-Control header that typically tells the browser to cache for 10 minutes.
|
|
14
|
+
The implementation of squirrels-studio-v2 should be compatible with all versions of Squirrels from v0.6.0 onwards.
|
|
15
|
+
-->
|
|
16
|
+
<script type="module" crossorigin src="{{ sqrl_studio_base_url }}/assets/index.js"></script>
|
|
17
|
+
<link rel="stylesheet" crossorigin href="{{ sqrl_studio_base_url }}/assets/index.css">
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<div id="root"></div>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|