API reference#

The public surface, in the order a caller meets it. This page is curated: it lists the names you import, not every symbol in the package. The task pages explain how they compose, and Errors and limits states what each failure means.

import tenspec loads neither array library. Import tenspec.numpy or tenspec.torch for the declarations of the library you use.

The three entry points#

tenspec.checked(function)#

Check the declared arguments and the declared return of one call.

One scope spans the argument checks, the body, and the return check, so the operands and the return share their dimension, dtype and device bindings. Every call starts fresh bindings, so a later call can use other extents. Tenspec replaces an accepted array only when its declaration names a transform. A caller’s own validator keeps its own behavior.

Place it under an ordinary @classmethod or @staticmethod, closest to the function. A method that returns Self is checked against the class that received the call.

Returns:

A function with the same signature, which checks its declarations on every call.

Raises:

AnnotationError – for an async function, and for a callable that is not a plain Python function. This release checks neither.

Parameters:

function (Callable[[P], R])

Return type:

Callable[[P], R]

class tenspec.TensorContracts#

Check the tensor fields of one model together, in one boundary.

Place it first among the bases, as class M(TensorContracts, ProjectBase). It adds no base of its own and keeps the caller’s model configuration, strictness, and validators. The caller permits arbitrary types for the array fields.

Each model validation opens its own boundary, so a nested model binds independently. Tenspec replaces a field value only when its declaration names a transform, such as CopyReadOnly, and it copies or seals nothing else. A caller’s own validator keeps its own behavior. A tensor field checks its default the way it checks a supplied value.

Preparation runs where Pydantic builds the model’s schema, so Pydantic has already resolved every annotation in the caller’s own namespace. A declaration written as a string, as a name local to the function that defines the model, or as a name defined after the model, therefore reaches preparation as a real type. The subclass keeps what the caller declared in __tensor_declarations__.

tenspec.validate(value, annotation)#

Check value against annotation, and return the accepted value.

One call owns one boundary, so every tensor inside a tuple declaration shares the dimension and dtype bindings. Two calls never relate their names. No binding stays active after the call returns.

A repeated annotation reuses its prepared schema. Every declared check still runs against this call’s own values, inside this call’s own bindings.

validate((a, b), tuple[Float[Shape[“rows”]], Float[Shape[“rows”]]]) refuses two arrays whose first axis disagrees.

Returns:

The accepted value. Tenspec returns an array by identity unless its declaration names a transform. A caller’s own validator can still replace it.

Parameters:
  • value (Any)

  • annotation (TypeForm)

Return type:

T

Backend aliases#

Each alias takes a shape first, then any transforms, then any properties: Float[Shape["rows features"]], or Float[Shape["rows features"], Finite] with a property. The spellings are the same in tenspec.numpy and tenspec.torch.

Alias

Accepts

Float

any supported floating format

Int

any supported signed integer format

UInt

any supported unsigned integer format

Complex

any supported complex format

Bool

the boolean format

Float16, Float32, Float64

that exact floating format

Int8, Int16, Int32, Int64

that exact signed integer format

UInt8, UInt16, UInt32, UInt64

that exact unsigned integer format

Complex64, Complex128

that exact complex format

tenspec.torch adds BFloat16, and the device markers a placement requirement names. See Dtype for what each backend represents, and Device for why NumPy takes no device.

These are parameterized type aliases. The table gives the spelling a caller writes, because the expanded form carries Tenspec’s internal declaration rather than anything a caller types.

Writing a shape#

tenspec.Shape is a public re-export of typing.Literal. A shape is its single string argument, as Shape["rows features"].

Write from typing import Literal as Shape in your own code. The re-export works at run time, but Ruff 0.16.6 does not follow it and reports a false diagnostic against the shape text. See Ruff and the Shape alias.

Common operations#

A property checks a value and changes nothing. A transform replaces the value every later operation and the caller receive. A declaration names every transform before its properties.

class tenspec.Finite#

Every element of the array is a finite number.

class tenspec.NonEmpty#

The array holds at least one element.

class tenspec.Contiguous#

The array uses C-contiguous storage.

class tenspec.Materialized#

The array holds actual data, rather than metadata alone.

These four import from tenspec and work on either backend. The next two are NumPy’s own, because they read a NumPy writeable flag:

class tenspec.numpy.ReadOnly#

This array’s own writeable flag is off.

It is a check of that flag. It is not a seal, and it never promises that another reference to the same storage cannot change the values.

class tenspec.numpy.CopyReadOnly#

Replace the array with a read-only copy of it, on separate storage.

The caller’s array keeps its own values and its writeable flag. The stored copy shares no memory with it, and refuses an ordinary write. This is not immutability: a caller who reaches the copy’s base array can still change the values.

It copies on every validation, so it costs time and memory each time.

CopyReadOnly against ReadOnly states what neither one promises.

Operation author bases#

Derive your own property or transform from the author base of your backend, which fixes the scalar domain the operation accepts.

class tenspec.numpy.ArrayProperty#

The base a NumPy property derives from. D is the scalar domain it accepts.

Write class NonPositive(ArrayProperty[np.floating[Any]]) and implement validate. A declaration must guarantee D, so a float64-only check refuses a broad Float declaration. Write an exact scalar class, or a family with Any.

abstractmethod static validate(values)#

Refuse values when they do not have this property.

Return None to accept. This method changes nothing and returns no replacement.

Raises:
  • ValueError – when the values measurably lack the property. TensorMismatch carries structured detail, and every built-in check raises it.

  • ConstraintEvaluationError – when this value’s state makes the check impossible, which is never a measured refusal.

Parameters:

values (A)

Return type:

None

class tenspec.numpy.ArrayTransform#

The base a NumPy transform derives from. D is the scalar domain it accepts.

Write class Owned(ArrayTransform[np.generic]) and implement transform. The result must keep the concrete class, the shape, the native dtype and the device of its input, and must leave the caller’s own array unchanged.

abstractmethod static transform(values)#

Return a replacement for values, with the same structure.

The result keeps the concrete class, the shape, the native dtype and the device of its input. The runtime checks each of those and refuses a result that changed one. Do not change the caller’s own array, and do not change shared storage.

Returns:

The replacement array.

Parameters:

values (A)

Return type:

A

tenspec.torch carries the same two names for the Torch backend. Its author bases take no scalar type parameter. A transform of your own shows both, with the rules a transform must keep.

Reading a bound axis#

tenspec.axis_size(name)#

The extent bound to name in the boundary that is validating now.

Call it inside a checked function, a model validator, or a standalone validation. It reads a bound dimension and never selects a tensor or reparses a shape.

Returns:

The extent bound to the name.

Raises:

BindingError – with reason NO_SCOPE outside a scope, UNBOUND for a name this boundary never bound, and NOT_SCALAR for a variadic group.

Parameters:

name (str)

Return type:

int

Errors#

class tenspec.AnnotationError#

A declaration is invalid, unsupported, or never reached Tenspec’s preparation.

class tenspec.ConstraintEvaluationError#

A backend cannot inspect a declared property of this value.

This never reports a measured mismatch. It reports that the check could not run.

class tenspec.errors.TensorMismatch(*, requirement, observed, bindings=None)#

An array disagrees with its declared requirement.

The requirement, the observed fact, and the relevant bindings stay readable on the error. The error holds no array.

Parameters:
  • requirement (str)

  • observed (str)

  • bindings (Mapping[str, int | tuple[int, ...]] | None)

class tenspec.errors.BindingError(reason, name=None)#

A required dimension was unavailable, rather than different.

The reason says which lookup failed. The name is the dimension the caller asked for.

Parameters:
  • reason (BindingReason)

  • name (str | None)

class tenspec.errors.TransformContractError(*, transform, fact, before, after)#

A transform changed a structural fact that it had to preserve.

This reports a defect in the transform, not invalid caller input, so it is a RuntimeError and never a ValueError. It names the transform and the one fact that changed. Every field is text, so the error holds no array.

Parameters:
  • transform (str)

  • fact (str)

  • before (str)

  • after (str)

The error classes says where each one is raised, and which of them a caller normally catches.

The advanced interfaces#

Each lower name keeps its defining module. Use them to check arrays outside an annotation, or to add a backend.

Name

Module

What it is

ArrayValidator

tenspec.runtime.validation

one resolved declaration, prepared once and reused for many arrays

validate_array

tenspec.runtime.validation

the one-shot form of the same check

TensorType

tenspec.types.tensor

the whole requirement: shape, dtype, device, transforms, properties

parse_shape

tenspec.types.shapes

the shape string as a ShapeSpec

Bindings, validation_scope, active_bindings

tenspec.runtime.bindings

the facts one boundary holds, and the boundary itself

prepare_annotation, prepare_declaration

tenspec.pydantic.annotations

a declaration as an ordinary Pydantic annotation

ArrayBackend

tenspec.runtime.arrays

the seam a new array library implements

import numpy as np

from tenspec.numpy import NUMPY_ARRAYS
from tenspec.runtime.bindings import validation_scope
from tenspec.runtime.validation import ArrayValidator
from tenspec.types.dtypes import DTypeFamily, DTypeRequirement
from tenspec.types.properties import NonEmpty
from tenspec.types.shapes import parse_shape
from tenspec.types.tensor import TensorType

requirement = TensorType(
    shape=parse_shape("rows cols"),
    dtype=DTypeRequirement(permitted=DTypeFamily.FLOATING),
    properties=(NonEmpty,),
)
validator = ArrayValidator(tensor_type=requirement, backend=NUMPY_ARRAYS)

with validation_scope() as bindings:
    validator.validate(np.ones((3, 4)), bindings=bindings)
    print(dict(bindings.dimensions))

ArrayValidator resolves its operations once, at construction, and refuses a requirement the backend cannot answer there. It holds no array and no bindings, so one validator serves many calls. validate_array does the same work in one call, and pays the preparation each time.

An ArrayBackend reads facts and compares nothing: shape, dtype, device, native_dtype and operation_formats. It receives neither a TensorType nor a Bindings, so a backend cannot read a declaration or decide whether two values agree. The runtime does that.