pipelogic.types
The pipelang type system, exposed to Python.
Use this module when you need to construct typed values explicitly. Most of the time the higher-level pipelogic.cv wrappers do this for you.
Module-level constructors
Atomic constructors
from pipelogic.types import (
create_int32, create_uint32, create_int64, create_uint64,
create_bool, create_char, create_float, create_double,
create_string, create_bytes,
)
Each returns a pipelang Type handle for the named atomic. You'll mostly use the high-level Python wrappers (Int32(42), String("hi")) instead of these constructors directly.
Built-in Maybe<t>
Maybe<t> is a built-in option type, semantically Nothing | Just<t>. The Cond transformation produces it; coercion lets a Maybe<t> flow into a t-receiving consumer through unpack_named / unpack_union. In C++ the same type is exposed as ppl::Maybe<t>.
You don't register Maybe — it's always available. Use it directly in component.yml:
worker:
input_type: "Maybe<Image>"
output_type: "Maybe<BoundingBox>"
Composite constructors
Three functions cover almost everything you'll need:
create_type(expr) → Type
Parses a pipelang type expression and returns a reusable Type handle.
from pipelogic.types import create_type
LIST_OF_INTS = create_type("[Int32]")
POINT = create_type("{x: Float, y: Float}")
IMAGE_OR_TEXT = create_type("Image | String")
Build the handle once at module scope and reuse it — re-parsing the expression on every tick is wasted work.
register_named(pairs) → None
Registers one or more named types globally. Idempotent — safe to call multiple times. Call it once at import time, near your wrapper-class definitions.
from pipelogic.types import register_named
register_named([
("Vector3D", "{x: Float, y: Float, z: Float}"),
("Trajectory", "[Vector3D]"),
])
After registration, the names are available everywhere — in component.yml, in other components, and via get_type.
get_type(name) → Type
Looks up a previously registered named type.
from pipelogic.types import get_type
IMAGE = get_type("Image")
VECTOR = get_type("Vector3D")
Lower-level shape-specific constructors — create_record, create_list, create_tuple, create_union, create_named — are also exported. They produce the same Type handles as create_type but skip the expression parser. Reach for them when you're building types programmatically (e.g. assembling a record whose fields aren't known statically).
Wrapper classes
Every typed value has a Python wrapper class. They all inherit from PipeObject. The worker runtime serializes them automatically when you return them from your function — you don't call any serialization method yourself.
Atomic wrappers
Int32, Int64, UInt32, UInt64, Bool, Char, Float, Double all derive from ConstSizeAtomic. They support .value() and equality with native Python numerics.
from pipelogic.types import Int32, Bool
Int32(42).value() # 42
Int32(42) == 42 # True
Bool(True) == True # True
The default Python int maps to Int64 and the default Python float maps to Double. To use Int32 or Float, construct explicitly or pass a type string to a composite constructor.
String
from pipelogic.types import String
s = String("hello")
str(s) # "hello"
len(s) # 5
s == "hello" # True
Bytes
A binary-safe blob with a numpy buffer protocol.
from pipelogic.types import Bytes
import numpy as np
b = Bytes(b"\x01\x02\x03")
b[0] # 1 (int, not b"\x01")
np.asarray(b) # array([1, 2, 3], dtype=uint8)
np.array(b, copy=True) # explicit copy
b.unsafe_numpy(dtype=np.uint8, shape=(3,)) # zero-copy view
b.safe_numpy() # always returns a fresh copy
np.asarray(b) is zero-copy by default — it returns a view of the underlying buffer. The view becomes invalid the next time the runtime advances the stream. If you need a value that outlives the current tick, copy explicitly with np.array(b, copy=True) or b.safe_numpy().
Bytes also supports indexing, len, __contains__, and Bytes.from_numpy(ndarray) to construct from raw numpy data.
List
A typed sequence. The element type is inferred from the first element or supplied as a type string.
from pipelogic.types import List
L = List([1, 2, 3]) # element type inferred as Int64
L = List([1, 2, 3], '[Int32]') # explicit Int32
L.append(4)
L[0] = 99
L.numpy_view = L.unsafe_numpy(shape=(4,)) # zero-copy view
L_arr = np.asarray(L) # zero-copy via __array__
L.safe_numpy() # always copies
List supports append, extend, clear, pop, index, count, __contains__, __add__, __mul__, slicing, and the numpy buffer protocol.
List.from_numpy(ndarray, type_string=None) is the canonical fast path for ndarray-backed lists. The element type is inferred from the dtype.
Tuple
A fixed-length positional product type.
from pipelogic.types import Tuple
t = Tuple(("Life of Brian", 1979), '(String, Int32)')
t[0] # String("Life of Brian")
t[1] = 1980 # mutable
len(t) # 2
Record
A named-field product type. Fields are accessed by attribute or key.
from pipelogic.types import Record
r = Record({"name": "Ada", "age": 36}, '{name: String, age: UInt32}')
r.name # String("Ada")
r["age"] # UInt32(36)
r.keys() # ["name", "age"]
r.items() # iterable of (key, value)
"name" in r # True
You can also let Record infer the type from values when unambiguous:
r = Record({"name": "Ada", "age": 36})
Named
A type alias wrapper. Used for domain types like Image, BoundingBox, AudioFrame. Once a name is registered with register_named, you can construct values by name:
from pipelogic.types import Named, Tuple, get_type, register_named
register_named([
("EmptyToken", "()"),
])
token = Named(Tuple(()), get_type("EmptyToken"))
token.type_name() # "EmptyToken"
token.get() # Tuple(())
Most components don't construct Named directly — they use the pipelogic.cv wrappers which call Named for you.
Union
A discriminated sum-type wrapper. The first argument is the value, the second is the union type.
from pipelogic.types import Union, create_type
UNION_TYPE = create_type("Int32 | String")
v = Union("oops", UNION_TYPE)
v.get() # String("oops")
v.get_type() # the resolved variant type
Numpy buffer protocol
Bytes and List participate in the numpy buffer protocol via __array__. The semantics are copy-on-write by default:
np.asarray(x)— zero-copy view, valid for the current tick.np.array(x)— explicit copy.x.unsafe_numpy(...)— zero-copy view (you're responsible for lifetime).x.safe_numpy()— always returns a copy.
Writing through a np.asarray view writes back into the underlying buffer. That's intentional — preprocessing backends operate on the view directly. But it means don't keep the view past the next runtime tick.
Type strings
create_type and the second argument to List, Tuple, Record, etc. accept a pipelang type expression. The full grammar:
| Form | Example |
|---|---|
| Atomic | Int32, Float, String, Bytes, Bool, Char |
| List | [Int32], [[Float]] |
| Tuple | (Float, Float), (String, Int32, Bool) |
| Record | {x: Float, y: Float} |
| Union | Int32 | Float, Image.BGR | Image.RGB |
| Named lookup | Image, BoundingBox, any registered name |
| Type variable | t, t0, t1 |
| Generic | Maybe<t>, [Maybe<t>] |
| Variadic pack | $ts... (used in worker input declarations) |
Whitespace around :, ,, and | is ignored.
Constants
from pipelogic.types import (
PIPELOGIC_TYPES, # tuple of all wrapper classes
PIPELOGIC_CONST_SIZE_ATOMIC_TYPES, # tuple of atomic wrappers
Type, # pipelang Type handle
TypeManager, # global type registry
)
isinstance(value, PIPELOGIC_TYPES) # True for any wrapper instance