squirrels 0.4.0__py3-none-any.whl → 0.5.0b1__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 (80) hide show
  1. squirrels/__init__.py +10 -6
  2. squirrels/_api_response_models.py +93 -44
  3. squirrels/_api_server.py +571 -219
  4. squirrels/_auth.py +451 -0
  5. squirrels/_command_line.py +61 -20
  6. squirrels/_connection_set.py +38 -25
  7. squirrels/_constants.py +44 -34
  8. squirrels/_dashboards_io.py +34 -16
  9. squirrels/_exceptions.py +57 -0
  10. squirrels/_initializer.py +117 -44
  11. squirrels/_manifest.py +124 -62
  12. squirrels/_model_builder.py +111 -0
  13. squirrels/_model_configs.py +74 -0
  14. squirrels/_model_queries.py +52 -0
  15. squirrels/_models.py +860 -354
  16. squirrels/_package_loader.py +8 -4
  17. squirrels/_parameter_configs.py +45 -65
  18. squirrels/_parameter_sets.py +15 -13
  19. squirrels/_project.py +561 -0
  20. squirrels/_py_module.py +4 -3
  21. squirrels/_seeds.py +35 -16
  22. squirrels/_sources.py +106 -0
  23. squirrels/_utils.py +166 -63
  24. squirrels/_version.py +1 -1
  25. squirrels/arguments/init_time_args.py +78 -15
  26. squirrels/arguments/run_time_args.py +62 -101
  27. squirrels/dashboards.py +4 -4
  28. squirrels/data_sources.py +94 -162
  29. squirrels/dataset_result.py +86 -0
  30. squirrels/dateutils.py +4 -4
  31. squirrels/package_data/base_project/.env +30 -0
  32. squirrels/package_data/base_project/.env.example +30 -0
  33. squirrels/package_data/base_project/.gitignore +3 -2
  34. squirrels/package_data/base_project/assets/expenses.db +0 -0
  35. squirrels/package_data/base_project/connections.yml +11 -3
  36. squirrels/package_data/base_project/dashboards/dashboard_example.py +15 -13
  37. squirrels/package_data/base_project/dashboards/dashboard_example.yml +22 -0
  38. squirrels/package_data/base_project/docker/.dockerignore +5 -2
  39. squirrels/package_data/base_project/docker/Dockerfile +3 -3
  40. squirrels/package_data/base_project/docker/compose.yml +1 -1
  41. squirrels/package_data/base_project/duckdb_init.sql +9 -0
  42. squirrels/package_data/base_project/macros/macros_example.sql +15 -0
  43. squirrels/package_data/base_project/models/builds/build_example.py +26 -0
  44. squirrels/package_data/base_project/models/builds/build_example.sql +16 -0
  45. squirrels/package_data/base_project/models/builds/build_example.yml +55 -0
  46. squirrels/package_data/base_project/models/dbviews/dbview_example.sql +12 -22
  47. squirrels/package_data/base_project/models/dbviews/dbview_example.yml +26 -0
  48. squirrels/package_data/base_project/models/federates/federate_example.py +38 -15
  49. squirrels/package_data/base_project/models/federates/federate_example.sql +16 -2
  50. squirrels/package_data/base_project/models/federates/federate_example.yml +65 -0
  51. squirrels/package_data/base_project/models/sources.yml +39 -0
  52. squirrels/package_data/base_project/parameters.yml +36 -21
  53. squirrels/package_data/base_project/pyconfigs/connections.py +6 -11
  54. squirrels/package_data/base_project/pyconfigs/context.py +20 -33
  55. squirrels/package_data/base_project/pyconfigs/parameters.py +19 -21
  56. squirrels/package_data/base_project/pyconfigs/user.py +23 -0
  57. squirrels/package_data/base_project/seeds/seed_categories.yml +15 -0
  58. squirrels/package_data/base_project/seeds/seed_subcategories.csv +15 -15
  59. squirrels/package_data/base_project/seeds/seed_subcategories.yml +21 -0
  60. squirrels/package_data/base_project/squirrels.yml.j2 +17 -40
  61. squirrels/parameters.py +20 -20
  62. {squirrels-0.4.0.dist-info → squirrels-0.5.0b1.dist-info}/METADATA +31 -32
  63. squirrels-0.5.0b1.dist-info/RECORD +70 -0
  64. {squirrels-0.4.0.dist-info → squirrels-0.5.0b1.dist-info}/WHEEL +1 -1
  65. squirrels-0.5.0b1.dist-info/entry_points.txt +3 -0
  66. {squirrels-0.4.0.dist-info → squirrels-0.5.0b1.dist-info/licenses}/LICENSE +1 -1
  67. squirrels/_authenticator.py +0 -85
  68. squirrels/_environcfg.py +0 -84
  69. squirrels/package_data/assets/favicon.ico +0 -0
  70. squirrels/package_data/assets/index.css +0 -1
  71. squirrels/package_data/assets/index.js +0 -58
  72. squirrels/package_data/base_project/dashboards.yml +0 -10
  73. squirrels/package_data/base_project/env.yml +0 -29
  74. squirrels/package_data/base_project/models/dbviews/dbview_example.py +0 -47
  75. squirrels/package_data/base_project/pyconfigs/auth.py +0 -45
  76. squirrels/package_data/templates/index.html +0 -18
  77. squirrels/project.py +0 -378
  78. squirrels/user_base.py +0 -55
  79. squirrels-0.4.0.dist-info/RECORD +0 -60
  80. squirrels-0.4.0.dist-info/entry_points.txt +0 -4
squirrels/parameters.py CHANGED
@@ -6,7 +6,7 @@ from decimal import Decimal
6
6
  from abc import ABCMeta, abstractmethod
7
7
 
8
8
  from . import _parameter_configs as _pc, _parameter_sets as ps, parameter_options as _po, data_sources as d
9
- from . import _api_response_models as arm, _utils as _u
9
+ from . import _api_response_models as arm, _utils as u
10
10
 
11
11
  IntOrFloat = TypeVar("IntOrFloat", int, float)
12
12
 
@@ -112,13 +112,13 @@ class Parameter(metaclass=ABCMeta):
112
112
 
113
113
  try:
114
114
  return curr_option._validate_date(input_date)
115
- except _u.ConfigurationError as e:
115
+ except u.ConfigurationError as e:
116
116
  raise self._config._invalid_input_error(str(input_date), str(e))
117
117
 
118
118
  def _validate_number(self, input_number: _po.Number, curr_option: _po._NumericParameterOption) -> Decimal:
119
119
  try:
120
120
  return curr_option._validate_value(input_number)
121
- except _u.ConfigurationError as e:
121
+ except u.ConfigurationError as e:
122
122
  raise self._config._invalid_input_error(str(input_number), str(e))
123
123
 
124
124
  @abstractmethod
@@ -127,7 +127,7 @@ class Parameter(metaclass=ABCMeta):
127
127
  Helper method to convert the derived Parameter class into a JSON dictionary
128
128
  """
129
129
  output = {
130
- "widget_type": self._config.widget_type, "name": self._config.name,
130
+ "widget_type": self._config.widget_type(), "name": self._config.name,
131
131
  "label": self._config.label, "description": self._config.description
132
132
  }
133
133
  if not self.is_enabled():
@@ -234,7 +234,7 @@ class SingleSelectParameter(_SelectionParameter):
234
234
  if field is not None:
235
235
  selected = selected.get_custom_field(field, default_field=default_field, default=default)
236
236
  return selected
237
- return _u.process_if_not_none(self._selected_id, get_selected_from_id)
237
+ return u.process_if_not_none(self._selected_id, get_selected_from_id)
238
238
 
239
239
  def get_selected_quoted(self, field: str, *, default_field: str | None = None, default: str | None = None, **kwargs) -> str | None:
240
240
  """
@@ -254,12 +254,12 @@ class SingleSelectParameter(_SelectionParameter):
254
254
 
255
255
  def _enquote(x: Any) -> str:
256
256
  if not isinstance(selected_value, str):
257
- raise _u.ConfigurationError(
257
+ raise u.ConfigurationError(
258
258
  f"Method 'get_selected_quoted' can only be used on fields with only string values"
259
259
  )
260
260
  return self._enquote(x)
261
261
 
262
- return _u.process_if_not_none(selected_value, _enquote)
262
+ return u.process_if_not_none(selected_value, _enquote)
263
263
 
264
264
  def get_selected_id(self, **kwargs) -> str | None:
265
265
  """
@@ -270,7 +270,7 @@ class SingleSelectParameter(_SelectionParameter):
270
270
  """
271
271
  def get_id(x: _po.SelectParameterOption):
272
272
  return x._identifier
273
- return _u.process_if_not_none(self.get_selected(), get_id)
273
+ return u.process_if_not_none(self.get_selected(), get_id)
274
274
 
275
275
  def get_selected_id_quoted(self, **kwargs) -> str | None:
276
276
  """
@@ -279,7 +279,7 @@ class SingleSelectParameter(_SelectionParameter):
279
279
  Returns:
280
280
  A string or None if there are no selectable options
281
281
  """
282
- return _u.process_if_not_none(self.get_selected_id(), self._enquote)
282
+ return u.process_if_not_none(self.get_selected_id(), self._enquote)
283
283
 
284
284
  def get_selected_label(self, **kwargs) -> str | None:
285
285
  """
@@ -289,7 +289,7 @@ class SingleSelectParameter(_SelectionParameter):
289
289
  A string or None if there are no selectable options
290
290
  """
291
291
  def get_label(x: _po.SelectParameterOption): return x._label
292
- return _u.process_if_not_none(self.get_selected(), get_label)
292
+ return u.process_if_not_none(self.get_selected(), get_label)
293
293
 
294
294
  def get_selected_label_quoted(self, **kwargs) -> str | None:
295
295
  """
@@ -298,7 +298,7 @@ class SingleSelectParameter(_SelectionParameter):
298
298
  Returns:
299
299
  A string or None if there are no selectable options
300
300
  """
301
- return _u.process_if_not_none(self.get_selected_label(), self._enquote)
301
+ return u.process_if_not_none(self.get_selected_label(), self._enquote)
302
302
 
303
303
  def _get_selected_ids_as_list(self) -> Sequence[str]:
304
304
  selected_id = self.get_selected_id()
@@ -480,7 +480,7 @@ class MultiSelectParameter(_SelectionParameter):
480
480
  list_of_strings: list[str] = []
481
481
  for selected in selected_list:
482
482
  if not isinstance(selected, str):
483
- raise _u.ConfigurationError(
483
+ raise u.ConfigurationError(
484
484
  f"Method '{method}' can only be used on fields with only string values"
485
485
  )
486
486
  list_of_strings.append(selected)
@@ -1041,7 +1041,7 @@ class TextValue:
1041
1041
  _value_do_not_touch: str
1042
1042
 
1043
1043
  def __repr__(self):
1044
- raise _u.ConfigurationError(
1044
+ raise u.ConfigurationError(
1045
1045
  "Cannot convert TextValue directly to string (to avoid SQL injection). Try using it through placeholders instead"
1046
1046
  )
1047
1047
 
@@ -1059,7 +1059,7 @@ class TextValue:
1059
1059
  """
1060
1060
  new_value = str_to_str_function(self._value_do_not_touch)
1061
1061
  if not isinstance(new_value, str):
1062
- raise _u.ConfigurationError("Function provided must return string")
1062
+ raise u.ConfigurationError("Function provided must return string")
1063
1063
  return TextValue(new_value)
1064
1064
 
1065
1065
  def apply_percent_wrap(self) -> TextValue:
@@ -1083,7 +1083,7 @@ class TextValue:
1083
1083
  """
1084
1084
  new_value = str_to_bool_function(self._value_do_not_touch)
1085
1085
  if not isinstance(new_value, bool):
1086
- raise _u.ConfigurationError("Function provided must return bool")
1086
+ raise u.ConfigurationError("Function provided must return bool")
1087
1087
  return new_value
1088
1088
 
1089
1089
  def apply_as_number(self, str_to_num_function: Callable[[str], IntOrFloat]) -> IntOrFloat:
@@ -1098,7 +1098,7 @@ class TextValue:
1098
1098
  """
1099
1099
  new_value = str_to_num_function(self._value_do_not_touch)
1100
1100
  if not isinstance(new_value, (int, float)):
1101
- raise _u.ConfigurationError("Function provided must return a number")
1101
+ raise u.ConfigurationError("Function provided must return a number")
1102
1102
  return new_value
1103
1103
 
1104
1104
  def apply_as_datetime(self, str_to_datetime_function: Callable[[str], datetime]) -> datetime:
@@ -1113,7 +1113,7 @@ class TextValue:
1113
1113
  """
1114
1114
  new_value = str_to_datetime_function(self._value_do_not_touch)
1115
1115
  if not isinstance(new_value, datetime):
1116
- raise _u.ConfigurationError("Function provided must return datetime")
1116
+ raise u.ConfigurationError("Function provided must return datetime")
1117
1117
  return new_value
1118
1118
 
1119
1119
 
@@ -1130,7 +1130,7 @@ class TextParameter(Parameter):
1130
1130
  if self.is_enabled() and isinstance(self._entered_text, str):
1131
1131
  try:
1132
1132
  self._entered_text = self._config.validate_entered_text(self._entered_text)
1133
- except _u.ConfigurationError as e:
1133
+ except u.ConfigurationError as e:
1134
1134
  raise self._config._invalid_input_error(self._entered_text, str(e))
1135
1135
 
1136
1136
  def is_enabled(self) -> bool:
@@ -1231,7 +1231,7 @@ class TextParameter(Parameter):
1231
1231
  Returns: int
1232
1232
  """
1233
1233
  if self._config.input_type != "number":
1234
- raise _u.ConfigurationError("Method 'get_entered_int' requires TextParameter to have input type 'number'")
1234
+ raise u.ConfigurationError("Method 'get_entered_int' requires TextParameter to have input type 'number'")
1235
1235
  text = self.get_entered_text()
1236
1236
  return text.apply_as_number(int)
1237
1237
 
@@ -1243,7 +1243,7 @@ class TextParameter(Parameter):
1243
1243
  """
1244
1244
  applicable_input_types = ["date", "datetime-local", "month", "time"]
1245
1245
  if self._config.input_type not in applicable_input_types:
1246
- raise _u.ConfigurationError(f"Method 'get_entered_datetime' requires TextParameter to have one of these input types: {applicable_input_types}")
1246
+ raise u.ConfigurationError(f"Method 'get_entered_datetime' requires TextParameter to have one of these input types: {applicable_input_types}")
1247
1247
  text = self.get_entered_text()
1248
1248
 
1249
1249
  date_formats = { "date": "%Y-%m-%d", "datetime-local": "%Y-%m-%dT%H:%M", "month": "%Y-%m", "time": "%H:%M" }
@@ -1,39 +1,39 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: squirrels
3
- Version: 0.4.0
3
+ Version: 0.5.0b1
4
4
  Summary: Squirrels - API Framework for Data Analytics
5
- Home-page: https://squirrels-analytics.github.io
6
- License: Apache-2.0
7
- Author: Tim Huang
8
- Author-email: tim.yuting@hotmail.com
9
- Requires-Python: >=3.10,<4.0
5
+ Project-URL: Homepage, https://squirrels-analytics.github.io
6
+ Project-URL: Repository, https://github.com/squirrels-analytics/squirrels
7
+ Project-URL: Documentation, https://squirrels-analytics.github.io
8
+ Author-email: Tim Huang <tim.yuting@hotmail.com>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
10
11
  Classifier: Intended Audience :: Developers
11
- Classifier: License :: OSI Approved :: Apache Software License
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.10
14
- Classifier: Programming Language :: Python :: 3.11
15
- Classifier: Programming Language :: Python :: 3.12
16
12
  Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
13
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
14
  Classifier: Typing :: Typed
19
- Provides-Extra: duckdb
20
- Requires-Dist: cachetools (>=5.3.2,<6.0.0)
21
- Requires-Dist: duckdb-engine (>=0.13.0,<1.0.0) ; extra == "duckdb"
22
- Requires-Dist: fastapi (>=0.112.1,<0.113.0)
23
- Requires-Dist: gitpython (>=3.1.41,<4.0.0)
24
- Requires-Dist: inquirer (>=3.2.1,<4.0.0)
25
- Requires-Dist: jinja2 (>=3.1.3,<4.0.0)
26
- Requires-Dist: matplotlib (>=3.8.3,<4.0.0)
27
- Requires-Dist: networkx (>=3.2.1,<4.0.0)
28
- Requires-Dist: pandas (>=2.1.4,<3.0.0)
29
- Requires-Dist: pydantic (>=2.8.2,<3.0.0)
30
- Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
31
- Requires-Dist: python-multipart (>=0.0.9,<0.0.10)
32
- Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
33
- Requires-Dist: sqlalchemy (>=2.0.25,<3.0.0)
34
- Requires-Dist: uvicorn (>=0.30.6,<0.31.0)
35
- Project-URL: Documentation, https://squirrels-analytics.github.io
36
- Project-URL: Repository, https://github.com/squirrels-analytics/squirrels
15
+ Requires-Python: ~=3.10
16
+ Requires-Dist: bcrypt<5,>=4.0.1
17
+ Requires-Dist: cachetools<6,>=5.3.2
18
+ Requires-Dist: duckdb<2,>=1.1.3
19
+ Requires-Dist: fastapi<1,>=0.112.1
20
+ Requires-Dist: gitpython<4,>=3.1.41
21
+ Requires-Dist: inquirer<4,>=3.2.1
22
+ Requires-Dist: jinja2<4,>=3.1.3
23
+ Requires-Dist: libpass<2,>=1.9.0
24
+ Requires-Dist: matplotlib<4,>=3.8.3
25
+ Requires-Dist: networkx<4,>=3.2.1
26
+ Requires-Dist: pandas<3,>=2.1.4
27
+ Requires-Dist: polars<2,>=1.14.0
28
+ Requires-Dist: pyarrow>=19.0.1
29
+ Requires-Dist: pydantic<3,>=2.8.2
30
+ Requires-Dist: pyjwt<3,>=2.8.0
31
+ Requires-Dist: python-dotenv<2,>=1.0.1
32
+ Requires-Dist: python-multipart<1,>=0.0.9
33
+ Requires-Dist: pyyaml<7,>=6.0.1
34
+ Requires-Dist: sqlalchemy<3,>=2.0.25
35
+ Requires-Dist: sqlglot>=26.12.1
36
+ Requires-Dist: uvicorn<1,>=0.30.6
37
37
  Description-Content-Type: text/markdown
38
38
 
39
39
  # Squirrels
@@ -60,7 +60,7 @@ Here are a few of the things that squirrels can do:
60
60
  - Connect to any database by specifying its SQLAlchemy url (in `squirrels.yml`) or by using its native connector library in python (in `connections.py`).
61
61
  - Configure API routes for datasets (in `squirrels.yml`) without writing code.
62
62
  - Configure parameter widgets (types include single-select, multi-select, date, number, etc.) for your datasets (in `parameters.py`).
63
- - Use Jinja SQL templates (just like dbt!) or python functions (that return a pandas dataframe) to define dynamic query logic based on parameter selections.
63
+ - Use Jinja SQL templates (just like dbt!) or python functions (that return a Python dataframe such as polars or pandas) to define dynamic query logic based on parameter selections.
64
64
  - Query multiple databases and join the results together in a final view in one API endpoint/dataset!
65
65
  - Test your API endpoints with an interactive UI or by a command line that generates rendered sql queries and results (for a given set of parameter selections).
66
66
  - Define authentication logic (in `auth.py`) and authorize privacy scope per dataset (in `squirrels.yml`). The user's attributes can even be used in your query logic!
@@ -114,4 +114,3 @@ The library version is maintained in both the `pyproject.toml` and the `squirrel
114
114
  When a user initializes a squirrels project using `sqrl init`, the files are copied from the `squirrels/package_data/base_project` folder. The contents in the `database` subfolder were constructed from the scripts in the `database_elt` folder.
115
115
 
116
116
  For the Squirrels UI activated by `sqrl run`, the HTML, CSS, and Javascript files can be found in the `static` and `templates` subfolders of `squirrels/package_data`. The CSS and Javascript files are minified and built from the source files in this project: https://github.com/squirrels-analytics/squirrels-testing-ui.
117
-
@@ -0,0 +1,70 @@
1
+ squirrels/__init__.py,sha256=Y62ldkfs81jsKcetWLDw_1a98gwALx9Ww1b_rLsBpbs,1010
2
+ squirrels/_api_response_models.py,sha256=fQWjEBGAyy8KbkaY4jjOKvxhEcvQPU1bF2dJRTVTRc4,13601
3
+ squirrels/_api_server.py,sha256=1jhUr7GwwwD0O5dqQOX11uuU3Ea-zKXheeeQM0cLrDE,51181
4
+ squirrels/_auth.py,sha256=XCC9i-Z7PaU74u-fqR6_d_eoc9qVh-5nC0CdYp4Svvg,19254
5
+ squirrels/_command_line.py,sha256=urMyS7xIyNCWoiBWDLr6lDz0M_3NvaoPfMIo0vvn5cQ,10663
6
+ squirrels/_connection_set.py,sha256=ZxnNAj9Cu_5ba0uwi6v_ItH6nurFnONrPgB47_GE-3I,3987
7
+ squirrels/_constants.py,sha256=RDoUo4HtiVIQXatVdLoMXaGs1pkNER-Kgnmgs4FUx_Y,3126
8
+ squirrels/_dashboards_io.py,sha256=PPZGB6TpPYxaWJ2pJMj0cZNT9Vo14QV9w-1idLb_eaE,2993
9
+ squirrels/_exceptions.py,sha256=OOywX0UhX-KnvC8MScl2E6KYq3bzLq-PSqOpF7NbUvc,2481
10
+ squirrels/_initializer.py,sha256=fwB_sSJqiBQqYseLshiItEyZCAWMc9bA_qqAW3l0ZIY,13265
11
+ squirrels/_manifest.py,sha256=SrnGj_dd6L1O5Ygfr_B4rvxv_zx9iZap8bSfHTjhx4o,10208
12
+ squirrels/_model_builder.py,sha256=_rUfGgmvvsyFhmXLvDzuP3gDPT_TLXgrZSusqjhhtRo,4921
13
+ squirrels/_model_configs.py,sha256=03gzjFwoMiPtEPMg3U4Ekm7vJtQ2NU3Ks2Aj6gY-2vs,3300
14
+ squirrels/_model_queries.py,sha256=xzfkPvHMq-5m22pnb-Zd045gRTdWe7UP6RmS9AZ-LRk,1086
15
+ squirrels/_models.py,sha256=JmkUSx02ccRRGzGqQNh_Hxxqk4KCATNcxTVekUWOExU,48680
16
+ squirrels/_package_loader.py,sha256=xcIur5Z38OWd-OVjsueFstVV567zlkK9UBnLS4NawJY,1158
17
+ squirrels/_parameter_configs.py,sha256=sWQEJ7Hr6s1TE91H6qZsUO7uUAZqlS0z2Af3ZNoqc7I,23981
18
+ squirrels/_parameter_sets.py,sha256=aE6IDELpLgytjt_G7eI9oTEMgDCgkb71dxATBsrnC38,9837
19
+ squirrels/_project.py,sha256=3Ve6waHnRh92tu-KJSy5GJ5imaIf4LqBQcq_dlokAJs,28314
20
+ squirrels/_py_module.py,sha256=LgILTjMx3jyb92o8Y35ezpntuk6u6ezYVGAICKhUSUM,2622
21
+ squirrels/_seeds.py,sha256=yyIYp4bn9Sg6lhgvsOYIJQHIpPbvLNsyGHVfswEyVd8,2225
22
+ squirrels/_sources.py,sha256=j5mY_EtA5cxoHwtk8RwTVHO74hleik2lS7mF9gVnG_A,4840
23
+ squirrels/_utils.py,sha256=gzcPAQCJna674YomsxTgVe4DZucGJTVotxMWawoudzw,12043
24
+ squirrels/_version.py,sha256=M8aFbJ4vlAi3Sk9b7leRuEfkNBjkkX5S_F9lA4h8GK4,105
25
+ squirrels/dashboards.py,sha256=pekv_ERwoHHpBLYjIXSJ7Z8X7suWmjyex_whpCpZY60,1974
26
+ squirrels/data_sources.py,sha256=W3wzXyC7wGVnz49JkWYZQxBqNuViC_QhulM90KEuLCY,26088
27
+ squirrels/dataset_result.py,sha256=wZvvRs4U1Hva_iyoFosAAu76S1yfv1Cd5SX3UMIw2PA,2838
28
+ squirrels/dateutils.py,sha256=kH2JNvQz0xjfYSh4xNTEAvm73f2EVXNpbXAB9zM2694,16730
29
+ squirrels/parameter_options.py,sha256=cWYKNoBUopHq6VfaeBu-nN2V0_IY3OgYpmYhKODNCew,16956
30
+ squirrels/parameters.py,sha256=kcMQ5-GEQ5lA9Zs_-R21n2Hq5_gULCygQy-Rf1FJK-4,56001
31
+ squirrels/arguments/init_time_args.py,sha256=wl9PrVr3iHsMuZQEfrUj_gyzjdSqMTdQbpYUPjWQEsc,3477
32
+ squirrels/arguments/run_time_args.py,sha256=3L8lQvICJPdH75J1rAqqeupM8raUIw6VedEHJL8nT0c,5705
33
+ squirrels/package_data/base_project/.env,sha256=3Tuk3SJl7RNMebm8P0jocqB-Ox91aW09Zreq8QjyUJ4,1026
34
+ squirrels/package_data/base_project/.env.example,sha256=dS5ZO_kyMT3GdZbj6nfiK9PzkvwGfse9-UiYkvrh7ys,1078
35
+ squirrels/package_data/base_project/.gitignore,sha256=B9OEkQ_j9fZGA2IAyVUvXeylxpya-AUwzLzqzMN4Bfc,155
36
+ squirrels/package_data/base_project/connections.yml,sha256=qZxh7OuI2xqf2cFKwpMo5TONrJXGVzQ7YfcWh4Go7Oo,1011
37
+ squirrels/package_data/base_project/duckdb_init.sql,sha256=iwKDoHbKhOEMe-Pu_sX5a9OauCgqxfZLD70S7RduBrE,196
38
+ squirrels/package_data/base_project/parameters.yml,sha256=McvLXWWI3DoYqTUPGBFo9ArsGJOCseh9mdzO9TxiJ8I,6755
39
+ squirrels/package_data/base_project/squirrels.yml.j2,sha256=j3X3w-rQNP25rSo9Nye8roOUJV98etlpqfsD_zHwxTo,2673
40
+ squirrels/package_data/base_project/assets/expenses.db,sha256=aO0QdApW9ad8LRc73MW1o3eimryzmOAH5vz9Vc3dWK0,77824
41
+ squirrels/package_data/base_project/assets/weather.db,sha256=dsHPO36gQdZ4ULAA726Hg3jp8a1dCdig1DhrGg8wTeg,86016
42
+ squirrels/package_data/base_project/dashboards/dashboard_example.py,sha256=8hg5MIrmeD5OpiKb7x2TCRkAsc4Rg36lf6I4KpJg7Os,1621
43
+ squirrels/package_data/base_project/dashboards/dashboard_example.yml,sha256=mxC1Zmt33TpjpSCEFPAkK7nOcVbSJBhbWh7wMGeHav0,410
44
+ squirrels/package_data/base_project/docker/.dockerignore,sha256=IN0ZmxwLdmYlw6I2ziTdzXkTbZWCUyV4kfUI9_lDz-A,201
45
+ squirrels/package_data/base_project/docker/Dockerfile,sha256=DBOfPajd7ikNr1Qg08TcQmQmWDYamlxoDv7u6FSWElE,470
46
+ squirrels/package_data/base_project/docker/compose.yml,sha256=xMAjfJeNVv49ypMGxR8bG27P5JSbPTNMR7UeGTHGyeA,105
47
+ squirrels/package_data/base_project/macros/macros_example.sql,sha256=oxC6TZmoqgTiXEjosQbiKwUV2-mAGbINmNoV36V1-PI,510
48
+ squirrels/package_data/base_project/models/sources.yml,sha256=Miujhj5QRnbN1Q6BkQyeyX8FZAROPDPLWzVgdsdZy9o,1890
49
+ squirrels/package_data/base_project/models/builds/build_example.py,sha256=SUfWN7E8lZJYl-Zi2c-KoLADha4S5sw4T6cVhhozLaY,1014
50
+ squirrels/package_data/base_project/models/builds/build_example.sql,sha256=UNpgvybUV7sVK-KN2h-ULDyDb4uwN3nYNGZUcBLBtrU,506
51
+ squirrels/package_data/base_project/models/builds/build_example.yml,sha256=bwA_r6j8CIgrzuRnxpVmR350nGvy_USDy6p7TOy4lBI,1284
52
+ squirrels/package_data/base_project/models/dbviews/dbview_example.sql,sha256=hGj7rNJZzgEKGxqzwjmkCAdL-0hOh4U7pIJCyz2SYHk,266
53
+ squirrels/package_data/base_project/models/dbviews/dbview_example.yml,sha256=BFTGdBe7OcyNWKjBh7IhWgmfFgG3nT4toCR3XG5ZgLI,947
54
+ squirrels/package_data/base_project/models/federates/federate_example.py,sha256=g30cVukwI9UIypZR6KOU1z0KWLwx2kGIkRHhrdmeZq8,1774
55
+ squirrels/package_data/base_project/models/federates/federate_example.sql,sha256=A_-ts4Qw5eO-btnrmGbagv3MWJ0TUj_hBS5P7jaCNP4,574
56
+ squirrels/package_data/base_project/models/federates/federate_example.yml,sha256=xhNmc4nT5zyc7J6tr_xSq_lQl3RP6pg4zUuxTPpaEXE,2254
57
+ squirrels/package_data/base_project/pyconfigs/connections.py,sha256=zueK24yEuWt1x_xcR0Q-ikXGe2dXsS4wwh3qlzKG39Q,600
58
+ squirrels/package_data/base_project/pyconfigs/context.py,sha256=9QuvSc7_oAipsIaaeT06bMnjGtUlpKdxmu5xnofE__Y,3717
59
+ squirrels/package_data/base_project/pyconfigs/parameters.py,sha256=VS6fC5H0JSY_bubnDb4iawOI5niEGESF3YFDQ9gv1co,5053
60
+ squirrels/package_data/base_project/pyconfigs/user.py,sha256=L-dj3skPFM2B46g1NgXhX7fgCB7MzLLhWcwMAan98xE,851
61
+ squirrels/package_data/base_project/seeds/seed_categories.csv,sha256=jppjf1nOIxy7-bi5lJn5CVqmnLfJHHq0ABgp6UqbXnw,104
62
+ squirrels/package_data/base_project/seeds/seed_categories.yml,sha256=NZ4BVvYYCEq6OnjRLrE_WOMhYsW0BQhRPWOgUchzdp4,435
63
+ squirrels/package_data/base_project/seeds/seed_subcategories.csv,sha256=Tta1oIgnc2nukNMDlUkIErRKNH_8YT5EPp1A2kQKcow,327
64
+ squirrels/package_data/base_project/seeds/seed_subcategories.yml,sha256=QTgw8Eld-p6Kntf53FyXyn7-7vKYI7IOJVu-Lr-FHCY,583
65
+ squirrels/package_data/base_project/tmp/.gitignore,sha256=XImoqcWvJY0C0L_TWCx1ljvqU7qh9fUTJmK4ACCmNFI,13
66
+ squirrels-0.5.0b1.dist-info/METADATA,sha256=eZ83IOAOLOTs9zsox-IS8Xd_CTCEiAu7qGtLdhvFoGI,5053
67
+ squirrels-0.5.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
68
+ squirrels-0.5.0b1.dist-info/entry_points.txt,sha256=i6vgjhJ3o_cdSFYofFcNY9DFMPr4MIcuwnkskSTXfJc,95
69
+ squirrels-0.5.0b1.dist-info/licenses/LICENSE,sha256=qqERuumQtQVsMrEXvJHuecJvV2sLxbleEubd_Zk8dY8,11338
70
+ squirrels-0.5.0b1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ sqrl = squirrels._command_line:main
3
+ squirrels = squirrels._command_line:main
@@ -186,7 +186,7 @@
186
186
  same "printed page" as the copyright notice for easier
187
187
  identification within third-party archives.
188
188
 
189
- Copyright 2024 Tim Huang
189
+ Copyright 2025 Tim Huang
190
190
 
191
191
  Licensed under the Apache License, Version 2.0 (the "License");
192
192
  you may not use this file except in compliance with the License.
@@ -1,85 +0,0 @@
1
- from typing import Type, Optional
2
- from datetime import datetime, timedelta, timezone
3
- from jwt.exceptions import InvalidTokenError
4
- import secrets, jwt
5
-
6
- from . import _utils as u, _constants as c
7
- from .arguments.run_time_args import AuthArgs
8
- from ._py_module import PyModule
9
- from .user_base import User, WrongPassword
10
- from ._environcfg import EnvironConfig
11
- from ._manifest import DatasetScope
12
- from ._connection_set import ConnectionsArgs, ConnectionSet
13
-
14
-
15
- class Authenticator:
16
-
17
- def __init__(self, base_path: str, env_cfg: EnvironConfig, conn_args: ConnectionsArgs, conn_set: ConnectionSet, token_expiry_minutes: int, *, auth_helper = None) -> None:
18
- self.env_cfg = env_cfg
19
- self.conn_args = conn_args
20
- self.conn_set = conn_set
21
- self.token_expiry_minutes = token_expiry_minutes
22
- self.auth_helper = self._get_auth_helper(base_path, default_auth_helper=auth_helper)
23
- self.user_cls: Type[User] = self.auth_helper.get_func_or_class("User", default_attr=User)
24
- self.secret_key = self._get_secret_key()
25
- self.algorithm = "HS256"
26
-
27
- def _get_auth_helper(self, base_path: str, *, default_auth_helper = None):
28
- auth_module_path = u.Path(base_path, c.PYCONFIGS_FOLDER, c.AUTH_FILE)
29
- return PyModule(auth_module_path, default_class=default_auth_helper)
30
-
31
- def _get_secret_key(self) -> str:
32
- secret_key = self.env_cfg.get_secret(c.JWT_SECRET_KEY, default_factory=lambda: secrets.token_hex(32))
33
- return str(secret_key)
34
-
35
- def _get_auth_args(self, username: str, password: str):
36
- connections = self.conn_set.get_engines_as_dict()
37
- return AuthArgs(self.conn_args.proj_vars, self.conn_args.env_vars, self.conn_args._get_credential, connections, username, password)
38
-
39
- def authenticate_user(self, username: str, password: str) -> Optional[User]:
40
- get_user = self.auth_helper.get_func_or_class(c.GET_USER_FUNC, is_required=False)
41
- try:
42
- real_user = get_user(self._get_auth_args(username, password)) if get_user is not None else None
43
- except Exception as e:
44
- raise u.FileExecutionError(f'Failed to run "{c.GET_USER_FUNC}" in {c.AUTH_FILE}', e) from e
45
-
46
- if isinstance(real_user, User):
47
- return real_user
48
-
49
- if not isinstance(real_user, WrongPassword):
50
- fake_users = self.env_cfg.get_users()
51
- if username in fake_users and secrets.compare_digest(fake_users[username].password, password):
52
- fake_user = fake_users[username].model_dump()
53
- fake_user.pop("username", "")
54
- is_internal = fake_user.pop("is_internal", False)
55
- try:
56
- return self.user_cls.Create(username, is_internal=is_internal, **fake_user)
57
- except Exception as e:
58
- raise u.FileExecutionError(f'Failed to create user from User model in {c.AUTH_FILE}', e) from e
59
-
60
- return None
61
-
62
- def create_access_token(self, user: User) -> tuple[str, datetime]:
63
- expire = datetime.now(timezone.utc) + timedelta(minutes=self.token_expiry_minutes)
64
- to_encode = {**vars(user), "exp": expire}
65
- encoded_jwt = jwt.encode(to_encode, self.secret_key, algorithm=self.algorithm)
66
- return encoded_jwt, expire
67
-
68
- def get_user_from_token(self, token: Optional[str]) -> Optional[User]:
69
- if token is not None:
70
- try:
71
- payload: dict = jwt.decode(token, self.secret_key, algorithms=[self.algorithm])
72
- payload.pop("exp")
73
- return self.user_cls._FromDict(payload)
74
- except InvalidTokenError:
75
- return None
76
-
77
- def can_user_access_scope(self, user: Optional[User], scope: DatasetScope) -> bool:
78
- if user is None:
79
- user_level = DatasetScope.PUBLIC
80
- elif not user.is_internal:
81
- user_level = DatasetScope.PROTECTED
82
- else:
83
- user_level = DatasetScope.PRIVATE
84
-
85
- return user_level.value >= scope.value
squirrels/_environcfg.py DELETED
@@ -1,84 +0,0 @@
1
- from pathlib import Path
2
- from typing import Any, Callable
3
- from pydantic import BaseModel, Field, field_validator, ValidationError
4
- import os, yaml, time
5
-
6
- from . import _constants as c, _utils as u
7
-
8
- _GLOBAL_SQUIRRELS_CFG_FILE = u.Path(os.path.expanduser('~'), '.squirrels', c.ENV_CONFIG_FILE)
9
-
10
-
11
- class _UserConfig(BaseModel, extra="allow"):
12
- username: str
13
- password: str
14
- is_internal: bool = False
15
-
16
-
17
- class _CredentialsConfig(BaseModel):
18
- username: str
19
- password: str
20
-
21
-
22
- class EnvironConfig(BaseModel):
23
- users: dict[str, _UserConfig] = Field(default_factory=dict)
24
- env_vars: dict[str, str] = Field(default_factory=dict)
25
- credentials: dict[str, _CredentialsConfig] = Field(default_factory=dict)
26
- secrets: dict[str, Any | None] = Field(default_factory=dict)
27
-
28
- @field_validator("users", mode="before")
29
- @classmethod
30
- def inject_username(cls, users: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]]:
31
- processed_users = {}
32
- for username, user in users.items():
33
- processed_users[username] = {"username": username, **user}
34
- return processed_users
35
-
36
- def get_users(self) -> dict[str, _UserConfig]:
37
- return self.users.copy()
38
-
39
- def get_all_env_vars(self) -> dict[str, str]:
40
- return self.env_vars.copy()
41
-
42
- def get_credential(self, key: str | None) -> tuple[str, str]:
43
- if not key:
44
- return "", ""
45
-
46
- try:
47
- credential = self.credentials[key]
48
- except KeyError as e:
49
- raise u.ConfigurationError(f'No credentials configured for "{key}"') from e
50
-
51
- return credential.username, credential.password
52
-
53
- def get_secret(self, key: str, default_factory: Callable[[], Any]) -> Any:
54
- if self.secrets.get(key) is None:
55
- self.secrets[key] = default_factory()
56
- return self.secrets[key]
57
-
58
-
59
- class EnvironConfigIO:
60
-
61
- @classmethod
62
- def load_from_file(cls, logger: u.Logger, base_path: str) -> EnvironConfig:
63
- start = time.time()
64
- def load_yaml(filename: str | Path) -> dict[str, dict]:
65
- try:
66
- with open(filename, 'r') as f:
67
- return yaml.safe_load(f)
68
- except FileNotFoundError:
69
- return {}
70
-
71
- master_env_config = load_yaml(_GLOBAL_SQUIRRELS_CFG_FILE)
72
- proj_env_config = load_yaml(Path(base_path, c.ENV_CONFIG_FILE))
73
-
74
- for key in proj_env_config:
75
- master_env_config.setdefault(key, {})
76
- master_env_config[key].update(proj_env_config[key])
77
-
78
- try:
79
- env_cfg = EnvironConfig(**master_env_config)
80
- except ValidationError as e:
81
- raise u.ConfigurationError(f"Failed to process {c.ENV_CONFIG_FILE} file. " + str(e)) from e
82
-
83
- logger.log_activity_time(f"loading {c.ENV_CONFIG_FILE} file", start)
84
- return env_cfg
Binary file
@@ -1 +0,0 @@
1
- .padded{padding:8px 20px}.widget{width:100%;box-sizing:border-box;border:1px solid #ccc;border-radius:4px}.widget-label{position:relative;padding-bottom:10px}.widget-container{display:flex;flex-direction:column;gap:20px}input[type=submit]{padding:12px 20px;cursor:pointer}button{cursor:pointer;border-radius:5px;padding:10px}.white-button{background-color:#fff;color:#00f}.white-button:hover{background-color:#d4dae1}.blue-button{background-color:#4285f4;color:#fff;border:none}.blue-button:hover{background-color:#3076c5}.horizontal-container{display:flex}.auth-container{display:flex;align-items:center;gap:10px}#main-container{height:97vh;width:99vw}#left-container{width:250px;min-width:250px;margin-right:20px;padding:10px;overflow-y:auto}#right-container{height:97vh;width:calc(99vw - 300px)}#header-container{background-color:#d4dae1;color:#000;display:flex;align-items:center;justify-content:space-between;padding:10px}#table-container{padding:10px 20px;height:calc(97vh - 70px);overflow-x:auto;overflow-y:auto}.modal-background{position:fixed;z-index:1;left:0;top:0;width:100%;height:100%;overflow:auto;background-color:#0006;padding-top:60px}.modal-content{background-color:#fefefe;padding:30px;margin:5px auto auto;border:2px solid black}#loading-indicator{justify-content:center;align-items:center;text-align:center;font-weight:700;position:absolute;top:0;left:0;width:100%;height:100%;background-color:#ffffff80}.spinner{margin:20px auto;width:40px;height:40px;position:relative;border-radius:50%;border:3px solid transparent;border-top-color:#3498db;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}table{border-collapse:collapse;width:100%}th,td{text-align:left;padding:8px;min-width:50px}th{background-color:#4285f4;color:#fff}tr:nth-child(2n){background-color:#f2f2f2}#result-table{margin:5px 0}.react-daterange-picker{display:inline-flex;position:relative}.react-daterange-picker,.react-daterange-picker *,.react-daterange-picker *:before,.react-daterange-picker *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.react-daterange-picker--disabled{background-color:#f0f0f0;color:#6d6d6d}.react-daterange-picker__wrapper{display:flex;flex-grow:1;flex-shrink:0;align-items:center;border:thin solid gray}.react-daterange-picker__inputGroup{min-width:calc((4px * 3) + .54em * 8 + .217em * 2);height:100%;flex-grow:1;padding:0 2px;box-sizing:content-box}.react-daterange-picker__inputGroup__divider{padding:1px 0;white-space:pre}.react-daterange-picker__inputGroup__divider,.react-daterange-picker__inputGroup__leadingZero{display:inline-block}.react-daterange-picker__inputGroup__input{min-width:.54em;height:100%;position:relative;padding:0 1px;border:0;background:none;color:currentColor;font:inherit;box-sizing:content-box;-webkit-appearance:textfield;-moz-appearance:textfield;appearance:textfield}.react-daterange-picker__inputGroup__input::-webkit-outer-spin-button,.react-daterange-picker__inputGroup__input::-webkit-inner-spin-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;margin:0}.react-daterange-picker__inputGroup__input:invalid{background:#ff00001a}.react-daterange-picker__inputGroup__input--hasLeadingZero{margin-left:-.54em;padding-left:calc(1px + .54em)}.react-daterange-picker__button{border:0;background:transparent;padding:4px 6px}.react-daterange-picker__button:enabled{cursor:pointer}.react-daterange-picker__button:enabled:hover .react-daterange-picker__button__icon,.react-daterange-picker__button:enabled:focus .react-daterange-picker__button__icon{stroke:#0078d7}.react-daterange-picker__button:disabled .react-daterange-picker__button__icon{stroke:#6d6d6d}.react-daterange-picker__button svg{display:inherit}.react-daterange-picker__calendar{width:350px;max-width:100vw;z-index:1}.react-daterange-picker__calendar--closed{display:none}.react-daterange-picker__calendar .react-calendar{border-width:thin}.react-calendar{width:350px;max-width:100%;background:#fff;border:1px solid #a0a096;font-family:Arial,Helvetica,sans-serif;line-height:1.125em}.react-calendar--doubleView{width:700px}.react-calendar--doubleView .react-calendar__viewContainer{display:flex;margin:-.5em}.react-calendar--doubleView .react-calendar__viewContainer>*{width:50%;margin:.5em}.react-calendar,.react-calendar *,.react-calendar *:before,.react-calendar *:after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}.react-calendar button{margin:0;border:0;outline:none}.react-calendar button:enabled:hover{cursor:pointer}.react-calendar__navigation{display:flex;height:44px;margin-bottom:1em}.react-calendar__navigation button{min-width:44px;background:none}.react-calendar__navigation button:disabled{background-color:#f0f0f0}.react-calendar__navigation button:enabled:hover,.react-calendar__navigation button:enabled:focus{background-color:#e6e6e6}.react-calendar__month-view__weekdays{text-align:center;text-transform:uppercase;font:inherit;font-size:.75em;font-weight:700}.react-calendar__month-view__weekdays__weekday{padding:.5em}.react-calendar__month-view__weekNumbers .react-calendar__tile{display:flex;align-items:center;justify-content:center;font:inherit;font-size:.75em;font-weight:700}.react-calendar__month-view__days__day--weekend{color:#d10000}.react-calendar__month-view__days__day--neighboringMonth,.react-calendar__decade-view__years__year--neighboringDecade,.react-calendar__century-view__decades__decade--neighboringCentury{color:#757575}.react-calendar__year-view .react-calendar__tile,.react-calendar__decade-view .react-calendar__tile,.react-calendar__century-view .react-calendar__tile{padding:2em .5em}.react-calendar__tile{max-width:100%;padding:10px 6.6667px;background:none;text-align:center;line-height:16px;font:inherit;font-size:.833em}.react-calendar__tile:disabled{background-color:#f0f0f0;color:#ababab}.react-calendar__month-view__days__day--neighboringMonth:disabled,.react-calendar__decade-view__years__year--neighboringDecade:disabled,.react-calendar__century-view__decades__decade--neighboringCentury:disabled{color:#cdcdcd}.react-calendar__tile:enabled:hover,.react-calendar__tile:enabled:focus{background-color:#e6e6e6}.react-calendar__tile--now{background:#ffff76}.react-calendar__tile--now:enabled:hover,.react-calendar__tile--now:enabled:focus{background:#ffffa9}.react-calendar__tile--hasActive{background:#76baff}.react-calendar__tile--hasActive:enabled:hover,.react-calendar__tile--hasActive:enabled:focus{background:#a9d4ff}.react-calendar__tile--active{background:#006edc;color:#fff}.react-calendar__tile--active:enabled:hover,.react-calendar__tile--active:enabled:focus{background:#1087ff}.react-calendar--selectRange .react-calendar__tile--hover{background-color:#e6e6e6}.rc-slider{position:relative;width:100%;height:14px;padding:5px 0;border-radius:6px;touch-action:none;box-sizing:border-box;-webkit-tap-highlight-color:rgba(0,0,0,0)}.rc-slider *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(0,0,0,0)}.rc-slider-rail{position:absolute;width:100%;height:4px;background-color:#e9e9e9;border-radius:6px}.rc-slider-track,.rc-slider-tracks{position:absolute;height:4px;background-color:#abe2fb;border-radius:6px}.rc-slider-track-draggable{z-index:1;box-sizing:content-box;background-clip:content-box;border-top:5px solid rgba(0,0,0,0);border-bottom:5px solid rgba(0,0,0,0);transform:translateY(-5px)}.rc-slider-handle{position:absolute;z-index:1;width:14px;height:14px;margin-top:-5px;background-color:#fff;border:solid 2px #96dbfa;border-radius:50%;cursor:pointer;cursor:-webkit-grab;cursor:grab;opacity:.8;touch-action:pan-x}.rc-slider-handle-dragging.rc-slider-handle-dragging.rc-slider-handle-dragging{border-color:#57c5f7;box-shadow:0 0 0 5px #96dbfa}.rc-slider-handle:focus{outline:none;box-shadow:none}.rc-slider-handle:focus-visible{border-color:#2db7f5;box-shadow:0 0 0 3px #96dbfa}.rc-slider-handle-click-focused:focus{border-color:#96dbfa;box-shadow:unset}.rc-slider-handle:hover{border-color:#57c5f7}.rc-slider-handle:active{border-color:#57c5f7;box-shadow:0 0 5px #57c5f7;cursor:-webkit-grabbing;cursor:grabbing}.rc-slider-mark{position:absolute;top:18px;left:0;width:100%;font-size:12px}.rc-slider-mark-text{position:absolute;display:inline-block;color:#999;text-align:center;vertical-align:middle;cursor:pointer}.rc-slider-mark-text-active{color:#666}.rc-slider-step{position:absolute;width:100%;height:4px;background:transparent}.rc-slider-dot{position:absolute;bottom:-2px;width:8px;height:8px;vertical-align:middle;background-color:#fff;border:2px solid #e9e9e9;border-radius:50%;cursor:pointer}.rc-slider-dot-active{border-color:#96dbfa}.rc-slider-dot-reverse{margin-right:-4px}.rc-slider-disabled{background-color:#e9e9e9}.rc-slider-disabled .rc-slider-track{background-color:#ccc}.rc-slider-disabled .rc-slider-handle,.rc-slider-disabled .rc-slider-dot{background-color:#fff;border-color:#ccc;box-shadow:none;cursor:not-allowed}.rc-slider-disabled .rc-slider-mark-text,.rc-slider-disabled .rc-slider-dot{cursor:not-allowed!important}.rc-slider-vertical{width:14px;height:100%;padding:0 5px}.rc-slider-vertical .rc-slider-rail{width:4px;height:100%}.rc-slider-vertical .rc-slider-track{bottom:0;left:5px;width:4px}.rc-slider-vertical .rc-slider-track-draggable{border-top:0;border-bottom:0;border-right:5px solid rgba(0,0,0,0);border-left:5px solid rgba(0,0,0,0);transform:translate(-5px)}.rc-slider-vertical .rc-slider-handle{position:absolute;z-index:1;margin-top:0;margin-left:-5px;touch-action:pan-y}.rc-slider-vertical .rc-slider-mark{top:0;left:18px;height:100%}.rc-slider-vertical .rc-slider-step{width:4px;height:100%}.rc-slider-vertical .rc-slider-dot{margin-left:-2px}.rc-slider-tooltip-zoom-down-enter,.rc-slider-tooltip-zoom-down-appear,.rc-slider-tooltip-zoom-down-leave{display:block!important;animation-duration:.3s;animation-fill-mode:both;animation-play-state:paused}.rc-slider-tooltip-zoom-down-enter.rc-slider-tooltip-zoom-down-enter-active,.rc-slider-tooltip-zoom-down-appear.rc-slider-tooltip-zoom-down-appear-active{animation-name:rcSliderTooltipZoomDownIn;animation-play-state:running}.rc-slider-tooltip-zoom-down-leave.rc-slider-tooltip-zoom-down-leave-active{animation-name:rcSliderTooltipZoomDownOut;animation-play-state:running}.rc-slider-tooltip-zoom-down-enter,.rc-slider-tooltip-zoom-down-appear{transform:scale(0);animation-timing-function:cubic-bezier(.23,1,.32,1)}.rc-slider-tooltip-zoom-down-leave{animation-timing-function:cubic-bezier(.755,.05,.855,.06)}@keyframes rcSliderTooltipZoomDownIn{0%{transform:scale(0);transform-origin:50% 100%;opacity:0}to{transform:scale(1);transform-origin:50% 100%}}@keyframes rcSliderTooltipZoomDownOut{0%{transform:scale(1);transform-origin:50% 100%}to{transform:scale(0);transform-origin:50% 100%;opacity:0}}.rc-slider-tooltip{position:absolute;top:-9999px;left:-9999px;visibility:visible;box-sizing:border-box;-webkit-tap-highlight-color:rgba(0,0,0,0)}.rc-slider-tooltip *{box-sizing:border-box;-webkit-tap-highlight-color:rgba(0,0,0,0)}.rc-slider-tooltip-hidden{display:none}.rc-slider-tooltip-placement-top{padding:4px 0 8px}.rc-slider-tooltip-inner{min-width:24px;height:24px;padding:6px 2px;color:#fff;font-size:12px;line-height:1;text-align:center;text-decoration:none;background-color:#6c6c6c;border-radius:6px;box-shadow:0 0 4px #d9d9d9}.rc-slider-tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.rc-slider-tooltip-placement-top .rc-slider-tooltip-arrow{bottom:4px;left:50%;margin-left:-4px;border-width:4px 4px 0;border-top-color:#6c6c6c}.hover-text{position:absolute;z-index:1;background-color:#f9f9f9;border:1px solid #ccc;border-radius:4px}input[type=range]{width:80%;padding:12px 0;border:0px}.slider-info{display:flex;font-size:13px;justify-content:space-between}.react-daterange-picker{height:40px;font-size:14px}.react-daterange-picker__wrapper{width:200px;border:0}.react-daterange-picker__inputGroup{min-width:0px;text-align:center}.slider-wrapper{display:flex;flex-direction:column;align-items:center}.rc-slider{width:95%;margin:10px 0}.rc-slider-handle{z-index:0;opacity:1;width:20px;height:20px;margin:-7px 0}