# Core

> Public API of the pyrula core: sum types, collections, serde, functional helpers.

Source: https://docs.pyrula.io/reference/core/

Generated from the type stubs and docstrings. Do not edit by hand.

## Types

## `AvroRegistrySerde`

Avro serde with Confluent Schema Registry integration.

Wire format: [0x00 magic][schema_id: 4 bytes BE][avro payload]

Schema registration is lazy (happens on first serialize() call). Reader
schemas are fetched from the registry on first deserialize() per schema ID
and cached for the serde's lifetime.

TLS/mTLS: pass ca_path to trust a custom CA, or cert_path + key_path for
mutual TLS (both required together; applies only when registry_url is
https://). Encrypted private keys are not supported - key_path must point
to an unencrypted PEM.

reader_schema: the schema every record is resolved INTO on deserialize
(standard Avro schema resolution); without one a record decodes to its own
writer schema's shape, fetched by the id in its framing.

logical_types and strict: as for AvroSerde. See the docsite for a usage
example including mTLS setup.

timeout_ms: the bound on one registry HTTP request, from connect to the last
byte of the response (default 5000, 1 to 300000; ValueError otherwise). A
call that makes one request fails within about this long when the registry is
down or silent. A failed lookup, including a 404 for an unknown schema id, is
remembered for 5 seconds, so records that need the same schema do not each ask
the registry again; a registry follower that is behind on a brand-new id keeps
that id failing for up to 5 seconds. A batch asks for each schema id at most
once and, once the registry is unreachable or answers 5xx, makes no further
requests; records that needed one fail with that error.

```python
AvroRegistrySerde(registry_url: str, subject: str, schema: str, username: Optional[str] = None, password: Optional[str] = None, ca_path: Optional[str] = None, cert_path: Optional[str] = None, key_path: Optional[str] = None, reader_schema: Optional[str] = None, *, logical_types: Literal['python', 'raw'] = 'python', strict: bool = False, timeout_ms: int = 5000)
```

### `for_topic`

```python
for_topic(registry_url: str, topic: str, schema: str, is_key: bool = False, username: Optional[str] = None, password: Optional[str] = None, ca_path: Optional[str] = None, cert_path: Optional[str] = None, key_path: Optional[str] = None, reader_schema: Optional[str] = None, *, logical_types: Literal['python', 'raw'] = 'python', strict: bool = False, timeout_ms: int = 5000) -> AvroRegistrySerde
```

### `register`

```python
register(self) -> Ok[int] | Err[SerdeError]
```

### `subject`

```python
subject(self) -> str
```

### `serialize`

```python
serialize(self, obj: Any) -> Ok[bytes] | Err[SerdeError]
```

### `deserialize`

```python
deserialize(self, data: bytes) -> Ok[Any] | Err[SerdeError]
```

### `serialize_many`

```python
serialize_many(self, items: Iterable[Any]) -> IList
```

### `serialize_many_bytes`

```python
serialize_many_bytes(self, items: Iterable[Any]) -> BytesList
```

### `deserialize_many`

```python
deserialize_many(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_objects`

```python
deserialize_many_objects(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_from_packed`

```python
deserialize_many_from_packed(self, packed: bytes, count: int) -> IList
```

### `check_compatibility`

```python
check_compatibility(self, schema: str, version: Optional[str] = None) -> Ok[bool] | Err[SerdeError]
```

### `list_versions`

```python
list_versions(self) -> Ok[list[int]] | Err[SerdeError]
```

### `get_schema_by_version`

```python
get_schema_by_version(self, version: int) -> Ok[str] | Err[SerdeError]
```

### `delete_version`

```python
delete_version(self, version: int) -> Ok[bool] | Err[SerdeError]
```

---

## `AvroSerde`

Avro serde over a single schema, returning Either instead of raising.

Logical types convert as fastavro (and so confluent-kafka) converts them:
timestamp-* to an aware UTC datetime, local-timestamp-* to a naive datetime,
date to a date, time-* to a time, decimal to a Decimal, uuid to a UUID. Writing
takes those types or the plain value on the wire.

logical_types: "raw" decodes logical types to their value on the wire instead -
int, bytes or str. Encoding takes either form whichever is chosen.

strict: refuse what fastavro converts by guessing - a naive datetime for a
timestamp-* (fastavro takes it as the host's local time), a datetime for a date
(fastavro drops its time), and a decoded decimal with more digits than its
precision (fastavro rounds it).

```python
AvroSerde(schema_json: str, *, logical_types: Literal['python', 'raw'] = 'python', strict: bool = False)
```

### `serialize`

```python
serialize(self, obj: Any) -> Ok[bytes] | Err[SerdeError]
```

### `deserialize`

```python
deserialize(self, data: bytes) -> Ok[Any] | Err[SerdeError]
```

### `serialize_many`

```python
serialize_many(self, items: Iterable[Any]) -> IList
```

### `serialize_many_bytes`

```python
serialize_many_bytes(self, items: Iterable[Any]) -> BytesList
```

### `deserialize_many`

```python
deserialize_many(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

---

## `BytesList`

```python
BytesList(items: Any = ...)
```

### `empty`

```python
empty(cls) -> BytesList
```

### `from_packed`

```python
from_packed(buf: bytes, count: int) -> BytesList
```

### `get`

```python
get(self, index: int) -> Some[bytes] | _Nothing
```

### `append`

```python
append(self, item: bytes) -> BytesList
```

### `to_list`

```python
to_list(self) -> list[bytes]
```

### `to_packed`

```python
to_packed(self) -> bytes
```

### `to_ilist`

```python
to_ilist(self) -> IList[bytes]
```

### `length`

```python
length(self) -> int
```

### `is_empty`

```python
is_empty(self) -> bool
```

### `index`

```python
index(self, value: Any, start: int = 0, stop: int = ...) -> int
```

### `count`

```python
count(self, value: Any) -> int
```

---

## `DeserializationError`

---

## `Duration`

A typed time-span value backed by an ``i64`` nanosecond count.

Mirrors Scala's ``FiniteDuration`` - a precise, immutable quantity of time
with unit-converting constructors and full arithmetic support.

### `nanos`

```python
nanos(n: int) -> Duration
```

### `micros`

```python
micros(n: int) -> Duration
```

### `millis`

```python
millis(n: int) -> Duration
```

### `seconds`

```python
seconds(n: float) -> Duration
```

### `minutes`

```python
minutes(n: float) -> Duration
```

### `hours`

```python
hours(n: float) -> Duration
```

### `days`

```python
days(n: float) -> Duration
```

### `from_timedelta`

```python
from_timedelta(td: Any) -> Duration
```

### `to_nanos`

```python
to_nanos(self) -> int
```

### `to_millis`

```python
to_millis(self) -> int
```

### `to_seconds`

```python
to_seconds(self) -> float
```

### `to_timedelta`

```python
to_timedelta(self) -> Any
```

---

## `Either`

A value (`Ok`) or an error (`Err`). Subclassed by `Ok` and `Err`, the same
way Scala's `Either` is by `Right`/`Left`, so `Either[A, E]` is a real generic
and `isinstance(x, Either)` holds for both. Abstract: build values through the
factories below (or `Ok`/`Err` directly), not `Either(...)`.

### `ok`

```python
ok(value: T) -> Ok[T]
```

### `err`

```python
err(error: E) -> Err[E]
```

### `try_of`

```python
try_of(f: Callable[[], T]) -> Either[T, Exception]
```

### `cond`

```python
cond(condition: bool, ok: T, err: E) -> Either[T, E]
```

### `sequence`

```python
sequence(values: Iterable[Either[T, E]]) -> Either[list[T], E]
```

### `traverse`

```python
traverse(values: Iterable[_A], fn: Callable[[_A], Either[U, F]]) -> Either[list[U], F]
```

### `map`

```python
map(self, f: Callable[..., Any]) -> Either[Any, E]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Either[Any, E]
```

### `map_err`

```python
map_err(self, f: Callable[..., Any]) -> Either[T, Any]
```

### `ensure`

```python
ensure(self, predicate: Callable[[T], bool], error: Callable[[T], F]) -> Either[T, E | F]
ensure(self, predicate: Callable[[T], bool], error: F) -> Either[T, E | F]
```

### `tap`

```python
tap(self, fn: Callable[[T], Any]) -> Either[T, E]
```

### `tap_err`

```python
tap_err(self, fn: Callable[[E], Any]) -> Either[T, E]
```

### `flatten`

```python
flatten(self: Either[Ok[U], E]) -> Either[U, E]
flatten(self: Either[Err[F], E]) -> Err[E | F]
flatten(self: Either[Either[U, F], E]) -> Either[U, E | F]
```

### `bimap`

```python
bimap(self, err_fn: Callable[[E], F], ok_fn: Callable[[T], U]) -> Either[U, F]
```

### `recover`

```python
recover(self, f: Callable[[E], Any]) -> Either[Any, Any]
```

### `recover_with`

```python
recover_with(self, f: Callable[[E], Either[Any, Any]]) -> Either[Any, Any]
```

### `or_else`

```python
or_else(self, alt: Either[Any, Any] | Callable[[], Either[Any, Any]]) -> Either[Any, Any]
```

### `fold`

```python
fold(self, err: Callable[..., Any], ok: Callable[..., Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> Any
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> T | U
```

### `unwrap_or_raise`

```python
unwrap_or_raise(self, exc: Any = None) -> Any
```

### `to_option`

```python
to_option(self) -> Option[T]
```

### `is_ok`

```python
is_ok(self) -> bool
```

### `is_err`

```python
is_err(self) -> bool
```

---

## `Err`

```python
Err(error: E)
```

**Fields:**

- `error: E`

### `map`

```python
map(self, f: Callable[..., Any]) -> Err[E]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Err[E]
```

### `recover`

```python
recover(self, f: Callable[[E], Any]) -> Either[Any, Any]
```

### `recover_with`

```python
recover_with(self, f: Callable[[E], Either[Any, Any]]) -> Either[Any, Any]
```

### `map_err`

```python
map_err(self, f: Callable[..., Any]) -> Err[Any]
```

### `ensure`

```python
ensure(self, predicate: Callable[[Any], bool], error: F | Callable[[Any], F]) -> Err[E]
```

### `tap`

```python
tap(self, fn: Callable[[Any], Any]) -> Err[E]
```

### `tap_err`

```python
tap_err(self, fn: Callable[[E], Any]) -> Err[E]
```

### `flatten`

```python
flatten(self) -> Err[E]
```

### `bimap`

```python
bimap(self, err_fn: Callable[[E], F], ok_fn: Callable[[Any], U]) -> Err[F]
```

### `or_else`

```python
or_else(self, alt: Either[Any, Any] | Callable[[], Either[Any, Any]]) -> Either[Any, Any]
```

### `fold`

```python
fold(self, err: Callable[..., Any], ok: Callable[..., Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> Any
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> U
```

### `unwrap_or_raise`

```python
unwrap_or_raise(self, exc: Any = None) -> Any
```

### `to_option`

```python
to_option(self) -> _Nothing
```

### `is_ok`

```python
is_ok(self) -> bool
```

### `is_err`

```python
is_err(self) -> bool
```

---

## `Failure`

```python
Failure(error: BaseException)
```

**Fields:**

- `error: BaseException`

### `map`

```python
map(self, f: Callable[..., Any]) -> Failure
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Failure
```

### `tap`

```python
tap(self, fn: Callable[[Any], Any]) -> Failure
```

### `tap_failure`

```python
tap_failure(self, fn: Callable[[BaseException], Any]) -> Failure
```

### `flatten`

```python
flatten(self) -> Failure
```

### `recover`

```python
recover(self, f: Callable[[Any], Any]) -> Try[Any]
```

### `recover_with`

```python
recover_with(self, f: Callable[[Any], Try[Any]]) -> Try[Any]
```

### `or_else`

```python
or_else(self, alt: Try[Any] | Callable[[], Try[Any]]) -> Try[Any]
```

### `fold`

```python
fold(self, failure: Callable[..., Any], success: Callable[..., Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> Any
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> U
```

### `to_either`

```python
to_either(self) -> Err[Any]
```

### `is_success`

```python
is_success(self) -> bool
```

### `is_failure`

```python
is_failure(self) -> bool
```

---

## `FloatList`

```python
FloatList(items: Any = ...)
```

### `empty`

```python
empty(cls) -> FloatList
```

### `map_float`

```python
map_float(self, f: Callable[[float], float]) -> FloatList
```

### `filter`

```python
filter(self, pred: Callable[[float], bool]) -> FloatList
```

### `fold_left`

```python
fold_left(self, initial: Any, f: Callable[..., Any]) -> Any
```

### `add_scalar`

```python
add_scalar(self, n: float) -> FloatList
```

### `sub_scalar`

```python
sub_scalar(self, n: float) -> FloatList
```

### `mul_scalar`

```python
mul_scalar(self, n: float) -> FloatList
```

### `div_scalar`

```python
div_scalar(self, n: float) -> FloatList
```

### `abs_vals`

```python
abs_vals(self) -> FloatList
```

### `clamp_vals`

```python
clamp_vals(self, lo: float, hi: float) -> FloatList
```

### `zip_add`

```python
zip_add(self, other: FloatList) -> FloatList
```

### `zip_mul`

```python
zip_mul(self, other: FloatList) -> FloatList
```

### `sum`

```python
sum(self) -> float
```

### `min`

```python
min(self) -> float
```

### `max`

```python
max(self) -> float
```

### `mean`

```python
mean(self) -> float
```

### `std_dev`

```python
std_dev(self) -> float
```

### `to_list`

```python
to_list(self) -> list[float]
```

### `to_ilist`

```python
to_ilist(self) -> IList[float]
```

### `length`

```python
length(self) -> int
```

### `is_empty`

```python
is_empty(self) -> bool
```

### `index`

```python
index(self, value: Any, start: int = 0, stop: int = ...) -> int
```

### `count`

```python
count(self, value: Any) -> int
```

---

## `IList`

```python
IList(items: Any = ...)
```

### `empty`

```python
empty(cls) -> IList[Any]
```

### `map`

```python
map(self, f: Callable[..., Any]) -> IList[Any]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> IList[Any]
```

### `filter`

```python
filter(self, pred: Callable[..., bool]) -> IList[T]
```

### `fold_left`

```python
fold_left(self, initial: Any, f: Callable[..., Any]) -> Any
```

### `head`

```python
head(self) -> Some[T] | _Nothing
```

### `last`

```python
last(self) -> Some[T] | _Nothing
```

### `tail`

```python
tail(self) -> IList[T]
```

### `find`

```python
find(self, pred: Callable[..., bool]) -> Some[T] | _Nothing
```

### `take`

```python
take(self, n: int) -> IList[T]
```

### `drop`

```python
drop(self, n: int) -> IList[T]
```

### `distinct`

```python
distinct(self) -> IList[T]
```

### `zip`

```python
zip(self, other: IList[Any]) -> IList[tuple[T, Any]]
```

### `append`

```python
append(self, item: T) -> IList[T]
```

### `prepend`

```python
prepend(self, item: T) -> IList[T]
```

### `concat`

```python
concat(self, other: IList[T]) -> IList[T]
```

### `to_list`

```python
to_list(self) -> list[T]
```

### `collect_ok`

```python
collect_ok(self) -> IList[Any]
```

### `collect_err`

```python
collect_err(self) -> IList[Any]
```

### `partition_either`

```python
partition_either(self) -> tuple[IList[Any], IList[Any]]
```

### `length`

```python
length(self) -> int
```

### `is_empty`

```python
is_empty(self) -> bool
```

### `index`

```python
index(self, value: Any, start: int = 0, stop: int = ...) -> int
```

### `count`

```python
count(self, value: Any) -> int
```

---

## `IMap`

Keys are found as in a dict: placed by Python's hash() and compared with ==, so any
key equal to a stored one finds it, whatever its type.

An IMap iterates in a fixed order that depends only on its contents, not on insertion
order, the process or PYTHONHASHSEED, when every key is an exact str or bytes, an int,
float, bool or None, or a tuple or frozenset of those. The order is by a 64-bit value
computed from each key's content, on first iteration, and it is neither insertion order
nor sorted order. A key that hashes by identity (a plain object), through a str (a plain
Enum member), or a str or bytes subclass with its own __hash__, iterates in a
process-dependent order, and NaN keys with identical bits, or distinct keys of another
type that share a hash, stay in insertion order among themselves.

```python
IMap(items: Any = ...)
```

### `empty`

```python
empty(cls) -> IMap[Any, Any]
```

### `get`

```python
get(self, key: object, /) -> Optional[V]
get(self, key: object, default: V, /) -> V
get(self, key: object, default: _T, /) -> V | _T
```

### `get_option`

```python
get_option(self, key: K) -> Some[V] | _Nothing
```

### `set`

```python
set(self, key: K, value: V) -> IMap[K, V]
```

### `remove`

```python
remove(self, key: K) -> IMap[K, V]
```

### `contains`

```python
contains(self, key: K) -> bool
```

### `to_dict`

```python
to_dict(self) -> dict
```

### `keys`

```python
keys(self) -> KeysView[K]
```

### `values`

```python
values(self) -> ValuesView[V]
```

### `items`

```python
items(self) -> ItemsView[K, V]
```

### `map_values`

```python
map_values(self, f: Callable[[V], Any]) -> IMap[K, Any]
```

### `filter_values`

```python
filter_values(self, pred: Callable[[V], bool]) -> IMap[K, V]
```

### `merge`

```python
merge(self, other: IMap[K, V]) -> IMap[K, V]
```

### `length`

```python
length(self) -> int
```

### `is_empty`

```python
is_empty(self) -> bool
```

---

## `ISet`

Keys are found as in a frozenset: placed by Python's hash() and compared with ==, so any
key equal to a stored one finds it, whatever its type.

An ISet iterates in a fixed order that depends only on its contents, not on insertion
order, the process or PYTHONHASHSEED, when every key is an exact str or bytes, an int,
float, bool or None, or a tuple or frozenset of those. The order is by a 64-bit value
computed from each key's content, on first iteration, and it is neither insertion order
nor sorted order. A key that hashes by identity (a plain object), through a str (a plain
Enum member), or a str or bytes subclass with its own __hash__, iterates in a
process-dependent order, and NaN keys with identical bits, or distinct keys of another
type that share a hash, stay in insertion order among themselves.

```python
ISet(items: Optional[Iterable[T]] = None)
```

### `empty`

```python
empty(cls) -> ISet[Any]
```

### `add`

```python
add(self, item: T) -> ISet[T]
```

### `remove`

```python
remove(self, item: T) -> ISet[T]
```

### `contains`

```python
contains(self, item: T) -> bool
```

### `union`

```python
union(self, other: ISet[T]) -> ISet[T]
```

### `intersection`

```python
intersection(self, other: ISet[T]) -> ISet[T]
```

### `difference`

```python
difference(self, other: ISet[T]) -> ISet[T]
```

### `is_subset_of`

```python
is_subset_of(self, other: ISet[T]) -> bool
```

### `map`

```python
map(self, f: Callable[[T], Any]) -> ISet[Any]
```

### `filter`

```python
filter(self, pred: Callable[[T], bool]) -> ISet[T]
```

### `to_set`

```python
to_set(self) -> set[T]
```

### `to_ilist`

```python
to_ilist(self) -> IList[T]
```

### `size`

```python
size(self) -> int
```

### `is_empty`

```python
is_empty(self) -> bool
```

### `isdisjoint`

```python
isdisjoint(self, other: Iterable[Any]) -> bool
```

---

## `IntList`

```python
IntList(items: Any = ...)
```

### `empty`

```python
empty(cls) -> IntList
```

### `map_int`

```python
map_int(self, f: Callable[[int], int]) -> IntList
```

### `filter`

```python
filter(self, pred: Callable[[int], bool]) -> IntList
```

### `fold_left`

```python
fold_left(self, initial: Any, f: Callable[..., Any]) -> Any
```

### `add_scalar`

```python
add_scalar(self, n: int) -> IntList
```

### `sub_scalar`

```python
sub_scalar(self, n: int) -> IntList
```

### `mul_scalar`

```python
mul_scalar(self, n: int) -> IntList
```

### `div_scalar`

```python
div_scalar(self, n: int) -> IntList
```

### `abs_vals`

```python
abs_vals(self) -> IntList
```

### `clamp_vals`

```python
clamp_vals(self, lo: int, hi: int) -> IntList
```

### `zip_add`

```python
zip_add(self, other: IntList) -> IntList
```

### `zip_mul`

```python
zip_mul(self, other: IntList) -> IntList
```

### `sum`

```python
sum(self) -> int
```

### `min`

```python
min(self) -> int
```

### `max`

```python
max(self) -> int
```

### `mean`

```python
mean(self) -> float
```

### `std_dev`

```python
std_dev(self) -> float
```

### `to_list`

```python
to_list(self) -> list[int]
```

### `to_ilist`

```python
to_ilist(self) -> IList[int]
```

### `length`

```python
length(self) -> int
```

### `is_empty`

```python
is_empty(self) -> bool
```

### `index`

```python
index(self, value: Any, start: int = 0, stop: int = ...) -> int
```

### `count`

```python
count(self, value: Any) -> int
```

---

## `IntSet`

```python
IntSet(items: Any = ...)
```

### `empty`

```python
empty(cls) -> IntSet
```

### `add`

```python
add(self, value: int) -> IntSet
```

### `remove`

```python
remove(self, value: int) -> IntSet
```

### `contains`

```python
contains(self, value: Any) -> bool
```

### `union`

```python
union(self, other: IntSet) -> IntSet
```

### `intersection`

```python
intersection(self, other: IntSet) -> IntSet
```

### `difference`

```python
difference(self, other: IntSet) -> IntSet
```

### `symmetric_difference`

```python
symmetric_difference(self, other: IntSet) -> IntSet
```

### `is_subset`

```python
is_subset(self, other: IntSet) -> bool
```

### `is_disjoint`

```python
is_disjoint(self, other: IntSet) -> bool
```

### `cardinality`

```python
cardinality(self) -> int
```

### `to_list`

```python
to_list(self) -> list[int]
```

### `to_bytes`

```python
to_bytes(self) -> bytes
```

### `from_bytes`

```python
from_bytes(data: bytes) -> IntSet
```

### `is_empty`

```python
is_empty(self) -> bool
```

### `isdisjoint`

```python
isdisjoint(self, other: Iterable[Any]) -> bool
```

---

## `Invalid`

```python
Invalid(error: E)
```

**Fields:**

- `errors: tuple[E, ...]`

### `sequence`

```python
sequence(values: Iterable[Validated[U, F]]) -> Validated[list[U], F]
```

### `traverse`

```python
traverse(values: Iterable[_A], fn: Callable[[_A], Validated[U, F]]) -> Validated[list[U], F]
```

---

## `JsonSchemaRegistrySerde`

JSON Schema serde with Confluent Schema Registry integration.

Wire format: [0x00 magic][schema_id: 4 bytes BE][json payload]

Same magic byte as Avro; content is JSON.
NOTE: payloads are NOT validated against the JSON Schema - this serde provides
Confluent wire framing ([0x00][schema_id:4B BE][json]) and registry
registration only. (confluent's JSONSerializer validates; pyrula does not yet.)

timeout_ms: the bound on one registry HTTP request, from connect to the last
byte of the response (default 5000, 1 to 300000; ValueError otherwise). A
failed registration is remembered for 5 seconds, so serializing many records
against a registry that is down makes one request, not one per record;
register() always asks again.

```python
JsonSchemaRegistrySerde(registry_url: str, subject: str, schema: str, username: Optional[str] = None, password: Optional[str] = None, ca_path: Optional[str] = None, cert_path: Optional[str] = None, key_path: Optional[str] = None, *, timeout_ms: int = 5000)
```

### `for_topic`

```python
for_topic(registry_url: str, topic: str, schema: str, is_key: bool = False, username: Optional[str] = None, password: Optional[str] = None, ca_path: Optional[str] = None, cert_path: Optional[str] = None, key_path: Optional[str] = None, *, timeout_ms: int = 5000) -> JsonSchemaRegistrySerde
```

### `register`

```python
register(self) -> Ok[int] | Err[SerdeError]
```

### `subject`

```python
subject(self) -> str
```

### `serialize`

```python
serialize(self, obj: Any) -> Ok[bytes] | Err[SerdeError]
```

### `deserialize`

```python
deserialize(self, data: bytes) -> Ok[Any] | Err[SerdeError]
```

### `serialize_many`

```python
serialize_many(self, items: Iterable[Any]) -> IList
```

### `serialize_many_bytes`

```python
serialize_many_bytes(self, items: Iterable[Any]) -> BytesList
```

### `deserialize_many`

```python
deserialize_many(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_objects`

```python
deserialize_many_objects(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_from_packed`

```python
deserialize_many_from_packed(self, packed: bytes, count: int) -> IList
```

### `check_compatibility`

```python
check_compatibility(self, schema: str, version: Optional[str] = None) -> Ok[bool] | Err[SerdeError]
```

### `list_versions`

```python
list_versions(self) -> Ok[list[int]] | Err[SerdeError]
```

### `get_schema_by_version`

```python
get_schema_by_version(self, version: int) -> Ok[str] | Err[SerdeError]
```

### `delete_version`

```python
delete_version(self, version: int) -> Ok[bool] | Err[SerdeError]
```

---

## `JsonSerde`

```python
JsonSerde()
```

### `serialize`

```python
serialize(self, obj: Any) -> Ok[bytes] | Err[SerdeError]
```

### `deserialize`

```python
deserialize(self, data: bytes) -> Ok[Any] | Err[SerdeError]
```

### `serialize_many`

```python
serialize_many(self, items: Iterable[Any]) -> IList
```

### `serialize_many_raw`

```python
serialize_many_raw(self, items: Iterable[Any]) -> BytesList
```

### `deserialize_many`

```python
deserialize_many(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_raw`

```python
deserialize_many_raw(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

---

## `NonEmptyList`

A list with at least one element.

`head`, `last`, and `reduce` are total - they return `T`, not `Option[T]`,
because the non-empty invariant is enforced at construction time.
`from_list` is the safe boundary: it returns `Option[NonEmptyList[T]]`.

```python
NonEmptyList(head: T, *tail: T)
```

### `of`

```python
of(head: T, *tail: T) -> NonEmptyList[T]
```

### `from_list`

```python
from_list(items: IList[T] | list[T]) -> Option[NonEmptyList[T]]
```

### `head`

```python
head(self) -> T
```

### `tail`

```python
tail(self) -> IList[T]
```

### `last`

```python
last(self) -> T
```

### `map`

```python
map(self, f: Callable[[T], Any]) -> NonEmptyList[Any]
```

### `flat_map`

```python
flat_map(self, f: Callable[[T], NonEmptyList[Any]]) -> NonEmptyList[Any]
```

### `reduce`

```python
reduce(self, f: Callable[[T, T], T]) -> T
```

### `fold_left`

```python
fold_left(self, init: Any, f: Callable[[Any, T], Any]) -> Any
```

### `append`

```python
append(self, item: T) -> NonEmptyList[T]
```

### `prepend`

```python
prepend(self, item: T) -> NonEmptyList[T]
```

### `to_ilist`

```python
to_ilist(self) -> IList[T]
```

### `to_list`

```python
to_list(self) -> list[T]
```

### `length`

```python
length(self) -> int
```

### `index`

```python
index(self, value: Any, start: int = 0, stop: int = ...) -> int
```

### `count`

```python
count(self, value: Any) -> int
```

---

## `Ok`

```python
Ok(value: T)
```

**Fields:**

- `value: T`

### `map`

```python
map(self, f: Callable[..., Any]) -> Ok[Any]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Any
```

### `map_err`

```python
map_err(self, f: Callable[..., Any]) -> Ok[T]
```

### `ensure`

```python
ensure(self, predicate: Callable[[T], bool], error: Callable[[T], F]) -> Either[T, F]
ensure(self, predicate: Callable[[T], bool], error: F) -> Either[T, F]
```

### `tap`

```python
tap(self, fn: Callable[[T], Any]) -> Ok[T]
```

### `tap_err`

```python
tap_err(self, fn: Callable[[Any], Any]) -> Ok[T]
```

### `flatten`

```python
flatten(self: Ok[_EitherResult]) -> _EitherResult
```

### `bimap`

```python
bimap(self, err_fn: Callable[[Any], F], ok_fn: Callable[[T], U]) -> Ok[U]
```

### `recover`

```python
recover(self, f: Callable[[Any], Any]) -> Ok[T]
```

### `recover_with`

```python
recover_with(self, f: Callable[[Any], Either[Any, Any]]) -> Ok[T]
```

### `or_else`

```python
or_else(self, alt: Either[Any, Any] | Callable[[], Either[Any, Any]]) -> Ok[T]
```

### `fold`

```python
fold(self, err: Callable[..., Any], ok: Callable[..., Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> T
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> T
```

### `unwrap_or_raise`

```python
unwrap_or_raise(self, exc: Any = None) -> T
```

### `to_option`

```python
to_option(self) -> Some[T]
```

### `is_ok`

```python
is_ok(self) -> bool
```

### `is_err`

```python
is_err(self) -> bool
```

---

## `Option`

`Some(value)` or `Nothing`; subclassed by both like Scala's `Option`.

### `of`

```python
of(value: Optional[T]) -> Option[T]
```

### `when`

```python
when(condition: bool, value: T) -> Option[T]
```

### `from_callable`

```python
from_callable(f: Callable[[], T]) -> Option[T]
```

### `sequence`

```python
sequence(values: Iterable[Option[T]]) -> Option[list[T]]
```

### `traverse`

```python
traverse(values: Iterable[_A], fn: Callable[[_A], Option[U]]) -> Option[list[U]]
```

### `map`

```python
map(self, f: Callable[..., Any]) -> Option[Any]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Option[Any]
```

### `tap`

```python
tap(self, fn: Callable[[T], Any]) -> Option[T]
```

### `flatten`

```python
flatten(self: Option[Some[U]]) -> Option[U]
flatten(self: Option[_Nothing]) -> _Nothing
flatten(self: Option[Option[U]]) -> Option[U]
```

### `filter`

```python
filter(self, pred: Callable[..., bool]) -> Option[T]
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> Any
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> T | U
```

### `or_else`

```python
or_else(self, alt: Option[Any] | Callable[[], Option[Any]]) -> Option[Any]
```

### `fold`

```python
fold(self, nothing_val: Any, some_fn: Callable[..., Any]) -> Any
```

### `to_either`

```python
to_either(self, left: Any) -> Either[T, Any]
```

### `to_either_with`

```python
to_either_with(self, error_factory: Callable[[], E]) -> Either[T, E]
```

### `to_list`

```python
to_list(self) -> IList[T]
```

### `is_some`

```python
is_some(self) -> bool
```

### `is_nothing`

```python
is_nothing(self) -> bool
```

### `is_empty`

```python
is_empty(self) -> bool
```

---

## `ProtobufRegistrySerde`

Protobuf serde with Confluent Schema Registry integration.

Wire format: [0x00 magic][schema_id: 4 bytes BE][msg-index zigzag varint array][protobuf payload]

Accepts a serialized FileDescriptorSet and a fully-qualified message name.
Deserialization decodes to Python dicts using prost-reflect; no compiled
message types needed. Registration requires ``proto_source`` (the ``.proto``
source text) so the Confluent SR can parse and validate the schema.

LIMITATION: the Confluent message-index is always written as [0] (first
message) - only correct when message_name is the first top-level message;
multi-message/nested .proto files will not interoperate with Confluent
consumers that rely on the message index.

NOTE: proto3 implicit-presence scalar fields at their default are omitted
on deserialize, absent from the returned dict (standard protobuf semantics).
NOTE: enum fields accept the enum value name (str) or number (int) on
serialize; deserialize returns the number (int).

timeout_ms: the bound on one registry HTTP request, from connect to the last
byte of the response (default 5000, 1 to 300000; ValueError otherwise). A
failed registration is remembered for 5 seconds, so serializing many records
against a registry that is down makes one request, not one per record;
register() always asks again.

```python
ProtobufRegistrySerde(registry_url: str, subject: str, file_descriptor_set: bytes, message_name: str, proto_source: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, ca_path: Optional[str] = None, cert_path: Optional[str] = None, key_path: Optional[str] = None, *, timeout_ms: int = 5000)
```

### `for_topic`

```python
for_topic(registry_url: str, topic: str, file_descriptor_set: bytes, message_name: str, is_key: bool = False, proto_source: Optional[str] = None, username: Optional[str] = None, password: Optional[str] = None, ca_path: Optional[str] = None, cert_path: Optional[str] = None, key_path: Optional[str] = None, *, timeout_ms: int = 5000) -> ProtobufRegistrySerde
```

### `register`

```python
register(self) -> Ok[int] | Err[SerdeError]
```

### `subject`

```python
subject(self) -> str
```

### `serialize`

```python
serialize(self, obj: Any) -> Ok[bytes] | Err[SerdeError]
```

### `serialize_with_id`

```python
serialize_with_id(self, obj: Any, schema_id: int) -> Ok[bytes] | Err[SerdeError]
```

### `deserialize`

```python
deserialize(self, data: bytes) -> Ok[Any] | Err[SerdeError]
```

### `serialize_many`

```python
serialize_many(self, items: Iterable[Any]) -> IList
```

### `serialize_many_bytes`

```python
serialize_many_bytes(self, items: Iterable[Any]) -> BytesList
```

### `deserialize_many`

```python
deserialize_many(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_objects`

```python
deserialize_many_objects(self, bytes_list: Iterable[bytes] | BytesList) -> IList
```

### `deserialize_many_from_packed`

```python
deserialize_many_from_packed(self, packed: bytes, count: int) -> IList
```

### `check_compatibility`

```python
check_compatibility(self, proto_source: str, version: Optional[str] = None) -> Ok[bool] | Err[SerdeError]
```

### `list_versions`

```python
list_versions(self) -> Ok[list[int]] | Err[SerdeError]
```

### `get_schema_by_version`

```python
get_schema_by_version(self, version: int) -> Ok[str] | Err[SerdeError]
```

### `delete_version`

```python
delete_version(self, version: int) -> Ok[bool] | Err[SerdeError]
```

---

## `RecordedError`

A journaled exception that could not be rebuilt as its original class.

``type_path`` is the original ``module:qualname`` and ``message`` its text;
``str()`` is the message, and serializing it again writes the original pair.

```python
RecordedError(type_path: str, message: str)
```

**Fields:**

- `type_path: str`
- `message: str`

---

## `SerdeError`

---

## `SerializationError`

---

## `Some`

```python
Some(value: T)
```

**Fields:**

- `value: T`

### `map`

```python
map(self, f: Callable[..., Any]) -> Some[Any]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Any
```

### `tap`

```python
tap(self, fn: Callable[[T], Any]) -> Some[T]
```

### `flatten`

```python
flatten(self: Some[_OptionResult]) -> _OptionResult
```

### `filter`

```python
filter(self, pred: Callable[..., bool]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> T
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> T
```

### `or_else`

```python
or_else(self, alt: Option[Any] | Callable[[], Option[Any]]) -> Some[T]
```

### `fold`

```python
fold(self, nothing_val: Any, some_fn: Callable[..., Any]) -> Any
```

### `to_either`

```python
to_either(self, left: Any) -> Ok[T]
```

### `to_either_with`

```python
to_either_with(self, error_factory: Callable[[], E]) -> Ok[T]
```

### `to_list`

```python
to_list(self) -> IList[T]
```

### `is_some`

```python
is_some(self) -> bool
```

### `is_nothing`

```python
is_nothing(self) -> bool
```

### `is_empty`

```python
is_empty(self) -> bool
```

---

## `Success`

```python
Success(value: T)
```

**Fields:**

- `value: T`

### `map`

```python
map(self, f: Callable[..., Any]) -> Any
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Any
```

### `tap`

```python
tap(self, fn: Callable[[T], Any]) -> Success[T]
```

### `tap_failure`

```python
tap_failure(self, fn: Callable[[BaseException], Any]) -> Success[T]
```

### `flatten`

```python
flatten(self: Success[_TryResult]) -> _TryResult
```

### `recover`

```python
recover(self, f: Callable[[Any], Any]) -> Success[T]
```

### `recover_with`

```python
recover_with(self, f: Callable[[Any], Try[Any]]) -> Success[T]
```

### `or_else`

```python
or_else(self, alt: Try[Any] | Callable[[], Try[Any]]) -> Success[T]
```

### `fold`

```python
fold(self, failure: Callable[..., Any], success: Callable[..., Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> T
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> T
```

### `to_either`

```python
to_either(self) -> Ok[T]
```

### `is_success`

```python
is_success(self) -> bool
```

### `is_failure`

```python
is_failure(self) -> bool
```

---

## `Try`

`Success(value)` or `Failure(exc)`; subclassed by both like Scala's `Try`.

### `apply`

```python
apply(f: Callable[[], T]) -> Try[T]
```

### `of`

```python
of(f: Callable[[], T]) -> Try[T]
```

### `sequence`

```python
sequence(values: Iterable[Try[T]]) -> Try[list[T]]
```

### `traverse`

```python
traverse(values: Iterable[_A], fn: Callable[[_A], Try[U]]) -> Try[list[U]]
```

### `map`

```python
map(self, f: Callable[..., Any]) -> Try[Any]
```

### `flat_map`

```python
flat_map(self, f: Callable[..., Any]) -> Try[Any]
```

### `tap`

```python
tap(self, fn: Callable[[T], Any]) -> Try[T]
```

### `tap_failure`

```python
tap_failure(self, fn: Callable[[BaseException], Any]) -> Try[T]
```

### `flatten`

```python
flatten(self: Try[Success[U]]) -> Try[U]
flatten(self: Try[Failure]) -> Failure
flatten(self: Try[Try[U]]) -> Try[U]
```

### `recover`

```python
recover(self, f: Callable[[Any], Any]) -> Try[Any]
```

### `recover_with`

```python
recover_with(self, f: Callable[[Any], Try[Any]]) -> Try[Any]
```

### `or_else`

```python
or_else(self, alt: Try[Any] | Callable[[], Try[Any]]) -> Try[Any]
```

### `fold`

```python
fold(self, failure: Callable[..., Any], success: Callable[..., Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: Any) -> Any
```

### `get_or_else_with`

```python
get_or_else_with(self, factory: Callable[[], U]) -> T | U
```

### `to_either`

```python
to_either(self) -> Either[T, Any]
```

### `is_success`

```python
is_success(self) -> bool
```

### `is_failure`

```python
is_failure(self) -> bool
```

---

## `Valid`

```python
Valid(value: T)
```

**Fields:**

- `value: T`

### `sequence`

```python
sequence(values: Iterable[Validated[U, F]]) -> Validated[list[U], F]
```

### `traverse`

```python
traverse(values: Iterable[_A], fn: Callable[[_A], Validated[U, F]]) -> Validated[list[U], F]
```

---

## `Validated`

Like `Either`, but `zip` accumulates errors instead of short-circuiting.

### `valid`

```python
valid(value: T) -> Validated[T, E]
```

### `invalid`

```python
invalid(error: E) -> Validated[T, E]
```

### `from_either`

```python
from_either(either: Either[T, E]) -> Validated[T, E]
```

### `cond`

```python
cond(condition: bool, value: T, error: E) -> Validated[T, E]
```

### `sequence`

```python
sequence(values: Iterable[Validated[U, F]]) -> Validated[list[U], F]
```

### `traverse`

```python
traverse(values: Iterable[_A], fn: Callable[[_A], Validated[U, F]]) -> Validated[list[U], F]
```

### `map`

```python
map(self, f: Callable[[T], Any]) -> Validated[Any, E]
```

### `map_err`

```python
map_err(self, f: Callable[[tuple[E, ...]], Any]) -> Validated[T, Any]
```

### `zip`

```python
zip(self, other: Validated[Any, E]) -> Validated[Any, E]
```

### `fold`

```python
fold(self, invalid: Callable[[tuple[E, ...]], Any], valid: Callable[[T], Any]) -> Any
```

### `get_or_else`

```python
get_or_else(self, default: T) -> T
```

### `to_either`

```python
to_either(self) -> Either[T, tuple[E, ...]]
```

### `is_valid`

```python
is_valid(self) -> bool
```

### `is_invalid`

```python
is_invalid(self) -> bool
```

## Constants

- `Nothing: _Nothing = _nothing()`

## Functions

### `case`

```python
case(cls: type[T]) -> type[T]
```

Make `cls` a frozen dataclass. Use `dataclasses.replace` and `dataclasses.asdict`.

### `do`

```python
do(func: Callable[..., Any]) -> Callable[..., Any]
```

### `do_async`

```python
do_async(func: Callable[..., Any]) -> Callable[..., Any]
```

### `flow`

```python
flow(*funcs: Callable[..., Any]) -> Callable[..., Any]
```

Compose `funcs` left to right into a single callable.

### `match`

```python
match(value: Any) -> Callable[..., Any]
```

`match(value)(on(T1, h1), on(T2, h2), ...)`: dispatch to the first matching
handler. For a `@sealed` hierarchy, raises if any concrete case is uncovered.

### `newtype`

```python
newtype(name: str, base_type: type[T]) -> type[T]
```

A lightweight subclass of `base_type` named `name`, for a distinct domain type.

### `newtype_strict`

```python
newtype_strict(name: str, base_type: type[T], *, validate: Optional[Callable[..., bool]] = None, err_msg: Optional[str] = None) -> type[T]
```

Like `newtype`, but raises on construction if `validate` rejects the value.

### `on`

```python
on(typ: type[T], handler: Callable[..., Any]) -> Any
```

Build a `match` arm: dispatch to `handler` when the value is an instance of `typ`.

### `partial`

```python
partial(f: Callable[..., Any], *args: Any, **kwargs: Any) -> Callable[..., Any]
```

### `pipe`

```python
pipe(value: Any, *funcs: Callable[..., Any]) -> Any
```

Thread `value` through `funcs` left to right, each result feeding the next.

### `sealed`

```python
sealed(cls: type[T]) -> type[T]
```

Mark `cls` as a closed hierarchy: `match` enforces exhaustiveness over its cases.

Subclasses may be declared only in the module that defines `cls`; any other module
gets a `TypeError` at class creation.
