Source code for openschemas.main.validate.criteria.structure

# See the LICENSE in the main repository at:
#    https://www.github.com/openschemas/openschemas-python

# These are validation functions referenced in the default (and other)
# For all of the below, "spec" refers to a loaded dictionary (or derivative)
# of a specification
# criteria.yml files (e.g., specification.yml)

from openschemas.logger import bot
import requests
import re

[docs]def optional(spec): '''optional_structure looks for a schema having optional fields, and issues a warning if doesn't exist. To implement this in a criteria.yml: checks: global: - name: Check for optional global sections and metadata - level: warning - function: openschemas.main.validate.criteria.structure.optional ''' optional_fields = [('gh_folder', str, True, True)] return _test_fields(spec, optional_fields)
[docs]def required(spec): '''required_structure looks for a schema's required fields, and issues an exit if doesn't exist. To implement this in a criteria.yml: checks: global: - name: Check for required global sections and metadata - level: error - function: openschemas.main.validate.criteria.structure.required ''' # (key, type, url, required) required_fields = [('description', str, False, True), ('edit_url', str, True, True), ('gh_tasks', str, True, True), ('hierarchy', list, False, True), ('mapping', list, False, True), ('name', str, False, True), ('parent_type', str, False, True), ('spec_info', dict, False, True), ('spec_type', str, False, True), ('status', str, False, True), ('subtitle', str, False, True), ('use_cases_url', str, True, True), ('version', str, False, True)] return _test_fields(spec, required_fields)
[docs]def spec_info(spec): '''test that the spec_info has all the required subfields spec_info: - name: Check that spec_info has all required subfields - level: error - function: openschemas.main.validate.criteria.structure.spec_info ''' if "spec_info" not in spec: bot.exit('"spec_info" key is missing from specification upper level!') required_fields = [('description', str, False, True), ('full_example', str, True, True), ('version', str, False, True), ('version_date', str, False, True)] _test_fields(spec['spec_info'], required_fields) # Test format of version date if not re.search('[0-9]{8}T[0-9]{6}', spec['spec_info']['version_date']): bot.exit('spec_info > version_date is malformed: "YYYYMMDDTHHMMSS"') return True
[docs]def semvar(spec): '''check that the specification uses semantic versioning semvar: - name: Check that the version strings use semantic versioning (x.x.x) - level: error - function: openschemas.main.validate.criteria.structure.semvar ''' if "version" not in spec: bot.exit('"version" key is missing from specification upper level!') # We don't check for "spec_info" because this test comes after required if "version" not in spec["spec_info"]: bot.exit('"version" key is missing from spec > spec_info!') versions = [spec['version'], spec['spec_info']['version']] # Ensure semantic versioning for version in versions: if not re.search('[0-9]+[.][0-9]+[.][0-9]+', version): bot.exit('''"version" %s needs to use semantic versioning (x.x.x), see semvar.org''' % version) return True
[docs]def mapping(spec): '''test the mapping subgroup in the specification mapping: - name: Check for valid structure of list of mappings - level: error - function: openschemas.main.validate.criteria.structure.mapping ''' if "mapping" not in spec: bot.exit('"mapping" key is missing from specification upper level!') required_fields = [('bsc_description', str, False, False), ('cardinality', str, False, True), ('controlled_vocab', str, False, False), ('description', str, False, True), ('example', str, False, False), ('expected_types', list, False, True), ('marginality', str, False, True), ('parent', str, False, True), ('property', str, False, True), ('type', str, False, False), ('type_url', str, False, False)] keys = [x[0] for x in required_fields] for entry in spec['mapping']: # Test required fields _test_fields(entry, required_fields) # Warning about extra fields extra_fields = [x for x in list(entry.keys()) if x not in keys] if len(extra_fields) > 0: bot.warning('Extra fields %s found, are they intentional?' % ','.join(extra_fields)) return True
################################################################################ # Helpers for structure tests ################################################## ################################################################################ def _test_url(url, passing_codes=None): '''ensure that a url, when using "GET" returns a passing code. Parameters ========== url: the string url to get passing_codes: a list of passing codes ''' if passing_codes is None: passing_codes = [200, 201] if not isinstance(passing_codes, list): passing_codes = [passing_codes] bot.custom(prefix='Testing', message= 'URL %s' % url, color='CYAN') response = requests.get(url) if response.status_code not in passing_codes: bot.exit('Invalid response code %s' % response.status_code) def _test_fields(spec, fields): '''the shared function to test for a particular set of fields! The input spec should be a list of tuples, with each entry as: (key, type, url, required) Parameters ========== spec: the dictionary (or derivative) of the loaded specification fields: a list of tuples, each with (key, type, url, required) ''' for entry in fields: name = entry[0] entry_type = entry[1] is_url = entry[2] required = entry[3] print('[field:%s}' % name) # Check 1. Check existence, if not valid, return if name not in spec: if required: bot.custom(prefix='Missing', message=spec, color='CYAN') bot.exit('%s is missing, invalid' % name) else: bot.test('%s is missing.' % name) else: # Check 2: check for type if not isinstance(spec[name], entry_type): bot.custom(prefix='Testing', message=entry, color='CYAN') bot.exit('Invalid type %s for %s, invalid' %(type(spec[name]), name)) # Check 3: if URL should return 200 if is_url: _test_url(spec[name]) # Check 4: if required, should be present and defined if required: # Case 1: string if entry_type == str: if spec[name] in ['', None]: bot.custom(prefix='Missing', message=spec, color='CYAN') bot.exit('%s is required, but not defined.' % name) return True