33# The code itself is released under the Apache 2.0 license and the help text is
44# subject to the license of the original schema.
55import copy
6+ import logging
67import os
78import pathlib
89import re
910import tempfile
1011import uuid as _uuid__ # pylint: disable=unused-import # noqa: F401
12+ import xml .sax # nosec
1113from abc import ABC , abstractmethod
1214from io import StringIO
1315from typing import (
2123 Tuple ,
2224 Type ,
2325 Union ,
26+ cast ,
2427)
2528from urllib .parse import quote , urlparse , urlsplit , urlunsplit
2629from urllib .request import pathname2url
2730
31+ from rdflib import Graph
32+ from rdflib .plugins .parsers .notation3 import BadSyntax
2833from ruamel .yaml .comments import CommentedMap
2934
3035from schema_salad .exceptions import SchemaSaladException , ValidationException
31- from schema_salad .fetcher import DefaultFetcher , Fetcher
36+ from schema_salad .fetcher import DefaultFetcher , Fetcher , MemoryCachingFetcher
3237from schema_salad .sourceline import SourceLine , add_lc_filename
3338from schema_salad .utils import yaml_no_ts # requires schema-salad v8.2+
3439
3540_vocab : Dict [str , str ] = {}
3641_rvocab : Dict [str , str ] = {}
3742
43+ _logger = logging .getLogger ("salad" )
44+
3845
3946class LoadingOptions :
4047 def __init__ (
4148 self ,
4249 fetcher : Optional [Fetcher ] = None ,
4350 namespaces : Optional [Dict [str , str ]] = None ,
44- schemas : Optional [Dict [ str , str ]] = None ,
51+ schemas : Optional [List [ str ]] = None ,
4552 fileuri : Optional [str ] = None ,
4653 copyfrom : Optional ["LoadingOptions" ] = None ,
4754 original_doc : Optional [Any ] = None ,
@@ -77,6 +84,10 @@ def __init__(
7784 else :
7885 self .fetcher = fetcher
7986
87+ self .cache = (
88+ self .fetcher .cache if isinstance (self .fetcher , MemoryCachingFetcher ) else {}
89+ )
90+
8091 self .vocab = _vocab
8192 self .rvocab = _rvocab
8293
@@ -87,6 +98,42 @@ def __init__(
8798 self .vocab [k ] = v
8899 self .rvocab [v ] = k
89100
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+
90137
91138class Savable (ABC ):
92139 """Mark classes than have a save() and fromDoc() function."""
@@ -138,7 +185,6 @@ def save(
138185 base_url : str = "" ,
139186 relative_uris : bool = True ,
140187) -> save_type :
141-
142188 if isinstance (val , Savable ):
143189 return val .save (top = top , base_url = base_url , relative_uris = relative_uris )
144190 if isinstance (val , MutableSequence ):
0 commit comments