def resolve_fragment(self, document, fragment): """ Resolve a ``fragment`` within the referenced ``document``. Arguments: document: The referrant document fragment (str): a URI fragment to resolve within it """ fragment = fragment.lstrip(u"/") parts = unquote(fragment).split(u"/") if fragment else [] for part in parts: part = part.replace(u"~1", u"/").replace(u"~0", u"~") if isinstance(document, Sequence): # Array indexes should be turned into integers try: part = int(part) except ValueError: pass try: document = document[part] except (TypeError, LookupError): raise RefResolutionError("Unresolvable JSON pointer: %r" % fragment) return document
def _resolve_refs(schema_root: str, json_spec: dict, context: str) -> dict: """ Resolve JSON Schema references in `json_spec` relative to `base_uri`, return `json_spec` with all references inlined. `context` is used to format error to provide (wait for it) context. """ resolver = _build_ref_resolver(schema_root, json_spec) def _resolve_ref(ref: str) -> dict: # Don't resolve local refs, since this would make loading recursive schemas impossible. if ref.startswith("#"): return {"$ref": ref} with resolver.resolving(ref) as resolved_spec: # resolved_spec might have unresolved refs in it, so we pass # it back to _resolve_refs to resolve them. This way, # we can fully resolve schemas with nested refs. try: res = _resolve_refs(schema_root, resolved_spec, ref) except RefResolutionError as e: raise RefResolutionError( f"Error resolving '$ref':{ref!r}: {e}") from e # as reslover uses cache we don't want to return mutable # objects, so we make a copy return copy.deepcopy(res) try: return _map_refs(json_spec, _resolve_ref) except RefResolutionError as e: raise RefResolutionError( f"Error resolving refs in {context!r}: {e}") from e
def resolving(self, ref): """ Context manager which resolves a JSON ``ref`` and enters the resolution scope of this ref. :argument str ref: reference to resolve """ full_uri = urljoin(self.resolution_scope, ref) uri, fragment = urldefrag(full_uri) if not uri: uri = self.base_uri if uri in self.store: document = self.store[uri] else: try: document = self.resolve_remote(uri) except Exception as exc: raise RefResolutionError(exc) old_base_uri, self.base_uri = self.base_uri, uri try: with self.in_scope(uri): yield self.resolve_fragment(document, fragment) finally: self.base_uri = old_base_uri
def pop_scope(self): try: self._scopes_stack.pop() except IndexError: raise RefResolutionError( "Failed to pop the scope from an empty stack. " "`pop_scope()` should only be called once for every " "`push_scope()`")
def resolve_from_url(self, url): url, fragment = urldefrag(url) try: document = self.store[url] except KeyError: try: document = self.resolve_remote(url) except Exception as exc: raise RefResolutionError(exc) return self.resolve_fragment(document, fragment)
def iter_jsonpointer_parts(jsonpath): """ Generates the ``jsonpath`` parts according to jsonpointer spec. :param str jsonpath: a jsonpath to resolve within document :return: The parts of the path as generator), without converting any step to int, and None if None. :author: Julian Berman, ankostis Examples:: >>> list(iter_jsonpointer_parts('/a/b')) ['a', 'b'] >>> list(iter_jsonpointer_parts('/a//b')) ['a', '', 'b'] >>> list(iter_jsonpointer_parts('/')) [''] >>> list(iter_jsonpointer_parts('')) [] But paths are strings begining (NOT_MPL: but not ending) with slash('/'):: >>> list(iter_jsonpointer_parts(None)) Traceback (most recent call last): AttributeError: 'NoneType' object has no attribute 'split' >>> list(iter_jsonpointer_parts('a')) Traceback (most recent call last): jsonschema.exceptions.RefResolutionError: Jsonpointer-path(a) must start with '/'! #>>> list(iter_jsonpointer_parts('/a/')) #Traceback (most recent call last): #jsonschema.exceptions.RefResolutionError: Jsonpointer-path(a) must NOT ends with '/'! """ # if jsonpath.endswith('/'): # msg = "Jsonpointer-path({}) must NOT finish with '/'!" # raise RefResolutionError(msg.format(jsonpath)) parts = jsonpath.split("/") if parts.pop(0) != "": msg = "Jsonpointer-path({}) must start with '/'!" raise RefResolutionError(msg.format(jsonpath)) for part in parts: part = unescape_jsonpointer_part(part) yield part
def resolve_jsonpointer(doc, jsonpointer, default=_scream): """ Resolve a ``jsonpointer`` within the referenced ``doc``. :param doc: the referrant document :param str path: a jsonpointer to resolve within document :param default: A value to return if path does not resolve. :return: the resolved doc-item or raises :class:`RefResolutionError` :raises: RefResolutionError (if cannot resolve path and no `default`) Examples: >>> dt = { ... 'pi':3.14, ... 'foo':'bar', ... 'df': pd.DataFrame(np.ones((3,2)), columns=list('VN')), ... 'sub': { ... 'sr': pd.Series({'abc':'def'}), ... } ... } >>> resolve_jsonpointer(dt, '/pi', default=_scream) 3.14 >>> resolve_jsonpointer(dt, '/pi/BAD') Traceback (most recent call last): jsonschema.exceptions.RefResolutionError: Unresolvable JSON pointer('/pi/BAD')@(BAD) >>> resolve_jsonpointer(dt, '/pi/BAD', 'Hi!') 'Hi!' :author: Julian Berman, ankostis """ for part in iter_jsonpointer_parts(jsonpointer): if isinstance(doc, cabc.Sequence): # Array indexes should be turned into integers try: part = int(part) except ValueError: pass try: doc = doc[part] except (TypeError, LookupError): if default is _scream: raise RefResolutionError( "Unresolvable JSON pointer(%r)@(%s)" % (jsonpointer, part) ) else: return default return doc
def _resolve_ref(ref: str) -> dict: # Don't resolve local refs, since this would make loading recursive schemas impossible. if ref.startswith("#"): return {"$ref": ref} with resolver.resolving(ref) as resolved_spec: # resolved_spec might have unresolved refs in it, so we pass # it back to _resolve_refs to resolve them. This way, # we can fully resolve schemas with nested refs. try: res = _resolve_refs(schema_root, resolved_spec, ref) except RefResolutionError as e: raise RefResolutionError( f"Error resolving '$ref':{ref!r}: {e}") from e # as reslover uses cache we don't want to return mutable # objects, so we make a copy return copy.deepcopy(res)
def set_jsonpointer(doc, jsonpointer, value, object_factory=dict): """ Resolve a ``jsonpointer`` within the referenced ``doc``. :param doc: the referrant document :param str jsonpointer: a jsonpointer to the node to modify :raises: RefResolutionError (if jsonpointer empty, missing, invalid-contet) """ parts = list(iter_jsonpointer_parts(jsonpointer)) # Will scream if used on 1st iteration. # pdoc = None ppart = None for i, part in enumerate(parts): if isinstance(doc, cabc.Sequence) and not isinstance(doc, str): # Array indexes should be turned into integers # doclen = len(doc) if part == "-": part = doclen else: try: part = int(part) except ValueError: raise RefResolutionError( "Expected numeric index(%s) for sequence at (%r)[%i]" % (part, jsonpointer, i) ) else: if part > doclen: raise RefResolutionError( "Index(%s) out of bounds(%i) of (%r)[%i]" % (part, doclen, jsonpointer, i) ) try: ndoc = doc[part] except (LookupError): break # Branch-extension needed. except (TypeError): # Maybe indexing a string... ndoc = object_factory() pdoc[ppart] = ndoc doc = ndoc break # Branch-extension needed. doc, pdoc, ppart = ndoc, doc, part else: doc = pdoc # If loop exhausted, cancel last assignment. # Build branch with value-leaf. # nbranch = value for part2 in reversed(parts[i + 1 :]): ndoc = object_factory() ndoc[part2] = nbranch nbranch = ndoc # Attach new-branch. try: doc[part] = nbranch # Inserting last sequence-element raises IndexError("list assignment index # out of range") except IndexError: doc.append(nbranch)
def resolve_path(doc, path, default=_scream, root=None): """ Like :func:`resolve_jsonpointer` also for relative-paths & attribute-branches. :param doc: the referrant document :param str path: An abdolute or relative path to resolve within document. :param default: A value to return if path does not resolve. :param root: Document for absolute paths, assumed `doc` if missing. :return: the resolved doc-item or raises :class:`RefResolutionError` :raises: RefResolutionError (if cannot resolve path and no `default`) Examples: >>> dt = { ... 'pi':3.14, ... 'foo':'bar', ... 'df': pd.DataFrame(np.ones((3,2)), columns=list('VN')), ... 'sub': { ... 'sr': pd.Series({'abc':'def'}), ... } ... } >>> resolve_path(dt, '/pi', default=_scream) 3.14 >>> resolve_path(dt, 'df/V') 0 1.0 1 1.0 2 1.0 Name: V, dtype: float64 >>> resolve_path(dt, '/pi/BAD', 'Hi!') 'Hi!' :author: Julian Berman, ankostis """ def resolve_root(d, p): if not p: return root or doc raise ValueError() part_resolvers = [ lambda d, p: d[int(p)], lambda d, p: d[p], lambda d, p: getattr(d, p), resolve_root, ] for part in iter_jsonpointer_parts_relaxed(path): start_i = 0 if isinstance(doc, cabc.Sequence) else 1 for resolver in part_resolvers[start_i:]: try: doc = resolver(doc, part) break except (ValueError, TypeError, LookupError, AttributeError): pass else: if default is _scream: raise RefResolutionError("Unresolvable path(%r)@(%s)" % (path, part)) return default return doc
def resolve_remote(self, uri): if uri in self.store[""]: return self.store[""][uri] else: raise RefResolutionError("Unable to find type %s" % uri)
def validate_against(self, instance, schemauris=[], strict=False): """ validate the instance against each of the schemas identified by the list of schemauris. For the instance to be considered valid, it must validate against each of the named schemas. $extensionSchema properties within the instance are ignored. :argument instance: a parsed JSON document to be validated. :argument list schemauris: a list of URIs of the schemas to validate against. :return list: a list of encountered errors in the form of exceptions; otherwise, an empty list if the instance is valid against all schemas. """ if isinstance(schemauris, (str, unicode)): schemauris = [schemauris] schema = None out = [] for uri in schemauris: val = self._validators.get(uri) if not val: (urib, frag) = self._spliturifrag(uri) schema = self._schemaStore.get(urib) if not schema: try: schema = self._loader(urib) except KeyError as e: ex = MissingSchemaDocument( "Unable to find schema document for " + urib) if strict: out.append(ex) continue resolver = jsch.RefResolver(uri, schema, self._schemaStore, handlers=self._handler) if frag: try: schema = resolver.resolve_fragment(schema, frag) except RefResolutionError as ex: exc = RefResolutionError( "Unable to resolve fragment, {0} from schema, {1} ({2})" .format(frag, urib, str(ex))) out.append(exc) continue cls = jsch.validator_for(schema) # check the schema for errors scherrs = [ SchemaError.create_from(err) \ for err in cls(cls.META_SCHEMA).iter_errors(schema) ] if len(scherrs) > 0: out.extend(scherrs) continue val = cls(schema, resolver=resolver) out.extend([err for err in val.iter_errors(instance)]) self._validators[uri] = val self._schemaStore.update(val.resolver.store) return out