deriva 1.7.10__py3-none-any.whl → 1.7.11__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.
- deriva/core/__init__.py +1 -1
- deriva/core/utils/credenza_auth_utils.py +299 -0
- {deriva-1.7.10.dist-info → deriva-1.7.11.dist-info}/METADATA +2 -3
- {deriva-1.7.10.dist-info → deriva-1.7.11.dist-info}/RECORD +8 -18
- {deriva-1.7.10.dist-info → deriva-1.7.11.dist-info}/WHEEL +1 -1
- {deriva-1.7.10.dist-info → deriva-1.7.11.dist-info}/entry_points.txt +1 -0
- {deriva-1.7.10.dist-info → deriva-1.7.11.dist-info}/top_level.txt +0 -1
- tests/deriva/__init__.py +0 -0
- tests/deriva/core/__init__.py +0 -0
- tests/deriva/core/mmo/__init__.py +0 -0
- tests/deriva/core/mmo/base.py +0 -300
- tests/deriva/core/mmo/test_mmo_drop.py +0 -252
- tests/deriva/core/mmo/test_mmo_find.py +0 -90
- tests/deriva/core/mmo/test_mmo_prune.py +0 -196
- tests/deriva/core/mmo/test_mmo_rename.py +0 -222
- tests/deriva/core/mmo/test_mmo_replace.py +0 -180
- tests/deriva/core/test_datapath.py +0 -893
- tests/deriva/core/test_ermrest_model.py +0 -782
- {deriva-1.7.10.dist-info → deriva-1.7.11.dist-info}/licenses/LICENSE +0 -0
|
@@ -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()
|