ckanext-validation-schema-generator 2.0.2__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.
Files changed (38) hide show
  1. bin/extract-id.py +5 -0
  2. ckanext/__init__.py +9 -0
  3. ckanext/validation_schema_generator/__init__.py +0 -0
  4. ckanext/validation_schema_generator/assets/css/validation_schema_generator.css +27 -0
  5. ckanext/validation_schema_generator/assets/js/validation_schema_generator.js +0 -0
  6. ckanext/validation_schema_generator/assets/webassets.yml +14 -0
  7. ckanext/validation_schema_generator/ckan_dataset.json +144 -0
  8. ckanext/validation_schema_generator/constants.py +30 -0
  9. ckanext/validation_schema_generator/controller.py +24 -0
  10. ckanext/validation_schema_generator/helpers.py +14 -0
  11. ckanext/validation_schema_generator/jobs.py +87 -0
  12. ckanext/validation_schema_generator/logic/__init__.py +0 -0
  13. ckanext/validation_schema_generator/logic/action.py +183 -0
  14. ckanext/validation_schema_generator/logic/auth.py +28 -0
  15. ckanext/validation_schema_generator/logic/schema.py +43 -0
  16. ckanext/validation_schema_generator/logic/validators.py +71 -0
  17. ckanext/validation_schema_generator/plugin.py +56 -0
  18. ckanext/validation_schema_generator/presets.json +16 -0
  19. ckanext/validation_schema_generator/templates/base.html +8 -0
  20. ckanext/validation_schema_generator/templates/package/resource_edit_base.html +9 -0
  21. ckanext/validation_schema_generator/templates/snippets/vsg_asset.html +2 -0
  22. ckanext/validation_schema_generator/templates/snippets/vsg_resource.html +2 -0
  23. ckanext/validation_schema_generator/templates/vsg/index.html +86 -0
  24. ckanext/validation_schema_generator/tests/__init__.py +0 -0
  25. ckanext/validation_schema_generator/tests/conftest.py +57 -0
  26. ckanext/validation_schema_generator/tests/test_action.py +264 -0
  27. ckanext/validation_schema_generator/tests/test_auth.py +34 -0
  28. ckanext/validation_schema_generator/tests/test_schema.json +117 -0
  29. ckanext/validation_schema_generator/utils.py +77 -0
  30. ckanext/validation_schema_generator/views/__init__.py +159 -0
  31. ckanext_validation_schema_generator-2.0.2.dist-info/METADATA +224 -0
  32. ckanext_validation_schema_generator-2.0.2.dist-info/RECORD +38 -0
  33. ckanext_validation_schema_generator-2.0.2.dist-info/WHEEL +5 -0
  34. ckanext_validation_schema_generator-2.0.2.dist-info/entry_points.txt +5 -0
  35. ckanext_validation_schema_generator-2.0.2.dist-info/licenses/LICENSE +661 -0
  36. ckanext_validation_schema_generator-2.0.2.dist-info/top_level.txt +3 -0
  37. test/features/environment.py +107 -0
  38. test/features/steps/steps.py +641 -0
bin/extract-id.py ADDED
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+ import json
3
+ import sys
4
+
5
+ print(json.loads(sys.stdin.read())['result']['id'])
ckanext/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ # encoding: utf-8
2
+
3
+ # this is a namespace package
4
+ try:
5
+ import pkg_resources
6
+ pkg_resources.declare_namespace(__name__)
7
+ except ImportError:
8
+ import pkgutil
9
+ __path__ = pkgutil.extend_path(__path__, __name__)
File without changes
@@ -0,0 +1,27 @@
1
+ .schema-generate .alert {
2
+ margin-bottom: 20px;
3
+ }
4
+
5
+ .generate-schema-form .btn-generate {
6
+ margin-bottom: 20px;
7
+ }
8
+
9
+ .generate-schema-form .table-schema {
10
+ margin-bottom: 30px;
11
+ }
12
+
13
+ .generate-schema-form .with-textarea .form-group {
14
+ margin: 0;
15
+ }
16
+
17
+ .generate-schema-form .with-textarea .form-group.control-select {
18
+ margin-bottom: 20px;
19
+ }
20
+
21
+ .generate-schema-form .with-textarea .form-group textarea {
22
+ min-height: 300px;
23
+ }
24
+
25
+ .generate-schema-form .with-textarea .control-label {
26
+ display: none;
27
+ }
@@ -0,0 +1,14 @@
1
+ vsg_js:
2
+ filter: rjsmin
3
+ output: ckanext-validation_schema_generator/%(version)s-validation_schema_generator.js
4
+ contents:
5
+ - js/validation_schema_generator.js
6
+ extra:
7
+ preload:
8
+ - base/main
9
+
10
+ vsg_css:
11
+ filter: cssrewrite
12
+ output: ckanext-validation_schema_generator/%(version)s-validation_schema_generator.css
13
+ contents:
14
+ - css/validation_schema_generator.css
@@ -0,0 +1,144 @@
1
+ {
2
+ "scheming_version": 1,
3
+ "dataset_type": "dataset",
4
+ "about": "A reimplementation of the default CKAN dataset schema",
5
+ "about_url": "http://github.com/ckan/ckanext-scheming",
6
+ "dataset_fields": [
7
+ {
8
+ "field_name": "title",
9
+ "label": "Title",
10
+ "preset": "title",
11
+ "form_placeholder": "eg. A descriptive title"
12
+ },
13
+ {
14
+ "field_name": "name",
15
+ "label": "URL",
16
+ "preset": "dataset_slug",
17
+ "form_placeholder": "eg. my-dataset"
18
+ },
19
+ {
20
+ "field_name": "notes",
21
+ "label": "Description",
22
+ "form_snippet": "markdown.html",
23
+ "form_placeholder": "eg. Some useful notes about the data",
24
+ "required": true
25
+ },
26
+ {
27
+ "field_name": "tag_string",
28
+ "label": "Tags",
29
+ "preset": "tag_string_autocomplete",
30
+ "form_placeholder": "eg. economy, mental health, government"
31
+ },
32
+ {
33
+ "field_name": "license_id",
34
+ "label": "License",
35
+ "form_snippet": "license.html",
36
+ "help_text": "License definitions and additional information can be found at http://opendefinition.org/",
37
+ "required": true
38
+ },
39
+ {
40
+ "field_name": "owner_org",
41
+ "label": "Organization",
42
+ "preset": "dataset_organization"
43
+ },
44
+ {
45
+ "field_name": "url",
46
+ "label": "Source",
47
+ "form_placeholder": "http://example.com/dataset.json",
48
+ "display_property": "foaf:homepage",
49
+ "display_snippet": "link.html"
50
+ },
51
+ {
52
+ "field_name": "version",
53
+ "label": "Version",
54
+ "validators": "scheming_required unicode_safe package_version_validator",
55
+ "form_placeholder": "1.0",
56
+ "required": true
57
+ },
58
+ {
59
+ "field_name": "author",
60
+ "label": "Author",
61
+ "form_placeholder": "Joe Bloggs",
62
+ "display_property": "dc:creator",
63
+ "display_snippet": null,
64
+ "form_snippet": null
65
+ },
66
+ {
67
+ "field_name": "author_email",
68
+ "label": "Email",
69
+ "form_placeholder": "joe@example.com",
70
+ "display_property": "dc:creator",
71
+ "display_snippet": "email.html",
72
+ "display_email_name_field": "author",
73
+ "required": true
74
+ },
75
+ {
76
+ "field_name": "maintainer",
77
+ "label": "Maintainer",
78
+ "form_placeholder": "Joe Bloggs",
79
+ "display_property": "dc:contributor",
80
+ "display_snippet": null,
81
+ "form_snippet": null
82
+ },
83
+ {
84
+ "field_name": "maintainer_email",
85
+ "label": "Maintainer Email",
86
+ "form_placeholder": "joe@example.com",
87
+ "display_property": "dc:contributor",
88
+ "display_email_name_field": "maintainer",
89
+ "display_snippet": null,
90
+ "form_snippet": null
91
+ },
92
+ {
93
+ "field_name": "schema_upload",
94
+ "form_snippet": null,
95
+ "display_snippet": null,
96
+ "validators": "ignore_missing"
97
+ },
98
+ {
99
+ "field_name": "schema_json",
100
+ "form_snippet": null,
101
+ "display_snippet": null,
102
+ "validators": "ignore_missing"
103
+ },
104
+ {
105
+ "field_name": "schema_url",
106
+ "form_snippet": null,
107
+ "display_snippet": null,
108
+ "validators": "ignore_missing"
109
+ }
110
+ ],
111
+ "resource_fields": [
112
+ {
113
+ "field_name": "url",
114
+ "label": "URL",
115
+ "preset": "resource_url_upload",
116
+ "required": true
117
+ },
118
+ {
119
+ "field_name": "name",
120
+ "label": "Name",
121
+ "form_placeholder": "eg. January 2011 Gold Prices",
122
+ "required": true
123
+ },
124
+ {
125
+ "field_name": "format",
126
+ "label": "Format",
127
+ "preset": "resource_format_autocomplete",
128
+ "required": true
129
+ },
130
+ {
131
+ "field_name": "description",
132
+ "label": "Description",
133
+ "form_snippet": "markdown.html",
134
+ "form_placeholder": "Some useful notes about the data",
135
+ "required": true
136
+ },
137
+ {
138
+ "field_name": "last_modified",
139
+ "display_snippet": null,
140
+ "form_snippet": null,
141
+ "validators": "ignore_empty"
142
+ }
143
+ ]
144
+ }
@@ -0,0 +1,30 @@
1
+ import ckan.plugins.toolkit as tk
2
+
3
+ TASK_TYPE = u"generate"
4
+ TASK_KEY = u"validation_schema_generator"
5
+ TASK_STATE_PENDING = u"Pending"
6
+ TASK_STATE_FINISHED = u"Complete"
7
+ TASK_STATE_NOT_GENERATED = u"Not generated"
8
+ TASK_STATE_ERROR = u"Failed"
9
+ VALID_STATES = [
10
+ TASK_STATE_ERROR,
11
+ TASK_STATE_FINISHED,
12
+ TASK_STATE_PENDING,
13
+ ]
14
+
15
+ CF_JOB_TIMEOUT = u"ckanext.validation_schema_generator.job_timeout"
16
+ CF_JOB_TIMEOUT_DF = 3600
17
+ CF_API_KEY = u"ckanext.validation_schema_generator.api_key"
18
+
19
+ APPLY_FOR_OPTIONS = [u"dataset", u"resource"]
20
+ APPLY_FOR_DATASET = APPLY_FOR_OPTIONS[0]
21
+ APPLY_FOR_RESOURCE = APPLY_FOR_OPTIONS[1]
22
+ APPLY_FOR_FIELD = u"apply_for"
23
+
24
+ RES_SCHEMA_FIELD = tk.config.get(
25
+ u"ckanext.validation_schema_generator.resource_schema_field_name", u"schema")
26
+ PKG_SCHEMA_FIELD = tk.config.get(
27
+ u"ckanext.validation_schema_generator.package_schema_field_name", u"default_data_schema")
28
+
29
+ EMPTY_SCHEMA = u""
30
+ UNAPPLIED = u""
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ from ckan.lib.base import BaseController
4
+ import ckan.plugins.toolkit as tk
5
+
6
+ from ckanext.validation_schema_generator.views import VSGIndexView
7
+
8
+
9
+ class VSGController(BaseController):
10
+ def _check_access(self, resource_id):
11
+ try:
12
+ tk.check_access('vsg_generate', {}, {"id": resource_id})
13
+ except tk.NotAuthorized:
14
+ tk.abort(403, tk._(u'You are not allowed to generate resource schema'))
15
+
16
+ def index(self, dataset_id, resource_id):
17
+ self._check_access(resource_id)
18
+
19
+ view = VSGIndexView()
20
+
21
+ if tk.request.method == 'GET':
22
+ return view.get(dataset_id, resource_id)
23
+
24
+ return view.post(dataset_id, resource_id)
@@ -0,0 +1,14 @@
1
+ # encoding: utf-8
2
+
3
+ def _get_helpers():
4
+ return {
5
+ "vsg_get_apply_for_options": vsg_get_apply_for_options
6
+ }
7
+
8
+
9
+ def vsg_get_apply_for_options():
10
+ return [
11
+ {'value': '', 'text': ''},
12
+ {'value': 'dataset', 'text': 'Dataset default'},
13
+ {'value': 'resource', 'text': 'Resource'}
14
+ ]
@@ -0,0 +1,87 @@
1
+ # encoding: utf-8
2
+
3
+ import requests
4
+
5
+ from frictionless import describe
6
+ from frictionless.exception import FrictionlessException
7
+
8
+ import ckan.model as model
9
+ import ckan.plugins.toolkit as tk
10
+
11
+ from ckanext.validation_schema_generator.constants import (
12
+ CF_API_KEY,
13
+ TASK_STATE_FINISHED,
14
+ TASK_STATE_ERROR,
15
+ )
16
+
17
+
18
+ def generate_schema_from_resource(input):
19
+ context = _make_context()
20
+
21
+ resource = tk.get_action(u'resource_show')(context, {
22
+ 'id': input[u'resource_id']
23
+ })
24
+
25
+ errors = {}
26
+ options = {}
27
+ source = None
28
+ schema = None
29
+
30
+ if not source:
31
+ source = resource[u'url']
32
+
33
+ try:
34
+ schema = describe(source, type='schema', **options)
35
+ except FrictionlessException as e:
36
+ errors[u'schema'] = str(e)
37
+ except Exception as e:
38
+ errors[u'undefined'] = str(e)
39
+ finally:
40
+ _update_task(input, errors, schema)
41
+
42
+
43
+ def _make_context():
44
+ user = _get_site_user()
45
+
46
+ return {
47
+ 'model': model,
48
+ 'session': model.Session,
49
+ 'ignore_auth': True,
50
+ 'user': user[u'name'],
51
+ 'auth_user_obj': None
52
+ }
53
+
54
+
55
+ def _get_site_user():
56
+ return tk.get_action(u'get_site_user')({
57
+ 'model': model,
58
+ 'ignore_auth': True
59
+ }, {})
60
+
61
+
62
+ def _make_session():
63
+ s = requests.Session()
64
+ s.headers.update({u'Authorization': _get_api_key()})
65
+ return s
66
+
67
+
68
+ def _get_api_key():
69
+ return tk.config.get(CF_API_KEY, _get_site_user_api_key())
70
+
71
+
72
+ def _get_site_user_api_key():
73
+ user = _get_site_user()
74
+ return user['apikey']
75
+
76
+
77
+ def _update_task(input, errors, schema):
78
+ context = _make_context()
79
+
80
+ data_dict = {
81
+ 'id': input[u'resource_id'],
82
+ 'status': TASK_STATE_ERROR if errors else TASK_STATE_FINISHED,
83
+ 'error': errors,
84
+ 'schema': schema.to_json() if schema else ''
85
+ }
86
+
87
+ tk.get_action('vsg_update')(context, data_dict)
File without changes
@@ -0,0 +1,183 @@
1
+ # encoding: utf-8
2
+
3
+ import json
4
+ import logging
5
+
6
+ import ckan.plugins.toolkit as tk
7
+ from ckan.logic import validate
8
+ from ckan.lib.jobs import enqueue as enqueue_job
9
+
10
+ import ckanext.validation_schema_generator.logic.schema as vsg_schema
11
+ from ckanext.validation_schema_generator import jobs, utils as vsg_utils, constants as const
12
+
13
+ log = logging.getLogger(__name__)
14
+
15
+
16
+ def _get_actions():
17
+ return {
18
+ 'vsg_generate': vsg_generate,
19
+ 'vsg_status': vsg_status,
20
+ 'vsg_update': vsg_update,
21
+ 'vsg_apply': vsg_apply,
22
+ 'vsg_unapply': vsg_unapply
23
+ }
24
+
25
+
26
+ @validate(vsg_schema.vsg_generate_schema)
27
+ def vsg_generate(context, data_dict):
28
+ """Generate a schema for a dataset
29
+
30
+ :param id: resource ID
31
+ :type id: string
32
+ """
33
+ tk.check_access('vsg_generate', context, data_dict)
34
+
35
+ task = vsg_utils.update_task(
36
+ context,
37
+ vsg_utils.generate_task_data(data_dict["id"],
38
+ const.TASK_STATE_PENDING))
39
+
40
+ data = {"resource_id": data_dict["id"], "task_id": task["id"]}
41
+
42
+ timeout = tk.asint(tk.config.get(
43
+ const.CF_JOB_TIMEOUT, const.CF_JOB_TIMEOUT_DF))
44
+ job = enqueue_job(jobs.generate_schema_from_resource, [data],
45
+ rq_kwargs={"timeout": timeout})
46
+
47
+ task['value'] = {'job_id': job.id}
48
+ task['error'] = {}
49
+
50
+ return vsg_utils.update_task(context, task)
51
+
52
+
53
+ @validate(vsg_schema.vsg_default_schema)
54
+ def vsg_status(context, data_dict):
55
+ """Return a status of schema generation for a specific resource
56
+
57
+ :param id: resource ID
58
+ :type id: string
59
+ """
60
+ tk.check_access('vsg_generate', context, data_dict)
61
+
62
+ try:
63
+ task = tk.get_action('task_status_show')(context, {
64
+ 'entity_id': data_dict["id"],
65
+ 'task_type': const.TASK_TYPE,
66
+ 'key': const.TASK_KEY
67
+ })
68
+ except tk.ObjectNotFound:
69
+ return {
70
+ 'state': const.TASK_STATE_NOT_GENERATED,
71
+ 'last_updated': None,
72
+ 'value': None,
73
+ 'error': None
74
+ }
75
+
76
+ task['value'] = json.loads(task['value'])
77
+ task['error'] = json.loads(task['error'])
78
+ return task
79
+
80
+
81
+ @validate(vsg_schema.vsg_hook_schema)
82
+ def vsg_update(context, data_dict):
83
+ """Hook to update vsg task from job"""
84
+ id, error, status = tk.get_or_bust(data_dict, ['id', 'error', 'status'])
85
+
86
+ tk.check_access('vsg_generate', context, {'id': id})
87
+ task = tk.get_action('vsg_status')(context, {'id': id})
88
+
89
+ task['state'] = status
90
+ task['last_updated'] = vsg_utils.get_current_time()
91
+ task['error'] = error
92
+
93
+ if data_dict.get("schema"):
94
+ task['value']['schema'] = json.loads(data_dict["schema"])
95
+
96
+ return vsg_utils.update_task(context, task)
97
+
98
+
99
+ @validate(vsg_schema.vsg_apply_schema)
100
+ def vsg_apply(context, data_dict):
101
+ """Apply a generated schema for a resource or dataset. The schema can be applied only
102
+ if the generation process is successfully completed
103
+
104
+ :param id: resource ID
105
+ :type id: string
106
+ :param apply_for: apply schema agaisnt the whole `dataset` or `resource` only.
107
+ :type apply_for: string
108
+ """
109
+ tk.check_access('vsg_generate', context, data_dict)
110
+
111
+ apply_for = data_dict.get(const.APPLY_FOR_FIELD)
112
+
113
+ task = tk.get_action('vsg_status')(context, data_dict)
114
+
115
+ if task['state'] in (const.TASK_STATE_NOT_GENERATED,
116
+ const.TASK_STATE_PENDING):
117
+ raise tk.ValidationError(tk._(u"Schema is not generated yet."))
118
+ elif task['state'] == const.TASK_STATE_ERROR:
119
+ raise tk.ValidationError(
120
+ tk._(u"Schema generation failed. Check status."))
121
+
122
+ task['last_updated'] = vsg_utils.get_current_time()
123
+ task['value'][const.APPLY_FOR_FIELD] = apply_for
124
+ schema = vsg_utils.dump_schema(task['value']['schema'])
125
+
126
+ log.debug("Applying generated schema to %s: [%s]", apply_for, schema)
127
+ if apply_for == const.APPLY_FOR_DATASET:
128
+ _apply_pkg_schema(schema, data_dict['id'])
129
+ else:
130
+ _apply_res_schema(schema, data_dict['id'])
131
+
132
+ return vsg_utils.update_task(context, task)
133
+
134
+
135
+ def _apply_res_schema(schema, resource_id):
136
+ """Apply the generated schema for resource and unapply for package"""
137
+ context = {"user": "", "ignore_auth": True}
138
+
139
+ res = tk.get_action(u'resource_show')(context, {u'id': resource_id})
140
+ res[const.RES_SCHEMA_FIELD] = schema
141
+
142
+ tk.get_action(u'resource_update')(context, res)
143
+
144
+
145
+ def _apply_pkg_schema(schema, resource_id):
146
+ """Apply the generated schema for package and resource"""
147
+ context = {"user": "", "ignore_auth": True}
148
+
149
+ res = tk.get_action(u'resource_show')(context, {u'id': resource_id})
150
+ pkg = tk.get_action(u'package_show')(context, {u'id': res['package_id']})
151
+
152
+ pkg[const.PKG_SCHEMA_FIELD] = schema
153
+
154
+ if schema:
155
+ for resource in pkg.get('resources', []):
156
+ if resource['id'] == resource_id:
157
+ resource[const.RES_SCHEMA_FIELD] = schema
158
+
159
+ tk.get_action(u'package_update')(context, pkg)
160
+
161
+
162
+ @validate(vsg_schema.vsg_default_schema)
163
+ def vsg_unapply(context, data_dict):
164
+ """Unapply the schema
165
+ """
166
+ tk.check_access('vsg_generate', context, data_dict)
167
+
168
+ task = tk.get_action('vsg_status')(context, data_dict)
169
+
170
+ if task['state'] in (const.TASK_STATE_NOT_GENERATED,
171
+ const.TASK_STATE_PENDING):
172
+ raise tk.ValidationError(u"Schema is not generated yet")
173
+ elif task['state'] == const.TASK_STATE_ERROR:
174
+ raise tk.ValidationError(u"Schema generation failed. Check status.")
175
+
176
+ applied_for = task['value'].get(const.APPLY_FOR_FIELD)
177
+
178
+ if not applied_for:
179
+ raise tk.ValidationError(u"The schema is not applied yet")
180
+
181
+ task['value'][const.APPLY_FOR_FIELD] = ''
182
+
183
+ return vsg_utils.update_task(context, task)
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ import ckan.plugins.toolkit as tk
4
+ import ckan.authz as authz
5
+
6
+
7
+ def _get_auth_functions():
8
+ return {
9
+ "vsg_generate": vsg_generate,
10
+ }
11
+
12
+
13
+ def vsg_generate(context, data_dict):
14
+ """Check if user is allowed to generate schema
15
+
16
+ :param id: the id of the resource to generate schema for
17
+ :type id: string
18
+ """
19
+ authorized = authz.is_authorized('resource_update', context,
20
+ data_dict).get('success')
21
+
22
+ if authorized:
23
+ return {'success': True}
24
+
25
+ msg = tk._('You are not authorized to generate schema for resource {}').format(
26
+ data_dict['id']
27
+ )
28
+ return {'success': False, 'msg': msg}
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ from ckan.logic.schema import validator_args
4
+
5
+ import ckanext.validation_schema_generator.constants as const
6
+
7
+
8
+ @validator_args
9
+ def vsg_default_schema(not_missing, vsg_resource_not_missing):
10
+ return {"id": [not_missing, vsg_resource_not_missing]}
11
+
12
+
13
+ @validator_args
14
+ def vsg_generate_schema(not_missing, vsg_is_resource_supportable,
15
+ vsg_resource_not_missing):
16
+ return {
17
+ "id":
18
+ [not_missing, vsg_resource_not_missing, vsg_is_resource_supportable]
19
+ }
20
+
21
+
22
+ @validator_args
23
+ def vsg_apply_schema(not_missing, resource_id_exists, one_of):
24
+ return {
25
+ "id": [not_missing, resource_id_exists],
26
+ "apply_for": [not_missing, one_of(const.APPLY_FOR_OPTIONS)]
27
+ }
28
+
29
+
30
+ @validator_args
31
+ def vsg_hook_schema(not_missing, resource_id_exists, one_of, ignore_missing,
32
+ vsg_validate_schema, unicode_safe, dict_only,
33
+ vsg_is_resource_supportable, vsg_generation_started):
34
+
35
+ return {
36
+ "id": [
37
+ not_missing, resource_id_exists, vsg_is_resource_supportable,
38
+ vsg_generation_started
39
+ ],
40
+ "error": [not_missing, dict_only],
41
+ "status": [not_missing, one_of(const.VALID_STATES)],
42
+ "schema": [ignore_missing, unicode_safe, vsg_validate_schema]
43
+ }