cave-utils 2.2.0b2__tar.gz → 2.2.0b4__tar.gz

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.
Files changed (37) hide show
  1. {cave_utils-2.2.0b2/cave_utils.egg-info → cave_utils-2.2.0b4}/PKG-INFO +1 -1
  2. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/__init__.py +3 -0
  3. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/appBar.py +2 -3
  4. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/groupedOutputs.py +32 -17
  5. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/mapFeatures.py +19 -10
  6. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/pages.py +3 -1
  7. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/settings.py +5 -0
  8. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api_utils/general.py +57 -32
  9. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api_utils/validator.py +2 -2
  10. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api_utils/validator_utils.py +90 -16
  11. cave_utils-2.2.0b4/cave_utils/socket.py +12 -0
  12. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4/cave_utils.egg-info}/PKG-INFO +1 -1
  13. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/pyproject.toml +1 -1
  14. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/setup.cfg +1 -1
  15. cave_utils-2.2.0b2/cave_utils/socket.py +0 -9
  16. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/LICENSE +0 -0
  17. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/NOTICE.md +0 -0
  18. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/README.md +0 -0
  19. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/__init__.py +0 -0
  20. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/extraKwargs.py +0 -0
  21. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/globalOutputs.py +0 -0
  22. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/maps.py +0 -0
  23. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api/panes.py +0 -0
  24. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/api_utils/__init__.py +0 -0
  25. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/arguments.py +0 -0
  26. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/builders/__init__.py +0 -0
  27. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/builders/groups.py +0 -0
  28. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils/log.py +0 -0
  29. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils.egg-info/SOURCES.txt +0 -0
  30. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils.egg-info/dependency_links.txt +0 -0
  31. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils.egg-info/requires.txt +0 -0
  32. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/cave_utils.egg-info/top_level.txt +0 -0
  33. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/test/test_arguments.py +0 -0
  34. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/test/test_builders_groups.py +0 -0
  35. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/test/test_import.py +0 -0
  36. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/test/test_log.py +0 -0
  37. {cave_utils-2.2.0b2 → cave_utils-2.2.0b4}/test/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cave_utils
3
- Version: 2.2.0b2
3
+ Version: 2.2.0b4
4
4
  Summary: Python wrapper for api use in the cave_app
5
5
  Author-email: Connor Makowski <conmak@mit.edu>
6
6
  Project-URL: Homepage, https://github.com/mit-cave/cave_utils
@@ -95,6 +95,9 @@ class Root(ApiValidator):
95
95
  root_data=self.data,
96
96
  **kwargs,
97
97
  )
98
+ # Special logic to add timeLength to kwargs
99
+ # This is used to validate timeValues across the app
100
+ kwargs["timeLength"] = pamda.path(["settings","time","timeLength"], self.data)
98
101
  # Validate panes
99
102
  panes_data = self.data.get("panes")
100
103
  pane_validPaneIds = []
@@ -52,9 +52,8 @@ class appBar_data_star(ApiValidator):
52
52
  bar: str,
53
53
  variant: [str, None] = None,
54
54
  color: [str, None] = None,
55
- order: [dict, None] = None,
56
55
  apiCommand: [str, None] = None,
57
- apiCommandKeys: [list, None] = None,
56
+ apiCommandKeys: [list[str], None] = None,
58
57
  **kwargs,
59
58
  ):
60
59
  """
@@ -88,7 +87,7 @@ class appBar_data_star(ApiValidator):
88
87
  * **Note**: It must be a valid RGBA string.
89
88
  * **Example**: `"rgba(255, 255, 255, 1)"`.
90
89
  * **`apiCommand`**: `[str]` = `None` &rarr; The name of the [API command][] to trigger.
91
- * **`apiCommandKeys`**: `[list]` = `None` &rarr;
90
+ * **`apiCommandKeys`**: `[list[str]]` = `None` &rarr;
92
91
  * The root API keys to pass to your `execute_command` function if an
93
92
  `apiCommand` is provided. If omitted, all API keys are
94
93
  passed to `execute_command`.
@@ -279,13 +279,13 @@ class groupedOutputs_groupings_star(ApiValidator):
279
279
  levels_data = self.data.get("levels", {})
280
280
  levels_data_keys = list(levels_data.keys())
281
281
  data_data = self.data.get("data", {})
282
-
283
282
  CustomKeyValidator(
284
283
  data=levels_data,
285
284
  log=self.log,
286
285
  prepend_path=["levels"],
287
286
  validator=groupedOutputs_groupings_star_levels_star,
288
287
  acceptable_parents=levels_data_keys,
288
+ acceptable_data_levels=data_data,
289
289
  **kwargs,
290
290
  )
291
291
  groupedOutputs_groupings_star_data(
@@ -348,7 +348,7 @@ class groupedOutputs_groupings_star_levels_star(ApiValidator):
348
348
  """
349
349
 
350
350
  @staticmethod
351
- def spec(name: str, parent: [str, None] = None, ordering: [list, None] = None, **kwargs):
351
+ def spec(name: str, parent: [str, None] = None, ordering: [list, None] = None, orderWithParent: bool = True, coloring: [dict, None] = None, **kwargs):
352
352
  """
353
353
  Arguments:
354
354
 
@@ -358,16 +358,22 @@ class groupedOutputs_groupings_star_levels_star(ApiValidator):
358
358
  * **Notes**:
359
359
  * The parent level key must be defined in `groupedOutputs.groupings.*.levels.*`
360
360
  * If `None`, this will be considered to be the root of the hierarchy.
361
+ * **`ordering`**: `[list]` &rarr;
362
+ * The ordering of individual values for this level in charts and tables.
363
+ * **Note**: If none, the ordering will be alphabetical.
364
+ * **Note**: If a partial ordering is provided, the provided values will be placed first in order.
365
+ * **Note**: If a partial ordering is provided, the remaining values will be placed in alphabetical order.
366
+ * **Note**: All items in this list must be defined in `groupedOutputs.groupings.*.levels.*.values.*`
367
+ * **`orderWithParent`**: `[bool]`=True &rarr;
368
+ * Weather or not to order this level based on the parent level.
369
+ * If `True`, the ordering of this level will also be based on the parent level.
370
+ * If `False`, the ordering will be based on the ordering of this level only.
371
+ * **`coloring`**: `[dict]` &rarr;
372
+ * A dictionary of colors to be used for the level.
373
+ * Each key in this dictionary is a value in the level.
374
+ * Each value in this dictionary is an rgba string.
375
+ * **See**: `cave_utils.api.groupedOutputs.groupedOutputs_groupings_star_levels_star_coloring`
361
376
  """
362
- # TODO: Figure out new way for ordering
363
- # - `ordering`:
364
- # - Type: list
365
- # - What: The ordering of individual values for this level in charts and tables.
366
- # - Note: If none, the ordering will be alphabetical.
367
- # - Note: If a partial ordering is provided, the provided values will be placed first in order.
368
- # - Note: If a partial ordering is provided, the remaining values will be placed in alphabetical order.
369
- # - Note: All items in this list must be defined in `groupedOutputs.groupings.*.levels.*.values.*`
370
- # """
371
377
  return {"kwargs": kwargs, "accepted_values": {}}
372
378
 
373
379
  def __extend_spec__(self, **kwargs):
@@ -378,9 +384,18 @@ class groupedOutputs_groupings_star_levels_star(ApiValidator):
378
384
  valid_values=kwargs.get("acceptable_parents", []),
379
385
  prepend_path=["parent"],
380
386
  )
381
- # ordering = self.data.get("ordering")
382
- # if ordering is not None:
383
- # print(ordering, kwargs.get("acceptable_data_keys", []))
384
- # self.__check_subset_valid__(
385
- # subset=ordering, valid_values=kwargs.get("acceptable_data_keys", []), prepend_path=["ordering"]
386
- # )
387
+ ordering = self.data.get("ordering")
388
+ if ordering is not None:
389
+ self.__check_subset_valid__(
390
+ subset=ordering, valid_values=kwargs.get("acceptable_data_levels", {}).get(kwargs.get('CustomKeyValidatorFieldId'),[]), prepend_path=["ordering"]
391
+ )
392
+
393
+ coloring = self.data.get("coloring")
394
+ if coloring is not None:
395
+ self.__check_subset_valid__(
396
+ subset=list(coloring.keys()),
397
+ valid_values=kwargs.get("acceptable_data_levels", {}).get(kwargs.get('CustomKeyValidatorFieldId'),[]),
398
+ prepend_path=["coloring"],
399
+ )
400
+ for key, value in coloring.items():
401
+ self.__check_rgba_string_valid__(rgba_string=value, prepend_path=['coloring', key])
@@ -155,8 +155,10 @@ class mapFeatures_data_star_data(ApiValidator):
155
155
  **kwargs,
156
156
  )
157
157
  # Validate that all lengths are the same
158
- lengths = [len(v) for k, v in location_data.items() if k not in ["order", "timeValues"]] + [
159
- len(v) for k, v in valueLists_data.items() if k not in ["order", "timeValues"]
158
+ lengths = [
159
+ len(v) for k, v in location_data.items() if k not in ['timeValues', 'order']
160
+ ] + [
161
+ len(v) for k, v in valueLists_data.items() if k not in ['timeValues', 'order']
160
162
  ]
161
163
  if len(set(lengths)) > 1:
162
164
  self.__error__(msg=f"location and valueLists keys must have the same length.", path=[])
@@ -185,16 +187,19 @@ class mapFeatures_data_star_data_location(ApiValidator):
185
187
  layer_type = kwargs.get("layer_type")
186
188
  layer_geoJson = kwargs.get("layer_geoJson")
187
189
  passed_keys = list(self.data.keys())
188
- # TODO Figure out way to handle timeValues and order
189
- optional_keys = ["timeValues", "order"]
190
- if layer_type == "geo" or (layer_type == "arc" and layer_geoJson):
190
+ optional_keys = []
191
+ if layer_type == "geo":
191
192
  required_keys = ["geoJsonValue"]
192
193
  elif layer_type == "arc":
193
- if "path" in passed_keys:
194
+ if layer_geoJson is not None:
195
+ required_keys = ["geoJsonValue"]
196
+ optional_keys += ["path", "startLatitude", "startLongitude", "endLatitude", "endLongitude","startAltitude", "endAltitude"]
197
+ elif "path" in passed_keys:
194
198
  required_keys = ["path"]
199
+ optional_keys += ["startLatitude", "startLongitude", "endLatitude", "endLongitude", "geoJsonValue","startAltitude", "endAltitude"]
195
200
  else:
196
201
  required_keys = ["startLatitude", "startLongitude", "endLatitude", "endLongitude"]
197
- optional_keys += ["startAltitude", "endAltitude"]
202
+ optional_keys += ["startAltitude", "endAltitude", "geoJsonValue", "path"]
198
203
  else:
199
204
  required_keys = ["latitude", "longitude"]
200
205
  optional_keys += ["altitude"]
@@ -209,8 +214,12 @@ class mapFeatures_data_star_data_location(ApiValidator):
209
214
  path=[],
210
215
  )
211
216
  continue
212
- if key in ["timeValues", "order"]:
213
- continue
217
+ if key == "geoJsonValue":
218
+ if len(value_list)!=len(set(value_list)):
219
+ self.__warn__(
220
+ msg=f"`geoJsonValue` should be a list of unique values. Otherwise, the corresponding map feature may not render correctly.",
221
+ path=[key],
222
+ )
214
223
  if not isinstance(value_list, list):
215
224
  self.__error__(
216
225
  msg=f"`{key}` must be a list but got {type(value_list)} instead.", path=[key]
@@ -240,7 +249,7 @@ class mapFeatures_data_star_data_location(ApiValidator):
240
249
  altitudes = None
241
250
  except:
242
251
  self.__error__(
243
- msg=f"`path` must be a list of lists of lists of length 2 or 3. EG: `[[[0,0],[1,1]],[[2,2],[3,3],[4,4],[5,5]]]`",
252
+ msg=f"`path` must be a list of lists of lists of length 2 [long,lat] or 3 [long,lat,alt]. EG: `[[[0,0],[1,1]],[[2,2],[3,3],[4,4],[5,5]]]`",
244
253
  path=[key],
245
254
  )
246
255
  continue
@@ -82,6 +82,7 @@ class pages_data_star_pageLayout(ApiValidator):
82
82
  statId: [str, list, None] = None,
83
83
  showToolbar: bool = True,
84
84
  maximized: bool = False,
85
+ defaultToZero: bool = False,
85
86
  **kwargs,
86
87
  ):
87
88
  """
@@ -135,7 +136,8 @@ class pages_data_star_pageLayout(ApiValidator):
135
136
  * **Note**: If left unspecified (i.e., `None`), it will default to `settings.showToolbar`.
136
137
  * **`maximized`**: `[bool]` = `False` &rarr; Whether or not the layout should be maximized.
137
138
  * **Note**: If more than one chart belonging to the same page layout is set to `True`, the first one found in the list will take precedence.
138
-
139
+ * **`defaultToZero`**: `[bool]` = `False` &rarr; Whether or not the chart should default missing values to zero.
140
+
139
141
  [area chart]: https://en.wikipedia.org/wiki/Area_chart
140
142
  [bar chart]: https://en.wikipedia.org/wiki/Bar_chart
141
143
  [stacked bar chart]: https://en.wikipedia.org/wiki/Bar_chart
@@ -291,3 +291,8 @@ class settings_time(ApiValidator):
291
291
  * **Example**: `"Decade"`.
292
292
  """
293
293
  return {"kwargs": kwargs, "accepted_values": {}}
294
+
295
+ def __extend_spec__(self, **kwargs):
296
+ timeLength = self.data.get("timeLength")
297
+ if timeLength < 1:
298
+ self.__error__(f"Time length must be greater than 0.", path=["timeLength"])
@@ -4,6 +4,7 @@ General API Spec items that are found in multiple places. This is not a key that
4
4
  from pamda import pamda
5
5
  import type_enforced
6
6
  from cave_utils.api_utils.validator_utils import ApiValidator, CustomKeyValidator
7
+ from typing import Literal
7
8
 
8
9
 
9
10
  @type_enforced.Enforcer
@@ -17,7 +18,7 @@ class props(ApiValidator):
17
18
  display: [bool, None] = None,
18
19
  enabled: [bool, None] = None,
19
20
  apiCommand: [str, None] = None,
20
- apiCommandKeys: [list, None] = None,
21
+ apiCommandKeys: [list[str], None] = None,
21
22
  options: [dict, None] = None,
22
23
  placeholder: [str, None] = None,
23
24
  maxValue: [float, int, None] = None,
@@ -29,7 +30,7 @@ class props(ApiValidator):
29
30
  precision: [int, None] = None,
30
31
  notationDisplay: [str, None] = None,
31
32
  unit: [str, None] = None,
32
- views: [list, None] = None,
33
+ views: [list[str], None] = None,
33
34
  legendNotation: [str, None] = None,
34
35
  legendPrecision: [int, None] = None,
35
36
  legendNotationDisplay: [str, None] = None,
@@ -99,7 +100,7 @@ class props(ApiValidator):
99
100
  * **`apiCommand`**: `[str]` = `None` &rarr; The name of the API command to trigger.
100
101
  * **Note**: If `None`, no `apiCommand` is triggered.
101
102
  * **Note**: This attribute is applicable to all props except `"head"` props.
102
- * **`apiCommandKeys`**: `[list]` = `None` &rarr;
103
+ * **`apiCommandKeys`**: `[list[str]]` = `None` &rarr;
103
104
  * The root API keys to pass to your `execute_command` function if an `apiCommand` is provided.
104
105
  * **Note**: If `None`, all API keys are passed to your `execute_command`.
105
106
  * **Note**: This attribute is applicable to all props except `"head"` props.
@@ -126,15 +127,28 @@ class props(ApiValidator):
126
127
  * **`rows`**: `[int]` = `None` &rarr;
127
128
  * The fixed number of rows to show for a `"textarea"` variant.
128
129
  * **Note**: This attribute is applicable exclusively to `"text"` props.
129
- * **`views`**: `[list]` = `["year", "day", "hours", "minutes"]` &rarr;
130
+ * **`views`**: `[list[str]]` &rarr;
130
131
  * The available time units for the represented date and/or time.
132
+ * **Default Value**:
133
+ * When **`variant`** == `"date"`: `["year", "day"]`
134
+ * When **`variant`** == `"time"`: `["hours", "minutes"]`
135
+ * When **`variant`** == `"datetime"`: `["year", "day", "hours", "minutes"]`
131
136
  * **Accepted Values**:
132
- * `"year"`: The year view
133
- * `"month"`: The month view
134
- * `"day"`: The day view
135
- * `"hours"`: The hours view
136
- * `"minutes"`: The minutes view
137
- * `"seconds"`: The seconds view
137
+ * When **`variant`** == `"date"`:
138
+ * `"year"`: The year view
139
+ * `"month"`: The month view
140
+ * `"day"`: The day view
141
+ * When **`variant`** == `"time"`:
142
+ * `"hours"`: The hours view
143
+ * `"minutes"`: The minutes view
144
+ * `"seconds"`: The seconds view
145
+ * When **`variant`** == `"datetime"`:
146
+ * `"year"`: The year view
147
+ * `"month"`: The month view
148
+ * `"day"`: The day view
149
+ * `"hours"`: The hours view
150
+ * `"minutes"`: The minutes view
151
+ * `"seconds"`: The seconds view
138
152
  * **Notes**:
139
153
  * The views will be presented in the order specified in the `views` array.
140
154
  * This attribute is applicable exclusively to `"date"` props.
@@ -173,7 +187,7 @@ class props(ApiValidator):
173
187
  * **Notes**:
174
188
  * If left unspecified (i.e., `None`), it will default to `settings.defaults.unitPlacement`.
175
189
  * This attribute is applicable exclusively to `"num"` props.
176
- * **`notation`**: `[int]` = `"standard"` &rarr; The formatting style of a numeric value.
190
+ * **`notation`**: `[str]` = `"standard"` &rarr; The formatting style of a numeric value.
177
191
  * **Accepted Values**:
178
192
  * `"standard"`: Plain number formatting
179
193
  * `"compact"`: Resembles the [metric prefix][] system
@@ -184,14 +198,18 @@ class props(ApiValidator):
184
198
  * This attribute is applicable exclusively to `"num"` props.
185
199
  * **`notationDisplay`**: `[str]` = `"e+"` | `"short"` &rarr; Further customize the formatting within the selected `notation`.
186
200
  * **Accepted Values**:
187
- * `"short"`: Add symbols `K`, `M`, `B`, and `T` (in `"en-US"`) to denote thousands, millions, billions, and trillions, respectively.
188
- * `"long"`: Present numeric values with the informal suffix words `thousand`, `million`, `billion`, and `trillion` (in `"en-US"`).
189
- * `"e"`: Exponent symbol in lowercase as per the chosen `locale` identifier
190
- * `"e+"`: Similar to `"e"`, but with a plus sign for positive exponents.
191
- * `"E"`: Exponent symbol in uppercase as per the chosen `locale` identifier
192
- * `"E+"`: Similar to `"E"`, but with a plus sign for positive exponents.
193
- * `"x10^"`: Formal scientific notation representation
194
- * `"x10^+"`: Similar to `"x10^"`, with a plus sign for positive exponents.
201
+ * When **`notation`** == `"compact"`:
202
+ * `"short"`: Add symbols `K`, `M`, `B`, and `T` (in `"en-US"`) to denote thousands, millions, billions, and trillions, respectively.
203
+ * `"long"`: Present numeric values with the informal suffix words `thousand`, `million`, `billion`, and `trillion` (in `"en-US"`).
204
+ * When **`notation`** == `"scientific"` or `"engineering"`:
205
+ * `"e"`: Exponent symbol in lowercase as per the chosen `locale` identifier
206
+ * `"e+"`: Similar to `"e"`, but with a plus sign for positive exponents.
207
+ * `"E"`: Exponent symbol in uppercase as per the chosen `locale` identifier
208
+ * `"E+"`: Similar to `"E"`, but with a plus sign for positive exponents.
209
+ * `"x10^"`: Formal scientific notation representation
210
+ * `"x10^+"`: Similar to `"x10^"`, with a plus sign for positive exponents.
211
+ * When **`notation`** == `"standard"`:
212
+ * No `notationDisplay` option is allowed for a `"standard"` notation
195
213
  * **Notes**:
196
214
  * No `notationDisplay` option is provided for a `"standard"` notation
197
215
  * The options `"short"` and `"long"` are only provided for the `"compact"` notation
@@ -304,20 +322,30 @@ class props(ApiValidator):
304
322
  for k, v in passed_values.items():
305
323
  if k not in required_fields + optional_fields:
306
324
  kwargs[k] = v
325
+ notationDisplay_options_dict = {
326
+ 'compact': ['short', 'long'],
327
+ 'scientific': ['e', 'e+', 'E', 'E+', 'x10^', 'x10^+'],
328
+ 'engineering': ['e', 'e+', 'E', 'E+', 'x10^', 'x10^+'],
329
+ 'standard': []
330
+ }
331
+ notation = passed_values.get('notation', 'standard')
332
+ legendNotation = passed_values.get('legendNotation', 'standard')
333
+ view_options_dict = {
334
+ "date": ["year", "month", "day"],
335
+ "time": ["hours", "minutes", "seconds"],
336
+ "datetime": ["year", "month", "day", "hours", "minutes", "seconds"],
337
+ }
338
+ variant = passed_values.get("variant", None)
307
339
  return {
308
340
  "kwargs": kwargs,
309
341
  "accepted_values": {
310
342
  "type": ["head", "num", "toggle", "button", "text", "selector", "date", "media"],
311
- "views": ["year", "month", "day", "hours", "minutes", "seconds"],
343
+ "views": view_options_dict.get(variant, []),
312
344
  "unitPlacement": ["after", "afterWithSpace", "before", "beforeWithSpace"],
313
- # TODO: Validate
314
- # compact: allowed notation displays -> "short", "long"
315
- # scientific|engineering: allowed notation displays -> "e", "e+", "E", "E+", "x10^", "x10^+"
316
- # standard: allowed notation displays -> None
317
345
  "notation": ["compact", "precision", "scientific", "engineering"],
318
- "notationDisplay": ["short", "long", "e", "e+", "E", "E+", "x10^", "x10^+"],
346
+ "notationDisplay": notationDisplay_options_dict.get(notation, []),
319
347
  "legendNotation": ["compact", "precision", "scientific", "engineering"],
320
- "legendNotationDisplay": ["short", "long", "e", "e+", "E", "E+", "x10^", "x10^+"],
348
+ "legendNotationDisplay": notationDisplay_options_dict.get(legendNotation, []),
321
349
  "variant": {
322
350
  "head": ["column", "row", "icon", "iconRow"],
323
351
  "text": ["textarea"],
@@ -349,15 +377,15 @@ class props(ApiValidator):
349
377
  **kwargs,
350
378
  )
351
379
 
352
-
380
+ @type_enforced.Enforcer
353
381
  class props_options(ApiValidator):
354
382
  @staticmethod
355
- def spec(name: str, path: [list, None] = None, **kwargs):
383
+ def spec(name: str, path: [list[str], None] = None, **kwargs):
356
384
  """
357
385
  Arguments:
358
386
 
359
387
  * **`name`**: `[str]` &rarr; The name of the option.
360
- * **`path`**: `[list]` = `None` &rarr; The path to an option.
388
+ * **`path`**: `[list[str]]` = `None` &rarr; The path to an option.
361
389
  * **Notes**:
362
390
  * If `None`, the option will not be selectable
363
391
  * This attribute is applicable exclusively to `"nested"` props
@@ -379,9 +407,6 @@ class props_options(ApiValidator):
379
407
  msg="`path` must be specified and a list of strings for nested options"
380
408
  )
381
409
  return
382
- self.__check_type_list__(
383
- data=self.data.get("path", []), types=(str,), prepend_path=["path"]
384
- )
385
410
 
386
411
 
387
412
  @type_enforced.Enforcer
@@ -8,7 +8,7 @@ import type_enforced
8
8
 
9
9
  @type_enforced.Enforcer
10
10
  class Validator:
11
- def __init__(self, session_data, ignore_keys: list = list(), **kwargs):
11
+ def __init__(self, session_data, ignore_keys: list[str] = list(), **kwargs):
12
12
  """
13
13
  Util to validate your session_data against the API spec.
14
14
 
@@ -16,7 +16,7 @@ class Validator:
16
16
 
17
17
  * **`session_data`**: `[dict]` &rarr; The data to validate.
18
18
  * **Note**: This should be the data you are sending to the server.
19
- * **`ignore_keys`**: `[list]` = `None` &rarr; Keys to ignore when validating.
19
+ * **`ignore_keys`**: `[list[str]]` = `None` &rarr; Keys to ignore when validating.
20
20
  * **Note**: Any keys specified here will be not be validated if encountered in the data at any level.
21
21
  """
22
22
  self.session_data = session_data
@@ -24,23 +24,23 @@ class ApiValidator:
24
24
  "accepted_values": {},
25
25
  }
26
26
 
27
- def __validate__(self, data, log: LogObject, prepend_path: list = list(), **kwargs):
27
+ def __validate__(self, data:dict, log: LogObject, prepend_path: list[str] = list(), **kwargs):
28
28
  """
29
29
  Run the API validation process for the passed data.
30
30
  """
31
- self.data = data
31
+ # Make a copy of the data to avoid modifying the original
32
+ self.data = {**data}
32
33
  self.ignore_keys = kwargs.get("ignore_keys", set())
33
34
  self.log = LogHelper(log=log, prepend_path=prepend_path)
34
35
  try:
36
+ self.__genericKeyValidation__(**kwargs)
35
37
  spec_output = self.spec(**self.data)
36
38
  extra_kwargs = spec_output.get("kwargs", {})
37
- # TODO: Find way to custom check timeValues and order
38
- extra_kwargs.pop("order", None)
39
- extra_kwargs.pop("timeValues", None)
40
39
  if extra_kwargs != {}:
41
40
  self.__warn__(
42
41
  msg=f"Unknown Fields: {str(list(extra_kwargs.keys()))}",
43
- )
42
+ )
43
+
44
44
  except Exception as e:
45
45
  self.__error__(
46
46
  msg=f"Error validating spec: {e}",
@@ -75,21 +75,95 @@ class ApiValidator:
75
75
  def __extend_spec__(self, **kwargs):
76
76
  pass
77
77
 
78
+ # Additional core validations for generic terms like `order` and `timeValues`
79
+ def __genericKeyValidation__(self, **kwargs):
80
+ # Remove `timeValues` out prior to each level validation
81
+ data_timeValues = self.data.pop("timeValues", None)
82
+ # Remove `order` out prior to each level validation
83
+ data_order = self.data.pop("order", None)
84
+ if data_timeValues is not None:
85
+ timeLength = kwargs.get("timeLength")
86
+ if timeLength is None:
87
+ self.__error__(
88
+ path=['timeValues'],
89
+ msg="`settings.time.timeLength` must be specified to validate `timeValues`",
90
+ )
91
+ else:
92
+ self.__timeValues_validation__(timeValues=data_timeValues, timeLength=timeLength)
93
+ if data_order is not None:
94
+ self.__order_validation__(order=data_order)
95
+
96
+ @type_enforced.Enforcer
97
+ def __order_validation__(self, order:dict[list[str,int]]):
98
+ """
99
+ Check that the ordering options are valid
100
+ """
101
+ orderable_data_keys = {key: list(value.keys()) for key, value in self.data.items() if isinstance(value, dict)}
102
+ if self.__check_subset_valid__(
103
+ subset=list(order.keys()),
104
+ valid_values=list(orderable_data_keys.keys()),
105
+ prepend_path=["order"],
106
+ ):
107
+ for order_key, order_list in order.items():
108
+ self.__check_subset_valid__(
109
+ subset=order_list,
110
+ valid_values=orderable_data_keys[order_key],
111
+ prepend_path=["order", order_key],
112
+ )
113
+
114
+ @type_enforced.Enforcer
115
+ def __timeValues_validation__(self, timeValues: [dict[dict],list[dict]], timeLength:int):
116
+ if len(timeValues) == 0:
117
+ return
118
+ if isinstance(timeValues, list):
119
+ if len(timeValues) != timeLength:
120
+ self.__error__(
121
+ path=['timeValues'],
122
+ msg=f"The length of `timeValues` (as a list) must be equal to `settings.time.timeLength` ({timeLength})",
123
+ )
124
+ return
125
+ if isinstance(timeValues, dict):
126
+ keys = list(timeValues.keys())
127
+ if not all(isinstance(key, int) for key in keys):
128
+ self.__error__(
129
+ path=['timeValues'],
130
+ msg="`timeValues` (as a dict) keys must be integers",
131
+ )
132
+ return
133
+ if not all(key >= 0 and key<timeLength for key in keys):
134
+ self.__error__(
135
+ path=['timeValues'],
136
+ msg=f"`timeValues` (as a dict) keys must be integers between 0 and {timeLength-1} inclusive (1 minus the value at `settings.time.timeLength`)",
137
+ )
138
+ return
139
+ timeValues = list(timeValues.values())
140
+ timeValueTypes = {k:type(v) for k,v in timeValues[0].items()}
141
+ for timeValue in timeValues:
142
+ if timeValueTypes != {k:type(v) for k,v in timeValue.items()}:
143
+ self.__error__(
144
+ path=['timeValues'],
145
+ msg="All timeValues must have the same keys and each key must have the same type",
146
+ )
147
+ return
148
+ # Update the data with the first timeValue prioritizing original data
149
+ # over the first timeValue
150
+ self.data = {**timeValues[0], **self.data}
151
+
78
152
  # Error and Warning Helpers
79
- def __error__(self, msg: str, path: list = list()):
153
+ def __error__(self, msg: str, path: list[str] = list()):
80
154
  """
81
155
  Raise an error for the log the log
82
156
  """
83
157
  self.log.add(path=path, msg=msg)
84
158
 
85
- def __warn__(self, msg: str, path: list = list()):
159
+ def __warn__(self, msg: str, path: list[str] = list()):
86
160
  """
87
161
  Raise a warning for the log the log
88
162
  """
89
163
  self.log.add(path=path, msg=msg, level="warning")
90
164
 
91
165
  # Useful Validator Checks
92
- def __check_rgba_string_valid__(self, rgba_string: str, prepend_path: list = list()):
166
+ def __check_rgba_string_valid__(self, rgba_string: str, prepend_path: list[str] = list()):
93
167
  """
94
168
  Validate an rgba string and if an issue is present, log an error
95
169
  """
@@ -112,7 +186,7 @@ class ApiValidator:
112
186
  except:
113
187
  self.__error__(path=prepend_path, msg=msg)
114
188
 
115
- def __check_pixel_string_valid__(self, pixel_string: str, prepend_path: list = list()):
189
+ def __check_pixel_string_valid__(self, pixel_string: str, prepend_path: list[str] = list()):
116
190
  """
117
191
  Validate a pixel string and if an issue is present, log an error
118
192
  """
@@ -126,7 +200,7 @@ class ApiValidator:
126
200
  except:
127
201
  self.__error__(path=prepend_path, msg=msg)
128
202
 
129
- def __check_url_valid__(self, url: str, prepend_path: list = list()):
203
+ def __check_url_valid__(self, url: str, prepend_path: list[str] = list()):
130
204
  """
131
205
  Validate a url and if an issue is present, log an error.
132
206
  """
@@ -146,7 +220,7 @@ class ApiValidator:
146
220
  return False
147
221
  return True
148
222
 
149
- def __check_date_valid__(self, input: str, date_variant: str, prepend_path: list = list()):
223
+ def __check_date_valid__(self, input: str, date_variant: str, prepend_path: list[str] = list()):
150
224
  """
151
225
  Validate a date string and if an issue is present, log an error.
152
226
  """
@@ -189,7 +263,7 @@ class ApiValidator:
189
263
  self,
190
264
  subset: list,
191
265
  valid_values: list,
192
- prepend_path: list = list(),
266
+ prepend_path: list[str] = list(),
193
267
  valid_values_count: int = 6,
194
268
  ):
195
269
  """
@@ -211,7 +285,7 @@ class ApiValidator:
211
285
  return False
212
286
  return True
213
287
 
214
- def __check_coord_path_valid__(self, coord_path: list, prepend_path: list = list()):
288
+ def __check_coord_path_valid__(self, coord_path: list[list[int,float]], prepend_path: list[str] = list()):
215
289
  """
216
290
  Validate a coordinate path and if an issue is present, log an error
217
291
  """
@@ -257,7 +331,7 @@ class ApiValidator:
257
331
  return False
258
332
  return True
259
333
 
260
- def __check_type_dict__(self, data: dict, types: tuple, prepend_path: list = list()):
334
+ def __check_type_dict__(self, data: dict, types: tuple, prepend_path: list[str] = list()):
261
335
  """
262
336
  Validate a dict only contains certain object types for values and if an issue is present, log an error
263
337
 
@@ -274,7 +348,7 @@ class ApiValidator:
274
348
  return False
275
349
  return True
276
350
 
277
- def __check_type__(self, value, check_type, prepend_path: list = list()):
351
+ def __check_type__(self, value, check_type, prepend_path: list[str] = list()):
278
352
  """
279
353
  Validate a value is a certain type and if an issue is present, log an error
280
354
 
@@ -0,0 +1,12 @@
1
+ class Socket:
2
+ def __init__(self, silent=False):
3
+ self.silent = silent
4
+ pass
5
+
6
+ def broadcast(self, *args, **kwargs):
7
+ if not self.silent:
8
+ print("broadcast: ", {"args": args, "kwargs": kwargs})
9
+
10
+ def notify(self, *args, **kwargs):
11
+ if not self.silent:
12
+ print("notify: ", {"args": args, "kwargs": kwargs})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cave_utils
3
- Version: 2.2.0b2
3
+ Version: 2.2.0b4
4
4
  Summary: Python wrapper for api use in the cave_app
5
5
  Author-email: Connor Makowski <conmak@mit.edu>
6
6
  Project-URL: Homepage, https://github.com/mit-cave/cave_utils
@@ -12,7 +12,7 @@ build-backend = "setuptools.build_meta"
12
12
 
13
13
  [project]
14
14
  name = "cave_utils"
15
- version = "2.2.0b2"
15
+ version = "2.2.0b4"
16
16
  description = "Python wrapper for api use in the cave_app"
17
17
  authors = [
18
18
  {name="Connor Makowski", email="conmak@mit.edu"}
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = cave_utils
3
- version = 2.2.0b2
3
+ version = 2.2.0b4
4
4
  description_file = README.md
5
5
 
6
6
  [options]
@@ -1,9 +0,0 @@
1
- class Socket:
2
- def __init__(self):
3
- pass
4
-
5
- def broadcast(self, *args, **kwargs):
6
- print("broadcast: ", {"args": args, "kwargs": kwargs})
7
-
8
- def notify(self, *args, **kwargs):
9
- print("notify: ", {"args": args, "kwargs": kwargs})
File without changes
File without changes
File without changes