19
19
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20
20
21
21
22
+ import contextlib
23
+ import gzip
24
+ import sys
22
25
import json
23
26
import logging
24
27
from argparse import Namespace
36
39
from rdflib .namespace import DCTERMS , RDF
37
40
from rdflib_pyld_compat import pyld_jsonld_from_rdflib_graph
38
41
39
- HELP = """A plugin for exporting rdf from OMERO
42
+ HELP = """A plugin for exporting RDF from OMERO
40
43
41
44
omero-rdf creates a stream of RDF triples from the starting object that
42
45
it is given. This may be one of: Image, Dataset, Project, Plate, and Screen.
43
46
44
47
Examples:
45
48
46
49
omero rdf Image:123 # Streams each triple found in N-Triples format
50
+
47
51
omero rdf -F=jsonld Image:123 # Collects all triples and prints formatted output
48
52
omero rdf -S=flat Project:123 # Do not recurse into containers ("flat-strategy")
49
53
omero rdf --trim-whitespace ... # Strip leading and trailing whitespace from text
50
54
omero rdf --first-handler-wins ... # First mapping wins; others will be ignored
51
55
56
+ omero rdf --file - ... # Write RDF triples to stdout
57
+ omero rdf --file output.nt ... # Write RDF triples to the specified file
58
+ omero rdf --file output.nt.gz # Write RDF triples to the specified file, gzipping
59
+
52
60
"""
53
61
54
62
# TYPE DEFINITIONS
60
68
Handlers = List [Callable [[URIRef , URIRef , Data ], Generator [Triple , None , bool ]]]
61
69
62
70
71
+ @contextlib .contextmanager
72
+ def open_with_default (filename = None , filehandle = None ):
73
+ """
74
+ Open a file for writing if given and close on completion.
75
+
76
+ No closing will happen if the file name is "-" since stdout will be used.
77
+ If no filehandle is given, stdout will also be used.
78
+ Otherwise return the given filehandle will be used.
79
+ """
80
+ close = False
81
+ if filename :
82
+ if filename == "-" :
83
+ fh = sys .stdout
84
+ else :
85
+ if filename .endswith (".gz" ):
86
+ fh = gzip .open (filename , "wt" )
87
+ else :
88
+ fh = open (filename , "w" )
89
+ close = True
90
+ else :
91
+ if filehandle is None :
92
+ filehandle = sys .stdout
93
+ fh = filehandle
94
+
95
+ try :
96
+ yield fh
97
+ finally :
98
+ if close :
99
+ fh .close ()
100
+
101
+
63
102
def gateway_required (func : Callable ) -> Callable : # type: ignore
64
103
"""
65
104
Decorator which initializes a client (self.client),
@@ -256,6 +295,7 @@ def __init__(
256
295
use_ellide = False ,
257
296
first_handler_wins = False ,
258
297
descent = "recursive" ,
298
+ filehandle = sys .stdout ,
259
299
) -> None :
260
300
self .gateway = gateway
261
301
self .cache : Set [URIRef ] = set ()
@@ -268,6 +308,7 @@ def __init__(
268
308
self ._descent_level = 0
269
309
self .annotation_handlers = self .load_handlers ()
270
310
self .info = self .load_server ()
311
+ self .filehandle = filehandle
271
312
272
313
def skip_descent (self ):
273
314
return self .descent != "recursive" and self ._descent_level > 0
@@ -375,13 +416,13 @@ def handle(self, data: Data) -> URIRef:
375
416
376
417
def emit (self , triple : Triple ):
377
418
if self .formatter .streaming :
378
- print (self .formatter .serialize_triple (triple ))
419
+ print (self .formatter .serialize_triple (triple ), file = self . filehandle )
379
420
else :
380
421
self .formatter .add (triple )
381
422
382
423
def close (self ):
383
424
if not self .formatter .streaming :
384
- print (self .formatter .serialize_graph ())
425
+ print (self .formatter .serialize_graph (), file = self . filehandle )
385
426
386
427
def rdf (
387
428
self ,
@@ -527,6 +568,12 @@ def _configure(self, parser: Parser) -> None:
527
568
default = False ,
528
569
help = "Remove leading and trailing whitespace from literals" ,
529
570
)
571
+ parser .add_argument (
572
+ "--file" ,
573
+ type = str ,
574
+ default = None ,
575
+ help = "Write RDF triples to the specified file" ,
576
+ )
530
577
parser .set_defaults (func = self .action )
531
578
532
579
@gateway_required
@@ -538,16 +585,18 @@ def action(self, args: Namespace) -> None:
538
585
else :
539
586
args .format = format_mapping ()[args .format ]
540
587
541
- handler = Handler (
542
- self .gateway ,
543
- formatter = args .format ,
544
- use_ellide = args .ellide ,
545
- trim_whitespace = args .trim_whitespace ,
546
- first_handler_wins = args .first_handler_wins ,
547
- descent = args .descent ,
548
- )
549
- self .descend (self .gateway , args .target , handler )
550
- handler .close ()
588
+ with open_with_default (args .file ) as fh :
589
+ handler = Handler (
590
+ self .gateway ,
591
+ formatter = args .format ,
592
+ use_ellide = args .ellide ,
593
+ trim_whitespace = args .trim_whitespace ,
594
+ first_handler_wins = args .first_handler_wins ,
595
+ descent = args .descent ,
596
+ filehandle = fh ,
597
+ )
598
+ self .descend (self .gateway , args .target , handler )
599
+ handler .close ()
551
600
552
601
# TODO: move to handler?
553
602
def descend (
0 commit comments