eegdash 0.0.1__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 eegdash might be problematic. Click here for more details.

Files changed (72) hide show
  1. eegdash/SignalStore/__init__.py +0 -0
  2. eegdash/SignalStore/signalstore/__init__.py +3 -0
  3. eegdash/SignalStore/signalstore/adapters/read_adapters/abstract_read_adapter.py +13 -0
  4. eegdash/SignalStore/signalstore/adapters/read_adapters/domain_modeling/schema_read_adapter.py +16 -0
  5. eegdash/SignalStore/signalstore/adapters/read_adapters/domain_modeling/vocabulary_read_adapter.py +19 -0
  6. eegdash/SignalStore/signalstore/adapters/read_adapters/handmade_records/excel_study_organizer_read_adapter.py +114 -0
  7. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/axona/axona_read_adapter.py +912 -0
  8. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/ReadIntanSpikeFile.py +140 -0
  9. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/intan_read_adapter.py +29 -0
  10. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/__init__.py +0 -0
  11. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/data_to_result.py +62 -0
  12. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/get_bytes_per_data_block.py +36 -0
  13. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/notch_filter.py +50 -0
  14. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/qstring.py +41 -0
  15. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/read_header.py +135 -0
  16. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/intanutil/read_one_data_block.py +45 -0
  17. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhd_format/load_intan_rhd_format.py +204 -0
  18. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/__init__.py +0 -0
  19. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/data_to_result.py +60 -0
  20. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/get_bytes_per_data_block.py +37 -0
  21. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/notch_filter.py +50 -0
  22. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/qstring.py +41 -0
  23. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/read_header.py +153 -0
  24. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/intanutil/read_one_data_block.py +47 -0
  25. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/intan/load_intan_rhs_format/load_intan_rhs_format.py +213 -0
  26. eegdash/SignalStore/signalstore/adapters/read_adapters/recording_acquisitions/neurodata_without_borders/neurodata_without_borders_read_adapter.py +14 -0
  27. eegdash/SignalStore/signalstore/operations/__init__.py +4 -0
  28. eegdash/SignalStore/signalstore/operations/handler_executor.py +22 -0
  29. eegdash/SignalStore/signalstore/operations/handler_factory.py +41 -0
  30. eegdash/SignalStore/signalstore/operations/handlers/base_handler.py +44 -0
  31. eegdash/SignalStore/signalstore/operations/handlers/domain/property_model_handlers.py +79 -0
  32. eegdash/SignalStore/signalstore/operations/handlers/domain/schema_handlers.py +3 -0
  33. eegdash/SignalStore/signalstore/operations/helpers/abstract_helper.py +17 -0
  34. eegdash/SignalStore/signalstore/operations/helpers/neuroscikit_extractor.py +33 -0
  35. eegdash/SignalStore/signalstore/operations/helpers/neuroscikit_rawio.py +165 -0
  36. eegdash/SignalStore/signalstore/operations/helpers/spikeinterface_helper.py +100 -0
  37. eegdash/SignalStore/signalstore/operations/helpers/wrappers/neo_wrappers.py +21 -0
  38. eegdash/SignalStore/signalstore/operations/helpers/wrappers/nwb_wrappers.py +27 -0
  39. eegdash/SignalStore/signalstore/store/__init__.py +8 -0
  40. eegdash/SignalStore/signalstore/store/data_access_objects.py +1181 -0
  41. eegdash/SignalStore/signalstore/store/datafile_adapters.py +131 -0
  42. eegdash/SignalStore/signalstore/store/repositories.py +928 -0
  43. eegdash/SignalStore/signalstore/store/store_errors.py +68 -0
  44. eegdash/SignalStore/signalstore/store/unit_of_work.py +97 -0
  45. eegdash/SignalStore/signalstore/store/unit_of_work_provider.py +67 -0
  46. eegdash/SignalStore/signalstore/utilities/data_adapters/spike_interface_adapters/si_recording.py +1 -0
  47. eegdash/SignalStore/signalstore/utilities/data_adapters/spike_interface_adapters/si_sorter.py +1 -0
  48. eegdash/SignalStore/signalstore/utilities/testing/data_mocks.py +513 -0
  49. eegdash/SignalStore/signalstore/utilities/tools/dataarrays.py +49 -0
  50. eegdash/SignalStore/signalstore/utilities/tools/mongo_records.py +25 -0
  51. eegdash/SignalStore/signalstore/utilities/tools/operation_response.py +78 -0
  52. eegdash/SignalStore/signalstore/utilities/tools/purge_orchestration_response.py +21 -0
  53. eegdash/SignalStore/signalstore/utilities/tools/quantities.py +15 -0
  54. eegdash/SignalStore/signalstore/utilities/tools/strings.py +38 -0
  55. eegdash/SignalStore/signalstore/utilities/tools/time.py +17 -0
  56. eegdash/SignalStore/tests/conftest.py +799 -0
  57. eegdash/SignalStore/tests/data/valid_data/data_arrays/make_fake_data.py +59 -0
  58. eegdash/SignalStore/tests/unit/store/conftest.py +0 -0
  59. eegdash/SignalStore/tests/unit/store/test_data_access_objects.py +1235 -0
  60. eegdash/SignalStore/tests/unit/store/test_repositories.py +1309 -0
  61. eegdash/SignalStore/tests/unit/store/test_unit_of_work.py +7 -0
  62. eegdash/SignalStore/tests/unit/test_ci_cd.py +8 -0
  63. eegdash/__init__.py +1 -0
  64. eegdash/aws_ingest.py +29 -0
  65. eegdash/data_utils.py +213 -0
  66. eegdash/main.py +17 -0
  67. eegdash/signalstore_data_utils.py +280 -0
  68. eegdash-0.0.1.dist-info/LICENSE +20 -0
  69. eegdash-0.0.1.dist-info/METADATA +72 -0
  70. eegdash-0.0.1.dist-info/RECORD +72 -0
  71. eegdash-0.0.1.dist-info/WHEEL +5 -0
  72. eegdash-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,799 @@
1
+ import pytest
2
+ import yaml
3
+ import json
4
+ import upath
5
+ import mongomock
6
+ import xarray as xr
7
+ import numpy as np
8
+ import fsspec
9
+ from fsspec.implementations.local import LocalFileSystem
10
+ from fsspec.implementations.dirfs import DirFileSystem
11
+ from upath import UPath
12
+ from time import sleep
13
+
14
+ from signalstore.store.data_access_objects import (
15
+ MongoDAO,
16
+ FileSystemDAO,
17
+ InMemoryObjectDAO,
18
+ datetime_to_microseconds,
19
+ microseconds_to_datetime,
20
+ )
21
+
22
+ from signalstore.store.repositories import (
23
+ DomainModelRepository, domain_model_json_schema,
24
+ DataRepository,
25
+ InMemoryObjectRepository,
26
+ )
27
+
28
+ from signalstore.store.datafile_adapters import (
29
+ XarrayDataArrayNetCDFAdapter,
30
+ XarrayDataArrayZarrAdapter,
31
+ AbstractDataFileAdapter,
32
+ )
33
+
34
+ from signalstore.store import UnitOfWorkProvider
35
+
36
+ from signalstore.operations.helpers.abstract_helper import AbstractMutableHelper
37
+
38
+ from datetime import datetime, timezone, timedelta
39
+
40
+
41
+ # ==========================================================
42
+ # ==========================================================
43
+ # Utility Fixtures
44
+ # ==========================================================
45
+ # ==========================================================
46
+
47
+ @pytest.fixture
48
+ def timestamp():
49
+ ts = datetime.now()
50
+ # convert to UTC
51
+ ts = ts.astimezone(timezone.utc)
52
+ return ts
53
+
54
+ @pytest.fixture
55
+ def project():
56
+ return "testproject"
57
+
58
+ # ==========================================================
59
+ # ==========================================================
60
+ # Client
61
+ # ==========================================================
62
+ # ==========================================================
63
+
64
+ @pytest.fixture
65
+ def empty_client():
66
+ return mongomock.MongoClient()
67
+
68
+ # ==========================================================
69
+ # ==========================================================
70
+ # Data Access Objects
71
+ # ==========================================================
72
+ # ==========================================================
73
+
74
+ test_data_path = upath.UPath(__file__).parent / "data" / "valid_data"
75
+ invalid_test_data_path = upath.UPath(__file__).parent / "data" / "invalid_data"
76
+
77
+ # ===================
78
+ # Model MongoDAO
79
+ # ===================
80
+
81
+ # Property Models
82
+ # -------------------
83
+ # Load the JSON file into a list of dictionaries
84
+ property_models_path = test_data_path / "models" / "property_models.json"
85
+ with open(property_models_path, 'r') as file:
86
+ raw_property_models = json.load(file)
87
+
88
+ # generate fixtures that provide individual property models
89
+ for property_model in raw_property_models:
90
+ schema_name = property_model["schema_name"]
91
+ namestring = f"{schema_name}_property_model"
92
+ @pytest.fixture(name=namestring)
93
+ def _property_model_fixture(timestamp, property_model=property_model):
94
+ property_model["time_of_save"] = timestamp
95
+ property_model["time_of_removal"] = None
96
+ return property_model
97
+ globals()[namestring] = _property_model_fixture
98
+
99
+ # generate a fixture that provides all the property models
100
+ @pytest.fixture(name="property_models")
101
+ def _property_models_fixture(timestamp):
102
+ property_models = []
103
+ for property_model in raw_property_models:
104
+ property_model = property_model.copy()
105
+ property_model["time_of_save"] = timestamp
106
+ property_model["time_of_removal"] = None
107
+ property_models.append(property_model)
108
+ return property_models
109
+
110
+ # Metamodels
111
+ # -------------------
112
+ # Load the JSON files from folder into a list of dictionaries
113
+ metamodels_dir = test_data_path / "models" / "metamodels"
114
+ metamodel_filepaths = list(metamodels_dir.glob("*.json"))
115
+ raw_metamodels = []
116
+ for filepath in metamodel_filepaths:
117
+ with open(filepath, 'r') as file:
118
+ raw_metamodels.append(json.load(file))
119
+
120
+ # generate fixtures that provide individual metamodels
121
+ for metamodel in raw_metamodels:
122
+ schema_name = metamodel["schema_name"]
123
+ namestring = f"{schema_name}_metamodel"
124
+ @pytest.fixture(name=namestring)
125
+ def _metamodel_fixture(timestamp, metamodel=metamodel):
126
+ metamodel["time_of_save"] = timestamp
127
+ metamodel["time_of_removal"] = None
128
+ return metamodel
129
+ globals()[namestring] = _metamodel_fixture
130
+
131
+ # generate a fixture that provides all the metamodels
132
+ @pytest.fixture(name="metamodels")
133
+ def _metamodels_fixture(timestamp):
134
+ metamodels = []
135
+ for metamodel in raw_metamodels:
136
+ metamodel = metamodel.copy()
137
+ metamodel["time_of_save"] = timestamp
138
+ metamodel["time_of_removal"] = None
139
+ metamodels.append(metamodel)
140
+ return metamodels
141
+
142
+
143
+ # Data Models
144
+ # -------------------
145
+ # Load the JSON files from folder into a list of dictionaries
146
+ data_models_dir = test_data_path / "models" / "data_models"
147
+ data_model_filepaths = list(data_models_dir.glob("*.json"))
148
+ raw_data_models = []
149
+ for filepath in data_model_filepaths:
150
+ with open(filepath, 'r') as file:
151
+ raw_data_models.append(json.load(file))
152
+
153
+ # generate fixtures that provide individual data models
154
+ for data_model in raw_data_models:
155
+ schema_name = data_model["schema_name"]
156
+ namestring = f"{schema_name}_data_model"
157
+ @pytest.fixture(name=namestring)
158
+ def _data_model_fixture(timestamp, data_model=data_model):
159
+ data_model["time_of_save"] = timestamp
160
+ data_model["time_of_removal"] = None
161
+ return data_model
162
+ globals()[namestring] = _data_model_fixture
163
+
164
+ # generate a fixture that provides all the data models
165
+ @pytest.fixture(name="data_models")
166
+ def _data_models_fixture(timestamp):
167
+ data_models = []
168
+ for data_model in raw_data_models:
169
+ data_model = data_model.copy()
170
+ data_model["time_of_save"] = timestamp
171
+ data_model["time_of_removal"] = None
172
+ data_models.append(data_model)
173
+ return data_models
174
+
175
+ # Invalid Property Models
176
+ # -------------------
177
+ # Load the JSON file into a list of dictionaries
178
+ invalid_property_models_path = invalid_test_data_path / "models" / "property_models.json"
179
+ with open(invalid_property_models_path, 'r') as file:
180
+ raw_invalid_property_models = json.load(file)
181
+
182
+ # generate fixtures that provide individual property models
183
+ for property_model in raw_invalid_property_models:
184
+ schema_name = property_model["schema_name"]
185
+ namestring = f"{schema_name}_property_model"
186
+ @pytest.fixture(name=namestring)
187
+ def _invalid_property_model_fixture(timestamp, property_model=property_model):
188
+ property_model["time_of_save"] = timestamp
189
+ property_model["time_of_removal"] = None
190
+ return property_model
191
+ globals()[namestring] = _invalid_property_model_fixture
192
+
193
+ @pytest.fixture(name="models")
194
+ def _models_fixture(timestamp, property_models, metamodels, data_models):
195
+ models = []
196
+ for property_model in property_models:
197
+ property_model = property_model.copy()
198
+ property_model["time_of_save"] = timestamp
199
+ property_model["time_of_removal"] = None
200
+ models.append(property_model)
201
+ for metamodel in metamodels:
202
+ metamodel = metamodel.copy()
203
+ metamodel["time_of_save"] = timestamp
204
+ metamodel["time_of_removal"] = None
205
+ models.append(metamodel)
206
+ for data_model in data_models:
207
+ data_model = data_model.copy()
208
+ data_model["time_of_save"] = timestamp
209
+ data_model["time_of_removal"] = None
210
+ models.append(data_model)
211
+ return models
212
+
213
+ # generate a fixture that provides all the property models
214
+ @pytest.fixture(name="invalid_property_models")
215
+ def _invalid_property_models_fixture(timestamp, unit_of_measure_property_model):
216
+ invalid_property_models = []
217
+ for property_model in raw_invalid_property_models:
218
+ property_model = property_model.copy()
219
+ property_model["time_of_save"] = timestamp
220
+ property_model["time_of_removal"] = None
221
+ invalid_property_models.append(property_model)
222
+ # add property models from the valid set that are just missing a required field
223
+ required_fields = ["schema_title", "schema_description", "schema_type", "json_schema"]
224
+ for field in required_fields:
225
+ invalid_property_model = unit_of_measure_property_model.copy()
226
+ invalid_property_model.pop(field)
227
+ invalid_property_model["time_of_save"] = timestamp
228
+ invalid_property_model["time_of_removal"] = None
229
+ # rename the schema_name to make it unique
230
+ invalid_property_model["schema_name"] = f"invalid_property_model_missing_{field}"
231
+ invalid_property_models.append(invalid_property_model)
232
+ return invalid_property_models
233
+
234
+ # Invalid Data Models
235
+ # -------------------
236
+ # Load the JSON files from folder into a list of dictionaries
237
+ invalid_data_models_dir = invalid_test_data_path / "models" / "data_models"
238
+ invalid_data_model_filepaths = list(invalid_data_models_dir.glob("*.json"))
239
+ raw_invalid_data_models = []
240
+ for filepath in invalid_data_model_filepaths:
241
+ with open(filepath, 'r') as file:
242
+ raw_invalid_data_models.append(json.load(file))
243
+
244
+ # generate fixtures that provide individual data models
245
+ for data_model in raw_invalid_data_models:
246
+ schema_name = data_model["schema_name"]
247
+ namestring = f"{schema_name}_data_model"
248
+ @pytest.fixture(name=namestring)
249
+ def _invalid_data_model_fixture(timestamp, data_model=data_model):
250
+ data_model["time_of_save"] = timestamp
251
+ data_model["time_of_removal"] = None
252
+ return data_model
253
+ globals()[namestring] = _invalid_data_model_fixture
254
+
255
+ # generate a fixture that provides all the data models
256
+ @pytest.fixture(name="invalid_data_models")
257
+ def _invalid_data_models_fixture(timestamp, session_data_model):
258
+ invalid_data_models = []
259
+ for data_model in raw_invalid_data_models:
260
+ data_model = data_model.copy()
261
+ data_model["time_of_save"] = timestamp
262
+ data_model["time_of_removal"] = None
263
+ invalid_data_models.append(data_model)
264
+ # add data models from the valid set that are just missing a required field
265
+ required_fields = ["schema_title", "schema_description", "schema_type", "json_schema"]
266
+ for field in required_fields:
267
+ invalid_data_model = session_data_model.copy()
268
+ invalid_data_model.pop(field)
269
+ invalid_data_model["time_of_save"] = timestamp
270
+ invalid_data_model["time_of_removal"] = None
271
+ # rename the schema_name to make it unique
272
+ invalid_data_model["schema_name"] = f"invalid_{invalid_data_model['schema_name']}_missing_{field}"
273
+ invalid_data_models.append(invalid_data_model)
274
+ return invalid_data_models
275
+
276
+
277
+ # DomainModelDAOs
278
+
279
+ @pytest.fixture
280
+ def populated_domain_model_dao(empty_client, project, property_models, metamodels, data_models, timestamp):
281
+ dao = MongoDAO(empty_client, project, "domain_models", ["schema_name"])
282
+ for property_model in property_models:
283
+ dao.add(property_model, timestamp=timestamp)
284
+ for metamodel in metamodels:
285
+ dao.add(metamodel, timestamp=timestamp)
286
+ for data_model in data_models:
287
+ dao.add(data_model, timestamp=timestamp)
288
+ return dao
289
+
290
+ @pytest.fixture
291
+ def populated_domain_model_dao_with_invalid_models(empty_client, project, property_models, invalid_property_models, metamodels, data_models, invalid_data_models, timestamp):
292
+ dao = MongoDAO(empty_client, project, "domain_models", ["schema_name"])
293
+ for property_model in property_models:
294
+ dao.add(property_model, timestamp=timestamp)
295
+ for metamodel in metamodels:
296
+ dao.add(metamodel, timestamp=timestamp)
297
+ for data_model in data_models:
298
+ dao.add(data_model, timestamp=timestamp)
299
+ for property_model in invalid_property_models:
300
+ dao.add(property_model, timestamp=timestamp)
301
+ # add a property model with an invalid (text) json schema
302
+ invalid_json_schema_property_model = {
303
+ "schema_name": "invalid_json_schema",
304
+ "schema_title": "Invalid JSON Schema Model",
305
+ "schema_description": "A JSON Schema Model with invalid JSON",
306
+ "schema_type": "property_model",
307
+ "json_schema": "invalid json schema",
308
+ "time_of_save": timestamp,
309
+ "time_of_removal": None
310
+ }
311
+ dao._collection.insert_one(invalid_json_schema_property_model)
312
+ for data_model in invalid_data_models:
313
+ dao.add(data_model, timestamp=timestamp)
314
+ return dao
315
+
316
+ @pytest.fixture
317
+ def invalid_model_schema_names(invalid_property_models, invalid_data_models):
318
+ required_fields = ["schema_title", "schema_description", "schema_type", "json_schema"]
319
+ missing_field_names = [f"invalid_property_model_missing_{field}" for field in required_fields]
320
+ other_invalid_property_model_names = [property_model.get("schema_name") for property_model in invalid_property_models if property_model.get("schema_name") is not None]
321
+ invalid_data_model_names = [data_model.get("schema_name") for data_model in invalid_data_models if data_model.get("schema_name") is not None]
322
+ return missing_field_names + other_invalid_property_model_names + invalid_data_model_names
323
+
324
+ # ===================
325
+ # Records
326
+ # ===================
327
+ # Load the JSON file into a list of dictionaries
328
+ # valid records
329
+ records_path = test_data_path / "records.json"
330
+ with open(records_path, 'r') as file:
331
+ raw_records = json.load(file)
332
+
333
+ # invalid records
334
+ invalid_records_path = invalid_test_data_path / "records.json"
335
+ with open(invalid_records_path, 'r') as file:
336
+ raw_invalid_records = json.load(file)
337
+
338
+ # generate fixtures that provide individual records
339
+ for record in raw_records:
340
+ schema_ref, data_name = record["schema_ref"], record["data_name"]
341
+ namestring = f"{schema_ref}_{data_name}_record"
342
+ @pytest.fixture(name=namestring)
343
+ def _record_fixture(timestamp, record=record):
344
+ record["time_of_save"] = datetime_to_microseconds(timestamp)
345
+ record["time_of_removal"] = None
346
+ return record
347
+ globals()[namestring] = _record_fixture
348
+
349
+ @pytest.fixture(name="test_record")
350
+ def _test_record_fixture(timestamp):
351
+ record = {"schema_ref": "test_schema", "name": "test_name", "key1": "value1", "key2": "value2"}
352
+ record["time_of_save"] = datetime_to_microseconds(timestamp)
353
+ record["time_of_removal"] = None
354
+ return record
355
+
356
+ # generate a fixture that provides all the records
357
+ @pytest.fixture(name="records")
358
+ def _records_fixture(timestamp):
359
+ records = []
360
+ for record in raw_records:
361
+ record = record.copy()
362
+ record["time_of_save"] = datetime_to_microseconds(timestamp)
363
+ record["time_of_removal"] = None
364
+ records.append(record)
365
+ return records
366
+
367
+ # generate a fixture that provides all the invalid records
368
+ @pytest.fixture(name="invalid_records")
369
+ def _invalid_records_fixture(timestamp):
370
+ invalid_records = []
371
+ for record in raw_invalid_records:
372
+ record = record.copy()
373
+ record["time_of_save"] = datetime_to_microseconds(timestamp)
374
+ record["time_of_removal"] = None
375
+ invalid_records.append(record)
376
+ return invalid_records
377
+
378
+ def record_dao_factory(client, project):
379
+ return MongoDAO(client, project, "records", ["schema_ref", "data_name", "version_timestamp"])
380
+
381
+ @pytest.fixture
382
+ def populated_record_dao(empty_client, project, records, timestamp):
383
+ dao = record_dao_factory(empty_client, project)
384
+ for record in records:
385
+ dao.add(record, timestamp=timestamp, versioning_on=False)
386
+ return dao
387
+
388
+ @pytest.fixture
389
+ def populated_record_dao_with_invalid_records(empty_client, project, records, invalid_records, timestamp):
390
+ dao = record_dao_factory(empty_client, project)
391
+ for record in records:
392
+ dao.add(record, timestamp=timestamp, versioning_on=False)
393
+ for record in invalid_records:
394
+ dao.add(record, timestamp=timestamp, versioning_on=False)
395
+ return dao
396
+
397
+ # ===================
398
+ # FileSystemDAO
399
+ # ===================
400
+ # Load NetCDF files into list of xarray DataArray objects
401
+
402
+ def deserialize_dataarray(data_object):
403
+ """Deserializes a data object.
404
+ Arguments:
405
+ data_object {dict} -- The data object to deserialize.
406
+ Returns:
407
+ dict -- The deserialized data object.
408
+ """
409
+ attrs = data_object.attrs.copy()
410
+ for key, value in attrs.items():
411
+ if isinstance(value, str):
412
+ if value == 'True':
413
+ attrs[key] = True
414
+ elif value == 'False':
415
+ attrs[key] = False
416
+ elif value == 'None':
417
+ attrs[key] = None
418
+ elif value.startswith('{'):
419
+ attrs[key] = json.loads(value)
420
+ if isinstance(value, np.ndarray):
421
+ attrs[key] = value.tolist()
422
+ data_object.attrs = attrs
423
+ return data_object
424
+
425
+ netcdf_dir = test_data_path / "data_arrays"
426
+ netcdf_files = list(netcdf_dir.glob("*.nc"))
427
+ dataarrays = []
428
+ for filepath in netcdf_files:
429
+ dataarray = xr.open_dataarray(filepath)
430
+ dataarray = deserialize_dataarray(dataarray)
431
+ dataarrays.append(dataarray)
432
+
433
+ @pytest.fixture(name="dataarrays")
434
+ def _dataarrays_fixture():
435
+ return dataarrays
436
+
437
+ # make a fixture that provides a single dataarray for each file of the form {schema_ref}_{name}_dataarray
438
+ for dataarray in dataarrays:
439
+ try:
440
+ schema_ref, data_name = dataarray.attrs["schema_ref"], dataarray.attrs["data_name"]
441
+ except:
442
+ raise ValueError(f"DataArray.attrs {dataarray.attrs} does not have schema_ref and data_name attributes")
443
+ namestring = f"{schema_ref}__{data_name}__dataarray"
444
+ @pytest.fixture(name=namestring)
445
+ def _dataarray_fixture(dataarray=dataarray):
446
+ return dataarray
447
+
448
+ globals()[namestring] = _dataarray_fixture
449
+
450
+ # make a fixture that provides a new dataarray with a different name
451
+ @pytest.fixture(name="test_dataarray")
452
+ def _test_dataarray_fixture(timestamp):
453
+ dataarray = xr.DataArray([[1, 2, 3], [4, 5, 6]], dims=("x", "y"), coords={"x": [10, 20]})
454
+ dataarray.attrs["schema_ref"] = "test"
455
+ dataarray.attrs["data_name"] = "test"
456
+ dataarray.attrs["version_timestamp"] = 0
457
+ dataarray.attrs["time_of_save"] = datetime_to_microseconds(timestamp)
458
+ dataarray.attrs["time_of_removal"] = ""
459
+ return dataarray
460
+
461
+
462
+ class MockMutableModelHelper(AbstractMutableHelper):
463
+ def __init__(self, schema_ref: str, data_name: str, version_timestamp: datetime=None):
464
+ state = np.array([0])
465
+ attrs = {"schema_ref": schema_ref,
466
+ "data_name": data_name,
467
+ "version_timestamp": version_timestamp
468
+ }
469
+ super().__init__(attrs=attrs, state=state)
470
+
471
+ def train(self, iterations: int=1):
472
+ # add a number to the state
473
+ for i in range(iterations):
474
+ self.state = np.append(self.state, self.state[-1] + 1)
475
+
476
+ class MockMutableModelNumpyAdapter(AbstractDataFileAdapter):
477
+
478
+ def read_file(self, path):
479
+ with self.filesystem.open(path, mode='rb') as f:
480
+ idkwargs = self.path_to_id_kwargs(path)
481
+ helper = MockMutableModelHelper(**idkwargs)
482
+ helper.state = np.load(f)
483
+ return helper
484
+
485
+ def write_file(self, path, data_object):
486
+ with self.filesystem.open(path, mode='wb') as f:
487
+ np.save(f, data_object.state)
488
+
489
+ def get_id_kwargs(self, data_object):
490
+ return {"schema_ref": data_object.attrs.get("schema_ref"),
491
+ "data_name": data_object.attrs.get("data_name"),
492
+ "version_timestamp": data_object.attrs.get("version_timestamp") or 0
493
+ }
494
+
495
+ def path_to_id_kwargs(self, path):
496
+ base_name = upath.UPath(path).stem
497
+ schema_ref, data_name, version_string = base_name.split("__")
498
+ version_timestamp = int(version_string.split("_")[1])
499
+ return {"schema_ref": schema_ref,
500
+ "data_name": data_name,
501
+ "version_timestamp": microseconds_to_datetime(version_timestamp)
502
+ }
503
+
504
+ @property
505
+ def file_extension(self):
506
+ return ".npy"
507
+
508
+ @property
509
+ def file_format(self):
510
+ return "Numpy"
511
+
512
+ @property
513
+ def data_object_type(self):
514
+ return type(MockMutableModelHelper("", ""))
515
+
516
+
517
+ # make a fixture that provides a MutableModelHelper object
518
+ @pytest.fixture(name="mutable_model_helper")
519
+ def _mutable_model_helper_fixture():
520
+ return MockMutableModelHelper(schema_ref="test", data_name="test")
521
+
522
+ @pytest.fixture(name="xarray_netcdf_adapter")
523
+ def _default_data_adapter_fixture():
524
+ return XarrayDataArrayNetCDFAdapter()
525
+
526
+ @pytest.fixture(name="xarray_zarr_adapter")
527
+ def _zarr_data_adapter_fixture():
528
+ return XarrayDataArrayZarrAdapter()
529
+
530
+ @pytest.fixture(name="model_numpy_adapter")
531
+ def _numpy_model_adapter_fixture():
532
+ return MockMutableModelNumpyAdapter()
533
+
534
+
535
+ # make a fixture that provides a populated file DAO
536
+ @pytest.fixture(name="populated_netcdf_file_dao")
537
+ def _populated_netcdf_file_dao_fixture(tmpdir, xarray_netcdf_adapter, dataarrays):
538
+ project_dir = str(tmpdir) + "/netcdf"
539
+ filesystem = LocalFileSystem(root=str(project_dir))
540
+ dao = FileSystemDAO(filesystem=filesystem,
541
+ project_dir=project_dir,
542
+ default_data_adapter=xarray_netcdf_adapter)
543
+ for dataarray in dataarrays:
544
+ dao.add(data_object=dataarray)
545
+ #TODO: add versioned objects with different data adapters
546
+ return dao
547
+
548
+ @pytest.fixture(name="populated_zarr_file_dao")
549
+ def _populated_zarr_file_dao_fixture(tmpdir, xarray_zarr_adapter, dataarrays):
550
+ project_dir = str(tmpdir) + "/zarr"
551
+ filesystem = LocalFileSystem(root=str(project_dir))
552
+ dao = FileSystemDAO(filesystem=filesystem,
553
+ project_dir=project_dir,
554
+ default_data_adapter=xarray_zarr_adapter)
555
+ for dataarray in dataarrays:
556
+ dao.add(data_object=dataarray)
557
+ #TODO: add versioned objects with different data adapters
558
+ return dao
559
+
560
+ @pytest.fixture(name="populated_numpy_file_dao")
561
+ def _populated_numpy_file_dao_fixture(tmpdir, model_numpy_adapter, mutable_model_helper, timestamp):
562
+ project_dir = str(tmpdir) + "/numpy"
563
+ filesystem = LocalFileSystem(root=str(project_dir))
564
+ dao = FileSystemDAO(filesystem = filesystem,
565
+ project_dir=project_dir,
566
+ default_data_adapter=model_numpy_adapter)
567
+ for i in range(1, 11):
568
+ mutable_model_helper.attrs["version_timestamp"] = timestamp + timedelta(seconds=i)
569
+ mutable_model_helper.train(i)
570
+ data_name = mutable_model_helper.attrs["data_name"]
571
+ schema_ref = mutable_model_helper.attrs["schema_ref"]
572
+ vts = mutable_model_helper.attrs["version_timestamp"]
573
+ if not dao.exists(data_name=data_name, schema_ref=schema_ref, version_timestamp=vts):
574
+ dao.add(data_object=mutable_model_helper)
575
+ return dao
576
+
577
+ @pytest.fixture(name="populated_file_dao")
578
+ def _populated_file_dao_fixture(tmpdir, dataarrays, xarray_netcdf_adapter, model_numpy_adapter, mutable_model_helper, timestamp):
579
+ project_dir = str(tmpdir)
580
+ filesystem = LocalFileSystem(root=str(project_dir))
581
+ dao = FileSystemDAO(filesystem=filesystem,
582
+ project_dir=project_dir,
583
+ default_data_adapter=xarray_netcdf_adapter)
584
+ for dataarray in dataarrays:
585
+ deserialize_dataarray(dataarray)
586
+ dao.add(data_object=dataarray)
587
+ for i in range(1, 11):
588
+ mutable_model_helper.attrs["version_timestamp"] = timestamp + timedelta(seconds=i)
589
+ mutable_model_helper.train(i)
590
+ dao.add(data_object=mutable_model_helper, data_adapter=model_numpy_adapter)
591
+ return dao
592
+
593
+ @pytest.fixture(name="file_dao_options")
594
+ def _file_dao_options_fixture(populated_netcdf_file_dao, populated_zarr_file_dao, populated_numpy_file_dao):
595
+ return {
596
+ "netcdf": populated_netcdf_file_dao,
597
+ "zarr": populated_zarr_file_dao,
598
+ "numpy": populated_numpy_file_dao
599
+ }
600
+
601
+ @pytest.fixture(name="data_adapter_options")
602
+ def _data_adapter_options_fixture(xarray_netcdf_adapter, xarray_zarr_adapter, model_numpy_adapter):
603
+ return {
604
+ "netcdf": xarray_netcdf_adapter,
605
+ "zarr": xarray_zarr_adapter,
606
+ "numpy": model_numpy_adapter
607
+ }
608
+
609
+ @pytest.fixture(name="new_object_options")
610
+ def _new_object_options_fixture(timestamp):
611
+ xarray = xr.DataArray([[1, 2, 3], [4, 5, 6]], dims=("x", "y"), coords={"x": [10, 20]}, attrs={"schema_ref": "new", "data_name": "new", "version_timestamp": None})
612
+ model = MockMutableModelHelper(schema_ref="new", data_name="new", version_timestamp=timestamp)
613
+ return {
614
+ "netcdf": xarray,
615
+ "zarr": xarray,
616
+ "numpy": model
617
+ }
618
+
619
+
620
+ # ===================
621
+ # InMemoryObjectDAO
622
+ # ===================
623
+
624
+ # make a fixture that provides a bunch of objects of varying types to be stored in memory
625
+
626
+ objects = [{"key1": "value1", "key2": "value2"},
627
+ [1, 2, 3],
628
+ "string",
629
+ True,
630
+ 1,
631
+ 1.0,
632
+ np.array([1, 2, 3]),
633
+ xr.DataArray([[1, 2, 3], [4, 5, 6]], dims=("x", "y"), coords={"x": [10, 20]}),
634
+ ]
635
+
636
+ @pytest.fixture(name="objects")
637
+ def _objects_fixture():
638
+ return objects
639
+
640
+ def make_typestring(object):
641
+ return type(object).__name__.lower()
642
+
643
+ # make fixtures for each object type
644
+ for object in objects:
645
+ typestring = make_typestring(object)
646
+ namestring = typestring + "_object"
647
+ @pytest.fixture(name=namestring)
648
+ def _object_fixture(object=object):
649
+ return object
650
+ globals()[namestring] = _object_fixture
651
+
652
+ # make a populated in memory object DAO
653
+ @pytest.fixture
654
+ def populated_memory_dao(objects):
655
+ dao = InMemoryObjectDAO(dict())
656
+ for object in objects:
657
+ typestring = make_typestring(object)
658
+ dao.add(object, 'test_' + typestring )
659
+ return dao
660
+
661
+ @pytest.fixture
662
+ def test_object():
663
+ return np.zeros((3, 3))
664
+
665
+
666
+ # ==========================================================
667
+ # ==========================================================
668
+ # Repositories
669
+ # ==========================================================
670
+ # ==========================================================
671
+
672
+ # ===================
673
+ # DomainModelRepository
674
+ # ===================
675
+
676
+ @pytest.fixture
677
+ def populated_domain_repo(populated_domain_model_dao_with_invalid_models):
678
+ domain_repo = DomainModelRepository(model_dao=populated_domain_model_dao_with_invalid_models,
679
+ model_metaschema=domain_model_json_schema)
680
+ return domain_repo
681
+
682
+ @pytest.fixture
683
+ def populated_valid_only_domain_repo(populated_domain_model_dao):
684
+ domain_repo = DomainModelRepository(model_dao=populated_domain_model_dao,
685
+ model_metaschema=domain_model_json_schema)
686
+ return domain_repo
687
+
688
+ @pytest.fixture
689
+ def empty_domain_repo(empty_client, project):
690
+ dao = MongoDAO(empty_client, project, "domain_models", ["schema_name"])
691
+ domain_repo = DomainModelRepository(model_dao=dao,
692
+ model_metaschema=domain_model_json_schema)
693
+ return domain_repo
694
+
695
+
696
+ # ===================
697
+ # DataRepository
698
+ # ===================
699
+ @pytest.fixture
700
+ def populated_data_repo(populated_domain_repo, populated_record_dao, populated_file_dao, mutable_model_helper, model_numpy_adapter, timestamp):
701
+ data_repo = DataRepository(record_dao=populated_record_dao,
702
+ file_dao=populated_file_dao,
703
+ domain_repo=populated_domain_repo)
704
+ for i in range(1, 11):
705
+ mutable_model_helper.attrs["version_timestamp"] = timestamp + timedelta(seconds=i)
706
+ mutable_model_helper.attrs["schema_ref"] = "numpy_test"
707
+ mutable_model_helper.attrs["data_name"] = "numpy_test"
708
+ mutable_model_helper.attrs["has_file"] = True
709
+ mutable_model_helper.train(i)
710
+ data_repo.add(object=mutable_model_helper, data_adapter=model_numpy_adapter, versioning_on=True)
711
+ data_repo.clear_operation_history()
712
+
713
+
714
+ return data_repo
715
+
716
+ @pytest.fixture
717
+ def populated_data_repo_with_invalid_records(populated_domain_repo, populated_record_dao_with_invalid_records, populated_file_dao, mutable_model_helper, model_numpy_adapter, timestamp):
718
+ data_repo = DataRepository(record_dao=populated_record_dao_with_invalid_records,
719
+ file_dao=populated_file_dao,
720
+ domain_repo=populated_domain_repo)
721
+ for i in range(1, 11):
722
+ mutable_model_helper.attrs["version_timestamp"] = timestamp + timedelta(seconds=i)
723
+ mutable_model_helper.attrs["schema_ref"] = "numpy_test"
724
+ mutable_model_helper.attrs["data_name"] = "numpy_test"
725
+ mutable_model_helper.attrs["has_file"] = True
726
+ mutable_model_helper.train(i)
727
+ data_repo.add(object=mutable_model_helper, data_adapter=model_numpy_adapter, versioning_on=True)
728
+ sleep(0.001)
729
+ data_repo.clear_operation_history()
730
+
731
+ return data_repo
732
+
733
+
734
+
735
+ # ===================
736
+ # InMemoryObjectRepository
737
+ # ===================
738
+ @pytest.fixture
739
+ def populated_memory_repo(populated_record_repository, populated_memory_dao):
740
+ return InMemoryObjectRepository(populated_record_repository, populated_memory_dao)
741
+
742
+ # ===================
743
+ # Unit of Work
744
+ # ===================
745
+ @pytest.fixture(name="unit_of_work")
746
+ def _unit_of_work_provider_fixture(tmpdir):
747
+ mongo_client = mongomock.MongoClient()
748
+ og_filesystem = LocalFileSystem(root=str(tmpdir))
749
+ filesystem = DirFileSystem(tmpdir, og_filesystem)
750
+ memory_store = dict()
751
+ uow_provider = UnitOfWorkProvider(mongo_client, filesystem, memory_store)
752
+ unit_of_work = uow_provider("testproject")
753
+ with unit_of_work as uow:
754
+ for property_model in raw_property_models:
755
+ uow.domain_models.add(property_model)
756
+ for metamodel in raw_metamodels:
757
+ uow.domain_models.add(metamodel)
758
+ for data_model in raw_data_models:
759
+ uow.domain_models.add(data_model)
760
+ for record in raw_records:
761
+ if not record.get("has_file"):
762
+ uow.data.add(record)
763
+ for dataarray in dataarrays:
764
+ if not dataarray.attrs.get("schema_ref") == "test":
765
+ uow.data.add(dataarray)
766
+ uow.commit()
767
+ return uow
768
+
769
+
770
+
771
+ # ==========================================================
772
+ # ==========================================================
773
+ # Helpers
774
+ # ==========================================================
775
+ # ==========================================================
776
+ # TODO: add helpers
777
+
778
+ # ==========================================================
779
+ # ==========================================================
780
+ # Handlers
781
+ # ==========================================================
782
+ # ==========================================================
783
+ # TODO: add handlers
784
+
785
+ # ==========================================================
786
+ # ==========================================================
787
+ # Dependency Injection
788
+ # ==========================================================
789
+ # ==========================================================
790
+
791
+ # ===================
792
+ # Handler Factory
793
+ # ===================
794
+ # TODO: add handler factory
795
+
796
+ # ===================
797
+ # Project Container
798
+ # ===================
799
+ # TODO: add project container