deriva 1.7.10__py3-none-any.whl → 1.7.12__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.
@@ -1,893 +0,0 @@
1
- # Tests for the datapath module.
2
- #
3
- # Environment variables:
4
- # DERIVA_PY_TEST_HOSTNAME: hostname of the test server
5
- # DERIVA_PY_TEST_CREDENTIAL: user credential, if none, it will attempt to get credentail for given hostname
6
- # DERIVA_PY_TEST_VERBOSE: set for verbose logging output to stdout
7
-
8
- from copy import deepcopy
9
- import logging
10
- from operator import itemgetter
11
- import os
12
- import unittest
13
- from deriva.core import DerivaServer, get_credential, ermrest_model as _em, __version__
14
- from deriva.core.datapath import *
15
-
16
- try:
17
- from pandas import DataFrame
18
- HAS_PANDAS = True
19
- except ImportError:
20
- HAS_PANDAS = False
21
-
22
- TEST_EXP_MAX = 100
23
- TEST_EXPTYPE_MAX = 10
24
- TEST_EXP_NAME_FORMAT = "experiment-{}"
25
- TEST_PROJ_MAX = 1
26
- TEST_PROJ_INVESTIGATOR = "Smith"
27
- TEST_PROJ_NUM = 1
28
-
29
- SPECIAL_CHARACTERS = '`~!@#$%^&*()_+-={}|[]\\;:"\',./<>?'
30
- INVALID_IDENTIFIER, INVALID_IDENTIFIER_FIXED = '9 %$ ', '_9____'
31
- RESERVED_IDENTIFIER = 'column_definitions'
32
- CONFLICTING_IDENTIFIER, CONFLICTING_IDENTIFIER_FIXED = RESERVED_IDENTIFIER + '1', RESERVED_IDENTIFIER + '2'
33
-
34
- SNAME_ISA = 'ISA'
35
- SNAME_VOCAB = 'Vocab'
36
- TNAME_PROJECT = 'Project'
37
- TNAME_EXPERIMENT = 'Experiment'
38
- TNAME_EXPERIMENT_TYPE = 'Experiment_Type'
39
- TNAME_EXPERIMENT_COPY = 'Experiment_Copy'
40
-
41
- hostname = os.getenv("DERIVA_PY_TEST_HOSTNAME")
42
- logger = logging.getLogger(__name__)
43
- if os.getenv("DERIVA_PY_TEST_VERBOSE"):
44
- logger.setLevel(logging.DEBUG)
45
- logger.addHandler(logging.StreamHandler())
46
-
47
-
48
- def define_test_schema(catalog):
49
- """Defines the test schema.
50
-
51
- A 'vocab' schema with an 'experiment_type' term table.
52
- An 'isa' schema with an 'experiment' table, with 'type' that references the vocab table.
53
- """
54
- model = catalog.getCatalogModel()
55
- vocab = model.create_schema(_em.Schema.define(SNAME_VOCAB))
56
- vocab.create_table(_em.Table.define_vocabulary(TNAME_EXPERIMENT_TYPE, "TEST:{RID}"))
57
- isa = model.create_schema(_em.Schema.define(SNAME_ISA))
58
-
59
- # create TNAME_PROJECT table
60
- table_def = _em.Table.define(
61
- TNAME_PROJECT,
62
- column_defs=[
63
- _em.Column.define(cname, ctype) for (cname, ctype) in [
64
- ('Investigator', _em.builtin_types.text),
65
- ('Num', _em.builtin_types.int4),
66
- (INVALID_IDENTIFIER, _em.builtin_types.int4),
67
- (RESERVED_IDENTIFIER, _em.builtin_types.text),
68
- (RESERVED_IDENTIFIER + '1', _em.builtin_types.text)
69
- ]
70
- ],
71
- key_defs=[
72
- _em.Key.define(['Investigator', 'Num'])
73
- ]
74
- )
75
- isa.create_table(table_def)
76
-
77
- # experiment table definition helper
78
- def exp_table_def(exp_table_name):
79
- return _em.Table.define(
80
- exp_table_name,
81
- column_defs=[
82
- _em.Column.define(cname, ctype) for (cname, ctype) in [
83
- ('Name', _em.builtin_types.text),
84
- ('Amount', _em.builtin_types.int4),
85
- ('Time', _em.builtin_types.timestamptz),
86
- ('Type', _em.builtin_types.text),
87
- ('Project Investigator', _em.builtin_types.text),
88
- ('Project_Num', _em.builtin_types.int4),
89
- ('Empty', _em.builtin_types.int4)
90
- ]
91
- ],
92
- key_defs=[
93
- _em.Key.define(['Name'])
94
- ],
95
- fkey_defs=[
96
- _em.ForeignKey.define(['Type'], SNAME_VOCAB, TNAME_EXPERIMENT_TYPE, ['ID']),
97
- _em.ForeignKey.define(['Project Investigator', 'Project_Num'], SNAME_ISA, TNAME_PROJECT, ['Investigator', 'Num'])
98
- ]
99
- )
100
-
101
- # create experiment tables
102
- isa.create_table(exp_table_def(TNAME_EXPERIMENT))
103
- isa.create_table(exp_table_def(TNAME_EXPERIMENT_COPY))
104
-
105
-
106
- def _generate_experiment_entities(types, count):
107
- """Generates experiment entities (content only)
108
-
109
- :param types: type entities to be referenced from entities
110
- :param count: number of entities to return
111
- :return: a list of dict objects (experiment entities)
112
- """
113
- return [
114
- {
115
- "Name": TEST_EXP_NAME_FORMAT.format(i),
116
- "Amount": i,
117
- "Time": "2018-01-{}T01:00:00.0".format(1 + (i % 31)),
118
- "Type": types[i % TEST_EXPTYPE_MAX]['ID'],
119
- "Project Investigator": TEST_PROJ_INVESTIGATOR,
120
- "Project_Num": TEST_PROJ_NUM,
121
- "Empty": None
122
- }
123
- for i in range(count)
124
- ]
125
-
126
-
127
- def populate_test_catalog(catalog):
128
- """Populate the test catalog."""
129
- paths = catalog.getPathBuilder()
130
- logger.debug("Inserting project...")
131
- proj_table = paths.schemas[SNAME_ISA].tables[TNAME_PROJECT]
132
- logger.debug("Inserting investigators...")
133
- proj_table.insert([
134
- {"Investigator": TEST_PROJ_INVESTIGATOR, "Num": TEST_PROJ_NUM}
135
- ])
136
- logger.debug("Inserting experiment types...")
137
- type_table = paths.schemas[SNAME_VOCAB].tables[TNAME_EXPERIMENT_TYPE]
138
- types = type_table.insert([
139
- {"Name": "{}".format(name), "Description": "NA"} for name in range(TEST_EXPTYPE_MAX)
140
- ], defaults=['ID', 'URI'])
141
- logger.debug("Inserting experiments...")
142
- exp = paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT]
143
- exp.insert(_generate_experiment_entities(types, TEST_EXP_MAX))
144
-
145
-
146
- @unittest.skipUnless(hostname, "Test host not specified")
147
- class DatapathTests (unittest.TestCase):
148
- catalog = None
149
-
150
- @classmethod
151
- def setUpClass(cls):
152
- logger.debug("setupUpClass begin")
153
- credential = os.getenv("DERIVA_PY_TEST_CREDENTIAL") or get_credential(hostname)
154
- server = DerivaServer('https', hostname, credentials=credential)
155
- cls.catalog = server.create_ermrest_catalog()
156
- try:
157
- define_test_schema(cls.catalog)
158
- populate_test_catalog(cls.catalog)
159
- except Exception:
160
- # on failure, delete catalog and re-raise exception
161
- cls.catalog.delete_ermrest_catalog(really=True)
162
- raise
163
- logger.debug("setupUpClass done")
164
-
165
- @classmethod
166
- def tearDownClass(cls):
167
- logger.debug("tearDownClass begin")
168
- cls.catalog.delete_ermrest_catalog(really=True)
169
- logger.debug("tearDownClass done")
170
-
171
- def setUp(self):
172
- self.paths = self.catalog.getPathBuilder()
173
- self.project = self.paths.schemas[SNAME_ISA].tables[TNAME_PROJECT]
174
- self.experiment = self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT]
175
- self.experiment_type = self.paths.schemas[SNAME_VOCAB].tables[TNAME_EXPERIMENT_TYPE]
176
- self.experiment_copy = self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT_COPY]
177
- self.types = list(self.experiment_type.entities())
178
- self.model = self.catalog.getCatalogModel()
179
-
180
- def tearDown(self):
181
- try:
182
- self.experiment_copy.path.delete()
183
- except DataPathException:
184
- # suppresses 404 errors when the table is empty
185
- pass
186
-
187
- def test_catalog_dir_base(self):
188
- self.assertIn('schemas', dir(self.paths))
189
-
190
- def test_schema_dir_base(self):
191
- self.assertLess({'_name', 'tables', 'describe'}, set(dir(self.paths.schemas[SNAME_ISA])))
192
-
193
- def test_datapath_dir_base(self):
194
- self.assertLess({'aggregates', 'groupby', 'attributes', 'context', 'delete', 'entities', 'filter',
195
- 'link', 'table_instances', 'uri'}, set(dir(self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT].path)))
196
-
197
- def test_table_dir_base(self):
198
- self.assertLess({'aggregates', 'alias', 'groupby', 'attributes', 'describe', 'entities', 'filter', 'insert',
199
- 'link', 'path', 'update', 'uri'}, set(dir(self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT])))
200
-
201
- def test_catalog_dir_with_schemas(self):
202
- self.assertLess({SNAME_ISA, SNAME_VOCAB}, set(dir(self.paths)))
203
-
204
- def test_schema_dir_with_tables(self):
205
- self.assertIn(TNAME_EXPERIMENT, dir(self.paths.ISA))
206
-
207
- def test_table_dir_with_columns(self):
208
- self.assertLess({'Name', 'Amount', 'Time', 'Type'}, set(dir(self.paths.ISA.Experiment)))
209
-
210
- def test_dir_path(self):
211
- self.assertIn(TNAME_EXPERIMENT, dir(self.paths.ISA.Experiment.path))
212
-
213
- def test_dir_invalid_identifier(self):
214
- self.assertIn(INVALID_IDENTIFIER_FIXED, dir(self.project))
215
- self.assertIsNotNone(getattr(self.project, INVALID_IDENTIFIER_FIXED))
216
-
217
- def test_dir_conflicting_identifier(self):
218
- self.assertIn(CONFLICTING_IDENTIFIER_FIXED, dir(self.project))
219
- self.assertIsNotNone(getattr(self.project, CONFLICTING_IDENTIFIER))
220
- self.assertIsNotNone(getattr(self.project, CONFLICTING_IDENTIFIER_FIXED))
221
-
222
- def test_describe_schema(self):
223
- with self.assertWarns(DeprecationWarning):
224
- self.paths.schemas[SNAME_ISA].describe()
225
-
226
- def test_describe_table(self):
227
- with self.assertWarns(DeprecationWarning):
228
- self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT].describe()
229
-
230
- def test_describe_column(self):
231
- with self.assertWarns(DeprecationWarning):
232
- self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT].column_definitions['Name'].describe()
233
-
234
- def test_unfiltered_fetch(self):
235
- results = self.experiment.entities()
236
- self.assertEqual(len(results), TEST_EXP_MAX)
237
-
238
- def test_fetch_with_headers(self):
239
- headers = {'User-Agent': __name__ + '/' + __version__}
240
- results = self.experiment.entities().fetch(headers=headers)
241
- self.assertEqual(len(results), TEST_EXP_MAX)
242
-
243
- def test_fetch_with_limit(self):
244
- results = self.experiment.entities()
245
- limit = TEST_EXP_MAX / 5
246
- results.fetch(limit=limit)
247
- self.assertEqual(len(results), limit)
248
-
249
- def test_fetch_with_sort(self):
250
- results = self.experiment.entities()
251
- results.sort(self.experiment.column_definitions['Amount'])
252
- self.assertEqual(results[0]['Amount'], 0)
253
-
254
- def test_fetch_attributes_with_sort(self):
255
- results = self.experiment.attributes(self.experiment.RID, self.experiment.Amount)
256
- results.sort(self.experiment.Amount)
257
- self.assertEqual(results[0]['Amount'], 0)
258
-
259
- def test_fetch_all_attributes_with_sort(self):
260
- results = self.experiment.attributes(self.experiment)
261
- results.sort(self.experiment.Amount)
262
- self.assertEqual(results[0]['Amount'], 0)
263
-
264
- def test_fetch_all_attributes_with_sort_desc(self):
265
- results = self.experiment.attributes(self.experiment)
266
- results.sort(self.experiment.Amount.desc)
267
- self.assertEqual(results[0]['Amount'], TEST_EXP_MAX-1)
268
-
269
- def test_fetch_from_path_attributes_with_sort_on_talias(self):
270
- path = self.experiment.path
271
- results = path.Experiment.attributes(path.Experiment.RID, path.Experiment.Amount)
272
- results.sort(path.Experiment.Amount)
273
- self.assertEqual(results[0]['Amount'], 0)
274
-
275
- def test_fetch_from_path_attributes_with_sort_on_talias_desc(self):
276
- path = self.experiment.path
277
- results = path.Experiment.attributes(path.Experiment.RID, path.Experiment.Amount)
278
- results.sort(path.Experiment.Amount.desc)
279
- self.assertEqual(results[0]['Amount'], TEST_EXP_MAX-1)
280
-
281
- def test_fetch_from_path_all_attributes_with_sort_on_talias(self):
282
- path = self.experiment.path
283
- results = path.Experiment.attributes(*path.Experiment.column_definitions.values())
284
- results.sort(path.Experiment.Amount)
285
- self.assertEqual(results[0]['Amount'], 0)
286
-
287
- def test_fetch_from_path_all_attributes_with_sort_on_alias_desc(self):
288
- path = self.experiment.path
289
- results = path.Experiment.attributes(*path.Experiment.column_definitions.values())
290
- results.sort(path.Experiment.Amount.desc)
291
- self.assertEqual(results[0]['Amount'], TEST_EXP_MAX-1)
292
-
293
- def test_fetch_all_cols_with_talias(self):
294
- path = self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT].alias('X').path
295
- results = path.attributes(path.X)
296
- result = results.fetch(limit=1)[0]
297
- self.assertIn('X:RID', result)
298
- self.assertIn('X:Name', result)
299
-
300
- def test_fetch_with_talias(self):
301
- path = self.paths.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT].alias('X').path
302
- results = path.attributes(path.X.RID, path.X.Name.alias('typeName'))
303
- result = results.fetch(limit=1)[0]
304
- self.assertIn('RID', result)
305
- self.assertIn('typeName', result)
306
-
307
- def test_attribute_projection(self):
308
- results = self.experiment.attributes(
309
- self.experiment.column_definitions['Name'],
310
- self.experiment.column_definitions['Amount']
311
- )
312
- result = results.fetch(limit=1)[0]
313
- self.assertIn('Name', result)
314
- self.assertIn('Amount', result)
315
-
316
- def test_attribute_err_table_attr(self):
317
- table_attr = ['_name', '_schema']
318
- for attr in table_attr:
319
- with self.assertRaises(TypeError):
320
- self.experiment.attributes(getattr(self.experiment, attr))
321
-
322
- def test_update_err_no_targets(self):
323
- entities = [{'RID': 1234}]
324
- with self.assertRaises(ValueError):
325
- self.experiment.update(entities)
326
-
327
- def test_aggregate_w_invalid_attributes(self):
328
- with self.assertRaises(TypeError):
329
- self.experiment.aggregates(Min(self.experiment.column_definitions['Amount']))
330
-
331
- def test_aggregate_w_invalid_renames(self):
332
- with self.assertRaises(TypeError):
333
- self.experiment.aggregates(
334
- self.experiment.column_definitions['Name'],
335
- Min(self.experiment.column_definitions['Amount'])
336
- )
337
-
338
- def test_aggregate_fns(self):
339
- tests = [
340
- ('min_amount', Min, 0),
341
- ('max_amount', Max, TEST_EXP_MAX-1),
342
- ('sum_amount', Sum, sum(range(TEST_EXP_MAX))),
343
- ('avg_amount', Avg, sum(range(TEST_EXP_MAX))/TEST_EXP_MAX),
344
- ('cnt_amount', Cnt, TEST_EXP_MAX),
345
- ('cnt_d_amount', CntD, TEST_EXP_MAX),
346
- ('array_amount', Array, list(range(TEST_EXP_MAX))),
347
- ('array_d_amount', ArrayD, list(range(TEST_EXP_MAX)))
348
- ]
349
- for name, Fn, value in tests:
350
- with self.subTest(name=name):
351
- # results = self.experiment.aggregates(**{name: Fn(self.experiment.column_definitions['Amount'])})
352
- results = self.experiment.aggregates(Fn(self.experiment.column_definitions['Amount']).alias(name))
353
- result = results.fetch()[0]
354
- self.assertIn(name, result)
355
- self.assertEqual(result[name], value)
356
-
357
- def test_aggregate_w_2_fns(self):
358
- results = self.experiment.aggregates(
359
- Min(self.experiment.column_definitions['Amount']).alias('min_amount'),
360
- Max(self.experiment.column_definitions['Amount']).alias('max_amount')
361
- )
362
- result = results.fetch()[0]
363
- self.assertIn('min_amount', result)
364
- self.assertEqual(result['min_amount'], 0)
365
- self.assertIn('max_amount', result)
366
- self.assertEqual(result['max_amount'], TEST_EXP_MAX-1)
367
-
368
- def test_aggregate_fns_array_star(self):
369
- path = self.experiment.path
370
- tests = [
371
- ('array_table_star', Array, self.experiment, self.experiment),
372
- ('array_alias_star', Array, path, path.Experiment),
373
- ('arrayd_table_star', ArrayD, self.experiment, self.experiment),
374
- ('arrayd_alias_star', ArrayD, path, path.Experiment)
375
- ]
376
- for name, Fn, path, instance in tests:
377
- results = path.aggregates(Fn(instance).alias('arr'))
378
- with self.subTest(name=name):
379
- result = results.fetch()[0]
380
- self.assertIn('arr', result)
381
- self.assertEqual(len(result['arr']), TEST_EXP_MAX)
382
- self.assertIn('Time', result['arr'][0])
383
-
384
- def test_aggregate_fns_cnt_star(self):
385
- path = self.experiment.path
386
- tests = [
387
- ('cnt_table_star', Cnt, self.experiment, self.experiment),
388
- ('cnt_alias_star', Cnt, path, path.Experiment)
389
- ]
390
- for name, Fn, path, instance in tests:
391
- results = path.aggregates(Fn(instance).alias('cnt'))
392
- with self.subTest(name=name):
393
- result = results.fetch()[0]
394
- self.assertIn('cnt', result)
395
- self.assertEqual(result['cnt'], TEST_EXP_MAX)
396
-
397
- def test_attributegroup_fns(self):
398
- tests = [
399
- ('one group key', [self.experiment.column_definitions['Type']]),
400
- ('two group keys', [self.experiment.column_definitions['Project_Num'], self.experiment.column_definitions['Type']]),
401
- ('aliased group key', [self.experiment.column_definitions['Type'].alias('The Type')])
402
- ]
403
- for test_name, group_key in tests:
404
- with self.subTest(name=test_name):
405
- self._do_attributegroup_fn_subtests(group_key)
406
-
407
- def _do_attributegroup_fn_subtests(self, group_key):
408
- """Helper method for running common attributegroup subtests for different group keys."""
409
- tests = [
410
- ('min_amount', Min, 0),
411
- ('max_amount', Max, TEST_EXP_MAX-TEST_EXPTYPE_MAX),
412
- ('sum_amount', Sum, sum(range(0, TEST_EXP_MAX, TEST_EXPTYPE_MAX))),
413
- ('avg_amount', Avg, sum(range(0, TEST_EXP_MAX, TEST_EXPTYPE_MAX))/TEST_EXPTYPE_MAX),
414
- ('cnt_amount', Cnt, TEST_EXPTYPE_MAX),
415
- ('cnt_d_amount', CntD, TEST_EXPTYPE_MAX),
416
- ('array_amount', Array, list(range(0, TEST_EXP_MAX, TEST_EXPTYPE_MAX))),
417
- ('array_d_amount', ArrayD, list(range(0, TEST_EXP_MAX, TEST_EXPTYPE_MAX)))
418
- ]
419
- for name, Fn, value in tests:
420
- with self.subTest(name=name):
421
- results = self.experiment.groupby(*group_key).attributes(
422
- Fn(self.experiment.column_definitions['Amount']).alias(name)).sort(*group_key)
423
-
424
- result = results[0]
425
- self.assertEqual(len(results), TEST_EXPTYPE_MAX)
426
- self.assertTrue(all(key._name in result for key in group_key))
427
- self.assertIn(name, result)
428
- self.assertEqual(result[name], value)
429
-
430
- def test_attributegroup_w_bin(self):
431
- tests = [
432
- ('min/max given', 0, TEST_EXP_MAX),
433
- ('min/max not given', None, None),
434
- ('min only given', 0, None),
435
- ('max only given', None, TEST_EXP_MAX)
436
- ]
437
- for testname, minval, maxval in tests:
438
- with self.subTest(name=testname):
439
- self._do_bin_subtests(minval, maxval)
440
-
441
- def _do_bin_subtests(self, minval, maxval):
442
- """Helper method for running common binning tests with & without min/max values."""
443
- new_name, bin_name = 'TheProj', 'ABin'
444
- nbins = int(TEST_EXP_MAX/20)
445
- group_key = [
446
- self.experiment.column_definitions['Project_Num'].alias(new_name),
447
- Bin(self.experiment.column_definitions['Amount'], nbins, minval=minval, maxval=maxval).alias(bin_name)
448
- ]
449
- tests = [
450
- ('min_amount', Min, lambda a, b: a >= b[1]),
451
- ('max_amount', Max, lambda a, b: a <= b[2]),
452
- ('sum_amount', Sum, lambda a, b: a >= b[1] + b[2]),
453
- ('avg_amount', Avg, lambda a, b: b[1] <= a <= b[2]),
454
- ('cnt_amount', Cnt, lambda a, b: a == TEST_EXP_MAX/nbins),
455
- ('cnt_d_amount', CntD, lambda a, b: a == TEST_EXP_MAX/nbins),
456
- ('array_amount', Array, lambda a, b: all(b[1] <= a_i <= b[2] for a_i in a)),
457
- ('array_d_amount', ArrayD, lambda a, b: all(b[1] <= a_i <= b[2] for a_i in a))
458
- ]
459
- for name, Fn, compare in tests:
460
- with self.subTest(name=name):
461
- results = self.experiment.groupby(*group_key).attributes(
462
- Fn(self.experiment.column_definitions['Amount']).alias(name)).fetch()
463
-
464
- self.assertTrue(all(key._name in results[0] for key in group_key))
465
- self.assertIn(name, results[0])
466
- for result in results:
467
- bin = result[bin_name]
468
- if not maxval and (bin[0] >= nbins):
469
- # skip the last 2 bins when maxval was resolved; those bins are not aligned like the others
470
- continue
471
- self.assertTrue(compare(result[name], bin))
472
-
473
- def test_attributegroup_w_bin_sort(self):
474
- bin_name = 'bin'
475
- nbins = int(TEST_EXP_MAX/20)
476
- bin = Bin(self.experiment.column_definitions['Amount'], nbins, 0, TEST_EXP_MAX).alias(bin_name)
477
- bin_desc = bin.desc
478
- asc_fn = lambda n, a, b: a[n] <= b[n]
479
- desc_fn = lambda n, a, b: a[n] >= b[n]
480
- tests = [
481
- ('min_amount', Min, bin, asc_fn),
482
- ('max_amount', Max, bin, asc_fn),
483
- ('sum_amount', Sum, bin, asc_fn),
484
- ('avg_amount', Avg, bin, asc_fn),
485
- ('min_amount', Min, bin_desc, desc_fn),
486
- ('max_amount', Max, bin_desc, desc_fn),
487
- ('sum_amount', Sum, bin_desc, desc_fn),
488
- ('avg_amount', Avg, bin_desc, desc_fn)
489
- ]
490
- for name, Fn, sort_key, compfn in tests:
491
- with self.subTest(name=name):
492
- results = self.experiment.groupby(bin).attributes(
493
- Fn(self.experiment.column_definitions['Amount']).alias(name)).sort(sort_key).fetch()
494
-
495
- self.assertIn(bin._name, results[0])
496
- self.assertIn(name, results[0])
497
- self.assertTrue(compfn(name, results[0], results[1]))
498
-
499
- def test_attributegroup_w_bin_resolution(self):
500
- binkey = self.experiment.column_definitions['Empty']
501
- binname = 'bin'
502
- tests = [
503
- ('min_max_valid', 0, 0, True),
504
- ('max_invalid', 0, None, False),
505
- ('min_invalid', None, 0, False),
506
- ('both_invalid', None, None, False)
507
- ]
508
- for name, minval, maxval, valid in tests:
509
- def _do_query():
510
- bin = Bin(binkey, 10, minval, maxval).alias(binname)
511
- return self.experiment.groupby(bin).attributes(Avg(binkey).alias(name)).fetch()
512
-
513
- with self.subTest(name=name):
514
- if valid:
515
- results = _do_query()
516
- self.assertIn(binname, results[0])
517
- self.assertIn(name, results[0])
518
- else:
519
- with self.assertRaises(ValueError):
520
- _do_query()
521
-
522
- def test_link_implicit(self):
523
- results = self.experiment.link(self.experiment_type).entities()
524
- self.assertEqual(TEST_EXPTYPE_MAX, len(results))
525
-
526
- def test_link_explicit_simple_key(self):
527
- results = self.experiment.link(
528
- self.experiment_type,
529
- on=(self.experiment.Type == self.experiment_type.ID)
530
- ).entities()
531
- self.assertEqual(TEST_EXPTYPE_MAX, len(results))
532
-
533
- def test_link_explicit_composite_key(self):
534
- path = self.experiment.link(
535
- self.project,
536
- on=(
537
- (self.experiment.Project_Investigator == self.project.Investigator) &
538
- (self.experiment.Project_Num == self.project.Num)
539
- )
540
- )
541
- results = path.entities()
542
- self.assertEqual(TEST_PROJ_MAX, len(results))
543
-
544
- def test_link_outbound_fkey(self):
545
- fkey_by_pk_table_name = {
546
- fkey.pk_table.name: fkey
547
- for fkey in self.model.schemas[SNAME_ISA].tables[TNAME_EXPERIMENT].foreign_keys
548
- }
549
-
550
- tests = [
551
- ('fkey-link-' + TNAME_PROJECT, fkey_by_pk_table_name[TNAME_PROJECT], self.project, TEST_PROJ_MAX),
552
- ('fkey-link-' + TNAME_EXPERIMENT_TYPE, fkey_by_pk_table_name[TNAME_EXPERIMENT_TYPE], self.project, TEST_EXPTYPE_MAX)
553
- ]
554
-
555
- for name, fkey, table, expected_results_len in tests:
556
- with self.subTest(name=name):
557
- results = self.experiment.link(table, on=fkey).entities()
558
- self.assertEqual(expected_results_len, len(results))
559
-
560
- def test_link_inbound_fkey(self):
561
- fkey_by_fk_table_name = {
562
- fkey.table.name: fkey
563
- for fkey in self.model.schemas[SNAME_VOCAB].tables[TNAME_EXPERIMENT_TYPE].referenced_by
564
- }
565
-
566
- tests = [
567
- ('fkey-link-' + TNAME_EXPERIMENT, fkey_by_fk_table_name[TNAME_EXPERIMENT], self.project, TEST_EXP_MAX)
568
- ]
569
-
570
- for name, fkey, table, expected_results_len in tests:
571
- with self.subTest(name=name):
572
- results = self.experiment_type.link(table, on=fkey).entities()
573
- self.assertEqual(expected_results_len, len(results))
574
-
575
- def test_filter_equality(self):
576
- # test with value
577
- results = self.experiment.filter(
578
- self.experiment.column_definitions['Name'] == TEST_EXP_NAME_FORMAT.format(1)
579
- ).entities()
580
- self.assertEqual(len(results), 1)
581
- # test again with null
582
- results = self.experiment.filter(
583
- self.experiment.column_definitions['Name'] == None
584
- ).entities()
585
- self.assertEqual(len(results), 0)
586
-
587
- def test_filter_notequal(self):
588
- # test with value
589
- results = self.experiment.filter(
590
- self.experiment.column_definitions['Amount'] != 0
591
- ).entities()
592
- self.assertEqual(len(results), TEST_EXP_MAX-1)
593
- # test again with null
594
- results = self.experiment.filter(
595
- self.experiment.column_definitions['Amount'] != None
596
- ).entities()
597
- self.assertEqual(len(results), TEST_EXP_MAX)
598
-
599
- def test_filter_inequality(self):
600
- results = self.experiment.filter(
601
- self.experiment.column_definitions['Amount'] < 10
602
- ).entities()
603
- self.assertEqual(len(results), 10)
604
-
605
- def test_filter_ciregexp(self):
606
- results = self.experiment.filter(
607
- self.experiment.column_definitions['Name'].ciregexp(TEST_EXP_NAME_FORMAT.format(0)[10:])
608
- ).entities()
609
- self.assertEqual(len(results), 1)
610
-
611
- @unittest.skipUnless(TEST_EXP_MAX >= 3, "this test was designed for at least 3 elements in the test set")
612
- def test_fitler_w_quantifier(self):
613
- int_args = [0, int(TEST_EXP_MAX/2), TEST_EXP_MAX-1, TEST_EXP_MAX*2]
614
- str_args = [TEST_EXP_NAME_FORMAT.format(i) for i in int_args]
615
- for name, cname, Q, args, expectation in [
616
- ('any_3_ints', 'Amount', Any, int_args, 3),
617
- ('all_1_int', 'Amount', All, [int_args[0]], 1),
618
- ('all_3_ints', 'Amount', All, int_args, 0),
619
- ('any_3_strs', 'Name', Any, str_args, 3),
620
- ('all_1_str', 'Name', All, [str_args[0]], 1),
621
- ('all_3_strs', 'Name', All, str_args, 0)
622
- ]:
623
- with self.subTest(name=name):
624
- results = self.experiment.filter(
625
- self.experiment.column_definitions[cname] == Q(*args)
626
- ).entities()
627
- self.assertEqual(len(results), expectation)
628
-
629
- def test_filter_negation(self):
630
- results = self.experiment.filter(
631
- ~ (self.experiment.column_definitions['Name'].ciregexp(TEST_EXP_NAME_FORMAT.format(0)[10:]))
632
- ).entities()
633
- self.assertEqual(len(results), TEST_EXP_MAX - 1)
634
-
635
- def test_filter_conjunction(self):
636
- results = self.experiment.filter(
637
- self.experiment.column_definitions['Name'].ciregexp(TEST_EXP_NAME_FORMAT.format(0)[10:])
638
- & (self.experiment.column_definitions['Amount'] == 0)
639
- ).entities()
640
- self.assertEqual(len(results), 1)
641
-
642
- def test_attribute_deprecated_rename(self):
643
- with self.assertRaises(TypeError):
644
- self.experiment.attributes(
645
- self.experiment.column_definitions['Name'],
646
- howmuch=self.experiment.column_definitions['Amount']
647
- )
648
-
649
- def test_attribute_rename(self):
650
- results = self.experiment.attributes(
651
- self.experiment.column_definitions['Name'],
652
- self.experiment.column_definitions['Amount'].alias('How many of them ?'),
653
- self.experiment.column_definitions['Project_Num'].alias('Project #')
654
- )
655
- result = results.fetch(limit=1)[0]
656
- self.assertIn('Name', result)
657
- self.assertIn('How many of them ?', result)
658
- self.assertIn('Project #', result)
659
-
660
- def test_attribute_rename_special_chars(self):
661
- # first test with only the `:` character present which would trigger a lexical error from ermrest
662
- special_character_out_alias = self.experiment._name + ':' + self.experiment.column_definitions['Name']._name
663
- results = self.experiment.attributes(self.experiment.column_definitions['Name'].alias(special_character_out_alias))
664
- result = results.fetch(limit=1)[0]
665
- self.assertIn(special_character_out_alias, result)
666
-
667
- # second test with url unsafe characters present which would trigger a bad request from the web server
668
- special_character_out_alias = SPECIAL_CHARACTERS
669
- results = self.experiment.attributes(self.experiment.column_definitions['Name'].alias(special_character_out_alias))
670
- result = results.fetch(limit=1)[0]
671
- self.assertIn(special_character_out_alias, result)
672
-
673
- def test_context(self):
674
- path = self.experiment.link(self.experiment_type)
675
- results = path.Experiment.entities()
676
- self.assertEqual(len(results), TEST_EXP_MAX)
677
-
678
- def test_path_table_instances(self):
679
- path = self.experiment.link(self.experiment_type)
680
- results = path.table_instances[TNAME_EXPERIMENT].entities()
681
- self.assertEqual(len(results), TEST_EXP_MAX)
682
-
683
- def test_path_project(self):
684
- path = self.experiment.link(self.experiment_type)
685
- results = path.Experiment.attributes(
686
- path.Experiment,
687
- path.Experiment_Type.column_definitions['URI'],
688
- path.Experiment_Type.column_definitions['Name'].alias('exptype')
689
- )
690
- result = results.fetch(limit=1)[0]
691
- self.assertIn('Experiment:Name', result)
692
- self.assertIn('Experiment:Time', result)
693
- self.assertIn('URI', result)
694
- self.assertIn('exptype', result)
695
-
696
- @unittest.skipUnless(HAS_PANDAS, "pandas library not available")
697
- def test_dataframe(self):
698
- results = self.experiment.entities()
699
- df = DataFrame(results)
700
- self.assertEqual(len(df), TEST_EXP_MAX)
701
-
702
- def test_insert_double_fetch(self):
703
- entities = _generate_experiment_entities(self.types, 2)
704
- results = self.experiment_copy.insert(entities)
705
- rows1 = results.fetch()
706
- rows2 = results.fetch()
707
- self.assertEqual(rows1, rows2)
708
-
709
- def test_insert_empty_entities(self):
710
- results = self.experiment_copy.insert(None)
711
- self.assertEqual(len(results), 0)
712
- results = self.experiment_copy.insert([])
713
- self.assertEqual(len(results), 0)
714
-
715
- def test_insert_entities_not_iterable(self):
716
- with self.assertRaises(TypeError):
717
- self.experiment_type.insert(1)
718
-
719
- def test_insert_entities0_not_dict(self):
720
- with self.assertRaises(TypeError):
721
- self.experiment_type.insert([1])
722
- with self.assertRaises(TypeError):
723
- self.experiment_type.insert('this is not a dict')
724
-
725
- def test_insert(self):
726
- results = self.experiment_copy.insert(_generate_experiment_entities(self.types, 10))
727
- self.assertEqual(len(results), 10)
728
-
729
- def test_insert_on_conflict_raise(self):
730
- entities = _generate_experiment_entities(self.types, 2)
731
- first = entities[0:1]
732
- results = self.experiment_copy.insert(first)
733
- self.assertEqual(len(results), 1)
734
- with self.assertRaises(DataPathException):
735
- self.experiment_copy.insert(entities)
736
-
737
- def test_insert_on_conflict_skip(self):
738
- entities = _generate_experiment_entities(self.types, 2)
739
- first = entities[0:1]
740
- results = self.experiment_copy.insert(first)
741
- self.assertEqual(len(results), 1)
742
- results = self.experiment_copy.insert(entities, on_conflict_skip=True)
743
- self.assertEqual(len(results), 1)
744
-
745
- def test_update(self):
746
- inserted = self.experiment_copy.insert(_generate_experiment_entities(self.types, 10))
747
- self.assertEqual(len(inserted), 10)
748
- # now change something in the first result
749
- updates = [dict(**inserted[0])]
750
- updates[0]['Name'] = '**CHANGED**'
751
- updated = self.experiment_copy.update(updates)
752
- self.assertEqual(len(updated), 1)
753
- self.assertEqual(inserted[0]['RID'], updated[0]['RID'])
754
- self.assertNotEqual(inserted[0]['Name'], updated[0]['Name'])
755
-
756
- def test_update_empty_entities(self):
757
- results = self.experiment_copy.update(None)
758
- self.assertEqual(len(results), 0)
759
- results = self.experiment_copy.update([])
760
- self.assertEqual(len(results), 0)
761
-
762
- def test_update_entities_not_iterable(self):
763
- with self.assertRaises(TypeError):
764
- self.experiment_type.update(1)
765
-
766
- def test_update_entities0_not_dict(self):
767
- with self.assertRaises(TypeError):
768
- self.experiment_type.update([1])
769
- with self.assertRaises(TypeError):
770
- self.experiment_type.update('this is not a dict')
771
-
772
- def test_delete_whole_path(self):
773
- self.experiment_copy.insert(_generate_experiment_entities(self.types, 10))
774
- self.assertEqual(len(self.experiment_copy.entities()), 10)
775
- self.experiment_copy.path.delete()
776
- self.assertEqual(len(self.experiment_copy.entities()), 0)
777
-
778
- def test_delete_filtered_path(self):
779
- self.experiment_copy.insert(_generate_experiment_entities(self.types, 10))
780
- expression = self.experiment_copy.column_definitions['Name'] == TEST_EXP_NAME_FORMAT.format(1)
781
- self.assertEqual(len(self.experiment_copy.filter(expression).entities()), 1)
782
- self.experiment_copy.filter(expression).delete()
783
- self.assertEqual(len(self.experiment_copy.filter(expression).entities()), 0)
784
-
785
- def test_delete_whole_table(self):
786
- self.experiment_copy.insert(_generate_experiment_entities(self.types, 10))
787
- self.assertEqual(len(self.experiment_copy.entities()), 10)
788
- self.experiment_copy.delete()
789
- self.assertEqual(len(self.experiment_copy.entities()), 0)
790
-
791
- def test_nondefaults(self):
792
- nondefaults = {'RID', 'RCB', 'RCT'}
793
- results = self.experiment.entities()
794
- self.assertEqual(len(results), TEST_EXP_MAX)
795
- entities_copy = self.experiment_copy.insert(results, nondefaults=nondefaults, add_system_defaults=False)
796
- self.assertEqual(len(results), len(entities_copy), 'entities not copied completely')
797
- ig = itemgetter(*nondefaults)
798
- for i in range(TEST_EXP_MAX):
799
- self.assertEqual(ig(results[i]), ig(entities_copy[i]), 'copied values do not match')
800
-
801
- def test_nondefaults_w_add_sys_defaults(self):
802
- nondefaults = {'RID', 'RCB', 'RCT'}
803
- results = self.experiment.entities()
804
- self.assertEqual(len(results), TEST_EXP_MAX)
805
- entities_copy = self.experiment_copy.insert(results, nondefaults=nondefaults)
806
- self.assertEqual(len(results), len(entities_copy), 'entities not copied completely')
807
- ig = itemgetter(*nondefaults)
808
- for i in range(TEST_EXP_MAX):
809
- self.assertEqual(ig(results[i]), ig(entities_copy[i]), 'copied values do not match')
810
-
811
- def test_deepcopy_of_paths(self):
812
- paths = [
813
- self.experiment.path,
814
- self.experiment.link(self.experiment_type),
815
- self.experiment.link(self.experiment_type, on=(self.experiment.Type == self.experiment_type.ID)),
816
- self.experiment.link(
817
- self.project,
818
- on=(
819
- (self.experiment.Project_Investigator == self.project.Investigator) &
820
- (self.experiment.Project_Num == self.project.Num)
821
- )
822
- ),
823
- self.project.filter(self.project.Num < 1000).link(self.experiment).link(self.experiment_type),
824
- self.experiment.alias('Exp').link(self.experiment_type.alias('ExpType')),
825
- self.experiment.filter(self.experiment.column_definitions['Name'] == TEST_EXP_NAME_FORMAT.format(1)),
826
- self.experiment.filter(self.experiment.column_definitions['Amount'] < 10),
827
- self.experiment.filter(
828
- self.experiment.column_definitions['Name'].ciregexp(TEST_EXP_NAME_FORMAT.format(0)[10:])
829
- ),
830
- self.experiment.filter(
831
- ~ (self.experiment.column_definitions['Name'].ciregexp(TEST_EXP_NAME_FORMAT.format(0)[10:]))
832
- ),
833
- self.experiment.filter(
834
- self.experiment.column_definitions['Name'].ciregexp(TEST_EXP_NAME_FORMAT.format(0)[10:])
835
- & (self.experiment.column_definitions['Amount'] == 0)
836
- )
837
- ]
838
- for path in paths:
839
- with self.subTest(name=path.uri):
840
- cp = deepcopy(path)
841
- self.assertNotEqual(path, cp)
842
- self.assertEqual(path.uri, cp.uri)
843
-
844
- def test_merge_paths(self):
845
- path1 = self.experiment.filter(self.experiment.Amount >= 0)
846
- path2 = self.experiment.link(self.experiment_type).filter(self.experiment_type.ID >= '0')
847
- path3 = self.experiment.link(self.project).filter(self.project.Num >= 0)
848
- original_uri = path1.uri
849
-
850
- # merge paths 1..3
851
- path1.merge(path2).merge(path3)
852
- self.assertNotEqual(path1.uri, original_uri, "Merged path's URI should have changed from its original URI")
853
- self.assertEqual(path1.context._name, path3.context._name, "Context of merged paths should equal far right-hand path's context")
854
- self.assertGreater(len(path1.Experiment.entities()), 0, "Should have returned results")
855
-
856
- def test_compose_paths(self):
857
- path1 = self.experiment.filter(self.experiment.Amount >= 0)
858
- path2 = self.experiment.link(self.experiment_type).filter(self.experiment_type.ID >= '0')
859
- path3 = self.experiment.link(self.project).filter(self.project.Num >= 0)
860
- original_uri = path1.uri
861
-
862
- # compose paths 1..3
863
- path = self.paths.compose(path1, path2, path3)
864
- self.assertNotEqual(path, path1, "Compose should have copied the first path rather than mutate it")
865
- self.assertNotEqual(path.uri, path1.uri, "Composed path URI should not match the first path URI")
866
- self.assertEqual(path1.uri, original_uri, "First path was changed")
867
- self.assertNotEqual(path.uri, original_uri, "Merged path's URI should have changed from its original URI")
868
- self.assertEqual(path.context._name, path3.context._name, "Context of composed paths should equal far right-hand path's context")
869
- self.assertGreater(len(path.Experiment.entities()), 0, "Should have returned results")
870
-
871
- def test_simple_denormalization(self):
872
- entities = self.experiment.entities()
873
- results = self.experiment.denormalize()
874
- self.assertEqual(len(entities), len(results))
875
- self.assertNotEqual(entities[0].keys(), results[0].keys())
876
- self.assertIn('Type', results[0])
877
- self.assertTrue(entities[0]['Type'].startswith('TEST:'))
878
- self.assertTrue(results[0]['Type'])
879
- self.assertFalse(results[0]['Type'].startswith('TEST:'))
880
-
881
- def test_simple_denormalization_w_entities(self):
882
- entities = self.experiment.entities()
883
- results = self.experiment.denormalize(heuristic=simple_denormalization_with_whole_entities)
884
- self.assertEqual(len(entities), len(results))
885
- self.assertLess(len(entities[0].keys()), len(results[0].keys()))
886
- self.assertIn('Experiment_Project Investigator_Project_Num_fkey', results[0])
887
- self.assertIsInstance(results[0]['Experiment_Project Investigator_Project_Num_fkey'], list)
888
- self.assertIsInstance(results[0]['Experiment_Project Investigator_Project_Num_fkey'][0], dict)
889
- self.assertIn('RID', results[0]['Experiment_Project Investigator_Project_Num_fkey'][0])
890
-
891
-
892
- if __name__ == '__main__':
893
- unittest.main()