API reference

Overview

Targets

Target

A user-constructable object.

Namespace

A SimpleNamespace that prints readably.

build

Build a target from a specification.

Artifacts

Artifact

A typed view into a directory.

DynamicArtifact

An artifact with dynamically named fields.

ProxyArtifactField

An artifact field that does not yet exist.

recover

Recover an existing artifact.

Context management

Context

A collection of target-construction options.

get_context

Return the call stack’s currently active context.

push_context

Replace the active context in the current call stack.

pop_context

Revert to the previously active context.

using_context

Return a context manager that makes a context active within its body.

Schema generation

get_spec_schema

Return a JSON Schema describing valid artifact specifications.

get_spec_list_schema

Return a JSON Schema describing lists of artifact specifications.

get_spec_dict_schema

Return a JSON Schema describing artifact specification dictionaries.

Interface generation

API

A WSGI server that provides access to an Artisan context.

Readers and writers

PersistentList

A list backed by a CBOR file.

PersistentArray

A numpy.memmap backed by a CBOR file.

read_text_file

Read a text file using Path.read_text.

read_json_file

Read a JSON file using json.load.

read_numpy_file

Read a NumPy array file or a NumPy archive using numpy.load.

read_cbor_file

Read a CBOR file.

read_opaque_file

Return the given path, unchanged.

write_object_as_cbor

Write a JSON-encodable object or a NumPy array to a CBOR file.

write_path

Create a symbolic link.

Targets

class Target(spec: Target.Spec)

A user-constructable object.

All target constructors should accept a specification as their first argument. Specification attributes can be boolean values, integers, floating-point numbers, strings, None values, pathlib.Path objects, artifacts, lists of allowed values, and namespace-like objects with __dict__ attributes containing only allowed values.

If spec has a type attribute, Target’s constructor will dereference it in the current target scope and return an instance of the resulting type. The default target scope contains every Target subclass defined outside of the artisan library, and uses keys in the form <subclass>.__qualname__ for uniquely named target types and f'{<subclass>.__qualname__} ({<subclass>.__module__})' for target types with non-unique names. artisan.push_context or artisan.using_context can be used to set the active target scope.

Target types can define an inner Spec class to indicate the expected type of spec. If <subclass>.Spec is not defined explicitly, it will be defined implicitly as a protocol with no required fields. Public, non-callable attributes of <subclass>.Spec will be used as default values for missing spec attributes.

Parameters

spec – The target’s specification.

class Spec[source]

A protocol denoting valid specifications.

class Namespace(*args: object, **kwargs: object)

A SimpleNamespace that prints readably.

build(cls: Type[SomeTarget], spec: object, *args: object, **kwargs: object) → SomeTarget

Build a target from a specification.

To support using JSON-encodable objects as specifications, mappings in spec are converted to namespaces and artifact-root-relative path strings (strings starting with “@/”) are converted to artifacts if they point to an existing directory, and Path objects otherwise.

args and kwargs are forwarded to the target’s constructor.

Artifacts

class Artifact(spec: Artifact.Spec)

A typed view into a directory.

Instantiation

When an artifact is instantiated, Artisan will search for a directory with a matching _meta_.json file in the active context’s root directory. The search is recursive, but when a directory with a _meta_.json file is found, its subdirectories will not be searched. If a matching directory does not exist, a new directory will be created, and the active context’s artifact builder will be called to build the artifact there. If spec has a _path_ field, the artifact will be located at that path. The “@” operator, as in ArtifactType @ path, can be used to load an existing artifact without requiring it to match a specification. In the default context, the root directory is the current working directory, and the artifact builder calls __init__ and logs metadata to _meta_.json.

Reading and writing files

Reading from and writing to an artifact’s attributes corresponds to reading from and writing to files in the corresponding directory. Support for new file types can be added by overriding an artifact type’s list of readers (_readers_) and/or its list of writers (_writers_). Readers should be functions that accept a path and return an object representing the data stored at that path. Writers should accept an extensionless path and a data object, write the data to a version of the path with an appropriate extension, and return that extension. To support concurrent reading and writing, files generated by writers are not moved into the artifact’s directory until after the writer returns.

When reading from or writing to files, the first reader/writer not to raise an exception when called will be used. For performance, Artisan may skip writers whose data argument type does not match the object being stored. Similarly, Artisan may skip readers whose path argument type is annotated with an incompatible extension requirement, e.g. Annotated[Path, '.txt'] or Annotated[Path, '.jpg', '.jpeg']. The Annotated type constructor can be imported from the typing module in Python 3.9+ and the typing_extensions module in earlier versions.

Attribute-access modes

Artifacts can be instantiated in “read-sync”, “read-async”, or “write” mode. In “read-sync” mode, attribute accesses will only return after the artifact has finished building. In “read-async” mode, attribute accesses will return as soon as a corresponding file or directory exists. In “write” mode, attribute accesses will return immediately, but a ProxyArtifactField will be returned if no corresponding file or directory is present. Artifacts are instantiated in “read-sync” mode by default, but if spec has a _mode_ attribute, that mode will be used. The default builder always executes artifacts’ __init__ methods in “write” mode (an other builders should as well), so it is generally only necessary to specify _mode_ when “read-async” behavior is desired.

Parameters

spec (Artifact.Spec) – The artifact’s specification.

Variables
  • _readers_ (ClassVar[List[Callable]]) – The deserialization functions artifacts of this type will try to use when their attributes are being accessed.

  • _writers_ (ClassVar[List[Callable]]) – The serialization functions artifacts of this type will try to use when their attributes are being assigned to.

  • _path_ (Path) – The artifact’s path on the filesystem.

  • _mode_ (Literal['read-sync', 'read-async', 'write']) – The artifact’s attribute-access mode.

class Spec

A protocol denoting valid specifications.

__dir__() → List[str][source]

Return the names of this artifact’s attributes.

__getattr__(key: str) → Any[source]

Return the data stored at {self._path_}/{key}{inferred_extension}.

__setattr__(key: str, value: object) → None[source]

Write data to {self._path_}/{key}{inferred_extension}.

The extension is determined based on the type of data stored. For performance, other entries with matching keys are not deleted. Use __delattr__ to delete those entries.

__delattr__(key: str) → None[source]

Delete all entries in self._path_ with the given stem.

__fspath__() → str[source]

Return this artifact’s path, as a string.

__truediv__(entry_name: str) → Path[source]

Return self._path_ / entry_name.

class DynamicArtifact(spec: DynamicArtifact.Spec)

An artifact with dynamically named fields.

Item access, assignment, deletion, and iteration can be used in place of attribute access, assignment, deletion, and iteration

class Spec

A protocol denoting valid specifications.

__len__() → int[source]
__iter__() → Iterator[str][source]
__contains__(key: object) → bool[source]
__getitem__(key: str) → T[source]
__setitem__(key: str, val: object) → None[source]
__delitem__(key: str) → None[source]
class ProxyArtifactField(root: Artifact, *keys: str)

An artifact field that does not yet exist.

Corresponding files and/or directories will be created when it is written to.

__getattr__(key: str)ProxyArtifactField[source]
__setattr__(key: str, value: object) → None[source]
__delattr__(key: str) → None[source]
append(item: object) → None[source]
extend(items: Iterable[object]) → None[source]
recover(cls: Type[SomeArtifact], path: os.PathLike | str, mode: str = 'read-sync') → SomeArtifact

Recover an existing artifact.

mode must be “read-sync”, “read-async”, or “write”.

Context management

class Context(*, root=None, scope=None, builder=None)

A collection of target-construction options.

Parameters
  • root (Path | str | None) – The default directory for artifact creation, and the directory that will be searched for matches when an artifact is instantiated from a specification. By default, root is the current working directory.

  • scope (Mapping[str, type] | None) – The mapping used to resolve type names in specifications during target instantiation. By default, scope contains all non-Artisan-defined target types.

  • builder (Callable[[Artifact, object], None] | None) – The function called to write files into artifact directories. builder accepts two arguments, the artifact to construct and its specification. The default builder calls artifact.__init__(spec) and logs metadata to a _meta_.json file. Custom builders can log additional information or offload work to other processes to build artifacts in parallel.

Variables
  • root (Path) – root as a Path, if root was provided, or the default, otherwise.

  • scope (Mapping[str, type]) – scope, if scope was provided, or the default, otherwise.

  • builder (Callable[[Artifact, object], None]) – builder, if builder was provided, or the default, otherwise.

get_context()Context

Return the call stack’s currently active context.

push_context(context=None, *, root=None, scope=None, builder=None)

Replace the active context in the current call stack.

pop_context can be called to revert to the previously active context.

Parameters
  • context (Context | None) – A context to make active. A new context will be created if one is not provided.

  • root (Path | str | None) – A value to override context.root.

  • scope (Mapping[str, type] | None) – A value to override context.scope.

  • builder (Callable[[Artifact, object], None] | None) – A value to override context.builder.

pop_context() → None

Revert to the previously active context.

A LookupError is raised if no context was previously active.

using_context(context=None, *, root=None, scope=None, builder=None)

Return a context manager that makes a context active within its body.

Parameters
  • context (Context | None) – A context to make active. A new context will be created if one is not provided.

  • root (Path | str | None) – A value to override context.root.

  • scope (Mapping[str, type] | None) – A value to override context.scope.

  • builder (Callable[[Artifact, object], None] | None) – A value to override context.builder.

Schema generation

get_spec_schema() → dict

Return a JSON Schema describing valid artifact specifications.

The schema will contain a definition corresponding to the Spec type of every entry in the active scope.

get_spec_list_schema() → dict

Return a JSON Schema describing lists of artifact specifications.

The schema will contain a definition corresponding to the Spec type of every entry in the active scope.

get_spec_dict_schema() → dict

Return a JSON Schema describing artifact specification dictionaries.

Objects whose entries are artifact specifications will be valid against the schema. The schema will contain a definition corresponding to the Spec type of every entry in the active scope.

Interface generation

class API(*, permissions=None, ui=None, root=None, scope=None, builder=None)

A WSGI server that provides access to an Artisan context.

The following request types are supported:

  • GET /artifacts{/path*}

  • POST /artifacts

  • DELETE /artifacts{/path*}

  • GET /schemas/spec

  • GET /schemas/spec-list

  • GET /schemas/spec-dict

  • GET /ui{/path*}

Analogous HEAD and OPTIONS requests are also supported.

Parameters
  • permissions – A mapping from passwords to permission sets. Permissions sets can contain “read”, “write”, and/or “delete”. The default permission policy is {'': ('read', 'write', 'delete')}, meaning even users who have not provided a password have “read”, “write”, and “delete” permissions.

  • ui – The WSGI server that should handle requests in the form /ui{/path*}. The default UI is a WebUI.

  • root – A path to override the active context’s root.

  • scope – A mapping to override the active context’s scope.

  • builder – A callable to override the active context’s builder.

serve(port: int = 8000) → None[source]

Start a server on the specified port.

This method uses the reference WSGI server defined in the standard library. Other servers, which can be installed via pip, may be more robust and performant.

Readers and writers

class PersistentList(file_: io.BufferedRandom, length: int)

A list backed by a CBOR file.

For performance, a PersistentList is invalidated when another object, including another PersistentList, writes to its backing file. An invalidated PersistentList is a potentially out-of-date read-only view into the file, and calling append or extend on it will corrupt the file.

append(item: object) → None[source]

Append object to the end of the list.

extend(items: Iterable[object]) → None[source]

Extend list by appending elements from the iterable.

class PersistentArray(filename, dtype='uint8', mode='r+', offset=0, shape=None, order='C')

A numpy.memmap backed by a CBOR file.

The file must contain a row-major multidimensional array as defined in IETF RFC 8746. For performance, a PersistentArray is invalidated when another object, including another PersistentArray, writes to its backing file. An invalidated PersistentArray is a potentially out-of-date read-only view into the file, and calling append or extend on it will corrupt the file.

Due to NumPy issue 4198 (https://github.com/numpy/numpy/issues/4198), PersistenArray extends np.memmap by proxy, meaning that it delegates attribute accesses and method calls to an internal np.memmap object instead of using Python’s native subclassing mechanism.

append(item: object) → None[source]

Append item to the array.

extend(items: object) → None[source]

Extend the array by appending elements from items.

read_text_file(path: Annotated[Path, ''.txt'']) → str

Read a text file using Path.read_text.

read_json_file(path: Annotated[Path, ''.json'']) → Any

Read a JSON file using json.load.

JSON objects are read as namespaces.

read_numpy_file(path: Annotated[Path, ''.npy'', ''.npz'']) → Any

Read a NumPy array file or a NumPy archive using numpy.load.

read_cbor_file(path: Annotated[Path, ''.cbor'']) → Any

Read a CBOR file.

If the file encodes an indefinite-length array, a PersistentList will be returned.

If the file encodes a 0–12-dimensional row-major array as specified in IETF RFC 8746, and the shape elements and byte string length are encoded as 8-byte unsigned integers, a PersistentArray will be returned.

Otherwise, a JSON-like object will be returned.

read_opaque_file(path: Path) → Path

Return the given path, unchanged.

write_object_as_cbor(path: Path, val: object) → str

Write a JSON-encodable object or a NumPy array to a CBOR file.

write_path(path: Path, val: Path) → str

Create a symbolic link.