squirrels 0.5.0b4__py3-none-any.whl → 0.5.1__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.
Potentially problematic release.
This version of squirrels might be problematic. Click here for more details.
- squirrels/__init__.py +2 -0
- squirrels/_api_routes/auth.py +83 -74
- squirrels/_api_routes/base.py +58 -41
- squirrels/_api_routes/dashboards.py +37 -21
- squirrels/_api_routes/data_management.py +72 -27
- squirrels/_api_routes/datasets.py +107 -84
- squirrels/_api_routes/oauth2.py +11 -13
- squirrels/_api_routes/project.py +71 -33
- squirrels/_api_server.py +130 -63
- squirrels/_arguments/run_time_args.py +9 -9
- squirrels/_auth.py +117 -162
- squirrels/_command_line.py +68 -32
- squirrels/_compile_prompts.py +147 -0
- squirrels/_connection_set.py +11 -2
- squirrels/_constants.py +22 -8
- squirrels/_data_sources.py +38 -32
- squirrels/_dataset_types.py +2 -4
- squirrels/_initializer.py +1 -1
- squirrels/_logging.py +117 -0
- squirrels/_manifest.py +125 -58
- squirrels/_model_builder.py +10 -54
- squirrels/_models.py +224 -108
- squirrels/_package_data/base_project/.env +15 -4
- squirrels/_package_data/base_project/.env.example +14 -3
- squirrels/_package_data/base_project/connections.yml +4 -3
- squirrels/_package_data/base_project/dashboards/dashboard_example.py +2 -2
- squirrels/_package_data/base_project/dashboards/dashboard_example.yml +4 -4
- squirrels/_package_data/base_project/duckdb_init.sql +1 -0
- squirrels/_package_data/base_project/models/dbviews/dbview_example.sql +7 -2
- squirrels/_package_data/base_project/models/dbviews/dbview_example.yml +16 -10
- squirrels/_package_data/base_project/models/federates/federate_example.py +22 -15
- squirrels/_package_data/base_project/models/federates/federate_example.sql +3 -7
- squirrels/_package_data/base_project/models/federates/federate_example.yml +1 -1
- squirrels/_package_data/base_project/models/sources.yml +5 -6
- squirrels/_package_data/base_project/parameters.yml +24 -38
- squirrels/_package_data/base_project/pyconfigs/connections.py +5 -1
- squirrels/_package_data/base_project/pyconfigs/context.py +23 -12
- squirrels/_package_data/base_project/pyconfigs/parameters.py +68 -33
- squirrels/_package_data/base_project/pyconfigs/user.py +11 -18
- squirrels/_package_data/base_project/seeds/seed_categories.yml +1 -1
- squirrels/_package_data/base_project/seeds/seed_subcategories.yml +1 -1
- squirrels/_package_data/base_project/squirrels.yml.j2 +18 -28
- squirrels/_package_data/templates/squirrels_studio.html +20 -0
- squirrels/_parameter_configs.py +43 -22
- squirrels/_parameter_options.py +1 -1
- squirrels/_parameter_sets.py +8 -10
- squirrels/_project.py +351 -234
- squirrels/_request_context.py +33 -0
- squirrels/_schemas/auth_models.py +32 -9
- squirrels/_schemas/query_param_models.py +9 -1
- squirrels/_schemas/response_models.py +36 -10
- squirrels/_seeds.py +1 -1
- squirrels/_sources.py +23 -19
- squirrels/_utils.py +83 -35
- squirrels/_version.py +1 -1
- squirrels/arguments.py +5 -0
- squirrels/auth.py +4 -1
- squirrels/connections.py +2 -0
- squirrels/dashboards.py +3 -1
- squirrels/data_sources.py +6 -0
- squirrels/parameter_options.py +5 -0
- squirrels/parameters.py +5 -0
- squirrels/types.py +6 -1
- {squirrels-0.5.0b4.dist-info → squirrels-0.5.1.dist-info}/METADATA +28 -13
- squirrels-0.5.1.dist-info/RECORD +98 -0
- squirrels-0.5.0b4.dist-info/RECORD +0 -94
- {squirrels-0.5.0b4.dist-info → squirrels-0.5.1.dist-info}/WHEEL +0 -0
- {squirrels-0.5.0b4.dist-info → squirrels-0.5.1.dist-info}/entry_points.txt +0 -0
- {squirrels-0.5.0b4.dist-info → squirrels-0.5.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,55 +1,75 @@
|
|
|
1
|
-
from squirrels import
|
|
1
|
+
from squirrels import parameters as p, parameter_options as po, data_sources as ds
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
## Example of creating SingleSelectParameter and specifying each option by code
|
|
5
5
|
@p.SingleSelectParameter.create_with_options(
|
|
6
|
-
"group_by", "Group By",
|
|
6
|
+
name="group_by", label="Group By",
|
|
7
|
+
description="Dimension(s) to aggregate by",
|
|
8
|
+
user_attribute="access_level"
|
|
7
9
|
)
|
|
8
10
|
def group_by_options():
|
|
9
11
|
return [
|
|
10
12
|
po.SelectParameterOption(
|
|
11
|
-
"trans", "Transaction",
|
|
13
|
+
id="trans", label="Transaction",
|
|
12
14
|
columns=["id","date","category","subcategory","description"],
|
|
13
|
-
aliases=["_id","date","category","subcategory","description"], # any alias starting with "_" will not be selected
|
|
14
|
-
user_groups=["
|
|
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"]
|
|
15
38
|
),
|
|
16
|
-
po.SelectParameterOption("day" , "Day" , columns=["date"], aliases=["day"] , user_groups=["manager","employee"]),
|
|
17
|
-
po.SelectParameterOption("month" , "Month" , columns=["month"] , user_groups=["manager","employee"]),
|
|
18
|
-
po.SelectParameterOption("cat" , "Category" , columns=["category"] , user_groups=["manager","employee"]),
|
|
19
|
-
po.SelectParameterOption("subcat" , "Subcategory" , columns=["category","subcategory"] , user_groups=["manager","employee"]),
|
|
20
39
|
]
|
|
21
40
|
|
|
22
41
|
|
|
23
|
-
## Example of creating NumberParameter with options
|
|
24
|
-
@p.NumberParameter.create_with_options(
|
|
25
|
-
"limit", "Max Number of Rows", description="Maximum number of rows to return", parent_name="group_by"
|
|
26
|
-
)
|
|
27
|
-
def limit_options():
|
|
28
|
-
return [po.NumberParameterOption(0, 1000, increment=10, default_value=1000, parent_option_ids="trans")]
|
|
29
|
-
|
|
30
|
-
|
|
31
42
|
## Example of creating DateParameter
|
|
32
43
|
@p.DateParameter.create_from_source(
|
|
33
|
-
"start_date", "Start Date",
|
|
44
|
+
name="start_date", label="Start Date",
|
|
45
|
+
description="Start date to filter transactions by"
|
|
34
46
|
)
|
|
35
47
|
def start_date_source():
|
|
36
48
|
return ds.DateDataSource(
|
|
37
|
-
"SELECT min(date) AS min_date, max(date) AS max_date FROM expenses",
|
|
38
|
-
default_date_col="min_date",
|
|
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",
|
|
39
52
|
)
|
|
40
53
|
|
|
41
54
|
|
|
42
55
|
## Example of creating DateParameter from list of DateParameterOption's
|
|
43
56
|
@p.DateParameter.create_with_options(
|
|
44
|
-
"end_date", "End Date",
|
|
57
|
+
name="end_date", label="End Date",
|
|
58
|
+
description="End date to filter transactions by"
|
|
45
59
|
)
|
|
46
60
|
def end_date_options():
|
|
47
|
-
return [
|
|
61
|
+
return [
|
|
62
|
+
po.DateParameterOption(
|
|
63
|
+
default_date="2024-12-31", min_date="2024-01-01", max_date="2024-12-31"
|
|
64
|
+
)
|
|
65
|
+
]
|
|
48
66
|
|
|
49
67
|
|
|
50
68
|
## Example of creating DateRangeParameter
|
|
51
69
|
@p.DateRangeParameter.create_simple(
|
|
52
|
-
"date_range", "Date Range",
|
|
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",
|
|
53
73
|
description="Date range to filter transactions by"
|
|
54
74
|
)
|
|
55
75
|
def date_range_options():
|
|
@@ -58,27 +78,38 @@ def date_range_options():
|
|
|
58
78
|
|
|
59
79
|
## Example of creating MultiSelectParameter from lookup query/table
|
|
60
80
|
@p.MultiSelectParameter.create_from_source(
|
|
61
|
-
"category", "Category Filter",
|
|
81
|
+
name="category", label="Category Filter",
|
|
62
82
|
description="The expense categories to filter transactions by"
|
|
63
83
|
)
|
|
64
84
|
def category_source():
|
|
65
|
-
return ds.SelectDataSource(
|
|
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
|
+
)
|
|
66
91
|
|
|
67
92
|
|
|
68
93
|
## Example of creating MultiSelectParameter with parent from lookup query/table
|
|
69
94
|
@p.MultiSelectParameter.create_from_source(
|
|
70
|
-
"subcategory", "Subcategory Filter",
|
|
71
|
-
description="The expense subcategories to filter transactions by (available options are based on selected value(s) of 'Category Filter')"
|
|
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"
|
|
72
98
|
)
|
|
73
99
|
def subcategory_source():
|
|
74
100
|
return ds.SelectDataSource(
|
|
75
|
-
"seed_subcategories",
|
|
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"
|
|
76
106
|
)
|
|
77
107
|
|
|
78
108
|
|
|
79
109
|
## Example of creating NumberParameter
|
|
80
110
|
@p.NumberParameter.create_simple(
|
|
81
|
-
"min_filter", "Amounts Greater Than",
|
|
111
|
+
name="min_filter", label="Amounts Greater Than",
|
|
112
|
+
min_value=0, max_value=300, increment=10,
|
|
82
113
|
description="Number to filter on transactions with an amount greater than this value"
|
|
83
114
|
)
|
|
84
115
|
def min_filter_options():
|
|
@@ -87,19 +118,23 @@ def min_filter_options():
|
|
|
87
118
|
|
|
88
119
|
## Example of creating NumberParameter from lookup query/table
|
|
89
120
|
@p.NumberParameter.create_from_source(
|
|
90
|
-
"max_filter", "Amounts Less Than",
|
|
121
|
+
name="max_filter", label="Amounts Less Than",
|
|
91
122
|
description="Number to filter on transactions with an amount less than this value"
|
|
92
123
|
)
|
|
93
124
|
def max_filter_source():
|
|
94
|
-
query = "SELECT 0 as min_value, 300 as max_value, 10 as increment"
|
|
95
125
|
return ds.NumberDataSource(
|
|
96
|
-
|
|
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"
|
|
97
130
|
)
|
|
98
131
|
|
|
99
132
|
|
|
100
133
|
## Example of creating NumberRangeParameter
|
|
101
134
|
@p.NumberRangeParameter.create_simple(
|
|
102
|
-
"between_filter", "Amounts Between",
|
|
135
|
+
name="between_filter", label="Amounts Between",
|
|
136
|
+
min_value=0, max_value=300,
|
|
137
|
+
default_lower_value=0, default_upper_value=300,
|
|
103
138
|
description="Number range to filter on transactions with an amount within this range"
|
|
104
139
|
)
|
|
105
140
|
def between_filter_options():
|
|
@@ -2,25 +2,17 @@ from typing import Literal
|
|
|
2
2
|
from squirrels import auth, arguments as args
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
class
|
|
5
|
+
class CustomUserFields(auth.CustomUserFields):
|
|
6
6
|
"""
|
|
7
|
-
Extend the
|
|
7
|
+
Extend the CustomUserFields class to add custom user attributes.
|
|
8
8
|
- Only the following types are supported: [str, int, float, bool, typing.Literal]
|
|
9
|
-
-
|
|
10
|
-
- Always set a default value for the
|
|
9
|
+
- Add "| None" after the type to make it nullable.
|
|
10
|
+
- Always set a default value for the field (use None if default is null).
|
|
11
11
|
|
|
12
12
|
Example:
|
|
13
13
|
organization: str | None = None
|
|
14
14
|
"""
|
|
15
|
-
role: Literal["manager", "
|
|
16
|
-
|
|
17
|
-
@classmethod
|
|
18
|
-
def dropped_columns(cls) -> list[str]:
|
|
19
|
-
"""
|
|
20
|
-
The fields defined above cannot be modified once added to the database.
|
|
21
|
-
However, you can choose to drop columns by adding them to this list.
|
|
22
|
-
"""
|
|
23
|
-
return []
|
|
15
|
+
role: Literal["manager", "staff", "customer"] = "staff"
|
|
24
16
|
|
|
25
17
|
|
|
26
18
|
# @auth.provider(name="google", label="Google", icon="https://www.google.com/favicon.ico")
|
|
@@ -31,11 +23,12 @@ def google_auth_provider(sqrl: args.AuthProviderArgs) -> auth.ProviderConfigs:
|
|
|
31
23
|
See the following page for setting up the CLIENT_ID and CLIENT_SECRET for Google specifically:
|
|
32
24
|
https://support.google.com/googleapi/answer/6158849?hl=en
|
|
33
25
|
"""
|
|
34
|
-
def get_sqrl_user(claims: dict) ->
|
|
35
|
-
|
|
26
|
+
def get_sqrl_user(claims: dict) -> auth.RegisteredUser:
|
|
27
|
+
custom_fields = CustomUserFields(role="customer")
|
|
28
|
+
return auth.RegisteredUser(
|
|
36
29
|
username=claims["email"],
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
access_level="member",
|
|
31
|
+
custom_fields=custom_fields
|
|
39
32
|
)
|
|
40
33
|
|
|
41
34
|
# TODO: Add GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET to the .env file
|
|
@@ -43,7 +36,7 @@ def google_auth_provider(sqrl: args.AuthProviderArgs) -> auth.ProviderConfigs:
|
|
|
43
36
|
provider_configs = auth.ProviderConfigs(
|
|
44
37
|
client_id="", # sqrl.env_vars["GOOGLE_CLIENT_ID"],
|
|
45
38
|
client_secret="", # sqrl.env_vars["GOOGLE_CLIENT_SECRET"],
|
|
46
|
-
|
|
39
|
+
server_url="https://accounts.google.com",
|
|
47
40
|
client_kwargs={"scope": "openid email profile"},
|
|
48
41
|
get_user=get_sqrl_user
|
|
49
42
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
description: |
|
|
2
2
|
Lookup table for the category IDs and names of transactions.
|
|
3
3
|
|
|
4
|
-
cast_column_types: true # optional, default is false -
|
|
4
|
+
cast_column_types: true # optional, default is false - if set to true, then SQRL_SEEDS__INFER_SCHEMA is ignored for this seed
|
|
5
5
|
|
|
6
6
|
columns:
|
|
7
7
|
- name: category_id
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
description: |
|
|
2
2
|
Lookup table for the subcategory IDs and names of transactions.
|
|
3
3
|
|
|
4
|
-
cast_column_types: true # optional, default is false -
|
|
4
|
+
cast_column_types: true # optional, default is false - if set to true, then SQRL_SEEDS__INFER_SCHEMA is ignored for this seed
|
|
5
5
|
|
|
6
6
|
columns:
|
|
7
7
|
- name: subcategory_id
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
project_variables:
|
|
2
|
-
name:
|
|
3
|
-
label: Sample
|
|
2
|
+
name: sample_expenses
|
|
3
|
+
label: "Sample Expenses"
|
|
4
4
|
description: This is a sample squirrels project for analyzing expense transactions
|
|
5
5
|
major_version: 1
|
|
6
6
|
|
|
@@ -21,51 +21,41 @@ packages: []
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
datasets:
|
|
24
|
-
- name:
|
|
25
|
-
label:
|
|
26
|
-
description:
|
|
24
|
+
- name: expense_transactions ## model name uses same name unless "model" field is specified
|
|
25
|
+
label: Expense Transactions - DBView Example
|
|
26
|
+
description: All expense transactions
|
|
27
27
|
model: dbview_example
|
|
28
|
-
scope:
|
|
28
|
+
scope: private ## optional - one of 'public' (default), 'protected', or 'private'
|
|
29
29
|
parameters: ## optional - if not specified, then all parameters are used
|
|
30
30
|
- start_date
|
|
31
31
|
- end_date
|
|
32
32
|
- min_filter
|
|
33
33
|
- max_filter
|
|
34
|
-
traits: {} ## optional - defaults to empty object
|
|
35
34
|
|
|
36
|
-
- name:
|
|
37
|
-
label:
|
|
35
|
+
- name: grouped_expenses
|
|
36
|
+
label: Grouped Expenses - Federate Example
|
|
38
37
|
description: Aggregated expense transactions by custom dimension using federate_example model
|
|
39
38
|
model: federate_example
|
|
40
|
-
scope:
|
|
39
|
+
scope: public ## using an auth.py file is suggested for protected or private datasets
|
|
41
40
|
parameters:
|
|
42
41
|
- group_by
|
|
43
|
-
- limit
|
|
44
42
|
- date_range
|
|
45
43
|
- category
|
|
46
44
|
- subcategory
|
|
47
45
|
- between_filter
|
|
48
|
-
default_test_set: auth_test1 ## optional - if not specified, uses setting 'selection_test_sets.default_name_used'
|
|
49
46
|
|
|
50
47
|
|
|
51
48
|
selection_test_sets:
|
|
52
|
-
- name:
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
parameters: ## optional section - if not provided, then assumes no parameters. For unspecified parameters, default value is used
|
|
56
|
-
start_date: 2024-07-01 ## this parameter only exists for dataset 'dataset_example'
|
|
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"
|
|
57
52
|
|
|
58
|
-
- name:
|
|
59
|
-
datasets:
|
|
60
|
-
- federate_dataset_example
|
|
61
|
-
user_attributes: ## optional section - required if using test set on non-public datasets
|
|
62
|
-
role: employee
|
|
53
|
+
- name: set_date_range
|
|
63
54
|
parameters:
|
|
64
|
-
date_range: [2024-02-01,2024-
|
|
55
|
+
date_range: [2024-02-01,2024-11-30] ## this parameter is only used by model "federate_example"
|
|
65
56
|
|
|
66
|
-
- name:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
role: manager
|
|
57
|
+
- name: use_admin_privileged_group_by
|
|
58
|
+
user:
|
|
59
|
+
access_level: admin
|
|
70
60
|
parameters:
|
|
71
|
-
group_by:
|
|
61
|
+
group_by: trans
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link id="favicon" rel="icon" type="image/x-icon" href="{{ sqrl_studio_base_url }}/favicon.ico" />
|
|
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_HOSTNAME = '';
|
|
11
|
+
window.DEFAULT_PROJECT_NAME = '{{ project_name }}';
|
|
12
|
+
window.DEFAULT_PROJECT_VERSION = '{{ project_version }}';
|
|
13
|
+
</script>
|
|
14
|
+
<script type="module" crossorigin src="{{ sqrl_studio_base_url }}/assets/index.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="{{ sqrl_studio_base_url }}/assets/index.css">
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="root"></div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
squirrels/_parameter_configs.py
CHANGED
|
@@ -11,7 +11,7 @@ import polars as pl, re
|
|
|
11
11
|
|
|
12
12
|
from . import _data_sources as d, _parameter_options as po, _parameters as p, _utils as u, _constants as c
|
|
13
13
|
from ._exceptions import InvalidInputError
|
|
14
|
-
from .
|
|
14
|
+
from ._schemas.auth_models import AbstractUser
|
|
15
15
|
from ._connection_set import ConnectionSet
|
|
16
16
|
from ._seeds import Seeds
|
|
17
17
|
|
|
@@ -56,12 +56,20 @@ class ParameterConfigBase(metaclass=ABCMeta):
|
|
|
56
56
|
user_attribute: str | None = field(default=None, kw_only=True)
|
|
57
57
|
parent_name: str | None = field(default=None, kw_only=True)
|
|
58
58
|
|
|
59
|
-
def _get_user_group(self, user:
|
|
60
|
-
if self.user_attribute is
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
def _get_user_group(self, user: AbstractUser) -> Any:
|
|
60
|
+
if self.user_attribute is None:
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
final_object = user
|
|
64
|
+
attribute = self.user_attribute
|
|
65
|
+
try:
|
|
66
|
+
if "." in attribute:
|
|
67
|
+
parts = attribute.split(".", 1)
|
|
68
|
+
final_object = getattr(final_object, parts[0])
|
|
69
|
+
attribute = parts[1]
|
|
70
|
+
return getattr(final_object, attribute)
|
|
71
|
+
except AttributeError:
|
|
72
|
+
raise u.ConfigurationError(f"User attribute '{self.user_attribute}' is not valid")
|
|
65
73
|
|
|
66
74
|
def copy(self):
|
|
67
75
|
"""
|
|
@@ -108,16 +116,16 @@ class ParameterConfig(Generic[ParamOptionType], ParameterConfigBase):
|
|
|
108
116
|
pass
|
|
109
117
|
|
|
110
118
|
def _invalid_input_error(self, selection: str, more_details: str = '') -> InvalidInputError:
|
|
111
|
-
return InvalidInputError(400, "
|
|
119
|
+
return InvalidInputError(400, "invalid_parameter_selection", f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details)
|
|
112
120
|
|
|
113
121
|
@abstractmethod
|
|
114
122
|
def with_selection(
|
|
115
|
-
self, selection: str | None, user:
|
|
123
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
116
124
|
) -> p.Parameter:
|
|
117
125
|
pass
|
|
118
126
|
|
|
119
127
|
def _get_options_iterator(
|
|
120
|
-
self, all_options: Sequence[ParamOptionType], user:
|
|
128
|
+
self, all_options: Sequence[ParamOptionType], user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
121
129
|
) -> Iterator[ParamOptionType]:
|
|
122
130
|
user_group = self._get_user_group(user)
|
|
123
131
|
selected_parent_option_ids = frozenset(parent_param._get_selected_ids_as_list()) if parent_param else None
|
|
@@ -153,7 +161,7 @@ class SelectionParameterConfig(ParameterConfig[po.SelectParameterOption]):
|
|
|
153
161
|
self.children[child.name] = child
|
|
154
162
|
self.trigger_refresh = True
|
|
155
163
|
|
|
156
|
-
def _get_options(self, user:
|
|
164
|
+
def _get_options(self, user: AbstractUser, parent_param: p._SelectionParameter | None) -> Sequence[po.SelectParameterOption]:
|
|
157
165
|
return tuple(self._get_options_iterator(self.all_options, user, parent_param))
|
|
158
166
|
|
|
159
167
|
def _get_default_ids_iterator(self, options: Sequence[po.SelectParameterOption]) -> Iterator[str]:
|
|
@@ -189,7 +197,7 @@ class SingleSelectParameterConfig(SelectionParameterConfig):
|
|
|
189
197
|
return d.SelectDataSource(*args, **kwargs)
|
|
190
198
|
|
|
191
199
|
def with_selection(
|
|
192
|
-
self, selection: str | None, user:
|
|
200
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
193
201
|
) -> p.SingleSelectParameter:
|
|
194
202
|
options = self._get_options(user, parent_param)
|
|
195
203
|
if selection is None:
|
|
@@ -237,7 +245,7 @@ class MultiSelectParameterConfig(SelectionParameterConfig):
|
|
|
237
245
|
return d.SelectDataSource(*args, **kwargs)
|
|
238
246
|
|
|
239
247
|
def with_selection(
|
|
240
|
-
self, selection: str | None, user:
|
|
248
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
241
249
|
) -> p.MultiSelectParameter:
|
|
242
250
|
options = self._get_options(user, parent_param)
|
|
243
251
|
if selection is None:
|
|
@@ -293,7 +301,7 @@ class DateParameterConfig(_DateTypeParameterConfig[po.DateParameterOption]):
|
|
|
293
301
|
return d.DateDataSource(*args, **kwargs)
|
|
294
302
|
|
|
295
303
|
def with_selection(
|
|
296
|
-
self, selection: str | None, user:
|
|
304
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
297
305
|
) -> p.DateParameter:
|
|
298
306
|
curr_option: po.DateParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
299
307
|
selected_date = curr_option._default_date if selection is None and curr_option is not None else selection
|
|
@@ -332,7 +340,7 @@ class DateRangeParameterConfig(_DateTypeParameterConfig[po.DateRangeParameterOpt
|
|
|
332
340
|
return d.DateRangeDataSource(*args, **kwargs)
|
|
333
341
|
|
|
334
342
|
def with_selection(
|
|
335
|
-
self, selection: str | None, user:
|
|
343
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
336
344
|
) -> p.DateRangeParameter:
|
|
337
345
|
curr_option: po.DateRangeParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
338
346
|
if selection is None:
|
|
@@ -395,7 +403,7 @@ class NumberParameterConfig(_NumericParameterConfig[po.NumberParameterOption]):
|
|
|
395
403
|
return d.NumberDataSource(*args, **kwargs)
|
|
396
404
|
|
|
397
405
|
def with_selection(
|
|
398
|
-
self, selection: str | None, user:
|
|
406
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
399
407
|
) -> p.NumberParameter:
|
|
400
408
|
curr_option: po.NumberParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
401
409
|
selected_value = curr_option._default_value if selection is None and curr_option is not None else selection
|
|
@@ -434,7 +442,7 @@ class NumberRangeParameterConfig(_NumericParameterConfig[po.NumberRangeParameter
|
|
|
434
442
|
return d.NumberRangeDataSource(*args, **kwargs)
|
|
435
443
|
|
|
436
444
|
def with_selection(
|
|
437
|
-
self, selection: str | None, user:
|
|
445
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
438
446
|
) -> p.NumberRangeParameter:
|
|
439
447
|
curr_option: po.NumberRangeParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
440
448
|
if selection is None:
|
|
@@ -524,7 +532,7 @@ class TextParameterConfig(ParameterConfig[po.TextParameterOption]):
|
|
|
524
532
|
return d.TextDataSource(*args, **kwargs)
|
|
525
533
|
|
|
526
534
|
def with_selection(
|
|
527
|
-
self, selection: str | None, user:
|
|
535
|
+
self, selection: str | None, user: AbstractUser, parent_param: p._SelectionParameter | None
|
|
528
536
|
) -> p.TextParameter:
|
|
529
537
|
curr_option: po.TextParameterOption | None = next(self._get_options_iterator(self.all_options, user, parent_param), None)
|
|
530
538
|
entered_text = curr_option._default_text if selection is None and curr_option is not None else selection
|
|
@@ -550,6 +558,8 @@ class DataSourceParameterConfig(Generic[ParamConfigType], ParameterConfigBase):
|
|
|
550
558
|
super().__init__(name, label, description=description, user_attribute=user_attribute, parent_name=parent_name)
|
|
551
559
|
self.parameter_type = parameter_type
|
|
552
560
|
if isinstance(data_source, dict):
|
|
561
|
+
if "source" in data_source:
|
|
562
|
+
data_source["source"] = d.SourceEnum(data_source["source"])
|
|
553
563
|
data_source = parameter_type.DataSource(**data_source)
|
|
554
564
|
self.data_source = data_source
|
|
555
565
|
self.extra_args = extra_args
|
|
@@ -557,15 +567,26 @@ class DataSourceParameterConfig(Generic[ParamConfigType], ParameterConfigBase):
|
|
|
557
567
|
def convert(self, df: pl.DataFrame) -> ParamConfigType:
|
|
558
568
|
return self.data_source._convert(self, df)
|
|
559
569
|
|
|
560
|
-
def get_dataframe(self, default_conn_name: str, conn_set: ConnectionSet, seeds: Seeds) -> pl.DataFrame:
|
|
570
|
+
def get_dataframe(self, default_conn_name: str, conn_set: ConnectionSet, seeds: Seeds, datalake_db_path: str = "") -> pl.DataFrame:
|
|
561
571
|
datasource = self.data_source
|
|
562
572
|
query = datasource._get_query()
|
|
563
|
-
if datasource.
|
|
573
|
+
if datasource._source == d.SourceEnum.SEEDS:
|
|
564
574
|
df = seeds.run_query(query)
|
|
565
|
-
|
|
575
|
+
elif datasource._source == d.SourceEnum.VDL:
|
|
576
|
+
vdl_conn = u.create_duckdb_connection(datalake_db_path)
|
|
577
|
+
try:
|
|
578
|
+
# Query the VDL database
|
|
579
|
+
df = vdl_conn.sql(query).pl()
|
|
580
|
+
except Exception as e:
|
|
581
|
+
raise u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}" from VDL') from e
|
|
582
|
+
finally:
|
|
583
|
+
vdl_conn.close()
|
|
584
|
+
else: # source == "connection"
|
|
585
|
+
conn_name = None
|
|
566
586
|
try:
|
|
567
587
|
conn_name = datasource._get_connection_name(default_conn_name)
|
|
568
588
|
df = conn_set.run_sql_query_from_conn_name(query, conn_name)
|
|
569
589
|
except RuntimeError as e:
|
|
570
|
-
|
|
590
|
+
ending = f' "{conn_name}"' if conn_name is not None else ""
|
|
591
|
+
raise u.ConfigurationError(f'Error executing query for datasource parameter "{self.name}" from connection{ending}') from e
|
|
571
592
|
return df
|
squirrels/_parameter_options.py
CHANGED
|
@@ -35,7 +35,7 @@ class ParameterOption(metaclass=ABCMeta):
|
|
|
35
35
|
|
|
36
36
|
Arguments:
|
|
37
37
|
user_group: The value of the user's "user group attribute". Only None when "user_attribute" is not specified
|
|
38
|
-
for the Parameter factory.
|
|
38
|
+
for the Parameter factory.
|
|
39
39
|
selected_parent_option_ids: List of selected option ids from the parent parameter. Only None when the Parameter
|
|
40
40
|
object has no parent parameter.
|
|
41
41
|
|
squirrels/_parameter_sets.py
CHANGED
|
@@ -10,7 +10,7 @@ from ._arguments.init_time_args import ParametersArgs
|
|
|
10
10
|
from ._manifest import ParametersConfig, ManifestConfig
|
|
11
11
|
from ._connection_set import ConnectionSet
|
|
12
12
|
from ._seeds import Seeds
|
|
13
|
-
from .
|
|
13
|
+
from ._schemas.auth_models import AbstractUser
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
@dataclass
|
|
@@ -104,13 +104,10 @@ class ParameterConfigsSet:
|
|
|
104
104
|
self.__validate_param_relationships()
|
|
105
105
|
|
|
106
106
|
def apply_selections(
|
|
107
|
-
self, dataset_params: Optional[Sequence[str]], selections: dict[str, Any], user:
|
|
107
|
+
self, dataset_params: Optional[Sequence[str]], selections: dict[str, Any], user: AbstractUser, *, parent_param: str | None = None
|
|
108
108
|
) -> ParameterSet:
|
|
109
109
|
if dataset_params is None:
|
|
110
|
-
|
|
111
|
-
dataset_params = [k for k, v in self._data.items() if v.user_attribute is None]
|
|
112
|
-
else:
|
|
113
|
-
dataset_params = list(self._data.keys())
|
|
110
|
+
dataset_params = list(self._data.keys())
|
|
114
111
|
|
|
115
112
|
parameters_by_name: dict[str, p.Parameter] = {}
|
|
116
113
|
params_to_process = [parent_param] if parent_param else dataset_params
|
|
@@ -158,11 +155,11 @@ class ParameterConfigsSetIO:
|
|
|
158
155
|
|
|
159
156
|
@classmethod
|
|
160
157
|
def _get_df_dict_from_data_sources(
|
|
161
|
-
cls, param_configs_set: ParameterConfigsSet, default_conn_name: str, seeds: Seeds, conn_set: ConnectionSet
|
|
158
|
+
cls, param_configs_set: ParameterConfigsSet, default_conn_name: str, seeds: Seeds, conn_set: ConnectionSet, datalake_db_path: str
|
|
162
159
|
) -> dict[str, pl.DataFrame]:
|
|
163
160
|
|
|
164
161
|
def get_dataframe(ds_param_config: pc.DataSourceParameterConfig) -> tuple[str, pl.DataFrame]:
|
|
165
|
-
return ds_param_config.name, ds_param_config.get_dataframe(default_conn_name, conn_set, seeds)
|
|
162
|
+
return ds_param_config.name, ds_param_config.get_dataframe(default_conn_name, conn_set, seeds, datalake_db_path)
|
|
166
163
|
|
|
167
164
|
ds_param_configs = param_configs_set._get_all_ds_param_configs()
|
|
168
165
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
@@ -179,7 +176,8 @@ class ParameterConfigsSetIO:
|
|
|
179
176
|
|
|
180
177
|
@classmethod
|
|
181
178
|
def load_from_file(
|
|
182
|
-
cls, logger: u.Logger, base_path: str, manifest_cfg: ManifestConfig, seeds: Seeds, conn_set: ConnectionSet, param_args: ParametersArgs
|
|
179
|
+
cls, logger: u.Logger, base_path: str, manifest_cfg: ManifestConfig, seeds: Seeds, conn_set: ConnectionSet, param_args: ParametersArgs,
|
|
180
|
+
datalake_db_path: str
|
|
183
181
|
) -> ParameterConfigsSet:
|
|
184
182
|
start = time.time()
|
|
185
183
|
param_configs_set = ParameterConfigsSet()
|
|
@@ -199,7 +197,7 @@ class ParameterConfigsSetIO:
|
|
|
199
197
|
param_configs_set.add(param_config)
|
|
200
198
|
|
|
201
199
|
default_conn_name = manifest_cfg.env_vars.get(c.SQRL_CONNECTIONS_DEFAULT_NAME_USED, "default")
|
|
202
|
-
df_dict = cls._get_df_dict_from_data_sources(param_configs_set, default_conn_name, seeds, conn_set)
|
|
200
|
+
df_dict = cls._get_df_dict_from_data_sources(param_configs_set, default_conn_name, seeds, conn_set, datalake_db_path)
|
|
203
201
|
param_configs_set._post_process_params(df_dict)
|
|
204
202
|
|
|
205
203
|
logger.log_activity_time("loading parameters", start)
|