API Reference#

Cross-specification, implementation-agnostic JSON referencing.

class referencing.Anchor(name: str, resource: Resource[D])[source]#

A simple anchor in a Resource.

name: str#
resource: Resource[D]#
resolve(resolver: Resolver[D])[source]#

Return the resource for this anchor.

class referencing.Registry(resources=HashTrieMap({}), anchors: HashTrieMap[tuple[URI, str], AnchorType[D]] = HashTrieMap({}), uncrawled: HashTrieSet[URI] = HashTrieSet({}), retrieve: Retrieve[D] = <function _fail_to_retrieve>)[source]#

A registry of Resources, each identified by their canonical URIs.

Registries store a collection of in-memory resources, and optionally enable additional resources which may be stored elsewhere (e.g. in a database, a separate set of files, over the network, etc.).

They also lazily walk their known resources, looking for subresources within them. In other words, subresources contained within any added resources will be retrievable via their own IDs (though this discovery of subresources will be delayed until necessary).

Registries are immutable, and their methods return new instances of the registry with the additional resources added to them.

The retrieve argument can be used to configure retrieval of resources dynamically, either over the network, from a database, or the like. Pass it a callable which will be called if any URI not present in the registry is accessed. It must either return a Resource or else raise a NoSuchResource exception indicating that the resource does not exist even according to the retrieval logic.

__getitem__(uri: str) Resource[D][source]#

Return the (already crawled) Resource identified by the given URI.

__iter__() Iterator[str][source]#

Iterate over all crawled URIs in the registry.

__len__() int[source]#

Count the total number of fully crawled resources in this registry.

__rmatmul__(new: Resource[D] | Iterable[Resource[D]]) Registry[D][source]#

Create a new registry with resource(s) added using their internal IDs.

Resources must have a internal IDs (e.g. the $id keyword in modern JSON Schema versions), otherwise an error will be raised.

Both a single resource as well as an iterable of resources works, i.e.:

  • resource @ registry or

  • [iterable, of, multiple, resources] @ registry

which – again, assuming the resources have internal IDs – is equivalent to calling Registry.with_resources as such:

registry.with_resources(
    (resource.id(), resource) for resource in new_resources
)
Raises:

NoInternalID – if the resource(s) in fact do not have IDs

get_or_retrieve(uri: str) Retrieved[D, Resource[D]][source]#

Get a resource from the registry, crawling or retrieving if necessary.

May involve crawling to find the given URI if it is not already known, so the returned object is a Retrieved object which contains both the resource value as well as the registry which ultimately contained it.

remove(uri: str)[source]#

Return a registry with the resource identified by a given URI removed.

anchor(uri: str, name: str)[source]#

Retrieve a given anchor from a resource which must already be crawled.

contents(uri: str) D[source]#

Retrieve the (already crawled) contents identified by the given URI.

crawl() Registry[D][source]#

Crawl all added resources, discovering subresources.

with_resource(uri: str, resource: Resource[D])[source]#

Add the given Resource to the registry, without crawling it.

with_resources(pairs: Iterable[tuple[str, Resource[D]]]) Registry[D][source]#

Add the given Resources to the registry, without crawling them.

with_contents(pairs: Iterable[tuple[str, D]], **kwargs: Any) Registry[D][source]#

Add the given contents to the registry, autodetecting when necessary.

combine(*registries: Registry[D]) Registry[D][source]#

Combine together one or more other registries, producing a unified one.

resolver(base_uri: str = '') Resolver[D][source]#

Return a Resolver which resolves references against this registry.

resolver_with_root(resource: Resource[D]) Resolver[D][source]#

Return a Resolver with a specific root resource.

class referencing.Resource(contents: D, specification: Specification[D])[source]#

A document (deserialized JSON) with a concrete interpretation under a spec.

In other words, a Python object, along with an instance of Specification which describes how the document interacts with referencing – both internally (how it refers to other Resources) and externally (how it should be identified such that it is referenceable by other documents).

contents: D#
classmethod from_contents(contents: ~referencing.typing.D, default_specification: type[~referencing._core.Specification[~referencing.typing.D]] | ~referencing._core.Specification[~referencing.typing.D] = <class 'referencing._core.Specification'>) Resource[D][source]#

Create a resource guessing which specification applies to the contents.

Raises:

CannotDetermineSpecification – if the given contents don’t have any discernible information which could be used to guess which specification they identify as

classmethod opaque(contents: D) Resource[D][source]#

Create an opaque Resource – i.e. one with opaque specification.

See Specification.OPAQUE for details.

id() str | None[source]#

Retrieve this resource’s (specification-specific) identifier.

subresources() Iterable[Resource[D]][source]#

Retrieve this resource’s subresources.

anchors() Iterable[Anchor[D]][source]#

Retrieve this resource’s (specification-specific) identifier.

pointer(pointer: str, resolver: Resolver[D]) Resolved[D][source]#

Resolve the given JSON pointer.

Raises:

exceptions.PointerToNowhere – if the pointer points to a location not present in the document

class referencing.Specification(name: str, id_of: Callable[[D], URI | None], subresources_of: Callable[[D], Iterable[D]], maybe_in_subresource: _MaybeInSubresource[D], anchors_in: Callable[[Specification[D], D], Iterable[AnchorType[D]]])[source]#

A specification which defines referencing behavior.

The various methods of a Specification allow for varying referencing behavior across JSON Schema specification versions, etc.

name: str#

A short human-readable name for the specification, used for debugging.

id_of: Callable[[D], str | None]#

Find the ID of a given document.

subresources_of: Callable[[D], Iterable[D]]#

Retrieve the subresources of the given document (without traversing into the subresources themselves).

maybe_in_subresource: _MaybeInSubresource[D]#

While resolving a JSON pointer, conditionally enter a subresource (if e.g. we have just entered a keyword whose value is a subresource)

OPAQUE: ClassVar[Specification[Any]] = <Specification name='opaque'>#

An opaque specification where resources have no subresources nor internal identifiers.

detect() Specification[D]#

Attempt to discern which specification applies to the given contents.

May be called either as an instance method or as a class method, with slightly different behavior in the following case:

Recall that not all contents contains enough internal information about which specification it is written for – the JSON Schema {}, for instance, is valid under many different dialects and may be interpreted as any one of them.

When this method is used as an instance method (i.e. called on a specific specification), that specification is used as the default if the given contents are unidentifiable.

On the other hand when called as a class method, an error is raised.

To reiterate, DRAFT202012.detect({}) will return DRAFT202012 whereas the class method Specification.detect({}) will raise an error.

(Note that of course DRAFT202012.detect(...) may return some other specification when given a schema which does identify as being for another version).

Raises:

CannotDetermineSpecification – if the given contents don’t have any discernible information which could be used to guess which specification they identify as

anchors_in(contents: D)[source]#

Retrieve the anchors contained in the given document.

create_resource(contents: D) Resource[D][source]#

Create a resource which is interpreted using this specification.

Private Objects#

The following objects are private in the sense that constructing or importing them is not part of the referencing public API, as their name indicates (by virtue of beginning with an underscore).

They are however public in the sense that other public API functions may return objects of these types.

Plainly then, you may rely on their methods and attributes not changing in backwards incompatible ways once referencing itself is stable, but may not rely on importing or constructing them yourself.

class referencing._core.Resolved(contents: D, resolver: Resolver[D])[source]#

A reference resolved to its contents by a Resolver.

contents: D#
resolver: Resolver[D]#
class referencing._core.Retrieved(value: AnchorOrResource, registry: Registry[D])[source]#

A value retrieved from a Registry.

value: AnchorOrResource#
registry: Registry[D]#
class referencing._core.AnchorOrResource#

An anchor or resource.

alias of TypeVar(‘AnchorOrResource’, ~referencing.typing.Anchor[~typing.Any], ~referencing._core.Resource[~typing.Any])

class referencing._core.Resolver(base_uri: URI, registry: Registry[D], previous: List[URI] = List([]))[source]#

A reference resolver.

Resolvers help resolve references (including relative ones) by pairing a fixed base URI with a Registry.

This object, under normal circumstances, is expected to be used by implementers of libraries built on top of referencing (e.g. JSON Schema implementations or other libraries resolving JSON references), not directly by end-users populating registries or while writing schemas or other resources.

References are resolved against the base URI, and the combined URI is then looked up within the registry.

The process of resolving a reference may itself involve calculating a new base URI for future reference resolution (e.g. if an intermediate resource sets a new base URI), or may involve encountering additional subresources and adding them to a new registry.

lookup(ref: str) Resolved[D][source]#

Resolve the given reference to the resource it points to.

Raises:
  • exceptions.Unresolvable – or a subclass thereof (see below) if the reference isn’t resolvable

  • exceptions.NoSuchAnchor – if the reference is to a URI where a resource exists but contains a plain name fragment which does not exist within the resource

  • exceptions.PointerToNowhere – if the reference is to a URI where a resource exists but contains a JSON pointer to a location within the resource that does not exist

in_subresource(subresource: Resource[D]) Resolver[D][source]#

Create a resolver for a subresource (which may have a new base URI).

dynamic_scope() Iterable[tuple[str, Registry[D]]][source]#

In specs with such a notion, return the URIs in the dynamic scope.

class referencing._core._MaybeInSubresource(*args, **kwargs)[source]#
class referencing._core._Unset#

A sentinel object used internally to satisfy the type checker.

Neither accessing nor explicitly passing this object anywhere is public API, and it is only documented here at all to get Sphinx to not complain.

Submodules#

referencing.jsonschema#

Referencing implementations for JSON Schema specs (historic & current).

referencing.jsonschema.ObjectSchema#

A JSON Schema which is a JSON object

alias of Mapping[str, Any]

referencing.jsonschema.Schema#

A JSON Schema of any kind

alias of Union[bool, Mapping[str, Any]]

referencing.jsonschema.SchemaResource#

A Resource whose contents are JSON Schemas

alias of Resource[Union[bool, Mapping[str, Any]]]

referencing.jsonschema.SchemaRegistry#

A JSON Schema Registry

alias of Registry[Union[bool, Mapping[str, Any]]]

referencing.jsonschema.EMPTY_REGISTRY: Registry[bool | Mapping[str, Any]] = <Registry (0 resources)>#

The empty JSON Schema Registry

exception referencing.jsonschema.UnknownDialect(uri: str)[source]#

A dialect identifier was found for a dialect unknown by this library.

If it’s a custom (“unofficial”) dialect, be sure you’ve registered it.

uri: str#
referencing.jsonschema.DRAFT202012 = <Specification name='draft2020-12'>#

JSON Schema draft 2020-12

referencing.jsonschema.DRAFT201909 = <Specification name='draft2019-09'>#

JSON Schema draft 2019-09

referencing.jsonschema.DRAFT7 = <Specification name='draft-07'>#

JSON Schema draft 7

referencing.jsonschema.DRAFT6 = <Specification name='draft-06'>#

JSON Schema draft 6

referencing.jsonschema.DRAFT4 = <Specification name='draft-04'>#

JSON Schema draft 4

referencing.jsonschema.DRAFT3 = <Specification name='draft-03'>#

JSON Schema draft 3

referencing.jsonschema.specification_with(dialect_id: str, default: Specification[Any] | _Unset = _Unset.SENTINEL) Specification[Any][source]#

Retrieve the Specification with the given dialect identifier.

Raises:

UnknownDialect – if the given dialect_id isn’t known

class referencing.jsonschema.DynamicAnchor(name: str, resource: Resource[bool | Mapping[str, Any]])[source]#

Dynamic anchors, introduced in draft 2020.

name: str#
resource: Resource[bool | Mapping[str, Any]]#
resolve(resolver: Resolver[bool | Mapping[str, Any]]) Resolved[bool | Mapping[str, Any]][source]#

Resolve this anchor dynamically.

referencing.jsonschema.lookup_recursive_ref(resolver: Resolver[bool | Mapping[str, Any]]) Resolved[bool | Mapping[str, Any]][source]#

Recursive references (via recursive anchors), present only in draft 2019.

As per the 2019 specification (§ 8.2.4.2.1), only the # recursive reference is supported (and is therefore assumed to be the relevant reference).

referencing.exceptions#

Errors, oh no!

exception referencing.exceptions.NoSuchResource(ref: URI)[source]#

Bases: KeyError

The given URI is not present in a registry.

Unlike most exceptions, this class is intended to be publicly instantiable and is part of the public API of the package.

ref: URI#
exception referencing.exceptions.NoInternalID(resource: Resource[Any])[source]#

Bases: Exception

A resource has no internal ID, but one is needed.

E.g. in modern JSON Schema drafts, this is the $id keyword.

One might be needed if a resource was to-be added to a registry but no other URI is available, and the resource doesn’t declare its canonical URI.

resource: Resource[Any]#
exception referencing.exceptions.Unretrievable(ref: URI)[source]#

Bases: KeyError

The given URI is not present in a registry, and retrieving it failed.

ref: URI#
exception referencing.exceptions.CannotDetermineSpecification(contents: Any)[source]#

Bases: Exception

Attempting to detect the appropriate Specification failed.

This happens if no discernible information is found in the contents of the new resource which would help identify it.

contents: Any#
exception referencing.exceptions.Unresolvable(ref: URI)[source]#

Bases: Exception

A reference was unresolvable.

ref: URI#
exception referencing.exceptions.PointerToNowhere(ref: URI, resource: Resource[Any])[source]#

Bases: Unresolvable

A JSON Pointer leads to a part of a document that does not exist.

resource: Resource[Any]#
exception referencing.exceptions.NoSuchAnchor(ref: URI, resource: Resource[Any], anchor: str)[source]#

Bases: Unresolvable

An anchor does not exist within a particular resource.

resource: Resource[Any]#
anchor: str#
exception referencing.exceptions.InvalidAnchor(ref: URI, resource: Resource[Any], anchor: str)[source]#

Bases: Unresolvable

An anchor which could never exist in a resource was dereferenced.

It is somehow syntactically invalid.

resource: Resource[Any]#
anchor: str#

referencing.retrieval#

Helpers related to (dynamic) resource retrieval.

referencing.retrieval.to_cached_resource(cache: Callable[[Retrieve[D]], Retrieve[D]] | None = None, loads: Callable[[_T], D] = <function loads>, from_contents: Callable[[D], Resource[D]] = <bound method Resource.from_contents of <class 'referencing._core.Resource'>>) Callable[[Callable[[URI], _T]], Retrieve[D]][source]#

Create a retriever which caches its return values from a simpler callable.

Takes a function which returns things like serialized JSON (strings) and returns something suitable for passing to Registry as a retrieve function.

This decorator both reduces a small bit of boilerplate for a common case (deserializing JSON from strings and creating Resource objects from the result) as well as makes the probable need for caching a bit easier. Retrievers which otherwise do expensive operations (like hitting the network) might otherwise be called repeatedly.

Examples

from referencing import Registry
from referencing.typing import URI
import referencing.retrieval

@referencing.retrieval.to_cached_resource()
def retrieve(uri: URI):
    print(f"Retrieved {uri}")

    # Normally, go get some expensive JSON from the network, a file ...
    return '''
        {
            "$schema": "https://json-schema.org/draft/2020-12/schema",
            "foo": "bar"
        }
    '''

one = Registry(retrieve=retrieve).get_or_retrieve("urn:example:foo")
print(one.value.contents["foo"])

# Retrieving the same URI again reuses the same value (and thus doesn't
# print another retrieval message here)
two = Registry(retrieve=retrieve).get_or_retrieve("urn:example:foo")
print(two.value.contents["foo"])
Retrieved urn:example:foo
bar
bar
class referencing.retrieval._T#

A serialized document (e.g. a JSON string)

alias of TypeVar(‘_T’)

referencing.typing#

Type-annotation related support for the referencing library.

referencing.typing.URI#

A URI which identifies a Resource.

class referencing.typing.D#

The type of documents within a registry.

alias of TypeVar(‘D’)

class referencing.typing.Retrieve(*args, **kwargs)[source]#

A retrieval callable, usable within a Registry for resource retrieval.

Does not make assumptions about where the resource might be coming from.

class referencing.typing.Anchor(*args, **kwargs)[source]#

An anchor within a Resource.

Beyond “simple” anchors, some specifications like JSON Schema’s 2020 version have dynamic anchors.

property name: str#

Return the name of this anchor.

resolve(resolver: Resolver[D]) Resolved[D][source]#

Return the resource for this anchor.