def test_acquisition_files(self): fw = self.fw project = fw.get_project(self.project_id) session = fw.get_session(self.session_id) acquisition = fw.get_acquisition(self.acquisition_id) # Upload a file to session and acquisition poem1 = 'When a vast image out of Spiritus Mundi' fw.upload_file_to_session(self.session_id, flywheel.FileSpec('yeats1.txt', poem1)) poem2 = 'Troubles my sight: a waste of desert sand;' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats2.txt', poem2)) # Create the download ticket for the container node = flywheel.DownloadNode(level='session', id=self.session_id) downloadSpec = flywheel.DownloadInput(nodes=[node], optional=True) ticket = fw.create_download_ticket(downloadSpec) self.assertIsNotNone(ticket) self.assertIsNotNone(ticket.ticket) self.assertEqual(2, ticket.file_cnt) self.assertGreater(ticket.size, 1) fd, self.tmpfile = tempfile.mkstemp(suffix='.tar.gz') os.close(fd) # Attempt to complete the download fw.download_ticket(ticket.ticket, self.tmpfile) self.assertTrue(os.path.isfile(self.tmpfile)) self.assertGreater(os.path.getsize(self.tmpfile), 0) sess_path = '{}/{}/{}/{}'.format(self.group_id, project['label'], session.get('subject', {}).get('code', 'unknown_subject'), session['label']) sess_file = '{}/{}'.format(sess_path, 'yeats1.txt') acq_path = '{}/{}'.format(sess_path, acquisition['label']) acq_file = '{}/{}'.format(acq_path, 'yeats2.txt') # Verify the download structure with tarfile.open(self.tmpfile, mode='r') as tar: tar_names = tar.getnames() self.assertEqual(2, len(tar_names)) self.assertIn(sess_file, tar_names) self.assertIn(acq_file, tar_names) # Read member data mem_f = tar.extractfile(sess_file) data = mem_f.read() self.assertEqual(six.u(poem1), data.decode('utf-8')) mem_f.close() mem_f = tar.extractfile(acq_file) data = mem_f.read() self.assertEqual(six.u(poem2), data.decode('utf-8')) mem_f.close()
def test_batch(self): fw = self.fw_device poem = 'The falcon cannot hear the falconer;' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats.txt', poem)) # Add tag = self.rand_string() acq = fw.get_acquisition(self.acquisition_id) gear = fw.get_gear(self.gear_id) proposal = gear.propose_batch([acq], tags=[tag]) self.assertIsNotNone(proposal) self.assertNotEmpty(proposal.id) self.assertEquals(proposal.gear_id, self.gear_id) self.assertIsNotNone(proposal.origin) self.assertEqual(proposal.origin.type, 'device') self.assertNotEmpty(proposal.origin.id) self.assertEqual(len(proposal.matched), 1) match = proposal.matched[0] self.assertEqual(match.id, self.acquisition_id) self.assertEqual(len(match.files), 1) self.assertEqual(match.files[0].name, 'yeats.txt') self.assertEmpty(proposal.ambiguous) self.assertEmpty(proposal.not_matched) self.assertEmpty(proposal.improper_permissions) self.assertTimestampBeforeNow(proposal.created) self.assertGreaterEqual(proposal.modified, proposal.created) # Get r_batch = fw.get_batch(proposal.id) self.assertIsNotNone(r_batch) self.assertEqual(r_batch.gear_id, self.gear_id) self.assertEqual(r_batch.state, 'pending') # Get all batches = fw.get_all_batches() self.assertIn(r_batch, batches) # Start jobs = proposal.run() self.assertEqual(len(jobs), 1) # Get again r_batch2 = fw.get_batch(proposal.id) self.assertEqual(r_batch2.state, 'running') self.assertTimestampAfter(r_batch2.modified, r_batch.modified) # Cancel cancelled = r_batch2.cancel() self.assertEqual(cancelled, 1)
def test_analysis_files(self): fw = self.fw # Upload to session poem = 'A gaze blank and pitiless as the sun,' fw.upload_file_to_session(self.session_id, flywheel.FileSpec('yeats.txt', poem)) file_ref = flywheel.FileReference(id=self.session_id, type='session', name='yeats.txt') analysis = flywheel.AnalysisInput(label=self.rand_string(), description=self.rand_string(), inputs=[file_ref]) # Add analysis_id = fw.add_session_analysis(self.session_id, analysis) # Download the input file and check content self.assertDownloadFileTextEquals( fw.download_input_from_analysis_as_data, analysis_id, 'yeats.txt', poem) self.assertDownloadFileTextEqualsWithTicket( fw.get_analysis_input_download_url, analysis_id, 'yeats.txt', poem) poem_out = 'Surely the Second Coming is at hand.' r_analysis = fw.get(analysis_id) r_analysis.upload_output(flywheel.FileSpec('yeats-out.txt', poem_out)) r_analysis = r_analysis.reload() self.assertEqual(len(r_analysis.files), 1) self.assertEqual(r_analysis.files[0].name, 'yeats-out.txt') self.assertEqual(r_analysis.files[0].size, 36) self.assertEqual(r_analysis.files[0].mimetype, 'text/plain') # Download and check content self.assertDownloadFileTextEquals( fw.download_output_from_analysis_as_data, analysis_id, 'yeats-out.txt', poem_out) self.assertDownloadFileTextEqualsWithTicket( fw.get_analysis_output_download_url, analysis_id, 'yeats-out.txt', poem_out)
def test_bad_uploads(self): fw = self.fw acquisition_id = 'INVALID_ACQUISITION_ID' # invalid file spec self.assertRaises(RuntimeError, fw.upload_file_to_acquisition, acquisition_id, flywheel.FileSpec(None)) # Non-existant upload path self.assertRaises(IOError, fw.upload_file_to_acquisition, acquisition_id, flywheel.FileSpec('/dev/null/does-not-exist')) # Invalid upload path poem = 'Are full of passionate intensity.' try: fw.upload_file_to_acquisition(acquisition_id, flywheel.FileSpec('yeats.txt', poem)) self.fail( 'Expected ApiException when uploading to nonexistent location') except flywheel.ApiException as e: self.assertEqual(e.status, 404)
def upload(self, container, name, fileobj, metadata=None): upload_fn = getattr( self.fw, 'upload_file_to_{}'.format(container.container_type), None) if not upload_fn: print('Skipping unsupported upload to container: {}'.format( container.container_type)) return log.debug('Uploading file %s to %s=%s', name, container.container_type, container.id) if self.supports_signed_url(): self.signed_url_upload(container, name, fileobj, metadata=metadata) else: upload_fn(container.id, flywheel.FileSpec(name, fileobj), metadata=json.dumps(metadata))
def test_session_analysis(self): fw = self.fw session = flywheel.Session(project=self.project_id, label=self.rand_string()) # Add session_id = fw.add_session(session) self.assertNotEmpty(session_id) poem = 'When a vast image out of Spiritus Mundi' fw.upload_file_to_session(session_id, flywheel.FileSpec('yeats.txt', poem)) file_ref = flywheel.FileReference(id=session_id, type='session', name='yeats.txt') analysis = flywheel.AnalysisInput(label=self.rand_string(), description=self.rand_string(), inputs=[file_ref]) # Add analysis_id = fw.add_session_analysis(session_id, analysis) self.assertNotEmpty(analysis_id) # Get the list of analyses in the session analyses = fw.get_session_analyses(session_id) self.assertEqual(len(analyses), 1) r_analysis = analyses[0] self.assertEqual(r_analysis.id, analysis_id) self.assertEmpty(r_analysis.job) self.assertTimestampBeforeNow(r_analysis.created) self.assertGreaterEqual(r_analysis.modified, r_analysis.created) self.assertEqual(len(r_analysis.inputs), 1) self.assertEqual(r_analysis.inputs[0].name, 'yeats.txt')
def test_project_analysis(self): fw = self.fw project = flywheel.Project(group=self.group_id, label=self.rand_string()) # Add self.project_id = project_id = fw.add_project(project) self.assertNotEmpty(project_id) poem = 'The Second Coming! Hardly are those words out' fw.upload_file_to_project(project_id, flywheel.FileSpec('yeats.txt', poem)) file_ref = flywheel.FileReference(id=project_id, type='project', name='yeats.txt') analysis = flywheel.AnalysisInput(label=self.rand_string(), description=self.rand_string(), inputs=[file_ref]) # Add analysis_id = fw.add_project_analysis(project_id, analysis) self.assertNotEmpty(analysis_id) # Get the list of analyses in the project analyses = fw.get_project_analyses(project_id) self.assertEqual(len(analyses), 1) r_analysis = analyses[0] self.assertEqual(r_analysis.id, analysis_id) self.assertEmpty(r_analysis.job) self.assertTimestampBeforeNow(r_analysis.created) self.assertGreaterEqual(r_analysis.modified, r_analysis.created) self.assertEqual(len(r_analysis.inputs), 1) self.assertEqual(r_analysis.inputs[0].name, 'yeats.txt')
def test_container_analysis(self): fw = self.fw acquisition = flywheel.Acquisition(session=self.session_id, label=self.rand_string()) # Add acquisition_id = fw.add_acquisition(acquisition) self.assertNotEmpty(acquisition_id) poem = 'Troubles my sight: a waste of desert sand;' fw.upload_file_to_container(acquisition_id, flywheel.FileSpec('yeats.txt', poem)) file_ref = flywheel.FileReference(id=acquisition_id, type='acquisition', name='yeats.txt') analysis = flywheel.AnalysisInput(label=self.rand_string(), description=self.rand_string(), inputs=[file_ref]) # Add analysis_id = fw.add_container_analysis(acquisition_id, analysis) self.assertNotEmpty(analysis_id) # Get the list of analyses in the acquisition analyses = fw.get_container_analyses(acquisition_id) self.assertEqual(len(analyses), 1) r_analysis = analyses[0] self.assertEqual(r_analysis.id, analysis_id) self.assertEmpty(r_analysis.job) self.assertTimestampBeforeNow(r_analysis.created) self.assertGreaterEqual(r_analysis.modified, r_analysis.created) self.assertEqual(len(r_analysis.inputs), 1) self.assertEqual(r_analysis.inputs[0].name, 'yeats.txt')
def test_collection_analysis(self): fw = self.fw collection = flywheel.Collection(label=self.rand_string()) # Add self.collection_id = collection_id = fw.add_collection(collection) self.assertNotEmpty(collection_id) poem = 'A shape with lion body and the head of a man,' fw.upload_file_to_collection(collection_id, flywheel.FileSpec('yeats.txt', poem)) file_ref = flywheel.FileReference(id=collection_id, type='collection', name='yeats.txt') analysis = flywheel.AnalysisInput(label=self.rand_string(), description=self.rand_string(), inputs=[file_ref]) # Add analysis_id = fw.add_collection_analysis(collection_id, analysis) self.assertNotEmpty(analysis_id) # Get the list of analyses in the collection r_collection = fw.get_collection(collection_id) self.assertEqual(len(r_collection.analyses), 1) r_analysis = r_collection.analyses[0] self.assertEqual(r_analysis.id, analysis_id) self.assertEmpty(r_analysis.job) self.assertTimestampBeforeNow(r_analysis.created) self.assertGreaterEqual(r_analysis.modified, r_analysis.created) self.assertEqual(len(r_analysis.inputs), 1) self.assertEqual(r_analysis.inputs[0].name, 'yeats.txt')
def create_hierarchy(fw): # Create group, project, session, subject, acquisitions fw.add_group({'_id': GROUP_ID}) project_id = fw.add_project({'group': GROUP_ID, 'label': PROJECT_LABEL}) subject_id = fw.add_subject({'project': project_id, 'code': SUBJECT_CODE}) session_id = fw.add_session({ 'project': project_id, 'subject': { '_id': subject_id }, 'label': SESSION_LABEL }) acquisition_id = fw.add_acquisition({ 'session': session_id, 'label': ACQUISITION_LABEL }) for filename in filenames(): fw.upload_file_to_acquisition( acquisition_id, flywheel.FileSpec(filename, 'Hello World')) return fw.get(acquisition_id)
def test_container_files(self): fw = self.fw acquisition = flywheel.Acquisition(label=self.rand_string(), session=self.session_id) acquisition_id = fw.add_acquisition(acquisition) # Upload a file poem = 'Turning and turning in the widening gyre' fw.upload_file_to_container(acquisition_id, flywheel.FileSpec('yeats.txt', poem)) # Check that the file was added to the acquisition c_acquisition = fw.get_container(acquisition_id) self.assertEqual(len(c_acquisition.files), 1) self.assertEqual(c_acquisition.files[0].name, 'yeats.txt') self.assertEqual(c_acquisition.files[0].size, 40) self.assertEqual(c_acquisition.files[0].mimetype, 'text/plain') # Download the file and check content self.assertDownloadFileTextEquals( fw.download_file_from_container_as_data, acquisition_id, 'yeats.txt', poem) # Test unauthorized download with ticket for the file self.assertDownloadFileTextEqualsWithTicket( fw.get_container_download_url, acquisition_id, 'yeats.txt', poem) # Test file attributes self.assertEqual(c_acquisition.files[0].modality, None) self.assertEmpty(c_acquisition.files[0].classification) self.assertEqual(c_acquisition.files[0].type, 'text') resp = fw.modify_container_file( acquisition_id, 'yeats.txt', flywheel.FileEntry(modality='modality', type='type')) # Check that no jobs were triggered, and attrs were modified self.assertEqual(resp.jobs_spawned, 0) c_acquisition = fw.get_container(acquisition_id) self.assertEqual(c_acquisition.files[0].modality, "modality") self.assertEmpty(c_acquisition.files[0].classification) self.assertEqual(c_acquisition.files[0].type, 'type') # Test classifications resp = fw.modify_container_file_classification( acquisition_id, 'yeats.txt', { 'modality': 'modality2', 'replace': { 'Custom': ['measurement1', 'measurement2'], } }) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) c_acquisition = fw.get_container(acquisition_id) self.assertEqual(c_acquisition.files[0].modality, 'modality2') self.assertEqual(c_acquisition.files[0].classification, {'Custom': ['measurement1', 'measurement2']}) resp = fw.set_container_file_classification(acquisition_id, 'yeats.txt', {'Custom': ['HelloWorld']}) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) resp = fw.delete_container_file_classification_fields( acquisition_id, 'yeats.txt', {'Custom': ['measurement2']}) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) c_acquisition = fw.get_container(acquisition_id) self.assertEqual(c_acquisition.files[0].classification, { 'Custom': ['measurement1', 'HelloWorld'], }) # Test file info self.assertEmpty(c_acquisition.files[0].info) fw.replace_container_file_info(acquisition_id, 'yeats.txt', { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }) fw.set_container_file_info(acquisition_id, 'yeats.txt', {'c': 5}) c_acquisition = fw.get_container(acquisition_id) self.assertEqual(c_acquisition.files[0].info['a'], 1) self.assertEqual(c_acquisition.files[0].info['b'], 2) self.assertEqual(c_acquisition.files[0].info['c'], 5) self.assertEqual(c_acquisition.files[0].info['d'], 4) fw.delete_container_file_info_fields(acquisition_id, 'yeats.txt', ['c', 'd']) c_acquisition = fw.get_container(acquisition_id) self.assertEqual(c_acquisition.files[0].info['a'], 1) self.assertEqual(c_acquisition.files[0].info['b'], 2) self.assertNotIn('c', c_acquisition.files[0].info) self.assertNotIn('d', c_acquisition.files[0].info) fw.replace_container_file_info(acquisition_id, 'yeats.txt', {}) c_acquisition = fw.get_container(acquisition_id) self.assertEmpty(c_acquisition.files[0].info) # Delete file fw.delete_container_file(acquisition_id, 'yeats.txt') c_acquisition = fw.get_container(acquisition_id) self.assertEmpty(c_acquisition.files) # Delete acquisition fw.delete_container(acquisition_id)
def test_project_files(self): fw = self.fw project = flywheel.Project(label=self.rand_string(), group=self.group_id) self.project_id = project_id = fw.add_project(project) # Upload a file poem = 'The ceremony of innocence is drowned;' fw.upload_file_to_project(project_id, flywheel.FileSpec('yeats.txt', poem)) # Check that the file was added to the project r_project = fw.get_project(project_id) self.assertEqual(len(r_project.files), 1) self.assertEqual(r_project.files[0].name, 'yeats.txt') self.assertEqual(r_project.files[0].size, 37) self.assertEqual(r_project.files[0].mimetype, 'text/plain') # Download the file and check content self.assertDownloadFileTextEquals( fw.download_file_from_project_as_data, project_id, 'yeats.txt', poem) # Test unauthorized download with ticket for the file self.assertDownloadFileTextEqualsWithTicket( fw.get_project_download_url, project_id, 'yeats.txt', poem) # Test file attributes self.assertEqual(r_project.files[0].modality, None) self.assertEmpty(r_project.files[0].classification) self.assertEqual(r_project.files[0].type, 'text') resp = fw.modify_project_file( project_id, 'yeats.txt', flywheel.FileEntry(modality='modality', type='type')) # Check that no jobs were triggered, and attrs were modified self.assertEqual(resp.jobs_spawned, 0) r_project = fw.get_project(project_id) self.assertEqual(r_project.files[0].modality, "modality") self.assertEmpty(r_project.files[0].classification) self.assertEqual(r_project.files[0].type, 'type') # Test classifications resp = fw.modify_project_file_classification( project_id, 'yeats.txt', {'replace': { 'Custom': ['measurement1', 'measurement2'], }}) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) r_project = fw.get_project(project_id) self.assertEqual(r_project.files[0].classification, {'Custom': ['measurement1', 'measurement2']}) resp = fw.modify_project_file_classification( project_id, 'yeats.txt', { 'add': { 'Custom': ['HelloWorld'], }, 'delete': { 'Custom': ['measurement2'] } }) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) r_project = fw.get_project(project_id) self.assertEqual(r_project.files[0].classification, { 'Custom': ['measurement1', 'HelloWorld'], }) # Test file info self.assertEmpty(r_project.files[0].info) fw.replace_project_file_info(project_id, 'yeats.txt', { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }) fw.set_project_file_info(project_id, 'yeats.txt', {'c': 5}) r_project = fw.get_project(project_id) self.assertEqual(r_project.files[0].info['a'], 1) self.assertEqual(r_project.files[0].info['b'], 2) self.assertEqual(r_project.files[0].info['c'], 5) self.assertEqual(r_project.files[0].info['d'], 4) fw.delete_project_file_info_fields(project_id, 'yeats.txt', ['c', 'd']) r_project = fw.get_project(project_id) self.assertEqual(r_project.files[0].info['a'], 1) self.assertEqual(r_project.files[0].info['b'], 2) self.assertNotIn('c', r_project.files[0].info) self.assertNotIn('d', r_project.files[0].info) fw.replace_project_file_info(project_id, 'yeats.txt', {}) r_project = fw.get_project(project_id) self.assertEmpty(r_project.files[0].info) # Delete file fw.delete_project_file(project_id, 'yeats.txt') r_project = fw.get_project(project_id) self.assertEmpty(r_project.files)
def test_ad_hoc_analysis(self): fw = self.fw # Upload to session poem = 'A gaze blank and pitiless as the sun,' fw.upload_file_to_session(self.session_id, flywheel.FileSpec('yeats.txt', poem)) file_ref = flywheel.FileReference(id=self.session_id, type='session', name='yeats.txt') analysis = flywheel.AnalysisInput(label=self.rand_string(), description=self.rand_string(), inputs=[file_ref]) # Add analysis_id = fw.add_session_analysis(self.session_id, analysis) self.assertNotEmpty(analysis_id) session = fw.get_session(self.session_id) self.assertEqual(len(session.analyses), 1) r_analysis = session.analyses[0] self.assertEqual(r_analysis.id, analysis_id) self.assertIsNone(r_analysis.job) self.assertTimestampBeforeNow(r_analysis.created) self.assertGreaterEqual(r_analysis.modified, r_analysis.created) self.assertEqual(len(r_analysis.inputs), 1) self.assertEqual(r_analysis.inputs[0].name, 'yeats.txt') # Access analysis directly r_analysis2 = fw.get_analysis(analysis_id) self.assertEqual(r_analysis, r_analysis2) # Generic Get is equivalent self.assertEqual(fw.get(analysis_id).to_dict(), r_analysis2.to_dict()) # Analysis Notes text = self.rand_string() fw.add_session_analysis_note(self.session_id, analysis_id, text) # Check session = fw.get_session(self.session_id) self.assertEqual(len(session.analyses), 1) r_analysis = session.analyses[0] self.assertEqual(len(r_analysis.notes), 1) self.assertNotEmpty(r_analysis.notes[0].user) self.assertEqual(r_analysis.notes[0].text, text) self.assertTimestampBeforeNow(r_analysis.notes[0].created) self.assertTimestampBeforeNow(r_analysis.notes[0].modified) self.assertTimestampAfter(r_analysis.notes[0].modified, r_analysis.created) self.assertGreaterEqual(r_analysis.notes[0].modified, r_analysis.notes[0].created) self.assertEqual(r_analysis.parent.id, session.id) # Access multiple analyses analysis2 = session.add_analysis(label=analysis.label, description=analysis.description, inputs=analysis.inputs) self.assertIsNotNone(analysis2) analysis_id2 = analysis2.id # Try getting analysis incorrectly self.assertRaises(flywheel.ApiException, fw.get_analyses, 'session', self.session_id, 'projects') # Get all Session level analyses in group analyses = fw.get_analyses('groups', self.group_id, 'sessions') self.assertEqual(len(analyses), 2) self.assertEqual( 1, len(list(filter(lambda x: x.id == r_analysis.id, analyses)))) self.assertEqual( 1, len(list(filter(lambda x: x.id == analysis_id2, analyses)))) # Get project level analyses in group (will be zero) analyses = fw.get_analyses('groups', self.group_id, 'projects') self.assertEmpty(analyses) # Info, tags tag = 'example-tag' fw.add_analysis_tag(analysis_id, tag) # Replace info fw.replace_analysis_info(analysis_id, {'foo': 3, 'bar': 'qaz'}) # Set info fw.set_analysis_info(analysis_id, {'foo': 42, 'hello': 'world'}) # Check r_analysis = fw.get_analysis(analysis_id) self.assertEqual(r_analysis.tags, [tag]) self.assertEqual(r_analysis.info, { 'foo': 42, 'bar': 'qaz', 'hello': 'world' }) # Delete info fields fw.delete_analysis_info_fields(analysis_id, ['foo', 'bar']) r_analysis = fw.get_analysis(analysis_id) self.assertEqual(r_analysis.info, {'hello': 'world'})
def test_data_view_files(self): fw = self.fw self.test_acquisition_id = fw.add_acquisition({'session': self.session_id, 'label': 'Acquisition2'}) data = 'col1,col2\n1,10\n2,20' fw.upload_file_to_acquisition(self.test_acquisition_id, flywheel.FileSpec('data1.csv', data)) fw.upload_file_to_acquisition(self.test_acquisition_id, flywheel.FileSpec('data2.csv', data)) # Get file data view = fw.View(container='acquisition', filename='*.csv', match='all', columns=['file.name', ('file_data.col2', 'value2', 'int')]) with fw.read_view_data(view, self.project_id) as resp: result = json.load(resp) self.assertIsNotNone(result) self.assertIn('data', result) rows = result['data'] self.assertEqual(len(rows), 5) self.assertEqual(rows[0]['acquisition.label'], 'Acquisition2') self.assertEqual(rows[0]['file.name'], 'data1.csv') self.assertEqual(rows[0]['col1'], '1') self.assertEqual(rows[0]['value2'], 10) self.assertEqual(rows[1]['acquisition.label'], 'Acquisition2') self.assertEqual(rows[1]['file.name'], 'data1.csv') self.assertEqual(rows[1]['col1'], '2') self.assertEqual(rows[1]['value2'], 20) self.assertEqual(rows[2]['acquisition.label'], 'Acquisition2') self.assertEqual(rows[2]['file.name'], 'data2.csv') self.assertEqual(rows[2]['col1'], '1') self.assertEqual(rows[2]['value2'], 10) self.assertEqual(rows[3]['acquisition.label'], 'Acquisition2') self.assertEqual(rows[3]['file.name'], 'data2.csv') self.assertEqual(rows[3]['col1'], '2') self.assertEqual(rows[3]['value2'], 20) self.assertEqual(rows[4]['acquisition.id'], self.acquisition_id) self.assertEqual(rows[4]['file.name'], None) self.assertEqual(rows[4]['col1'], None) self.assertEqual(rows[4]['value2'], None) # Test list files view = fw.View(columns=['acquisition.file']) with fw.read_view_data(view, self.project_id) as resp: result = json.load(resp) self.assertIsNotNone(result) self.assertIn('data', result) data = result['data'] self.assertEqual(len(data), 2) row = data[0] self.assertEqual(row['project.id'], self.project_id) self.assertEqual(row['subject.id'], self.subject.id) self.assertEqual(row['session.id'], self.session_id) self.assertEqual(row['acquisition.id'], self.test_acquisition_id) self.assertEqual(row['acquisition.file.name'], 'data1.csv') self.assertEqual(row['acquisition.file.size'], 19) self.assertEqual(row['acquisition.file.type'], 'tabular data') self.assertIn('acquisition.file.id', row) self.assertIn('acquisition.file.classification', row) row = data[1] self.assertEqual(row['project.id'], self.project_id) self.assertEqual(row['subject.id'], self.subject.id) self.assertEqual(row['session.id'], self.session_id) self.assertEqual(row['acquisition.id'], self.test_acquisition_id) self.assertEqual(row['acquisition.file.name'], 'data2.csv') self.assertEqual(row['acquisition.file.size'], 19) self.assertEqual(row['acquisition.file.type'], 'tabular data') # List files, only 2 columns view = fw.View(columns=['acquisition.file.name', 'acquisition.file.size']) with fw.read_view_data(view, self.project_id) as resp: result = json.load(resp) self.assertIsNotNone(result) self.assertIn('data', result) data = result['data'] self.assertEqual(len(data), 2) row = data[0] self.assertEqual(row['project.id'], self.project_id) self.assertEqual(row['subject.id'], self.subject.id) self.assertEqual(row['session.id'], self.session_id) self.assertEqual(row['acquisition.id'], self.test_acquisition_id) self.assertEqual(row['acquisition.file.name'], 'data1.csv') self.assertEqual(row['acquisition.file.size'], 19) self.assertNotIn('acquisition.file.type', row) self.assertNotIn('acquisition.file.id', row) self.assertNotIn('acquisition.file.classification', row) row = data[1] self.assertEqual(row['project.id'], self.project_id) self.assertEqual(row['subject.id'], self.subject.id) self.assertEqual(row['session.id'], self.session_id) self.assertEqual(row['acquisition.id'], self.test_acquisition_id) self.assertEqual(row['acquisition.file.name'], 'data2.csv') self.assertEqual(row['acquisition.file.size'], 19)
def test_job_based_analysis(self): fw = self.fw gear = fw.get_gear(self.gear_id) # Upload to session poem = 'A gaze blank and pitiless as the sun,' fw.upload_file_to_session(self.session_id, flywheel.FileSpec('yeats.txt', poem)) session = fw.get_session(self.session_id) any_file = session.files[0] self.assertEqual(any_file.name, 'yeats.txt') tag = self.rand_string() analysis_label = self.rand_string() # Add analysis_id = gear.run(analysis_label=analysis_label, destination=session, tags=[tag], inputs={'any-file': any_file}) self.assertNotEmpty(analysis_id) session = session.reload() self.assertEqual(len(session.analyses), 1) r_analysis = session.analyses[0] self.assertEqual(r_analysis.id, analysis_id) gear_info = r_analysis.gear_info self.assertEqual(gear_info.id, self.gear_id) self.assertEqual(gear_info.category, gear.category) self.assertEqual(gear_info.name, gear.gear.name) self.assertEqual(gear_info.version, gear.gear.version) self.assertEqual(r_analysis.job.state, 'pending') self.assertTimestampBeforeNow(r_analysis.created) self.assertGreaterEqual(r_analysis.modified, r_analysis.created) self.assertEqual(len(r_analysis.inputs), 1) self.assertEqual(r_analysis.inputs[0].name, 'yeats.txt') # Verify job r_job = fw.get_job(r_analysis.job.id) self.assertEqual(r_analysis.job.state, 'pending') # Access analysis directly r_analysis2 = fw.get_analysis(analysis_id) self.assertEqual(r_analysis, r_analysis2) # Project based analysis project = fw.get_project(self.project_id) # Add analysis_id = gear.run(analysis_label=analysis_label, destination=project, tags=[tag], inputs={'any-file': any_file}) self.assertNotEmpty(analysis_id) project = fw.get_project(self.project_id) self.assertEqual(len(project.analyses), 1) r_analysis = project.analyses[0] self.assertEqual(r_analysis.id, analysis_id) # Verify job r_job = fw.get_job(r_analysis.job.id) self.assertEqual(r_analysis.job.state, 'pending') # Access analysis directly r_analysis2 = fw.get_analysis(analysis_id) self.assertEqual(r_analysis, r_analysis2)
def test_job(self): fw = self.fw gear = fw.get_gear(self.gear_id) self.assertIsNotNone(gear) poem = 'Mere anarchy is loosed upon the world,' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats.txt', poem)) tag = self.rand_string() # Get the acquisition destination and file input acq = fw.get_acquisition(self.acquisition_id) any_file = acq.files[0] self.assertEqual(any_file.name, 'yeats.txt') # Add job_id = gear.run(destination=acq, tags=[tag], inputs={'any-file': any_file}) self.assertNotEmpty(job_id) # Get r_job = fw.get_job(job_id) self.assertEqual(r_job.gear_id, self.gear_id) gear_info = r_job.gear_info self.assertEqual(gear_info.category, gear.category) self.assertEqual(gear_info.name, gear.gear.name) self.assertEqual(gear_info.version, gear.gear.version) self.assertEqual(r_job.state, 'pending') self.assertEqual(r_job.attempt, 1) self.assertIsNotNone(r_job.origin) self.assertEqual(r_job.origin.type, 'user') self.assertNotEmpty(r_job.origin.id) self.assertIn(tag, r_job.tags) self.assertTimestampBeforeNow(r_job.created) self.assertGreaterEqual(r_job.modified, r_job.created) # Modify tag2 = self.rand_string() r_job.update(tags=[tag2]) # Check r_job = fw.get_job(job_id) self.assertEqual(r_job.state, 'pending') self.assertNotIn(tag, r_job.tags) self.assertIn(tag2, r_job.tags) # Get session jobs jobs = fw.get_session_jobs(self.session_id) self.assertIsNotNone(jobs) self.assertIsNotNone(jobs.jobs) self.assertEqual(1, len(jobs.jobs)) # job list doesn't info field r_job.config['inputs']['any-file']['object'].pop('info', None) self.assertIn(r_job, jobs.jobs) # Get all jobs jobs = fw.jobs(limit=100, sort='created:desc') self.assertIsNotNone(jobs) self.assertGreaterEqual(len(jobs), 1) self.assertTrue(any([job.id == job_id for job in jobs])) # Cancel job_mod = flywheel.Job(state='cancelled') fw.modify_job(job_id, job_mod) # Check r_job = fw.get_job(job_id) self.assertEqual(r_job.state, 'cancelled')
def test_child_mixins(self): fw = self.fw # Upload file to project poem = b'When a vast image out of Spiritus Mundi' fw.upload_file_to_project(self.project_id, flywheel.FileSpec('yeats.dat', poem)) # TODO: Test Analyses # GROUP r_group = fw.get_group(self.group_id) projects = r_group.projects() self.assertIsNotNone(projects) self.assertEqual(len(projects), 1) r_project = projects[0] self.assertEqual(r_project.id, self.project_id) self.assertEqual(r_project.container_type, 'project') sessions = r_project.sessions() self.assertEqual(1, len(sessions)) r_session = sessions[0] self.assertEqual(r_session.id, self.session_id) self.assertEqual(r_session.project, self.project_id) # Assert that file has a parent self.assertEqual(1, len(r_project.files)) self.assertEqual(r_project.files[0].parent, r_project) # Test load files r_project._files = None project_files = r_project.get_files() self.assertEqual(1, len(project_files)) r_file = project_files[0] self.assertEqual(r_file.parent, r_project) self.assertEqual(r_file.name, 'yeats.dat') self.assertEqual(r_file.size, len(poem)) # Read file data = r_file.read() self.assertEqual(data, poem) # Get url ticket_url1 = r_file.url() self.assertIsNotNone(ticket_url1) ticket_url2 = r_file.url() self.assertNotEqual(ticket_url1, ticket_url2) # Download file fd, path = tempfile.mkstemp() os.close(fd) try: r_file.download(path) with open(path, 'rb') as f: data = f.read() self.assertEqual(data, poem) finally: os.remove(path) # Read acquisitions acquisitions = r_session.acquisitions() self.assertIsNotNone(acquisitions) self.assertEqual(len(acquisitions), 1) r_acquisition = acquisitions[0] self.assertEqual(r_acquisition.id, self.acquisition_id) self.assertEqual(r_acquisition.session, self.session_id)
def test_resolver(self): fw = self.fw group_id = self.group_id # Create test gear self.gear_id = create_test_gear() gear = self.fw.get_gear(self.gear_id) self.assertIsNotNone(gear) # Upload file acquisition poem = 'The Second Coming! Hardly are those words out' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats.txt', poem)) # Resolve group children result = fw.resolve([group_id]) self.assertEqual(len(result.path), 1) r_group = result.path[0] self.assertEqual(r_group.id, group_id) self.assertEqual(len(result.children), 1) r_project = result.children[0] self.assertEqual(r_project.id, self.project_id) # Resolve project children result = fw.resolve('{0}/{1}'.format(group_id, r_project.label)) self.assertEqual(len(result.path), 2) self.assertEqual(result.path[0], r_group) self.assertEqual(result.path[1], r_project) self.assertEqual(len(result.children), 1) r_subject = result.children[0] self.assertEqual(r_subject.project, self.project_id) self.assertEqual(r_subject.id, self.subject_id) # Resolve subject children (using id string) result = fw.resolve('{0}/{1}/<id:{2}>'.format(group_id, r_project.label, self.subject_id)) self.assertEqual(len(result.path), 3) self.assertEqual(result.path[0], r_group) self.assertEqual(result.path[1], r_project) self.assertEqual(result.path[2], r_subject) self.assertEqual(len(result.children), 1) r_session = result.children[0] self.assertEqual(r_session.project, self.project_id) self.assertEqual(r_session.subject.id, self.subject_id) self.assertEqual(r_session.id, self.session_id) # Resolve session children (using id string) result = fw.resolve('{0}/{1}/<id:{2}>/<id:{3}>'.format( group_id, r_project.label, self.subject_id, self.session_id)) self.assertEqual(len(result.path), 4) self.assertEqual(result.path[0], r_group) self.assertEqual(result.path[1], r_project) self.assertEqual(result.path[2], r_subject) self.assertEqual(result.path[3], r_session) self.assertEqual(len(result.children), 1) r_acquisition = result.children[0] self.assertEqual(r_acquisition.session, self.session_id) self.assertEqual(r_acquisition.id, self.acquisition_id) # Finally, resolve acquisition files result = fw.resolve([ group_id, r_project.label, r_subject.label, r_session.label, r_acquisition.label ]) self.assertEqual(len(result.path), 5) self.assertEqual(result.path[0], r_group) self.assertEqual(result.path[1], r_project) self.assertEqual(result.path[2], r_subject) self.assertEqual(result.path[3], r_session) self.assertEqual(result.path[4], r_acquisition) self.assertEqual(len(result.children), 1) r_file = result.children[0] self.assertEqual(r_file.name, 'yeats.txt') self.assertEqual(r_file.size, len(poem)) # TODO: Test Analyses # Test resolve gears result = fw.resolve('gears') self.assertEmpty(result.path) self.assertGreaterEqual(len(result.children), 1) found = False for child in result.children: if child.id == self.gear_id: found = True break self.assertTrue(found)
def test_lookup(self): fw = self.fw group_id = self.group_id # Get labels for everything result = fw.resolve([ group_id, idz(self.project_id), idz(self.subject_id), idz(self.session_id) ]) self.assertEqual(4, len(result.path)) self.assertEqual(1, len(result.children)) group = result.path[0] project = result.path[1] subject = result.path[2] session = result.path[3] acquisition = result.children[0] # Create test gear self.gear_id = create_test_gear() gear = self.fw.get_gear(self.gear_id) self.assertIsNotNone(gear) # Upload file acquisition poem = 'The Second Coming! Hardly are those words out' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats.txt', poem)) # Resolve group r_group = fw.lookup([group_id]) self.assertEqual(r_group.id, group_id) self.assertIsNotNone(r_group.label) self.assertNotEmpty(r_group.permissions) # Resolve project r_project = fw.lookup('{0}/{1}'.format(group_id, project.label)) self.assertEqual(r_project.id, self.project_id) # Resolve subject r_subject = fw.lookup('{0}/{1}/<id:{2}>'.format( group_id, project.label, self.subject_id)) self.assertEqual(r_subject.id, self.subject_id) # Resolve session r_session = fw.lookup('{0}/{1}/<id:{2}>/<id:{3}>'.format( group_id, project.label, self.subject_id, self.session_id)) self.assertEqual(r_session.id, self.session_id) # Resolve acquisition r_acquisition = fw.lookup([ group_id, project.label, subject.label, session.label, acquisition.label ]) self.assertEqual(r_acquisition.id, self.acquisition_id) # Resolve acquisition file r_file = fw.lookup([ group_id, project.label, subject.label, session.label, acquisition.label, 'files', 'yeats.txt' ]) self.assertEqual(r_file.name, 'yeats.txt') self.assertEqual(r_file.size, len(poem)) # Test not found try: fw.lookup('NOT-A-GROUP/NOT-A-PROJECT') self.fail('Expected ApiException!') except flywheel.ApiException as e: self.assertEqual(e.status, 404) # TODO: Test Analyses # Test resolve gears r_gear = fw.lookup(['gears', gear.gear.name]) self.assertEqual(r_gear.gear, gear.gear)
def test_packfile(self): fw = self.fw session = fw.get_session(self.session_id) self.assertNotEmpty(session.label) acquisitions = fw.get_session_acquisitions(self.session_id) self.assertEmpty(acquisitions) poem1 = 'Surely some revelation is at hand;' poem2 = 'Surely the Second Coming is at hand.' token = fw.start_project_packfile_upload(self.project_id) self.assertNotEmpty(token) files = [ flywheel.FileSpec('yeats1.txt', poem1), flywheel.FileSpec('yeats2.txt', poem2) ] fw.project_packfile_upload(self.project_id, token, files) acquisition_label = self.rand_string() metadata = { 'project': { '_id': self.project_id }, 'session': { 'label': session.label }, 'acquisition': { 'label': acquisition_label }, 'packfile': { 'type': 'text' } } metastr = json.dumps(metadata) resp = fw.end_project_packfile_upload(self.project_id, token, metastr, _preload_content=False) self.assertEqual(resp.status_code, 200) for line in resp.iter_lines(): if six.PY3: line = line.decode('utf-8') print('response line: ' + line) acquisitions = fw.get_session_acquisitions(self.session_id) self.assertEqual(len(acquisitions), 1) self.assertEqual(acquisitions[0].label, acquisition_label) self.assertEqual(len(acquisitions[0].files), 1) self.assertEqual(acquisitions[0].files[0].name, acquisition_label + '.zip') zip_data = fw.download_file_from_acquisition_as_data( acquisitions[0].id, acquisition_label + '.zip') zip_file = zipfile.ZipFile(six.BytesIO(zip_data)) names = zip_file.namelist() self.assertIn(acquisition_label + '/yeats1.txt', names) self.assertIn(acquisition_label + '/yeats2.txt', names) with zip_file.open(acquisition_label + '/yeats1.txt') as f: data = f.read() if six.PY3: data = data.decode('utf-8') self.assertEqual(data, poem1) with zip_file.open(acquisition_label + '/yeats2.txt') as f: data = f.read() if six.PY3: data = data.decode('utf-8') self.assertEqual(data, poem2)
def upload_attachment(client, target_object, level, attachment_dict, subject_rename=None, session_rename=None, folders=['anat', 'dwi', 'func', 'fmap', 'perf'], dry_run=True): '''processes and uploads the attachment ''' bids = {"Filename": None, "Folder": None, "Path": None} if level == 'project': bids.update({"Filename": attachment_dict['name'], "Path": '.'}) else: # manipulate sub and ses labels subj_replace = none_replace if subject_rename is None else subject_rename subj_label = subj_replace( force_label_format(target_object.subject.label)) ses_replace = none_replace if session_rename is None else session_rename sess_label = ses_replace(force_label_format(target_object.label)) attachment_dict['name'] = force_template_format( attachment_dict['name']) attachment_dict['name'] = attachment_dict['name'].format( subject=subj_label, session=sess_label) # get the dir/folder/path dirs = Path(attachment_dict['name']).parts folder = [x for x in dirs if x in folders] if not folder: folder = None else: folder = folder[0] path = str(Path(attachment_dict['name']).parent) # get filename attachment_dict['name'] = str(Path(attachment_dict['name']).name) # get BIDS ready bids.update({ "Filename": str(Path(attachment_dict['name']).name), "Folder": folder, "Path": path }) logger.debug( "Attachment details:\n\tFilename: {}\n\tData: {}\n\tMIMEType: {}". format(attachment_dict['name'], attachment_dict['data'], attachment_dict['type'])) logger.debug("Updating BIDS: \n\t{}".format(bids)) verify_name, verify_data, verify_type = verify_attachment( attachment_dict['name'], attachment_dict['data'], attachment_dict['type']) if not all([verify_name, verify_data, verify_type]): logger.warning("Attachments may not be valid for upload!") logger.debug( "\tFilename valid: {}\n\tData valid: {}\n\tMIMEType valid: {}". format(verify_name, verify_data, verify_type)) if not dry_run: file_spec = flywheel.FileSpec(attachment_dict['name'], attachment_dict['data'], attachment_dict['type']) target_object.upload_file(file_spec) target_object = target_object.reload() target_object.update_file_info(attachment_dict['name'], {'BIDS': bids}) logger.info("Attachment uploaded!")
def test_batch_with_jobs(self): fw = self.fw_device gear = fw.get_gear(self.gear_id) self.assertIsNotNone(gear) # Make a couple jobs poem = 'Mere anarchy is loosed upon the world,' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats.txt', poem)) inputs = { 'any-file': flywheel.FileReference(id=self.acquisition_id, type='acquisition', name='yeats.txt') } destination = flywheel.JobDestination(id=self.acquisition_id, type='acquisition') tag = self.rand_string() jobs = [ flywheel.Job(gear_id=self.gear_id, destination=destination, inputs=inputs, tags=[tag]), flywheel.Job(gear_id=self.gear_id, destination=destination, inputs=inputs, tags=[tag]) ] # Propose batch jobs proposal = fw.create_batch_job_from_jobs( flywheel.BatchJobsProposalInput(jobs=jobs)) self.assertIsNotNone(proposal) self.assertNotEmpty(proposal.id) # Gear Id should be none, each job already knows its gear self.assertIsNone(proposal.gear_id) self.assertIsNotNone(proposal.origin) self.assertEqual(proposal.origin.type, 'device') self.assertNotEmpty(proposal.origin.id) self.assertTimestampBeforeNow(proposal.created) self.assertGreaterEqual(proposal.modified, proposal.created) # Get r_batch = fw.get_batch(proposal.id) self.assertIsNotNone(r_batch) # Gear Id should be none, each job already knows its gear self.assertIsNone(proposal.gear_id) self.assertEqual(r_batch.state, 'pending') # Get all batches = fw.get_all_batches() self.assertIn(r_batch, batches) # Start jobs = fw.start_batch(proposal.id) self.assertEqual(len(jobs), 2) # Get again r_batch2 = fw.get_batch(proposal.id) self.assertEqual(r_batch2.state, 'running') self.assertTimestampAfter(r_batch2.modified, r_batch.modified) # Cancel cancelled = fw.cancel_batch(proposal.id) self.assertEqual(cancelled, 2)
def test_job_queue(self): fw = self.fw poem = 'The blood-dimmed tide is loosed, and everywhere' fw.upload_file_to_acquisition(self.acquisition_id, flywheel.FileSpec('yeats.txt', poem)) tag = self.rand_string() job = flywheel.Job(gear_id=self.gear_id, destination=flywheel.JobDestination( id=self.acquisition_id, type='acquisition'), inputs={ 'any-file': flywheel.FileReference(id=self.acquisition_id, type='acquisition', name='yeats.txt') }, tags=[tag]) # Add job_id = fw.add_job(job) self.assertNotEmpty(job_id) # Check r_job = fw.get_job(job_id) self.assertEqual(r_job.state, 'pending') # Run r_job = fw.get_next_job(tags=[tag]) self.assertIsNotNone(r_job) self.assertEqual(r_job.id, job_id) self.assertIsNotNone(r_job.request) self.assertIn('dir', r_job.request.target) self.assertEqual(r_job.request.target['dir'], '/flywheel/v0') # Next fetch should not find any jobs try: fw.get_next_job(tags=[tag]) self.fail('Expected an error retrieving next job') except flywheel.ApiException as e: self.assertEqual(e.status, 400) # Heartbeat fw.modify_job(job_id, {}) r_job2 = fw.get_job(job_id) self.assertTimestampAfter(r_job2.modified, r_job.modified) # Add logs log1 = [flywheel.JobLogStatement(fd=-1, msg='System message')] log2 = [ flywheel.JobLogStatement(fd=1, msg='Standard out'), flywheel.JobLogStatement(fd=2, msg='Standard err') ] fw.add_job_logs(job_id, log1) fw.add_job_logs(job_id, log2) # Finish r_job.change_state('complete') r_job3 = fw.get_job(job_id) self.assertEqual(r_job3.state, 'complete') self.assertTimestampAfter(r_job3.modified, r_job2.modified) logs = fw.get_job_logs(job_id) self.assertEqual(len(logs.logs), 4) self.assertEqual(logs.logs[1], log1[0]) self.assertEqual(logs.logs[2], log2[0]) self.assertEqual(logs.logs[3], log2[1])
def test_subject_files(self): fw = self.fw subject = flywheel.Subject(code=self.rand_string(), project=self.project_id) subject_id = fw.add_subject(subject) # Upload a file poem = 'The best lack all conviction, while the worst' fw.upload_file_to_subject(subject_id, flywheel.FileSpec('yeats.txt', poem)) # Check that the file was added to the subject r_subject = fw.get_subject(subject_id) self.assertEqual(len(r_subject.files), 1) self.assertEqual(r_subject.files[0].name, 'yeats.txt') self.assertEqual(r_subject.files[0].size, 45) self.assertEqual(r_subject.files[0].mimetype, 'text/plain') # Download the file and check content self.assertDownloadFileTextEquals(fw.download_file_from_subject_as_data, subject_id, 'yeats.txt', poem) # Test unauthorized download with ticket for the file self.assertDownloadFileTextEqualsWithTicket(fw.get_subject_download_url, subject_id, 'yeats.txt', poem) # Test file attributes self.assertEqual(r_subject.files[0].modality, None) self.assertEmpty(r_subject.files[0].classification) self.assertEqual(r_subject.files[0].type, 'text') resp = r_subject.files[0].update(type='type', modality='modality') # Check that no jobs were triggered, and attrs were modified self.assertEqual(resp.jobs_spawned, 0) r_subject = fw.get_subject(subject_id) self.assertEqual(r_subject.files[0].modality, "modality") self.assertEmpty(r_subject.files[0].classification) self.assertEqual(r_subject.files[0].type, 'type') # Test classifications resp = fw.replace_subject_file_classification(subject_id, 'yeats.txt', { 'Custom': ['measurement1', 'measurement2'], }) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) r_subject = fw.get_subject(subject_id) self.assertEqual(r_subject.files[0].classification, { 'Custom': ['measurement1', 'measurement2'] }); resp = fw.modify_subject_file_classification(subject_id, 'yeats.txt', { 'add': { 'Custom': ['HelloWorld'], }, 'delete': { 'Custom': ['measurement2'] } }) self.assertEqual(resp.modified, 1) self.assertEqual(resp.jobs_spawned, 0) r_subject = fw.get_subject(subject_id) self.assertEqual(r_subject.files[0].classification, { 'Custom': ['measurement1', 'HelloWorld'], }); # Test file info self.assertEmpty(r_subject.files[0].info) fw.replace_subject_file_info(subject_id, 'yeats.txt', { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }) fw.set_subject_file_info(subject_id, 'yeats.txt', { 'c': 5 }) r_subject = fw.get_subject(subject_id) self.assertEqual(r_subject.files[0].info['a'], 1) self.assertEqual(r_subject.files[0].info['b'], 2) self.assertEqual(r_subject.files[0].info['c'], 5) self.assertEqual(r_subject.files[0].info['d'], 4) fw.delete_subject_file_info_fields(subject_id, 'yeats.txt', ['c', 'd']) r_subject = fw.get_subject(subject_id) self.assertEqual(r_subject.files[0].info['a'], 1) self.assertEqual(r_subject.files[0].info['b'], 2) self.assertNotIn('c', r_subject.files[0].info) self.assertNotIn('d', r_subject.files[0].info) fw.replace_subject_file_info(subject_id, 'yeats.txt', {}) r_subject = fw.get_subject(subject_id) self.assertEmpty(r_subject.files[0].info) # Delete file fw.delete_subject_file(subject_id, 'yeats.txt') r_subject = fw.get_subject(subject_id) self.assertEmpty(r_subject.files) # Delete subject fw.delete_subject(subject_id)