squirrels 0.4.1__py3-none-any.whl → 0.5.0rc0__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 +10 -6
- squirrels/_api_response_models.py +93 -44
- squirrels/_api_server.py +571 -219
- squirrels/_auth.py +451 -0
- squirrels/_command_line.py +61 -20
- squirrels/_connection_set.py +38 -25
- squirrels/_constants.py +44 -34
- squirrels/_dashboards_io.py +34 -16
- squirrels/_exceptions.py +57 -0
- squirrels/_initializer.py +117 -44
- squirrels/_manifest.py +124 -62
- squirrels/_model_builder.py +111 -0
- squirrels/_model_configs.py +74 -0
- squirrels/_model_queries.py +52 -0
- squirrels/_models.py +860 -354
- squirrels/_package_loader.py +8 -4
- squirrels/_parameter_configs.py +45 -65
- squirrels/_parameter_sets.py +15 -13
- squirrels/_project.py +561 -0
- squirrels/_py_module.py +4 -3
- squirrels/_seeds.py +35 -16
- squirrels/_sources.py +106 -0
- squirrels/_utils.py +166 -63
- squirrels/_version.py +1 -1
- squirrels/arguments/init_time_args.py +78 -15
- squirrels/arguments/run_time_args.py +62 -101
- squirrels/dashboards.py +4 -4
- squirrels/data_sources.py +94 -162
- squirrels/dataset_result.py +86 -0
- squirrels/dateutils.py +4 -4
- squirrels/package_data/base_project/.env +30 -0
- squirrels/package_data/base_project/.env.example +30 -0
- squirrels/package_data/base_project/.gitignore +3 -2
- squirrels/package_data/base_project/assets/expenses.db +0 -0
- squirrels/package_data/base_project/connections.yml +11 -3
- squirrels/package_data/base_project/dashboards/dashboard_example.py +15 -13
- squirrels/package_data/base_project/dashboards/dashboard_example.yml +22 -0
- squirrels/package_data/base_project/docker/.dockerignore +5 -2
- squirrels/package_data/base_project/docker/Dockerfile +3 -3
- squirrels/package_data/base_project/docker/compose.yml +1 -1
- squirrels/package_data/base_project/duckdb_init.sql +9 -0
- squirrels/package_data/base_project/macros/macros_example.sql +15 -0
- squirrels/package_data/base_project/models/builds/build_example.py +26 -0
- squirrels/package_data/base_project/models/builds/build_example.sql +16 -0
- squirrels/package_data/base_project/models/builds/build_example.yml +55 -0
- squirrels/package_data/base_project/models/dbviews/dbview_example.sql +12 -22
- squirrels/package_data/base_project/models/dbviews/dbview_example.yml +26 -0
- squirrels/package_data/base_project/models/federates/federate_example.py +38 -15
- squirrels/package_data/base_project/models/federates/federate_example.sql +16 -2
- squirrels/package_data/base_project/models/federates/federate_example.yml +65 -0
- squirrels/package_data/base_project/models/sources.yml +39 -0
- squirrels/package_data/base_project/parameters.yml +36 -21
- squirrels/package_data/base_project/pyconfigs/connections.py +6 -11
- squirrels/package_data/base_project/pyconfigs/context.py +20 -33
- squirrels/package_data/base_project/pyconfigs/parameters.py +19 -21
- squirrels/package_data/base_project/pyconfigs/user.py +23 -0
- squirrels/package_data/base_project/seeds/seed_categories.yml +15 -0
- squirrels/package_data/base_project/seeds/seed_subcategories.csv +15 -15
- squirrels/package_data/base_project/seeds/seed_subcategories.yml +21 -0
- squirrels/package_data/base_project/squirrels.yml.j2 +17 -40
- squirrels/parameters.py +20 -20
- {squirrels-0.4.1.dist-info → squirrels-0.5.0rc0.dist-info}/METADATA +31 -32
- squirrels-0.5.0rc0.dist-info/RECORD +70 -0
- {squirrels-0.4.1.dist-info → squirrels-0.5.0rc0.dist-info}/WHEEL +1 -1
- squirrels-0.5.0rc0.dist-info/entry_points.txt +3 -0
- {squirrels-0.4.1.dist-info → squirrels-0.5.0rc0.dist-info/licenses}/LICENSE +1 -1
- squirrels/_authenticator.py +0 -85
- squirrels/_environcfg.py +0 -84
- squirrels/package_data/assets/favicon.ico +0 -0
- squirrels/package_data/assets/index.css +0 -1
- squirrels/package_data/assets/index.js +0 -58
- squirrels/package_data/base_project/dashboards.yml +0 -10
- squirrels/package_data/base_project/env.yml +0 -29
- squirrels/package_data/base_project/models/dbviews/dbview_example.py +0 -47
- squirrels/package_data/base_project/pyconfigs/auth.py +0 -45
- squirrels/package_data/templates/index.html +0 -18
- squirrels/project.py +0 -378
- squirrels/user_base.py +0 -55
- squirrels-0.4.1.dist-info/RECORD +0 -60
- squirrels-0.4.1.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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: squirrels
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0rc0
|
|
4
4
|
Summary: Squirrels - API Framework for Data Analytics
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Author-email: tim.yuting@hotmail.com
|
|
9
|
-
|
|
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
|
-
|
|
20
|
-
Requires-Dist:
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist:
|
|
23
|
-
Requires-Dist:
|
|
24
|
-
Requires-Dist:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
Requires-Dist:
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
|
|
36
|
-
|
|
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: matplotlib<4,>=3.8.3
|
|
24
|
+
Requires-Dist: networkx<4,>=3.2.1
|
|
25
|
+
Requires-Dist: pandas<3,>=2.1.4
|
|
26
|
+
Requires-Dist: passlib<2,>=1.7.4
|
|
27
|
+
Requires-Dist: polars<2,>=1.14.0
|
|
28
|
+
Requires-Dist: pyarrow<19,>=18.0.0
|
|
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,>=25.32.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
|
|
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=oM0GTYfMFNsrtbt7jo8OsnNqT3u1LAB538ZozVXzlpk,19251
|
|
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=KICcmNPi-zmLrja7lm4yA1C3roo9ZpY_J2uwbeUJKxQ,48677
|
|
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.0rc0.dist-info/METADATA,sha256=bVz9vvh6PAR4ewL-TVzImp60HYlzLYvvc2SUIUWW9zE,5062
|
|
67
|
+
squirrels-0.5.0rc0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
68
|
+
squirrels-0.5.0rc0.dist-info/entry_points.txt,sha256=i6vgjhJ3o_cdSFYofFcNY9DFMPr4MIcuwnkskSTXfJc,95
|
|
69
|
+
squirrels-0.5.0rc0.dist-info/licenses/LICENSE,sha256=qqERuumQtQVsMrEXvJHuecJvV2sLxbleEubd_Zk8dY8,11338
|
|
70
|
+
squirrels-0.5.0rc0.dist-info/RECORD,,
|
|
@@ -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
|
|
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.
|
squirrels/_authenticator.py
DELETED
|
@@ -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}
|