squirrels 0.2.1__py3-none-any.whl → 0.3.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.

Potentially problematic release.


This version of squirrels might be problematic. Click here for more details.

Files changed (48) hide show
  1. squirrels/__init__.py +11 -4
  2. squirrels/_api_response_models.py +118 -0
  3. squirrels/_api_server.py +140 -75
  4. squirrels/_authenticator.py +10 -8
  5. squirrels/_command_line.py +17 -11
  6. squirrels/_connection_set.py +2 -2
  7. squirrels/_constants.py +13 -5
  8. squirrels/_initializer.py +23 -13
  9. squirrels/_manifest.py +20 -10
  10. squirrels/_models.py +303 -148
  11. squirrels/_parameter_configs.py +195 -57
  12. squirrels/_parameter_sets.py +14 -17
  13. squirrels/_py_module.py +2 -4
  14. squirrels/_seeds.py +38 -0
  15. squirrels/_utils.py +41 -33
  16. squirrels/arguments/run_time_args.py +76 -34
  17. squirrels/data_sources.py +172 -51
  18. squirrels/dateutils.py +3 -3
  19. squirrels/package_data/assets/index.js +14 -14
  20. squirrels/package_data/base_project/connections.yml +1 -1
  21. squirrels/package_data/base_project/database/expenses.db +0 -0
  22. squirrels/package_data/base_project/docker/Dockerfile +1 -1
  23. squirrels/package_data/base_project/environcfg.yml +7 -7
  24. squirrels/package_data/base_project/models/dbviews/database_view1.py +25 -14
  25. squirrels/package_data/base_project/models/dbviews/database_view1.sql +21 -14
  26. squirrels/package_data/base_project/models/federates/dataset_example.py +6 -5
  27. squirrels/package_data/base_project/models/federates/dataset_example.sql +1 -1
  28. squirrels/package_data/base_project/parameters.yml +57 -28
  29. squirrels/package_data/base_project/pyconfigs/auth.py +11 -10
  30. squirrels/package_data/base_project/pyconfigs/connections.py +6 -8
  31. squirrels/package_data/base_project/pyconfigs/context.py +49 -33
  32. squirrels/package_data/base_project/pyconfigs/parameters.py +62 -30
  33. squirrels/package_data/base_project/seeds/seed_categories.csv +6 -0
  34. squirrels/package_data/base_project/seeds/seed_subcategories.csv +15 -0
  35. squirrels/package_data/base_project/squirrels.yml.j2 +37 -20
  36. squirrels/parameter_options.py +30 -10
  37. squirrels/parameters.py +300 -70
  38. squirrels/user_base.py +3 -13
  39. squirrels-0.3.0.dist-info/LICENSE +201 -0
  40. {squirrels-0.2.1.dist-info → squirrels-0.3.0.dist-info}/METADATA +15 -15
  41. squirrels-0.3.0.dist-info/RECORD +56 -0
  42. {squirrels-0.2.1.dist-info → squirrels-0.3.0.dist-info}/WHEEL +1 -1
  43. squirrels/package_data/base_project/seeds/mocks/category.csv +0 -3
  44. squirrels/package_data/base_project/seeds/mocks/max_filter.csv +0 -2
  45. squirrels/package_data/base_project/seeds/mocks/subcategory.csv +0 -6
  46. squirrels-0.2.1.dist-info/LICENSE +0 -22
  47. squirrels-0.2.1.dist-info/RECORD +0 -55
  48. {squirrels-0.2.1.dist-info → squirrels-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -2,6 +2,6 @@
2
2
  connections:
3
3
  - name: default
4
4
  credential: null
5
- url: {{ sqlite_conn_str }} ## using Jinja to substitute environment variable from environcfg.yml
5
+ url: {{ env_vars.sqlite_conn_str }} ## using Jinja to substitute environment variable from environcfg.yml
6
6
 
7
7
 
@@ -3,7 +3,7 @@ FROM python:3.11-slim
3
3
  WORKDIR /app
4
4
 
5
5
  # Needed if any python dependencies are installed from git, or for
6
- # "squirrels deps" if there are modules in "squirrels.yml"
6
+ # "squirrels deps" command if there are packages defined in "squirrels.yml"
7
7
  RUN apt-get update && apt-get install -y git
8
8
 
9
9
  COPY requirements-lock.txt .
@@ -1,18 +1,18 @@
1
1
  ## Note: You can copy this file to the .squirrels folder in your home directory to make
2
2
  ## the configurations global for all squirrels projects on the current machine
3
3
 
4
- ## Fake users for local development testing
4
+ ## Fake users for local development testing. Must have an 'auth.py' file with a 'User' model to use custom attributes like 'role'
5
5
  users:
6
- johndoe:
6
+ alice:
7
7
  is_internal: True
8
8
  password: I<3Squirrels
9
- full_name: John Doe
10
- organization: org1
11
- mattdoe:
9
+ full_name: Alice Doe
10
+ role: employee
11
+ bob:
12
12
  is_internal: False
13
13
  password: abcd5678
14
- full_name: Matt Doe
15
- organization: org2
14
+ full_name: Bob Doe
15
+ role: customer
16
16
 
17
17
  ## Custom environment variables / secrets
18
18
  env_vars:
@@ -1,35 +1,46 @@
1
1
  from textwrap import dedent
2
- import pandas as pd, squirrels as sr
2
+ from squirrels import ModelArgs
3
+ import pandas as pd
3
4
 
4
5
 
5
- def main(sqrl: sr.ModelArgs) -> pd.DataFrame:
6
+ def main(sqrl: ModelArgs) -> pd.DataFrame:
6
7
  """
7
8
  Create a database view model in Python by sending an external query to a database or API, and return a
8
9
  pandas DataFrame of the result in this function. Since the result is loaded into server memory, be mindful of
9
10
  the size of the results coming from the external query.
10
11
  """
11
12
 
12
- """ If working with sqlalchemy ORMs, use 'sqrl.connections' to get a sqlalchemy engine """
13
+ ## If working with sqlalchemy ORMs, use 'sqrl.connections' to get a sqlalchemy engine
13
14
  # from typing import Union
14
15
  # engine1 = sqrl.connections[sqrl.connection_name] ## using the pre-assigned key
15
16
  # engine2 = sqrl.connections["my_connection_name"] ## or use any defined key
16
17
 
17
- """ Example with building and running a sql query """
18
- category_clause = f'AND category IN ({sqrl.ctx["categories"]})\n' if sqrl.ctx["has_categories"] else ''
19
- subcategory_clause = f'AND subcategory IN ({sqrl.ctx["subcategories"]})\n' if sqrl.ctx["has_subcategories"] else ''
18
+ ## Example with building and running a sql query
19
+ masked_id = "id" if getattr(sqrl.user, "role", "") == "manager" else "'***'"
20
+ desc_cond = "description LIKE :desc_pattern" if sqrl.is_placeholder("desc_pattern") else "true"
21
+ category_cond = f"category IN ({sqrl.ctx['categories']})" if sqrl.ctx["has_categories"] else "true"
22
+ subcategory_cond = f"subcategory IN ({sqrl.ctx['subcategories']})" if sqrl.ctx["has_subcategories"] else "true"
20
23
  query = dedent(f"""
21
- SELECT {sqrl.ctx["group_by_cols"]}
24
+ WITH
25
+ transactions_with_masked_id AS (
26
+ SELECT *,
27
+ {masked_id} as masked_id
28
+ FROM transactions
29
+ )
30
+ SELECT {sqrl.ctx["select_dim_cols"]}
22
31
  , sum(-amount) as total_amount
23
- FROM transactions
24
- WHERE 1=1
25
- {category_clause}{subcategory_clause}AND date >= {sqrl.ctx["start_date"]}
26
- AND date <= {sqrl.ctx["end_date"]}
27
- AND -amount >= {sqrl.ctx["min_amount"]}
28
- AND -amount <= {sqrl.ctx["max_amount"]}
32
+ FROM transactions_with_masked_id
33
+ WHERE date >= :start_date
34
+ AND date <= :end_date
35
+ AND -amount >= :min_amount
36
+ AND -amount <= :max_amount
37
+ AND {desc_cond}
38
+ AND {category_cond}
39
+ AND {subcategory_cond}
29
40
  GROUP BY {sqrl.ctx["group_by_cols"]}
30
41
  """).strip()
31
42
 
32
43
  return sqrl.run_external_sql(query)
33
44
 
34
- """ A 'connection_name' argument is available to use a different connection key """
45
+ ## A 'connection_name' argument is available to use a different connection key
35
46
  # return sqrl.run_external_sql(query, connection_name="different_key")
@@ -1,15 +1,22 @@
1
- SELECT {{ ctx["group_by_cols"] }}
2
- , sum(-amount) as total_amount
3
- FROM transactions
4
- WHERE 1=1
5
- {%- if ctx["has_categories"] %}
6
- AND category IN ({{ ctx["categories"] }})
7
- {%- endif %}
8
- {%- if ctx["has_subcategories"] %}
9
- AND subcategory IN ({{ ctx["subcategories"] }})
1
+ WITH
2
+ transactions_with_masked_id AS (
3
+ SELECT *,
4
+ {%- if user.role == "manager" %}
5
+ id as masked_id
6
+ {%- else %}
7
+ '***' as masked_id
10
8
  {%- endif %}
11
- AND date >= {{ ctx["start_date"] }}
12
- AND date <= {{ ctx["end_date"] }}
13
- AND -amount >= {{ ctx["min_amount"] }}
14
- AND -amount <= {{ ctx["max_amount"] }}
15
- GROUP BY {{ ctx["group_by_cols"] }}
9
+ FROM transactions
10
+ )
11
+ SELECT {{ ctx.select_dim_cols }}
12
+ , sum(-amount) as total_amount
13
+ FROM transactions_with_masked_id
14
+ WHERE true
15
+ {% if is_placeholder("start_date") -%} AND date >= :start_date {%- endif %}
16
+ {% if is_placeholder("end_date") -%} AND date <= :end_date {%- endif %}
17
+ {% if is_placeholder("min_amount") -%} AND -amount >= :min_amount {%- endif %}
18
+ {% if is_placeholder("max_amount") -%} AND -amount <= :max_amount {%- endif %}
19
+ {% if is_placeholder("desc_pattern") -%} AND description LIKE :desc_pattern {%- endif %}
20
+ {% if ctx.has_categories -%} AND category IN ({{ ctx.categories }}) {%- endif %}
21
+ {% if ctx.has_subcategories -%} AND subcategory IN ({{ ctx.subcategories }}) {%- endif %}
22
+ GROUP BY {{ ctx.group_by_cols }}
@@ -1,8 +1,9 @@
1
1
  from typing import Iterable
2
- import pandas as pd, squirrels as sr
2
+ from squirrels import ModelDepsArgs, ModelArgs
3
+ import pandas as pd
3
4
 
4
5
 
5
- def dependencies(sqrl: sr.ModelDepsArgs) -> Iterable[str]:
6
+ def dependencies(sqrl: ModelDepsArgs) -> Iterable[str]:
6
7
  """
7
8
  Define list of dependent models here. This will determine the dependencies first, at compile-time,
8
9
  before running the model.
@@ -10,11 +11,11 @@ def dependencies(sqrl: sr.ModelDepsArgs) -> Iterable[str]:
10
11
  return ["database_view1"]
11
12
 
12
13
 
13
- def main(sqrl: sr.ModelArgs) -> pd.DataFrame:
14
+ def main(sqrl: ModelArgs) -> pd.DataFrame:
14
15
  """
15
16
  Create federated models by joining/processing dependent database views and/or other federated models to
16
17
  form and return the result as a new pandas DataFrame.
17
18
  """
18
19
  df = sqrl.ref("database_view1")
19
- group_by_cols: str = sqrl.ctx["group_by_cols_list"]
20
- return df.sort_values(group_by_cols, ascending=False)
20
+ order_by_cols: str = sqrl.ctx["order_by_cols_list"]
21
+ return df.sort_values(order_by_cols, ascending=False)
@@ -1,3 +1,3 @@
1
1
  SELECT *
2
2
  FROM {{ ref("database_view1") }}
3
- ORDER BY {{ ctx["order_by_cols"] }}
3
+ ORDER BY {{ ctx.order_by_cols }}
@@ -1,16 +1,18 @@
1
1
  parameters:
2
2
  - type: SingleSelectParameter
3
- factory: Create ## one of 'Create', 'CreateSimple', or 'CreateFromSource'
4
- arguments: ## arguments to specify depend on values for 'type' and 'factory'
3
+ factory: Create ## one of 'Create', 'CreateSimple', or 'CreateFromSource'
4
+ arguments: ## arguments to specify depend on values for 'type' and 'factory'
5
5
  name: group_by
6
6
  label: Group By
7
+ description: Dimension to aggregate by ## optional, default is empty string
7
8
  all_options:
8
9
  - id: g0
9
10
  label: Transaction
10
- columns: [id, date] ## custom field
11
- is_default: false ## optional, default, exists for SingleSelect or MultiSelect options only
12
- user_groups: [] ## optional, default, exists for all parameter options
13
- parent_option_ids: [] ## optional, default, exists for all parameter options
11
+ columns: ["masked_id", "date", "description"] ## custom field
12
+ aliases: ["id", "date", "description"] ## custom field
13
+ is_default: false ## optional, default, exists for SingleSelect or MultiSelect options only
14
+ user_groups: [] ## optional, default, exists for all parameter options
15
+ parent_option_ids: [] ## optional, default, exists for all parameter options
14
16
  - id: g1
15
17
  label: Date
16
18
  columns: [date]
@@ -20,90 +22,117 @@ parameters:
20
22
  - id: g3
21
23
  label: Subcategory
22
24
  columns: [category, subcategory]
23
- is_hidden: false ## optional, default, exists for all parameter types
24
- user_attribute: null ## optional, default, exists for all parameter types
25
- parent_name: null ## optional, default, exists for all parameter types
25
+ user_attribute: null ## optional, default, exists for all parameter types
26
+ parent_name: null ## optional, default, exists for all parameter types
27
+
28
+ - type: TextParameter
29
+ factory: Create
30
+ arguments:
31
+ name: description_filter
32
+ label: Description Contains
33
+ description: Filter by transactions with this description
34
+ parent_name: group_by
35
+ all_options:
36
+ - parent_option_ids: g0
37
+
26
38
  - type: DateParameter
27
39
  factory: Create
28
40
  arguments:
29
41
  name: start_date
30
42
  label: Start Date
43
+ description: Filter by transactions after this date
31
44
  all_options:
32
45
  - default_date: 2023-01-01
33
- date_format: '%Y-%m-%d' ## optional, default, format comes from python datetime, exists for Date and DateRange parameter options
46
+ date_format: '%Y-%m-%d' ## optional, default, format comes from python datetime, exists for Date and DateRange parameter options
47
+
34
48
  - type: DateParameter
35
49
  factory: Create
36
50
  arguments:
37
51
  name: end_date
38
52
  label: End Date
53
+ description: Filter by transactions before this date
39
54
  all_options:
40
55
  - default_date: 2023-12-31
56
+
41
57
  - type: DateRangeParameter
42
58
  factory: Create
43
59
  arguments:
44
60
  name: date_range
45
61
  label: Date Range
62
+ description: Filter by transactions within this date range
46
63
  all_options:
47
64
  - default_start_date: 2023-01-01
48
65
  default_end_date: 2023-12-31
66
+
49
67
  - type: MultiSelectParameter
50
68
  factory: CreateFromSource
51
69
  arguments:
52
70
  name: category
53
71
  label: Category Filter
72
+ description: The expense categories to filter by
54
73
  data_source:
55
- table_or_query: categories
74
+ table_or_query: seed_categories
56
75
  id_col: category_id
57
76
  options_col: category
58
- order_by_col: null ## optional, default, exists for SingleSelect and MultiSelect
59
- is_default_col: null ## optional, default, exists for SingleSelect and MultiSelect
60
- custom_cols: {} ## optional, default, exists for SingleSelect and MultiSelect
61
- include_all: true ## optional, default, exists for MultiSelect only
62
- order_matters: false ## optional, default, exists for MultiSelect only
63
- user_group_col: null ## optional, default, exists for all parameters
64
- connection_name: default ## optional, default, exists for all parameters
77
+ from_seeds: true ## optional, default is false, exists for data_source of any parameters
78
+ order_by_col: null ## optional, default, exists for data_source of SingleSelect and MultiSelect
79
+ is_default_col: null ## optional, default, exists for data_source of SingleSelect and MultiSelect
80
+ custom_cols: {} ## optional, default, exists for data_source of SingleSelect and MultiSelect
81
+ include_all: true ## optional, default, exists for data_source of MultiSelect only
82
+ order_matters: false ## optional, default, exists for data_source of MultiSelect only
83
+ user_group_col: null ## optional, default, exists for data_source of any parameters
84
+ connection_name: default ## optional, default, exists for data_source of any parameters
85
+
65
86
  - type: MultiSelectParameter
66
87
  factory: CreateFromSource
67
88
  arguments:
68
89
  name: subcategory
69
90
  label: Subcategory Filter
91
+ description: The expense subcategories to filter by (available options based on selected 'Category Filter')
92
+ parent_name: category
70
93
  data_source:
71
- table_or_query: subcategories
94
+ table_or_query: seed_subcategories
72
95
  id_col: subcategory_id
73
96
  options_col: subcategory
74
- parent_id_col: category_id ## optional, default is null, exists for all parameter types
75
- parent_name: category
97
+ from_seeds: true
98
+ parent_id_col: category_id ## optional, default is null, exists for all parameter types
99
+
76
100
  - type: NumberParameter
77
101
  factory: Create
78
102
  arguments:
79
103
  name: min_filter
80
104
  label: Amounts Greater Than
105
+ description: Filter by transactions greater than this amount
81
106
  all_options:
82
107
  - min_value: 0
83
108
  max_value: 500
84
- increment: 1 ## optional, default, exists for Number and NumRange options
85
- default_value: null ## optional, default, exists for Number options only
109
+ increment: 10 ## optional, default is 1, exists for Number and NumberRange options
110
+ default_value: null ## optional, default, exists for Number options only
111
+
86
112
  - type: NumberParameter
87
113
  factory: CreateFromSource
88
114
  arguments:
89
115
  name: max_filter
90
116
  label: Amounts Less Than
117
+ description: Filter by transactions less than this amount
91
118
  data_source:
92
119
  table_or_query: "SELECT 0 as min_value, max(-amount) as max_value, 10 as increment FROM transactions WHERE category <> 'Income'"
93
120
  min_value_col: min_value
94
121
  max_value_col: max_value
95
- increment_col: increment ## optional, default is null
96
- default_value_col: max_value ## optional, default is null
97
- id_col: null ## optional, default, required for SingleSelect and MultiSelect, optional for all others
122
+ increment_col: increment ## optional, default is null
123
+ default_value_col: max_value ## optional, default is null
124
+ id_col: null ## optional, default, required for SingleSelect and MultiSelect, optional for all others
125
+
98
126
  - type: NumberRangeParameter
99
127
  factory: Create
100
128
  arguments:
101
129
  name: between_filter
102
130
  label: Amounts Between
131
+ description: Filter by transaction amounts within this range
103
132
  all_options:
104
133
  - min_value: 0
105
134
  max_value: 500
106
- default_lower_value: 10 ## optional, default is null (or min_value), exists for NumRange options only
107
- default_upper_value: 400 ## optional, default is null (or max_value), exists for NumRange options only
135
+ default_lower_value: 10 ## optional, default is null (or min_value), exists for NumRange options only
136
+ default_upper_value: 400 ## optional, default is null (or max_value), exists for NumRange options only
108
137
 
109
138
 
@@ -1,13 +1,14 @@
1
- from typing import Union, Any
1
+ from typing import Union
2
2
  from squirrels import User as UserBase, AuthArgs, WrongPassword
3
3
 
4
4
 
5
5
  class User(UserBase):
6
- def set_attributes(self, user_dict: dict[str, Any]) -> None:
6
+ def set_attributes(self, **kwargs) -> None:
7
7
  """
8
- Use this method to add custom attributes in the User model that don't exist in UserBase (username, is_internal, etc.)
8
+ Use this method to add custom attributes in the User model that don't exist in UserBase
9
+ (i.e., anything that's not 'username' or 'is_internal')
9
10
  """
10
- self.organization = user_dict["organization"]
11
+ self.role = kwargs["role"]
11
12
 
12
13
 
13
14
  def get_user_if_valid(sqrl: AuthArgs) -> Union[User, WrongPassword, None]:
@@ -23,22 +24,22 @@ def get_user_if_valid(sqrl: AuthArgs) -> Union[User, WrongPassword, None]:
23
24
  "johndoe": {
24
25
  "username": "johndoe",
25
26
  "is_admin": True,
26
- "organization": "org1",
27
+ "role": "manager",
27
28
  "hashed_password": str(hash("I<3Squirrels"))
28
29
  },
29
30
  "mattdoe": {
30
31
  "username": "mattdoe",
31
32
  "is_admin": False,
32
- "organization": "org2",
33
+ "role": "customer",
33
34
  "hashed_password": str(hash("abcd5678"))
34
35
  }
35
36
  }
36
37
 
37
- user_dict = mock_users_db.get(sqrl.username)
38
- if user_dict is None:
38
+ user_obj = mock_users_db.get(sqrl.username)
39
+ if user_obj is None:
39
40
  return None
40
41
 
41
- if str(hash(sqrl.password)) == user_dict["hashed_password"]:
42
- return User.Create(sqrl.username, user_dict, is_internal=user_dict["is_admin"])
42
+ if str(hash(sqrl.password)) == user_obj["hashed_password"]:
43
+ return User.Create(sqrl.username, is_internal=user_obj["is_admin"], role=user_obj["role"])
43
44
  else:
44
45
  return WrongPassword()
@@ -1,21 +1,19 @@
1
1
  from sqlalchemy import create_engine, Engine
2
- import squirrels as sr
2
+ from squirrels import ConnectionsArgs
3
3
 
4
4
 
5
- def main(connections: dict[str, Engine], sqrl: sr.ConnectionsArgs) -> None:
5
+ def main(connections: dict[str, Engine], sqrl: ConnectionsArgs) -> None:
6
6
  """
7
7
  Define sqlalchemy engines by adding them to the "connections" dictionary
8
8
  """
9
9
 
10
- """ Example of getting the username and password """
11
- # username, password = sqrl.get_credential('my_key')
12
-
13
- """ SQLAlchemy URL for a connection engine """
10
+ ## SQLAlchemy URL for a connection engine
14
11
  conn_str = 'sqlite:///database/expenses.db'
15
12
 
16
- """ Can also leverage environment variables in the environcfg.yml file for connection details """
13
+ ## Can also leverage environment variables and credentials in the environcfg.yml file for connection details
17
14
  # conn_str_raw: str = sqrl.env_vars["sqlite_conn_str"]
15
+ # username, password = sqrl.get_credential('my_key')
18
16
  # conn_str = conn_str_raw.format(username=username, password=password)
19
17
 
20
- """ Assigning names to connection engines """
18
+ ## Assigning names to connection engines
21
19
  connections["default"] = create_engine(conn_str)
@@ -1,8 +1,8 @@
1
1
  from typing import Any
2
- import squirrels as sr
2
+ from squirrels import ContextArgs, parameters as p
3
3
 
4
4
 
5
- def main(ctx: dict[str, Any], sqrl: sr.ContextArgs) -> None:
5
+ def main(ctx: dict[str, Any], sqrl: ContextArgs) -> None:
6
6
  """
7
7
  Define context variables AFTER parameter selections are made by adding entries to the dictionary "ctx".
8
8
  These context variables can then be used in the models.
@@ -10,46 +10,62 @@ def main(ctx: dict[str, Any], sqrl: sr.ContextArgs) -> None:
10
10
  Note that the code here is used by all datasets, regardless of the parameters they use. You can use
11
11
  sqrl.prms and/or sqrl.traits to determine the conditions to execute certain blocks of code.
12
12
  """
13
+ if sqrl.param_exists("group_by"):
14
+ group_by_param: p.SingleSelectParameter = sqrl.prms["group_by"]
15
+ columns = group_by_param.get_selected("columns")
16
+ aliases = group_by_param.get_selected("aliases", default_field="columns")
13
17
 
14
- if "group_by" in sqrl.prms:
15
- group_by_param: sr.SingleSelectParameter = sqrl.prms["group_by"]
16
- ctx["group_by_cols_list"] = group_by_param.get_selected("columns")
17
- ctx["group_by_cols"] = ",".join(ctx["group_by_cols_list"])
18
- ctx["order_by_cols"] = ",".join((x+" DESC") for x in ctx["group_by_cols_list"])
18
+ ctx["select_dim_cols"] = ", ".join(x+" as "+y for x, y in zip(columns, aliases))
19
+ ctx["group_by_cols"] = ", ".join(columns)
20
+ ctx["order_by_cols"] = ", ".join((x+" DESC") for x in aliases)
21
+ ctx["order_by_cols_list"] = aliases
19
22
 
20
- if "start_date" in sqrl.prms:
21
- start_date_param: sr.DateParameter = sqrl.prms["start_date"]
22
- ctx["start_date"] = start_date_param.get_selected_date_quoted()
23
+ if sqrl.param_exists("description_filter"):
24
+ descript_param: p.TextParameter = sqrl.prms["description_filter"]
25
+ desc_pattern = descript_param.get_entered_text().apply_percent_wrap()
26
+ sqrl.set_placeholder("desc_pattern", desc_pattern)
27
+
28
+ if sqrl.param_exists("start_date"):
29
+ start_date_param: p.DateParameter = sqrl.prms["start_date"]
30
+ start_date = start_date_param.get_selected_date()
31
+ sqrl.set_placeholder("start_date", start_date)
23
32
 
24
- if "end_date" in sqrl.prms:
25
- end_date_param: sr.DateParameter = sqrl.prms["end_date"]
26
- ctx["end_date"] = end_date_param.get_selected_date_quoted()
27
-
28
- if "date_range" in sqrl.prms:
29
- date_range_param: sr.DateRangeParameter = sqrl.prms["date_range"]
30
- ctx["start_date"] = date_range_param.get_selected_start_date_quoted()
31
- ctx["end_date"] = date_range_param.get_selected_end_date_quoted()
33
+ if sqrl.param_exists("end_date"):
34
+ end_date_param: p.DateParameter = sqrl.prms["end_date"]
35
+ end_date = end_date_param.get_selected_date()
36
+ sqrl.set_placeholder("end_date", end_date)
37
+
38
+ if sqrl.param_exists("date_range"):
39
+ date_range_param: p.DateRangeParameter = sqrl.prms["date_range"]
40
+ start_date = date_range_param.get_selected_start_date()
41
+ end_date = date_range_param.get_selected_end_date()
42
+ sqrl.set_placeholder("start_date", start_date)
43
+ sqrl.set_placeholder("end_date", end_date)
32
44
 
33
- if "category" in sqrl.prms:
34
- category_param: sr.MultiSelectParameter = sqrl.prms["category"]
45
+ if sqrl.param_exists("category"):
46
+ category_param: p.MultiSelectParameter = sqrl.prms["category"]
35
47
  ctx["has_categories"] = category_param.has_non_empty_selection()
36
48
  ctx["categories"] = category_param.get_selected_labels_quoted_joined()
37
49
 
38
- if "subcategory" in sqrl.prms:
39
- subcategory_param: sr.MultiSelectParameter = sqrl.prms["subcategory"]
50
+ if sqrl.param_exists("subcategory"):
51
+ subcategory_param: p.MultiSelectParameter = sqrl.prms["subcategory"]
40
52
  ctx["has_subcategories"] = subcategory_param.has_non_empty_selection()
41
53
  ctx["subcategories"] = subcategory_param.get_selected_labels_quoted_joined()
42
54
 
43
- if "min_filter" in sqrl.prms:
44
- min_amount_filter: sr.NumberParameter = sqrl.prms["min_filter"]
45
- ctx["min_amount"] = min_amount_filter.get_selected_value()
55
+ if sqrl.param_exists("min_filter"):
56
+ min_amount_filter: p.NumberParameter = sqrl.prms["min_filter"]
57
+ min_amount = min_amount_filter.get_selected_value()
58
+ sqrl.set_placeholder("min_amount", min_amount)
46
59
 
47
- if "max_filter" in sqrl.prms:
48
- max_amount_filter: sr.NumberParameter = sqrl.prms["max_filter"]
49
- ctx["max_amount"] = max_amount_filter.get_selected_value()
50
-
51
- if "between_filter" in sqrl.prms:
52
- between_filter: sr.NumberRangeParameter = sqrl.prms["between_filter"]
53
- ctx["min_amount"] = between_filter.get_selected_lower_value()
54
- ctx["max_amount"] = between_filter.get_selected_upper_value()
60
+ if sqrl.param_exists("max_filter"):
61
+ max_amount_filter: p.NumberParameter = sqrl.prms["max_filter"]
62
+ max_amount = max_amount_filter.get_selected_value()
63
+ sqrl.set_placeholder("max_amount", max_amount)
64
+
65
+ if sqrl.param_exists("between_filter"):
66
+ between_filter: p.NumberRangeParameter = sqrl.prms["between_filter"]
67
+ min_amount = between_filter.get_selected_lower_value()
68
+ max_amount = between_filter.get_selected_upper_value()
69
+ sqrl.set_placeholder("min_amount", min_amount)
70
+ sqrl.set_placeholder("max_amount", max_amount)
55
71