comparison env/lib/python3.9/site-packages/schema_salad/main.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 """Command line interface to schema-salad."""
2
3 import argparse
4 import logging
5 import os
6 import sys
7 from typing import Any, Dict, List, Mapping, MutableSequence, Optional, Union, cast
8 from urllib.parse import urlparse
9
10 import pkg_resources # part of setuptools
11 from rdflib.parser import Parser
12 from rdflib.plugin import register
13 from ruamel.yaml.comments import CommentedMap, CommentedSeq
14
15 from . import codegen, jsonld_context, schema
16 from .avro.schema import SchemaParseException
17 from .exceptions import ValidationException, to_one_line_messages
18 from .makedoc import makedoc
19 from .ref_resolver import Loader, file_uri
20 from .utils import json_dumps
21
22 register("json-ld", Parser, "rdflib_jsonld.parser", "JsonLDParser")
23 _logger = logging.getLogger("salad")
24
25
26 def printrdf(
27 workflow: str,
28 wf: Union[CommentedMap, CommentedSeq],
29 ctx: Dict[str, Any],
30 sr: str,
31 ) -> None:
32 g = jsonld_context.makerdf(workflow, wf, ctx)
33 print(g.serialize(format=sr, encoding="utf-8").decode("utf-8"))
34
35
36 def main(argsl: Optional[List[str]] = None) -> int:
37 if argsl is None:
38 argsl = sys.argv[1:]
39
40 parser = argparse.ArgumentParser()
41 parser.add_argument(
42 "--rdf-serializer",
43 help="Output RDF serialization format used by --print-rdf"
44 "(one of turtle (default), n3, nt, xml)",
45 default="turtle",
46 )
47
48 parser.add_argument(
49 "--skip-schemas",
50 action="store_true",
51 default=False,
52 help="If specified, ignore $schemas sections.",
53 )
54 parser.add_argument(
55 "--strict-foreign-properties",
56 action="store_true",
57 help="Strict checking of foreign properties",
58 default=False,
59 )
60
61 exgroup = parser.add_mutually_exclusive_group()
62 exgroup.add_argument(
63 "--print-jsonld-context",
64 action="store_true",
65 help="Print JSON-LD context for schema",
66 )
67 exgroup.add_argument("--print-rdfs", action="store_true", help="Print RDF schema")
68 exgroup.add_argument("--print-avro", action="store_true", help="Print Avro schema")
69
70 exgroup.add_argument(
71 "--print-rdf",
72 action="store_true",
73 help="Print corresponding RDF graph for document",
74 )
75 exgroup.add_argument(
76 "--print-pre", action="store_true", help="Print document after preprocessing"
77 )
78 exgroup.add_argument("--print-index", action="store_true", help="Print node index")
79 exgroup.add_argument(
80 "--print-metadata", action="store_true", help="Print document metadata"
81 )
82 exgroup.add_argument(
83 "--print-inheritance-dot",
84 action="store_true",
85 help="Print graphviz file of inheritance",
86 )
87 exgroup.add_argument(
88 "--print-fieldrefs-dot",
89 action="store_true",
90 help="Print graphviz file of field refs",
91 )
92
93 exgroup.add_argument(
94 "--codegen",
95 type=str,
96 metavar="language",
97 help="Generate classes in target language, currently supported: python, java",
98 )
99
100 parser.add_argument(
101 "--codegen-target",
102 type=str,
103 default=None,
104 help="Defaults to sys.stdout for python and ./ for Java",
105 )
106
107 parser.add_argument(
108 "--codegen-examples",
109 type=str,
110 metavar="directory",
111 default=None,
112 help="Directory of example documents for test case generation (Java only).",
113 )
114
115 parser.add_argument(
116 "--codegen-package",
117 type=str,
118 metavar="dotted.package",
119 default=None,
120 help="Optional override of the package name which is other derived "
121 "from the base URL (Java only).",
122 ),
123
124 parser.add_argument(
125 "--codegen-copyright",
126 type=str,
127 metavar="copyright_string",
128 default=None,
129 help="Optional copyright of the input schema.",
130 ),
131
132 exgroup.add_argument(
133 "--print-oneline",
134 action="store_true",
135 help="Print each error message in oneline",
136 )
137
138 exgroup.add_argument(
139 "--print-doc", action="store_true", help="Print HTML schema documentation page"
140 )
141
142 exgroup_strict = parser.add_mutually_exclusive_group()
143 exgroup_strict.add_argument(
144 "--strict",
145 action="store_true",
146 help="Strict validation (unrecognized or out of place fields are error)",
147 default=True,
148 dest="strict",
149 )
150 exgroup_strict.add_argument(
151 "--non-strict",
152 action="store_false",
153 help="Lenient validation (ignore unrecognized fields)",
154 default=True,
155 dest="strict",
156 )
157
158 exgroup_volume = parser.add_mutually_exclusive_group()
159 exgroup_volume.add_argument(
160 "--verbose", action="store_true", help="Default logging"
161 )
162 exgroup_volume.add_argument(
163 "--quiet", action="store_true", help="Only print warnings and errors."
164 )
165 exgroup_volume.add_argument(
166 "--debug", action="store_true", help="Print even more logging"
167 )
168
169 parser.add_argument(
170 "--only",
171 action="append",
172 help="Use with --print-doc, document only listed types",
173 )
174 parser.add_argument(
175 "--redirect",
176 action="append",
177 help="Use with --print-doc, override default link for type",
178 )
179 parser.add_argument(
180 "--brand", help="Use with --print-doc, set the 'brand' text in nav bar"
181 )
182 parser.add_argument(
183 "--brandlink", help="Use with --print-doc, set the link for 'brand' in nav bar"
184 )
185 parser.add_argument(
186 "--primtype",
187 default="#PrimitiveType",
188 help="Use with --print-doc, link to use for primitive types (string, int etc)",
189 )
190
191 parser.add_argument("schema", type=str, nargs="?", default=None)
192 parser.add_argument("document", type=str, nargs="?", default=None)
193 parser.add_argument(
194 "--version", "-v", action="store_true", help="Print version", default=None
195 )
196
197 args = parser.parse_args(argsl)
198
199 if args.version is None and args.schema is None:
200 print("{}: error: too few arguments".format(sys.argv[0]))
201 return 1
202
203 if args.quiet:
204 _logger.setLevel(logging.WARN)
205 if args.debug:
206 _logger.setLevel(logging.DEBUG)
207
208 pkg = pkg_resources.require("schema_salad")
209 if pkg:
210 if args.version:
211 print("{} Current version: {}".format(sys.argv[0], pkg[0].version))
212 return 0
213 else:
214 _logger.info("%s Current version: %s", sys.argv[0], pkg[0].version)
215
216 # Get the metaschema to validate the schema
217 metaschema_names, metaschema_doc, metaschema_loader = schema.get_metaschema()
218
219 # Load schema document and resolve refs
220
221 schema_uri = args.schema
222 if not (
223 urlparse(schema_uri)[0] and urlparse(schema_uri)[0] in ["http", "https", "file"]
224 ):
225 schema_uri = file_uri(os.path.abspath(schema_uri))
226 schema_raw_doc = metaschema_loader.fetch(schema_uri)
227
228 try:
229 schema_doc, schema_metadata = metaschema_loader.resolve_all(
230 schema_raw_doc, schema_uri
231 )
232 except ValidationException as e:
233 _logger.error(
234 "Schema `%s` failed link checking:\n%s",
235 args.schema,
236 str(e),
237 exc_info=(True if args.debug else False),
238 )
239 _logger.debug("Index is %s", list(metaschema_loader.idx.keys()))
240 _logger.debug("Vocabulary is %s", list(metaschema_loader.vocab.keys()))
241 return 1
242 except (RuntimeError) as e:
243 _logger.error(
244 "Schema `%s` read error:\n%s",
245 args.schema,
246 str(e),
247 exc_info=(True if args.debug else False),
248 )
249 return 1
250
251 if args.print_doc:
252 makedoc(args)
253 return 0
254
255 # Optionally print the schema after ref resolution
256 if not args.document and args.print_pre:
257 print(json_dumps(schema_doc, indent=4))
258 return 0
259
260 if not args.document and args.print_index:
261 print(json_dumps(list(metaschema_loader.idx.keys()), indent=4))
262 return 0
263
264 # Validate the schema document against the metaschema
265 try:
266 schema.validate_doc(
267 metaschema_names, schema_doc, metaschema_loader, args.strict
268 )
269 except ValidationException as e:
270 _logger.error("While validating schema `%s`:\n%s", args.schema, str(e))
271 return 1
272
273 # Get the json-ld context and RDFS representation from the schema
274 metactx = schema.collect_namespaces(schema_metadata)
275 if "$base" in schema_metadata:
276 metactx["@base"] = schema_metadata["$base"]
277 if isinstance(schema_doc, CommentedSeq):
278 (schema_ctx, rdfs) = jsonld_context.salad_to_jsonld_context(schema_doc, metactx)
279 else:
280 raise ValidationException(
281 "Expected a CommentedSeq, got {}: {}.".format(type(schema_doc), schema_doc)
282 )
283
284 # Create the loader that will be used to load the target document.
285 document_loader = Loader(schema_ctx, skip_schemas=args.skip_schemas)
286
287 if args.codegen:
288 codegen.codegen(
289 args.codegen,
290 cast(List[Dict[str, Any]], schema_doc),
291 schema_metadata,
292 document_loader,
293 target=args.codegen_target,
294 examples=args.codegen_examples,
295 package=args.codegen_package,
296 copyright=args.codegen_copyright,
297 )
298 return 0
299
300 # Make the Avro validation that will be used to validate the target
301 # document
302 if isinstance(schema_doc, MutableSequence):
303 avsc_obj = schema.make_avro(schema_doc, document_loader)
304 try:
305 avsc_names = schema.make_avro_schema_from_avro(avsc_obj)
306 except SchemaParseException as err:
307 _logger.error(
308 "Schema `%s` error:\n%s",
309 args.schema,
310 str(err),
311 exc_info=((type(err), err, None) if args.debug else None),
312 )
313 if args.print_avro:
314 print(json_dumps(avsc_obj, indent=4))
315 return 1
316 else:
317 _logger.error("Schema `%s` must be a list.", args.schema)
318 return 1
319
320 # Optionally print Avro-compatible schema from schema
321 if args.print_avro:
322 print(json_dumps(avsc_obj, indent=4))
323 return 0
324
325 # Optionally print the json-ld context from the schema
326 if args.print_jsonld_context:
327 j = {"@context": schema_ctx}
328 print(json_dumps(j, indent=4, sort_keys=True))
329 return 0
330
331 # Optionally print the RDFS graph from the schema
332 if args.print_rdfs:
333 print(rdfs.serialize(format=args.rdf_serializer).decode("utf-8"))
334 return 0
335
336 if args.print_metadata and not args.document:
337 print(json_dumps(schema_metadata, indent=4))
338 return 0
339
340 if args.print_inheritance_dot:
341 schema.print_inheritance(schema_doc, sys.stdout)
342 return 0
343
344 if args.print_fieldrefs_dot:
345 schema.print_fieldrefs(schema_doc, document_loader, sys.stdout)
346 return 0
347
348 # If no document specified, all done.
349 if not args.document:
350 print(f"Schema `{args.schema}` is valid")
351 return 0
352
353 # Load target document and resolve refs
354 try:
355 uri = args.document
356 document, doc_metadata = document_loader.resolve_ref(
357 uri, strict_foreign_properties=args.strict_foreign_properties
358 )
359 except ValidationException as e:
360 msg = to_one_line_messages(e) if args.print_oneline else str(e)
361 _logger.error(
362 "Document `%s` failed validation:\n%s",
363 args.document,
364 msg,
365 exc_info=args.debug,
366 )
367 return 1
368
369 # Optionally print the document after ref resolution
370 if args.print_pre:
371 print(json_dumps(document, indent=4))
372 return 0
373
374 if args.print_index:
375 print(json_dumps(list(document_loader.idx.keys()), indent=4))
376 return 0
377
378 # Validate the user document against the schema
379 try:
380 schema.validate_doc(
381 avsc_names,
382 document,
383 document_loader,
384 args.strict,
385 strict_foreign_properties=args.strict_foreign_properties,
386 )
387 except ValidationException as e:
388 msg2 = to_one_line_messages(e) if args.print_oneline else str(e)
389 _logger.error(f"While validating document `{args.document}`:\n{msg2}")
390 return 1
391
392 # Optionally convert the document to RDF
393 if args.print_rdf:
394 if isinstance(document, (Mapping, MutableSequence)):
395 printrdf(args.document, document, schema_ctx, args.rdf_serializer)
396 return 0
397 else:
398 print("Document must be a dictionary or list.")
399 return 1
400
401 if args.print_metadata:
402 print(json_dumps(doc_metadata, indent=4))
403 return 0
404
405 print(f"Document `{args.document}` is valid")
406
407 return 0
408
409
410 if __name__ == "__main__":
411 sys.exit(main(sys.argv[1:]))