def validate_archive(self, filename): reader = CombineArchiveReader() name = os.path.relpath(filename, EXAMPLES_DIR) temp_dirname = os.path.join(self.temp_dirname, name) if not os.path.isdir(temp_dirname): os.makedirs(temp_dirname) archive = reader.run(filename, temp_dirname) config = Config( OMEX_METADATA_SCHEMA=OmexMetadataSchema.biosimulations, ) error_msgs, warning_msgs = validate( archive, temp_dirname, formats_to_validate=list( CombineArchiveContentFormat.__members__.values()), config=config, ) if warning_msgs: msg = 'The COMBINE/OMEX archive may be invalid.\n {}'.format( flatten_nested_list_of_strings(warning_msgs).replace( '\n', '\n ')) warnings.warn(msg, BioSimulatorsWarning) if error_msgs: msg = 'The COMBINE/OMEX archive is not valid.\n {}'.format( flatten_nested_list_of_strings(error_msgs).replace( '\n', '\n ')) raise ValueError(msg)
def test_validate_no_metadata(self): os.remove(os.path.join(self.tmp_dir, 'thumbnail.png')) config = Config(VALIDATE_OMEX_METADATA=True) archive = CombineArchiveReader().run(os.path.join( self.FIXTURES_DIR, 'no-metadata.omex'), self.tmp_dir, config=config) errors, warnings = validate(archive, self.tmp_dir, config=config) self.assertEqual(errors, []) config = Config(VALIDATE_OMEX_METADATA=False) archive = CombineArchiveReader().run(os.path.join( self.FIXTURES_DIR, 'no-metadata.omex'), self.tmp_dir, config=config) errors, warnings = validate(archive, self.tmp_dir, config=config) self.assertEqual(errors, []) config = Config(VALIDATE_OMEX_METADATA=True) archive = CombineArchiveReader().run(os.path.join( self.FIXTURES_DIR, 'no-metadata.omex'), self.tmp_dir, config=config) errors, warnings = validate( archive, self.tmp_dir, formats_to_validate=list( CombineArchiveContentFormat.__members__.values()), config=config) self.assertNotEqual(errors, []) config = Config(VALIDATE_OMEX_METADATA=False) archive = CombineArchiveReader().run(os.path.join( self.FIXTURES_DIR, 'no-metadata.omex'), self.tmp_dir, config=config) errors, warnings = validate( archive, self.tmp_dir, formats_to_validate=list( CombineArchiveContentFormat.__members__.values()), config=config) self.assertEqual(errors, [])
def test_manifest_in_manifest(self): out_dir = os.path.join(self.tmp_dir, 'out') archive = CombineArchiveReader().run( os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'manifest-in-manifest.omex'), out_dir) errors, warnings = validate(archive, out_dir) self.assertEqual(errors, []) self.assertIn( 'manifests should not contain content entries for themselves', flatten_nested_list_of_strings(warnings)) out_dir = os.path.join(self.tmp_dir, 'out') archive = CombineArchiveReader().run( os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'multiple-manifests.omex'), out_dir) errors, warnings = validate(archive, out_dir) self.assertEqual(errors, []) self.assertIn( 'manifests should not contain content entries for themselves', flatten_nested_list_of_strings(warnings))
def test_validate(self): os.remove(os.path.join(self.tmp_dir, 'thumbnail.png')) archive = CombineArchiveReader().run(self.OMEX_FIXTURE, self.tmp_dir) errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(errors, []) self.assertNotEqual(warnings, []) archive2 = copy.deepcopy(archive) for content in archive.contents: archive2.contents.append(content) errors, warnings = validate(archive2, self.tmp_dir) self.assertIn('contains repeated content items', flatten_nested_list_of_strings(errors)) archive2 = copy.deepcopy(archive) archive2.contents = [] errors, warnings = validate(archive2, self.tmp_dir) self.assertIn('does not contain content items', flatten_nested_list_of_strings(errors))
def test_sedml_validation_examples(self): dirname = os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'sedml-validation') filename = os.path.join( dirname, 'invalid-omex-manifest-missing-attribute.omex') with self.assertRaisesRegex(ValueError, 'must have the required attributes'): io.CombineArchiveReader().run(filename, os.path.join(self.temp_dir, 'a')) filename = os.path.join(dirname, 'invalid-sedml-missing-attribute.omex') archive = io.CombineArchiveReader().run( filename, os.path.join(self.temp_dir, 'b')) errors, warnings = validation.validate( archive, os.path.join(self.temp_dir, 'b')) self.assertNotEqual(errors, []) filename = os.path.join(dirname, 'invalid-sedml-missing-namespace.omex') archive = io.CombineArchiveReader().run( filename, os.path.join(self.temp_dir, 'c')) errors, warnings = validation.validate( archive, os.path.join(self.temp_dir, 'c')) self.assertNotEqual(errors, []) filename = os.path.join(dirname, 'invalid-sedml-multiple-errors.omex') archive = io.CombineArchiveReader().run( filename, os.path.join(self.temp_dir, 'd')) errors, warnings = validation.validate( archive, os.path.join(self.temp_dir, 'd')) self.assertNotEqual(errors, []) filename = os.path.join(dirname, 'warnings-sedml-sbml.omex') archive = io.CombineArchiveReader().run( filename, os.path.join(self.temp_dir, 'e')) errors, warnings = validation.validate( archive, os.path.join(self.temp_dir, 'e')) self.assertEqual(errors, []) self.assertNotEqual(warnings, []) filename = os.path.join(dirname, 'valid-sedml-sbml-qual.omex') archive = io.CombineArchiveReader().run( filename, os.path.join(self.temp_dir, 'f')) errors, warnings = validation.validate( archive, os.path.join(self.temp_dir, 'f')) self.assertEqual(errors, []) filename = os.path.join(dirname, 'valid-sedml-bngl.omex') archive = io.CombineArchiveReader().run( filename, os.path.join(self.temp_dir, 'g')) errors, warnings = validation.validate( archive, os.path.join(self.temp_dir, 'g')) self.assertEqual(errors, [])
def handler(body, file=None): ''' Validate a COMBINE/OMEX archive Args: body (:obj:`dict`): dictionary in schema ``ValidateCombineArchiveFileOrUrl`` with keys * ``url`` whose value has schema ``Url`` with the URL for a COMBINE/OMEX archive * ``omexMetadataFormat`` (:obj:`str`): format of the OMEX Metadata files * ``omexMetadataSchema`` (:obj:`str`): schema for validating the OMEX Metadata files * ``validateOmexManifest`` (:obj:`bool`, optional): Whether to validate the OMEX manifest file in the archive * ``validateSedml`` (:obj:`bool`, optional): Whether to validate the SED-ML files in the archive * ``validateSedmlModels`` (:obj:`bool`, optional): Whether to validate the sources of the models in the SED-ML files in the archive * ``validateOmexMetadata`` (:obj:`bool`, optional): Whether to validate the OMEX metdata files in the archive according to `BioSimulators' conventions <https://docs.biosimulations.org/concepts/conventions/simulation-project-metadata/>`_ * ``validateImages`` (:obj:`bool`, optional): Whether to validate the images (BMP, GIF, JPEG, PNG, TIFF WEBP) files in the archive file (:obj:`werkzeug.datastructures.FileStorage`): COMBINE/OMEX archive file Returns: ``ValidationReport``: information about the validity or lack thereof of a COMBINE/OMEX archive ''' try: omexMetadataInputFormat = OmexMetadataInputFormat( body['omexMetadataFormat']) except ValueError as exception: raise BadRequestException( title='`omexMetadataFormat` must be a recognized format.', exception=exception) try: omexMetadataSchema = OmexMetadataSchema(body['omexMetadataSchema']) except ValueError as exception: raise BadRequestException( title='`omexMetadataSchema` must be a recognized schema.', exception=exception) config = Config( OMEX_METADATA_INPUT_FORMAT=omexMetadataInputFormat, OMEX_METADATA_SCHEMA=omexMetadataSchema, VALIDATE_OMEX_MANIFESTS=body.get('validateOmexManifest', True), VALIDATE_SEDML=body.get('validateSedml', True), VALIDATE_SEDML_MODELS=body.get('validateSedmlModels', True), VALIDATE_OMEX_METADATA=body.get('validateOmexMetadata', True), VALIDATE_IMAGES=body.get('validateImages', True), ) archive_file = file archive_url = body.get('url', None) if archive_url and archive_file: raise BadRequestException( title='Only one of `file` or `url` can be used at a time.', instance=ValueError(), ) if not archive_url and not archive_file: raise BadRequestException( title='One of `file` or `url` must be used.', instance=ValueError(), ) # create temporary working directory temp_dirname = get_temp_dir() archive_filename = os.path.join(temp_dirname, 'archive.omex') # get COMBINE/OMEX archive if archive_file: archive_file.save(archive_filename) else: try: response = requests.get(archive_url) response.raise_for_status() except requests.exceptions.RequestException as exception: title = 'COMBINE/OMEX archive could not be loaded from `{}`'.format( archive_url) raise BadRequestException( title=title, instance=exception, ) # save archive to local temporary file with open(archive_filename, 'wb') as file: file.write(response.content) # read archive archive_dirname = os.path.join(temp_dirname, 'archive') reader = CombineArchiveReader() errors = [] warnings = [] try: archive = reader.run(archive_filename, archive_dirname, config=config) except Exception as exception: errors = [[ 'The file could not be parsed as a COMBINE/OMEX archive.', [[str(exception)]] ]] if not errors: errors, warnings = validate( archive, archive_dirname, formats_to_validate=list( CombineArchiveContentFormat.__members__.values()), config=config, ) return make_validation_report(errors, warnings, filenames=[archive_filename])
def test_no_validation(self): archive_dirname = os.path.join(self.tmp_dir, 'archive') os.mkdir(archive_dirname) # OMEX manifests archive = CombineArchive() errors, warnings = validate(archive, archive_dirname) self.assertIn('must have at least one content', flatten_nested_list_of_strings(errors)) self.assertNotEqual(warnings, []) with mock.patch.dict('os.environ', {'VALIDATE_OMEX_MANIFESTS': '0'}): errors, warnings = validate(archive, archive_dirname) self.assertEqual(errors, []) self.assertEqual(warnings, []) # SED-ML archive = CombineArchive() archive.contents.append( CombineArchiveContent( location='simulation.sedml', format=CombineArchiveContentFormat.SED_ML.value, )) sedml_filename = os.path.join(archive_dirname, 'simulation.sedml') with open(sedml_filename, 'w') as file: file.write('invalid') errors, warnings = validate(archive, archive_dirname) self.assertIn('Missing XML declaration', flatten_nested_list_of_strings(errors)) self.assertEqual(warnings, []) with mock.patch.dict('os.environ', { 'VALIDATE_OMEX_MANIFESTS': '0', 'VALIDATE_SEDML': '0', }): errors, warnings = validate(archive, archive_dirname) self.assertEqual(errors, []) self.assertEqual(warnings, []) os.remove(sedml_filename) # models archive = CombineArchive() archive.contents.append( CombineArchiveContent( location='simulation.sedml', format=CombineArchiveContentFormat.SED_ML.value, )) model_filename = os.path.join(archive_dirname, 'model.xml') shutil.copyfile( os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'BIOMD0000000297.xml'), model_filename) sed_doc = SedDocument() sed_doc.models.append( Model(id='model', source=model_filename, language=ModelLanguage.SBML.value)) sedml_filename = os.path.join(archive_dirname, 'simulation.sedml') SedmlSimulationWriter().run(sed_doc, sedml_filename) with open(model_filename, 'w') as file: file.write('invalid') errors, warnings = validate(archive, archive_dirname) self.assertIn('Missing XML declaration', flatten_nested_list_of_strings(errors)) self.assertEqual(warnings, []) with mock.patch.dict('os.environ', { 'VALIDATE_OMEX_MANIFESTS': '0', 'VALIDATE_SEDML_MODELS': '0', }): errors, warnings = validate(archive, archive_dirname) self.assertEqual(errors, []) self.assertEqual(warnings, []) os.remove(sedml_filename) os.remove(model_filename) # images archive = CombineArchive() archive.contents.append( CombineArchiveContent( location='image.png', format=CombineArchiveContentFormat.PNG.value, )) errors, warnings = validate( archive, archive_dirname, formats_to_validate=[CombineArchiveContentFormat.PNG]) self.assertIn('The PNG file at location `image.png` is invalid.', flatten_nested_list_of_strings(errors)) self.assertNotEqual(warnings, []) with mock.patch.dict('os.environ', { 'VALIDATE_OMEX_MANIFESTS': '0', 'VALIDATE_IMAGES': '0', }): errors, warnings = validate( archive, archive_dirname, formats_to_validate=[CombineArchiveContentFormat.PNG]) self.assertEqual(errors, []) self.assertEqual(warnings, []) # OMEX metadata archive = CombineArchive() archive.contents.append( CombineArchiveContent( location='metadata.rdf', format=CombineArchiveContentFormat.OMEX_METADATA.value, )) metadata_file = os.path.join(archive_dirname, 'metadata.rdf') shutil.copyfile( os.path.join(os.path.dirname(__file__), '..', 'fixtures', 'omex-metadata', 'invalid.rdf'), metadata_file) errors, warnings = validate( archive, archive_dirname, formats_to_validate=[CombineArchiveContentFormat.OMEX_METADATA]) self.assertIn( 'The OMEX Metadata file at location `metadata.rdf` is invalid.', flatten_nested_list_of_strings(errors)) self.assertNotEqual(warnings, []) with mock.patch.dict('os.environ', { 'VALIDATE_OMEX_MANIFESTS': '0', 'VALIDATE_OMEX_METADATA': '0', }): errors, warnings = validate( archive, archive_dirname, formats_to_validate=[ CombineArchiveContentFormat.OMEX_METADATA ]) self.assertEqual(errors, []) self.assertEqual(warnings, []) os.remove(metadata_file)
def test_error_handling(self): os.remove(os.path.join(self.tmp_dir, 'thumbnail.png')) archive = CombineArchive() errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(len(errors), 1) self.assertEqual(len(errors[0]), 1) self.assertIn('must have at least one content element', errors[0][0]) self.assertEqual(len(warnings), 1) self.assertEqual(len(warnings[0]), 1) self.assertIn('does not contain any SED-ML files', warnings[0][0]) archive = CombineArchive(contents=[ None, ]) errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(len(errors), 1) self.assertEqual(len(warnings), 1) self.assertIn('must be an instance of', flatten_nested_list_of_strings(errors)) self.assertIn('does not contain any SED-ML files', flatten_nested_list_of_strings(warnings)) archive = CombineArchive(contents=[ CombineArchiveContent(), ]) errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(len(errors), 1) self.assertEqual(len(warnings), 1) self.assertIn('must have a location', flatten_nested_list_of_strings(errors)) self.assertIn('must have a format', flatten_nested_list_of_strings(errors)) self.assertIn('does not contain any SED-ML files', flatten_nested_list_of_strings(warnings)) archive = CombineArchive(contents=[ CombineArchiveContent( location='plain.txt', format='plain/text', ), ]) errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(len(errors), 1) self.assertEqual(len(warnings), 1) self.assertIn('is not a file', flatten_nested_list_of_strings(errors)) self.assertIn('does not contain any SED-ML files', flatten_nested_list_of_strings(warnings)) with open(os.path.join(self.tmp_dir, 'sim.sedml'), 'w') as file: pass archive = CombineArchive(contents=[ CombineArchiveContent( location='sim.sedml', format=CombineArchiveContentFormat.SED_ML, ), ]) errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(len(errors), 1) self.assertEqual(warnings, []) self.assertIn('is invalid', flatten_nested_list_of_strings(errors)) archive = CombineArchive(contents=[ CombineArchiveContent( location='sim.sedml', format=CombineArchiveContentFormat.SED_ML, ), ]) with mock.patch.object(SedmlSimulationReader, 'run', side_effect=ValueError('other error')): with self.assertRaisesRegex(ValueError, 'other error'): validate(archive, self.tmp_dir) def side_effect(self, filename, validate_models_with_languages=False, config=None): self.warnings = [['my warning']] with mock.patch.object(SedmlSimulationReader, 'run', side_effect): errors, warnings = validate(archive, self.tmp_dir) self.assertEqual(errors, []) self.assertEqual(len(warnings), 1) self.assertIn('may be invalid', flatten_nested_list_of_strings(warnings)) self.assertIn('my warning', flatten_nested_list_of_strings(warnings))