3
3
# The code itself is released under the Apache 2.0 license and the help text is
4
4
# subject to the license of the original schema.
5
5
import copy
6
+ import logging
6
7
import os
7
8
import pathlib
8
9
import re
9
10
import tempfile
10
11
import uuid as _uuid__ # pylint: disable=unused-import # noqa: F401
12
+ import xml .sax # nosec
11
13
from abc import ABC , abstractmethod
12
14
from io import StringIO
13
15
from typing import (
21
23
Tuple ,
22
24
Type ,
23
25
Union ,
26
+ cast ,
24
27
)
25
28
from urllib .parse import quote , urlparse , urlsplit , urlunsplit
26
29
from urllib .request import pathname2url
27
30
31
+ from rdflib import Graph
32
+ from rdflib .plugins .parsers .notation3 import BadSyntax
28
33
from ruamel .yaml .comments import CommentedMap
29
34
30
35
from schema_salad .exceptions import SchemaSaladException , ValidationException
31
- from schema_salad .fetcher import DefaultFetcher , Fetcher
36
+ from schema_salad .fetcher import DefaultFetcher , Fetcher , MemoryCachingFetcher
32
37
from schema_salad .sourceline import SourceLine , add_lc_filename
33
38
from schema_salad .utils import yaml_no_ts # requires schema-salad v8.2+
34
39
35
40
_vocab : Dict [str , str ] = {}
36
41
_rvocab : Dict [str , str ] = {}
37
42
43
+ _logger = logging .getLogger ("salad" )
44
+
38
45
39
46
class LoadingOptions :
40
47
def __init__ (
41
48
self ,
42
49
fetcher : Optional [Fetcher ] = None ,
43
50
namespaces : Optional [Dict [str , str ]] = None ,
44
- schemas : Optional [Dict [ str , str ]] = None ,
51
+ schemas : Optional [List [ str ]] = None ,
45
52
fileuri : Optional [str ] = None ,
46
53
copyfrom : Optional ["LoadingOptions" ] = None ,
47
54
original_doc : Optional [Any ] = None ,
@@ -77,6 +84,10 @@ def __init__(
77
84
else :
78
85
self .fetcher = fetcher
79
86
87
+ self .cache = (
88
+ self .fetcher .cache if isinstance (self .fetcher , MemoryCachingFetcher ) else {}
89
+ )
90
+
80
91
self .vocab = _vocab
81
92
self .rvocab = _rvocab
82
93
@@ -87,6 +98,42 @@ def __init__(
87
98
self .vocab [k ] = v
88
99
self .rvocab [v ] = k
89
100
101
+ @property
102
+ def graph (self ) -> Graph :
103
+ """Generate a merged rdflib.Graph from all entries in self.schemas."""
104
+ graph = Graph ()
105
+ if not self .schemas :
106
+ return graph
107
+ key = str (hash (tuple (self .schemas )))
108
+ if key in self .cache :
109
+ return cast (Graph , self .cache [key ])
110
+ for schema in self .schemas :
111
+ fetchurl = (
112
+ self .fetcher .urljoin (self .fileuri , schema )
113
+ if self .fileuri is not None
114
+ else pathlib .Path (schema ).resolve ().as_uri ()
115
+ )
116
+ try :
117
+ if fetchurl not in self .cache or self .cache [fetchurl ] is True :
118
+ _logger .debug ("Getting external schema %s" , fetchurl )
119
+ content = self .fetcher .fetch_text (fetchurl )
120
+ self .cache [fetchurl ] = newGraph = Graph ()
121
+ for fmt in ["xml" , "turtle" ]:
122
+ try :
123
+ newGraph .parse (
124
+ data = content , format = fmt , publicID = str (fetchurl )
125
+ )
126
+ break
127
+ except (xml .sax .SAXParseException , TypeError , BadSyntax ):
128
+ pass
129
+ graph += self .cache [fetchurl ]
130
+ except Exception as e :
131
+ _logger .warning (
132
+ "Could not load extension schema %s: %s" , fetchurl , str (e )
133
+ )
134
+ self .cache [key ] = graph
135
+ return graph
136
+
90
137
91
138
class Savable (ABC ):
92
139
"""Mark classes than have a save() and fromDoc() function."""
@@ -138,7 +185,6 @@ def save(
138
185
base_url : str = "" ,
139
186
relative_uris : bool = True ,
140
187
) -> save_type :
141
-
142
188
if isinstance (val , Savable ):
143
189
return val .save (top = top , base_url = base_url , relative_uris = relative_uris )
144
190
if isinstance (val , MutableSequence ):
0 commit comments