squirrels 0.5.0b3__py3-none-any.whl → 0.5.0b4__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 (47) hide show
  1. squirrels/__init__.py +2 -0
  2. squirrels/_api_routes/__init__.py +5 -0
  3. squirrels/_api_routes/auth.py +262 -0
  4. squirrels/_api_routes/base.py +154 -0
  5. squirrels/_api_routes/dashboards.py +142 -0
  6. squirrels/_api_routes/data_management.py +103 -0
  7. squirrels/_api_routes/datasets.py +242 -0
  8. squirrels/_api_routes/oauth2.py +300 -0
  9. squirrels/_api_routes/project.py +214 -0
  10. squirrels/_api_server.py +142 -745
  11. squirrels/_arguments/__init__.py +0 -0
  12. squirrels/_arguments/{_init_time_args.py → init_time_args.py} +5 -0
  13. squirrels/_arguments/{_run_time_args.py → run_time_args.py} +1 -1
  14. squirrels/_auth.py +645 -92
  15. squirrels/_connection_set.py +1 -1
  16. squirrels/_constants.py +6 -0
  17. squirrels/{_dashboards_io.py → _dashboards.py} +87 -6
  18. squirrels/_exceptions.py +9 -37
  19. squirrels/_model_builder.py +1 -1
  20. squirrels/_model_queries.py +1 -1
  21. squirrels/_models.py +13 -12
  22. squirrels/_package_data/base_project/.env +1 -0
  23. squirrels/_package_data/base_project/.env.example +1 -0
  24. squirrels/_package_data/base_project/pyconfigs/parameters.py +84 -76
  25. squirrels/_package_data/base_project/pyconfigs/user.py +30 -2
  26. squirrels/_package_data/templates/dataset_results.html +112 -0
  27. squirrels/_package_data/templates/oauth_login.html +271 -0
  28. squirrels/_parameter_configs.py +1 -1
  29. squirrels/_parameter_sets.py +31 -21
  30. squirrels/_parameters.py +521 -123
  31. squirrels/_project.py +43 -24
  32. squirrels/_py_module.py +3 -2
  33. squirrels/_schemas/__init__.py +0 -0
  34. squirrels/_schemas/auth_models.py +144 -0
  35. squirrels/_schemas/query_param_models.py +67 -0
  36. squirrels/{_api_response_models.py → _schemas/response_models.py} +12 -8
  37. squirrels/_utils.py +34 -2
  38. squirrels/arguments.py +2 -2
  39. squirrels/auth.py +1 -0
  40. squirrels/dashboards.py +1 -1
  41. squirrels/types.py +3 -3
  42. {squirrels-0.5.0b3.dist-info → squirrels-0.5.0b4.dist-info}/METADATA +4 -1
  43. {squirrels-0.5.0b3.dist-info → squirrels-0.5.0b4.dist-info}/RECORD +46 -32
  44. squirrels/_dashboard_types.py +0 -82
  45. {squirrels-0.5.0b3.dist-info → squirrels-0.5.0b4.dist-info}/WHEEL +0 -0
  46. {squirrels-0.5.0b3.dist-info → squirrels-0.5.0b4.dist-info}/entry_points.txt +0 -0
  47. {squirrels-0.5.0b3.dist-info → squirrels-0.5.0b4.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,271 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Login - OAuth Authorization</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
16
+ background: #f5f7fa;
17
+ min-height: 100vh;
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ padding: 20px;
22
+ }
23
+
24
+ .login-container {
25
+ background: white;
26
+ border-radius: 12px;
27
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
28
+ padding: 40px;
29
+ width: 100%;
30
+ max-width: 450px;
31
+ position: relative;
32
+ }
33
+
34
+ .logo {
35
+ text-align: center;
36
+ margin-bottom: 30px;
37
+ }
38
+
39
+ .logo h1 {
40
+ color: #1f2937;
41
+ font-size: 24px;
42
+ font-weight: 700;
43
+ margin-bottom: 8px;
44
+ line-height: 1.3;
45
+ }
46
+
47
+ .logo .brand {
48
+ color: #1f2937;
49
+ font-weight: 700;
50
+ }
51
+
52
+ .logo .project {
53
+ color: #6b7280;
54
+ font-weight: 600;
55
+ font-size: 20px;
56
+ }
57
+
58
+ .logo p {
59
+ color: #6b7280;
60
+ font-size: 14px;
61
+ }
62
+
63
+ .client-info {
64
+ background: #f8f9fa;
65
+ border-radius: 8px;
66
+ padding: 16px;
67
+ margin-bottom: 24px;
68
+ border-left: 4px solid #2563eb;
69
+ }
70
+
71
+ .client-info h3 {
72
+ color: #333;
73
+ font-size: 16px;
74
+ margin-bottom: 4px;
75
+ }
76
+
77
+ .client-info p {
78
+ color: #666;
79
+ font-size: 14px;
80
+ }
81
+
82
+ .form-group {
83
+ margin-bottom: 20px;
84
+ }
85
+
86
+ .form-group label {
87
+ display: block;
88
+ margin-bottom: 6px;
89
+ color: #333;
90
+ font-weight: 500;
91
+ font-size: 14px;
92
+ }
93
+
94
+ .form-group input {
95
+ width: 100%;
96
+ padding: 12px 16px;
97
+ border: 2px solid #e1e5e9;
98
+ border-radius: 8px;
99
+ font-size: 16px;
100
+ transition: border-color 0.3s ease;
101
+ background-color: #fff;
102
+ }
103
+
104
+ .form-group input:focus {
105
+ outline: none;
106
+ border-color: #2563eb;
107
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
108
+ }
109
+
110
+ .btn {
111
+ width: 100%;
112
+ padding: 12px 16px;
113
+ border: none;
114
+ border-radius: 8px;
115
+ font-size: 16px;
116
+ font-weight: 600;
117
+ cursor: pointer;
118
+ transition: all 0.3s ease;
119
+ text-decoration: none;
120
+ display: inline-block;
121
+ text-align: center;
122
+ }
123
+
124
+ .btn-primary {
125
+ background: #2563eb;
126
+ color: white;
127
+ }
128
+
129
+ .btn-primary:hover {
130
+ background: #1d4ed8;
131
+ transform: translateY(-1px);
132
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
133
+ }
134
+
135
+ .btn-provider {
136
+ background: #fff;
137
+ color: #333;
138
+ border: 2px solid #e1e5e9;
139
+ margin-bottom: 12px;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ gap: 12px;
144
+ }
145
+
146
+ .btn-provider:hover {
147
+ border-color: #2563eb;
148
+ background: #f8f9fa;
149
+ transform: translateY(-1px);
150
+ }
151
+
152
+ .provider-icon {
153
+ width: 20px;
154
+ height: 20px;
155
+ border-radius: 50%;
156
+ }
157
+
158
+ .divider {
159
+ text-align: center;
160
+ margin: 24px 0;
161
+ position: relative;
162
+ color: #666;
163
+ font-size: 14px;
164
+ }
165
+
166
+ .divider::before {
167
+ content: '';
168
+ position: absolute;
169
+ top: 50%;
170
+ left: 0;
171
+ right: 0;
172
+ height: 1px;
173
+ background: #e1e5e9;
174
+ z-index: 1;
175
+ }
176
+
177
+ .divider span {
178
+ background: white;
179
+ padding: 0 16px;
180
+ position: relative;
181
+ z-index: 2;
182
+ }
183
+
184
+ .error-message {
185
+ background: #fee;
186
+ border: 1px solid #fcc;
187
+ color: #c33;
188
+ padding: 12px;
189
+ border-radius: 8px;
190
+ margin-bottom: 20px;
191
+ font-size: 14px;
192
+ }
193
+
194
+ .info-message {
195
+ background: #e3f2fd;
196
+ border: 1px solid #bbdefb;
197
+ color: #1976d2;
198
+ padding: 12px;
199
+ border-radius: 8px;
200
+ margin-bottom: 20px;
201
+ font-size: 14px;
202
+ }
203
+
204
+ @media (max-width: 480px) {
205
+ .login-container {
206
+ padding: 24px;
207
+ }
208
+
209
+ .logo h1 {
210
+ font-size: 24px;
211
+ }
212
+ }
213
+ </style>
214
+ </head>
215
+ <body>
216
+ <div class="login-container">
217
+ <div class="logo">
218
+ <h1>
219
+ <span class="brand">Squirrels Project</span>
220
+ {% if project_name %}
221
+ <br>
222
+ <span class="project">{{ project_name }}</span>
223
+ {% endif %}
224
+ </h1>
225
+ <p>Please sign in to continue</p>
226
+ </div>
227
+
228
+ {% if client_name %}
229
+ <div class="client-info">
230
+ <h3>{{ client_name }}</h3>
231
+ <p>wants to access your account</p>
232
+ </div>
233
+ {% endif %}
234
+
235
+ <!-- Username/Password Login Form -->
236
+ <form method="post" action="{{ login_url }}?redirect_url={{ return_url | urlencode }}">
237
+ <div class="form-group">
238
+ <label for="username">Username</label>
239
+ <input type="text" id="username" name="username" required autocomplete="username">
240
+ </div>
241
+
242
+ <div class="form-group">
243
+ <label for="password">Password</label>
244
+ <input type="password" id="password" name="password" required autocomplete="current-password">
245
+ </div>
246
+
247
+ <input type="hidden" name="redirect_url" value="{{ return_url }}">
248
+
249
+ <button type="submit" class="btn btn-primary">
250
+ Sign In
251
+ </button>
252
+ </form>
253
+
254
+ {% if providers and providers|length > 0 %}
255
+ <div class="divider">
256
+ <span>or</span>
257
+ </div>
258
+
259
+ <!-- OAuth Provider Login Options -->
260
+ <div class="providers-section">
261
+ {% for provider in providers %}
262
+ <a href="{{ provider.login_url }}?redirect_url={{ return_url | urlencode }}" class="btn btn-provider">
263
+ <img src="{{ provider.icon }}" alt="{{ provider.label }}" class="provider-icon" onerror="this.style.display='none'">
264
+ <span>Continue with {{ provider.label }}</span>
265
+ </a>
266
+ {% endfor %}
267
+ </div>
268
+ {% endif %}
269
+ </div>
270
+ </body>
271
+ </html>
@@ -108,7 +108,7 @@ class ParameterConfig(Generic[ParamOptionType], ParameterConfigBase):
108
108
  pass
109
109
 
110
110
  def _invalid_input_error(self, selection: str, more_details: str = '') -> InvalidInputError:
111
- return InvalidInputError(200, f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details)
111
+ return InvalidInputError(400, "Invalid parameter selection", f'Selected value "{selection}" is not valid for parameter "{self.name}". ' + more_details)
112
112
 
113
113
  @abstractmethod
114
114
  def with_selection(
@@ -1,13 +1,14 @@
1
1
  from __future__ import annotations
2
- from typing import Optional, Sequence, Any
2
+ from typing import Optional, Sequence, Callable, Any
3
3
  from dataclasses import dataclass, field
4
4
  from collections import OrderedDict
5
5
  import time, concurrent.futures, polars as pl
6
6
 
7
- from . import _parameters as p, _utils as u, _constants as c, _parameter_configs as pc, _py_module as pm, _api_response_models as arm
8
- from ._arguments._init_time_args import ParametersArgs
7
+ from . import _parameters as p, _utils as u, _constants as c, _parameter_configs as pc, _py_module as pm
8
+ from ._schemas import response_models as rm
9
+ from ._arguments.init_time_args import ParametersArgs
9
10
  from ._manifest import ParametersConfig, ManifestConfig
10
- from ._connection_set import ConnectionSet, ConnectionsArgs
11
+ from ._connection_set import ConnectionSet
11
12
  from ._seeds import Seeds
12
13
  from ._auth import BaseUser
13
14
 
@@ -22,11 +23,11 @@ class ParameterSet:
22
23
  def get_parameters_as_dict(self) -> dict[str, p.Parameter]:
23
24
  return self._parameters_dict.copy()
24
25
 
25
- def to_api_response_model0(self) -> arm.ParametersModel:
26
+ def to_api_response_model0(self) -> rm.ParametersModel:
26
27
  parameters = []
27
28
  for x in self._parameters_dict.values():
28
29
  parameters.append(x._to_api_response_model0())
29
- return arm.ParametersModel(parameters=parameters)
30
+ return rm.ParametersModel(parameters=parameters)
30
31
 
31
32
 
32
33
  @dataclass
@@ -153,44 +154,53 @@ class ParameterConfigsSetIO:
153
154
  """
154
155
  Static class for the singleton object of ParameterConfigsSet
155
156
  """
156
- obj: ParameterConfigsSet # this is static (set in load_from_file) to simplify development experience for pyconfigs/parameters.py
157
+ param_factories: list[Callable[[ParametersArgs], pc.ParameterConfigBase]] = [] # this is static (set in load_from_file) to stage the functions from pyconfigs/parameters.py before using them
157
158
 
158
159
  @classmethod
159
- def _get_df_dict_from_data_sources(cls, default_conn_name: str, seeds: Seeds, conn_set: ConnectionSet) -> dict[str, pl.DataFrame]:
160
+ def _get_df_dict_from_data_sources(
161
+ cls, param_configs_set: ParameterConfigsSet, default_conn_name: str, seeds: Seeds, conn_set: ConnectionSet
162
+ ) -> dict[str, pl.DataFrame]:
163
+
160
164
  def get_dataframe(ds_param_config: pc.DataSourceParameterConfig) -> tuple[str, pl.DataFrame]:
161
165
  return ds_param_config.name, ds_param_config.get_dataframe(default_conn_name, conn_set, seeds)
162
166
 
163
- ds_param_configs = cls.obj._get_all_ds_param_configs()
167
+ ds_param_configs = param_configs_set._get_all_ds_param_configs()
164
168
  with concurrent.futures.ThreadPoolExecutor() as executor:
165
169
  df_dict = dict(executor.map(get_dataframe, ds_param_configs))
166
170
 
167
171
  return df_dict
168
172
 
169
173
  @classmethod
170
- def _add_from_dict(cls, param_config: ParametersConfig) -> None:
174
+ def _add_from_dict(cls, param_configs_set: ParameterConfigsSet, param_config: ParametersConfig) -> None:
171
175
  ptype = getattr(p, param_config.type)
172
176
  factory = getattr(ptype, param_config.factory)
173
- factory(**param_config.arguments)
174
-
175
- @classmethod
176
- def get_param_args(cls, conn_args: ConnectionsArgs) -> ParametersArgs:
177
- return ParametersArgs(conn_args.project_path, conn_args.proj_vars, conn_args.env_vars)
177
+ obj = factory(**param_config.arguments)
178
+ param_configs_set.add(obj)
178
179
 
179
180
  @classmethod
180
181
  def load_from_file(
181
182
  cls, logger: u.Logger, base_path: str, manifest_cfg: ManifestConfig, seeds: Seeds, conn_set: ConnectionSet, param_args: ParametersArgs
182
183
  ) -> ParameterConfigsSet:
183
184
  start = time.time()
184
- cls.obj = ParameterConfigsSet()
185
+ param_configs_set = ParameterConfigsSet()
185
186
 
186
187
  for param_as_dict in manifest_cfg.parameters:
187
- cls._add_from_dict(param_as_dict)
188
+ cls._add_from_dict(param_configs_set, param_as_dict)
189
+
190
+ main_result = pm.run_pyconfig_main(base_path, c.PARAMETERS_FILE, {"sqrl": param_args}) # adds to cls.param_factories as side effect
191
+ param_factories = cls.param_factories
192
+ cls.param_factories = []
193
+
194
+ for param_factory in param_factories:
195
+ param_configs_set.add(param_factory(param_args))
188
196
 
189
- pm.run_pyconfig_main(base_path, c.PARAMETERS_FILE, {"sqrl": param_args})
197
+ if isinstance(main_result, list):
198
+ for param_config in main_result:
199
+ param_configs_set.add(param_config)
190
200
 
191
201
  default_conn_name = manifest_cfg.env_vars.get(c.SQRL_CONNECTIONS_DEFAULT_NAME_USED, "default")
192
- df_dict = cls._get_df_dict_from_data_sources(default_conn_name, seeds, conn_set)
193
- cls.obj._post_process_params(df_dict)
202
+ df_dict = cls._get_df_dict_from_data_sources(param_configs_set, default_conn_name, seeds, conn_set)
203
+ param_configs_set._post_process_params(df_dict)
194
204
 
195
205
  logger.log_activity_time("loading parameters", start)
196
- return cls.obj
206
+ return param_configs_set