Skip to main content

zopy

Zod for Python — the Zod API, typed for Pylance / pyright, validated by a Rust engine.

from zopy import z


class User(z.Object):
    name = z.string().min(1)
    email = z.email()
    age = z.int().nonnegative().optional()


user = User.parse({"name": "Ada", "email": "ada@example.com"})  # => User
user.name  # => "Ada"  (str)
user.age  # => None   (int | None)

Schemas, checks and helpers keep Zod's names and behaviour (z.string().min(1), safeParse, refine, transform, pipe, codec, discriminatedUnion, prettifyError, toJSONSchema, …), so the Zod documentation is the API reference. This README covers what is specific to zopy: how Zod maps onto Python.

Contents

Installation

zopy requires Python 3.11+:

uv add zopy
# or
pip install zopy

Everything is reached through z:

from zopy import z  # or: import zopy as z

Quick start

class Player(z.Object):
    username = z.string()
    xp = z.number()


player = Player.parse({"username": "billie", "xp": 100})  # => Player
player.username  # => "billie"

result = Player.safeParse({"username": 42, "xp": "100"})
if not result.success:
    print(z.prettifyError(result.error))
    # ✖ Invalid input: expected string, received number
    #   → at username
    # ✖ Invalid input: expected number, received string
    #   → at xp

.parse() returns the parsed value or raises z.ZodError; .safeParse() returns SafeParseSuccess[Player] | SafeParseError, and checking result.success narrows it.

Differences from Zod

Zod (TypeScript) zopy (Python)
z.object({ name: z.string() }) class X(z.Object): name = z.string()
z.strictObject({...}) / z.looseObject({...}) class X(z.StrictObject) / class X(z.LooseObject)
A.extend({...}) / A.merge(B) a subclass: class C(A): ...
A.pick / omit / partial / required declare the shape you need as its own class
z.intersection(A, B) / A.and(B) multiple inheritance: class C(A, B): ...
z.infer<typeof X> the class X, or the return type of parse()
z.enum(["a", "b"]) z.enum(("a", "b")) — a tuple
z.tuple([a, b]) z.tuple((a, b)) — a tuple
undefined None (and a missing key)
.or(b) .or_(b) or a | b
.brand<"UserId">() .brand(UserId) with UserId = NewType("UserId", str)

Only APIs whose output type pyright can infer are provided. That is why object schemas are classes, and why the shape-deriving helpers (pick, omit, partial, extend, merge, intersections) and list-form z.enum([...]) / z.tuple([...]) are not: their results could only be typed as Any. Most camelCase names also have a snake_case alias (safe_parse, to_json_schema, …).

The message, required_error, invalid_type_error and errorMap params, .strict() / .passthrough(), z.string().email() and the other format methods, z.nativeEnum, error.format() / .flatten() … are not provided.

Schemas

The primitive schemas and their checks. Objects are covered in Objects are classes.

Primitives

z.string()  # str
z.number()  # float (ints are accepted; bool is not)
z.int()  # int
z.bigint()  # int (no safe-integer bound)
z.boolean()  # bool
z.date()  # datetime / date
z.symbol()

# None-like
z.null()  # None
z.undefined()  # None
z.void()  # None

# catch-all
z.any()
z.unknown()

# accepts nothing
z.never()
z.nan()  # float("nan")

Coercion

z.coerce converts the input before validating it:

z.coerce.string().parse(42)  # => "42"
z.coerce.number().parse("4.5e2")  # => 450.0
z.coerce.boolean().parse("false")  # => True   (truthiness, like JavaScript's Boolean())
z.coerce.bigint().parse("99")  # => 99
z.coerce.date().parse("2024-01-01T00:00:00Z")  # => datetime(2024, 1, 1, tzinfo=utc)
z.coerce.date().parse(1_704_067_200_000)  # => datetime(2024, 1, 1, tzinfo=utc)  (epoch ms)

Literals

tuna = z.literal("tuna")  # Literal["tuna"]
twelve = z.literal(12)
yes = z.literal(True)

colors = z.literal(["red", "green", "blue"])  # any of several literals
colors.parse("green")  # => "green"

Strings

z.string().max(5)
z.string().min(5)
z.string().length(5)
z.string().nonempty()
z.string().regex(r"^[a-z]+$")
z.string().startsWith("aaa")
z.string().endsWith("zzz")
z.string().includes("---")
z.string().uppercase()
z.string().lowercase()

String transforms run as part of parsing:

z.string().trim().parse("  hello  ")  # => "hello"
z.string().toLowerCase().parse("HeLLo")  # => "hello"
z.string().toUpperCase().parse("hello")  # => "HELLO"
z.string().normalize().parse("hello")  # NFC by default; .normalize("NFKC") etc.

String formats

Formats are top-level functions, as in Zod.

z.email()
z.uuid()
z.uuidv4()
z.uuidv7()
z.url()
z.httpUrl()  # http / https only
z.hostname()
z.emoji()
z.base64()
z.base64url()
z.hex()
z.jwt()
z.nanoid()
z.cuid()
z.cuid2()
z.ulid()
z.ipv4()
z.ipv6()
z.mac()
z.cidrv4()
z.cidrv6()
z.hash("sha256")  # or "sha1", "sha384", "sha512", "md5"
z.e164()  # phone numbers
z.creditCard()  # Luhn check
z.iban()
z.currencyCode()  # ISO 4217
z.iso.date()
z.iso.time()
z.iso.datetime()
z.iso.duration()

Formats take options:

z.email({"pattern": z.regexes.html5Email})  # the regex browsers use
z.url({"protocol": r"^https$", "hostname": r"^example\.com$"})
z.iso.datetime({"offset": True})  # allow +09:00 offsets (default: Z only)
z.iso.datetime({"precision": 3})  # exactly millisecond precision
z.mac({"delimiter": "-"})
z.jwt({"alg": "HS256"})
z.hash("sha256", {"enc": "base64url"})

Define your own format with a regex or a predicate:

ticket = z.stringFormat("ticket", r"^[A-Z]+-\d+$")
cool = z.stringFormat("cool-id", lambda value: value.startswith("cool-"))

ticket.parse("ABC-123")  # => "ABC-123"
cool.safeParse("nope").error.issues[0]["message"]  # => "Invalid cool-id"

Template literals

css = z.templateLiteral([z.number(), z.enum(("px", "em", "rem"))])
css.parse("1.5rem")  # => "1.5rem"
css.safeParse("1.5pt").success  # => False

Numbers

z.number() accepts any finite int or float (not bool, not NaN / infinity).

z.number().gt(5)
z.number().gte(5)  # alias: .min(5)
z.number().lt(5)
z.number().lte(5)  # alias: .max(5)
z.number().positive()
z.number().nonnegative()
z.number().negative()
z.number().nonpositive()
z.number().multipleOf(5)  # alias: .step(5)

Integers

z.int()  # a safe integer; 5.0 is accepted and returned as 5
z.int32()
z.uint32()

z.int().parse(5.0)  # => 5
z.int().safeParse(5.5).success  # => False

BigInts

Python has one int type; z.bigint() is an int without the safe-integer bounds of z.int():

z.bigint().parse(2**80)  # => 1208925819614629174706176
z.int64()
z.uint64()

Booleans

z.boolean().parse(True)  # => True
z.boolean().safeParse(1).success  # => False

Stringbool

z.stringbool() parses environment-variable-style strings into bool:

flag = z.stringbool()
flag.parse("yes")  # => True
flag.parse("OFF")  # => False   (case-insensitive by default)

z.stringbool({"truthy": ["はい"], "falsy": ["いいえ"]}).parse("はい")  # => True

Dates

z.date() accepts datetime and date objects:

from datetime import datetime, timezone

z.date().min(datetime(1900, 1, 1, tzinfo=timezone.utc))
z.date().max(datetime(2100, 1, 1, tzinfo=timezone.utc))

For ISO 8601 strings, use z.iso.date(), z.iso.datetime() … (see String formats).

Enums

Pass a tuple of strings — a tuple (unlike a list) keeps the literal types:

Fish = z.enum(("Salmon", "Tuna", "Trout"))
Fish.parse("Salmon")  # => "Salmon"  (Literal["Salmon", "Tuna", "Trout"])
Fish.safeParse("Swordfish").success  # => False

Fish.enum.Salmon  # => "Salmon"
Fish.options  # => ["Salmon", "Tuna", "Trout"]

Fish.extract(("Salmon", "Trout")).options  # => ["Salmon", "Trout"]
Fish.exclude(("Salmon",)).options  # => ["Tuna", "Trout"]

Python Enum classes work too; members and their values are accepted and a member is returned:

from enum import Enum


class Color(Enum):
    RED = "red"
    GREEN = "green"


z.enum(Color).parse("red")  # => Color.RED
z.enum(Color).parse(Color.GREEN)  # => Color.GREEN

Optionals and nullables

z.string().optional()  # str | None — the key may be missing (or None)
z.string().nullable()  # str | None — None is allowed, but the key must be present
z.string().nullish()  # both
z.string().exactOptional()  # the key may be missing, but an explicit None is rejected

z.string().optional().unwrap()  # => the inner z.string()
z.string().optional().nonoptional()  # rejects None again

Objects are classes

An object schema is a class deriving from z.Object. The class is the schema, and its instances are the parsed values, so the class is also the type:

class Dog(z.Object):
    name = z.string()
    age = z.int().nonnegative()
    tags = z.array(z.string()).default(list)
    nickname = z.string().optional()


dog = Dog.parse({"name": "Rex", "age": 3})  # => Dog(name='Rex', age=3, tags=[])

Fields are assigned, not annotated: name: z.string() raises TypeError when the class is defined. Attributes starting with _ and ClassVars are ordinary class attributes.

Instances

An instance is an attribute object that also reads like a mapping:

dog.name  # => "Rex"
dog["name"]  # => "Rex"
dog.get("nickname", "-")  # => "-"
dog.to_dict()  # => {"name": "Rex", "age": 3, "tags": []}
dog == {"name": "Rex", "age": 3, "tags": []}  # => True

The constructor validates too:

Dog(name="Fido", age=1)  # => Dog(name='Fido', age=1, tags=[])
try:
    Dog(name="Fido", age=-1)
except z.ZodError:
    pass  # age: Too small

Nesting

A class is used as a field like any other schema, and z.array(Dog), z.union([Cat, Dog]), Dog.optional() … keep the class as the type:

class Owner(z.Object):
    name = z.string()
    dog = Dog
    pets = z.array(Dog).default(list)


owner = Owner.parse({"name": "Ada", "dog": {"name": "Rex", "age": 3}})
owner.dog.name  # => "Rex"   (owner.dog is a Dog)

Refer to a class before it exists (recursion) with z.lazy:

class Category(z.Object):
    name = z.string()
    subcategories = z.lazy(lambda: z.array(Category)).default(list)


Category.parse({"name": "root", "subcategories": [{"name": "leaf"}]}).subcategories[0].name
# => "leaf"

Unknown keys

Unknown keys are stripped by default. Choose another policy with the base class or a class keyword:

class Strict(z.StrictObject):  # unknown keys are an error
    a = z.string()


class Loose(z.LooseObject):  # unknown keys are kept
    a = z.string()


class Extras(z.Object, catchall=z.number()):  # unknown keys must be numbers
    a = z.string()


Strict.safeParse({"a": "x", "b": 1}).success  # => False
Loose.parse({"a": "x", "b": 1}).to_dict()  # => {"a": "x", "b": 1}

class X(z.Object, strict=True) / loose=True are the same. For a strict or loose variant of an existing class, inherit from it: class StrictDog(Dog, z.StrictObject): ....

Deriving shapes

Where Zod derives a shape (extend, pick, omit, partial), declare it as a class and share fields through inheritance, so every shape stays fully typed:

class Animal(z.Object):  # the smaller shape is the base
    name = z.string()


class Cat(Animal):  # Animal.extend({ lives: ... })
    lives = z.int().min(0).max(9)


class CatPatch(z.Object):  # Cat.partial()
    name = z.string().optional()
    lives = z.int().min(0).max(9).optional()


isinstance(Cat.parse({"name": "Tom", "lives": 9}), Animal)  # => True

Combining objects

The intersection of object schemas is multiple inheritance:

class Person(z.Object):
    name = z.string()


class Employee(z.Object):
    role = z.string()


class EmployedPerson(Person, Employee):  # z.intersection(Person, Employee)
    pass

If two bases declare the same field differently, defining the subclass raises TypeError until the subclass redeclares the field — no base's checks are silently dropped.

Class-level helpers

Methods on the class keep the class as the type, and callbacks receive instances:

class SignupForm(z.Object):
    password = z.string().min(8)
    confirm = z.string()


Signup = SignupForm.refine(
    lambda form: form.password == form.confirm,  # form: SignupForm
    {"error": "Passwords don't match", "path": ["confirm"]},
)

Dog.default({"name": "Rex", "age": 3})  # a dict default is parsed into a Dog
Dog.shape  # => {"name": ZodString(), "age": ZodInt(), "tags": ZodDefault(), "nickname": ZodOptional()}
Dog.keyof().options  # => ["name", "age", "tags", "nickname"]

Type inference

There is no z.infer (Python has no typeof); the type lives on parse(), and for objects on the class. Hover a value in VS Code to see it:

z.string().trim().parse("  Ada  ")  # => "Ada"           str
z.int().optional().parse(None)  # => None            int | None
z.enum(("admin", "user")).parse("admin")  # => "admin"         Literal["admin", "user"]
z.tuple((z.string(), Dog)).parse(["x", {"name": "Rex", "age": 3}])  # tuple[str, Dog]
z.union([z.string(), z.int()]).parse(1)  # => 1               int | str
z.record(z.string(), Dog).parse({})  # => {}              dict[str, Dog]
z.string().transform(len).parse("hello")  # => 5               int

Lambdas get their parameter types from the schema (transform(lambda s: ...) knows s: str), and the code that uses parsed values is type-checked as usual.

Pass tuples to z.enum and z.tuple: a tuple keeps each literal / position type, a list would not (and is rejected).

Python types

Zod zopy schema Python value
string z.string() str
number z.number() float (an int is accepted; bool is not)
int z.int() int (a safe integer; 5.0 is returned as 5)
bigint z.bigint() int (no safe-integer bound)
boolean z.boolean() bool
Date z.date() datetime / date
null / undefined z.null() / z.undefined() None
T[] z.array(t) list (a tuple input is accepted)
[A, B] z.tuple((a, b)) tuple
Record<K, V> z.record(k, v) dict
Map<K, V> z.map(k, v) dict (keys of any type)
Set<T> z.set(t) set
File z.file() bytes
TS enum z.enum(MyEnum) the Enum member (values are accepted as input)
branded type .brand(MyNewType) the typing.NewType
from enum import Enum
from typing import NewType


class Color(Enum):
    RED = "red"


UserId = NewType("UserId", str)

z.enum(Color).parse("red")  # => Color.RED
z.uuid().brand(UserId).parse("123e4567-e89b-12d3-a456-426614174000")  # UserId

Missing keys and None

None plays the part of undefined. An optional field that was not given stays absent from the instance, but reading it as an attribute gives None:

dog = Dog.parse({"name": "Rex", "age": 3})
"nickname" in dog  # => False
dog.nickname  # => None
dog.to_dict()  # => {"name": "Rex", "age": 3, "tags": []}

dog = Dog.parse({"name": "Rex", "age": 3, "nickname": None})
"nickname" in dog  # => True

.exactOptional() accepts a missing key but rejects an explicit None.

Readonly

.readonly() freezes the parsed value shallowly (like Zod's Object.freeze), and the output type is the frozen type:

schema value type
z.array(t).readonly() tuple tuple[T, ...]
z.record(k, v).readonly() / z.map(k, v).readonly() mappingproxy Mapping[K, V]
z.set(t).readonly() frozenset frozenset[T]
z.tuple(...).readonly() tuple the same tuple[...]
MyObject.readonly() a frozen instance MyObject
tags = z.array(z.string()).readonly().parse(["a"])  # => ("a",)   tuple[str, ...]
# tags.append("b") is a type error, as it would be in TypeScript

frozen = Dog.readonly().parse({"name": "Rex", "age": 3})
try:
    frozen.name = "Max"
except AttributeError:
    pass  # Dog is readonly: cannot change 'name'

A frozen instance rejects attribute assignment and deletion at runtime. Python's type system has no Readonly<T> for classes, so pyright does not flag frozen.name = ... statically.

Brands

Zod's .brand<"UserId">() makes a nominal type: structurally a string, but not assignable from a plain string or from another brand. Python's equivalent is typing.NewType, so .brand() takes one:

from typing import NewType

UserId = NewType("UserId", str)
OrderId = NewType("OrderId", str)

UserIdSchema: z.ZodType[UserId] = z.uuid().brand(UserId)

uid = UserIdSchema.parse("123e4567-e89b-12d3-a456-426614174000")  # UserId
z.array(UserIdSchema).parse([])  # list[UserId]


def load_user(user_id: UserId) -> None: ...


load_user(uid)  # OK
load_user("123e4567-e89b-12d3-a456-426614174000")  # type error: a plain str is not a UserId
load_user(OrderId("x"))  # type error: nor is another brand

Annotate the schema variable (z.ZodType[UserId]): type checkers cannot infer the branded type from a NewType argument, so the annotation is what gives .parse() its output type. Without it the schema is a ZodType[Any].

As in Zod, a brand is static only. The parsed value is unchanged (type(uid) is str), and validation, errors and toJSONSchema are those of the schema being branded. Brand after the checks, which keep running as before:

Age = NewType("Age", int)
AgeSchema: z.ZodType[Age] = z.int().nonnegative().brand(Age)

The NewType must fit what the schema outputs. An obvious mismatch is caught when the schema is built:

try:
    z.int().brand(UserId)
except TypeError:
    pass  # brand(UserId): UserId is a NewType of str, but this number schema outputs int | float

.brand("UserId") with a string label is also accepted, for code ported from Zod. It is a no-op for typing: the schema keeps its own type (z.string().brand("UserId") parses to str). Anything other than a label or a NewType (a class such as str, for instance) raises TypeError.

Refinements

.refine(check, options) adds a custom check: return a truthy value to accept the input. The options are Zod's:

option
"error" the message, or a function of the issue (see Customizing errors)
"path" where the issue points, e.g. ["confirm"]
"abort" True: stop at this check when it fails (by default later checks still run)
"when" when(payload) decides whether the check runs (see below)
Password = (
    z.string()
    .min(8)
    .refine(
        lambda s: any(c.isdigit() for c in s),
        {"error": "Must contain a digit"},
    )
)
Password.safeParse("password").error.issues[0]["message"]  # => "Must contain a digit"

Even = z.int().refine(lambda n: n % 2 == 0, {"error": lambda issue: f"{issue['input']} is odd"})
Even.safeParse(3).error.issues[0]["message"]  # => "3 is odd"

On a class, the check receives the instance:

class PasswordForm(z.Object):
    password = z.string().min(8)
    confirm = z.string()


Passwords = PasswordForm.refine(
    lambda form: form.password == form.confirm,  # form: PasswordForm
    {"error": "Passwords don't match", "path": ["confirm"]},
)
Passwords.safeParse({"password": "secret123", "confirm": "nope"}).error.issues[0]["path"]
# => ["confirm"]

All failing refinements are reported; "abort": True stops at the first:

Both = z.string().refine(lambda s: False, {"error": "a"}).refine(lambda s: False, {"error": "b"})
[issue["message"] for issue in Both.safeParse("x").error.issues]  # => ["a", "b"]

First = (
    z.string().refine(lambda s: False, {"error": "a", "abort": True}).refine(lambda s: False, {"error": "b"})
)
[issue["message"] for issue in First.safeParse("x").error.issues]  # => ["a"]

when

A refinement does not run once the value already has an aborting issue — for an object, for example, a missing field. "when" replaces that rule: it receives the payload (payload.value, payload.issues) and returns whether to run. The object has not been built into an instance at that point, so read the fields with ["..."], which works on both:

class SignupForm(z.Object):
    password = z.string().min(8)
    confirm = z.string()
    nickname = z.string()


def passwords_given(payload):
    value = payload.value
    return isinstance(value.get("password"), str) and isinstance(value.get("confirm"), str)


Signup = SignupForm.refine(
    lambda form: form["password"] == form["confirm"],
    {"error": "Passwords don't match", "path": ["confirm"], "when": passwords_given},
)
result = Signup.safeParse({"password": "secret123", "confirm": "nope"})  # nickname is missing
[issue["path"] for issue in result.error.issues]  # => [["nickname"], ["confirm"]]

superRefine

.superRefine(fn, options) adds any number of issues through ctx.addIssue. An issue is a dict with a code (usually "custom"), its message, and optionally a path relative to the value:

def unique(items, ctx):
    if len(items) != len(set(items)):
        ctx.addIssue({"code": "custom", "message": "No duplicates allowed"})
    if len(items) > 3:
        ctx.addIssue(
            {
                "code": "too_big",
                "maximum": 3,
                "origin": "array",
                "inclusive": True,
                "message": "Too many items",
            }
        )


Tags = z.array(z.string()).superRefine(unique)
[issue["message"] for issue in Tags.safeParse(["a", "a", "b", "c"]).error.issues]
# => ["No duplicates allowed", "Too many items"]

An issue with "continue": False aborts the remaining checks. superRefine takes the same "error", "abort" and "when" options as refine.

check

.check(fn) is the low-level form: fn(ctx) reads ctx.value and pushes issues onto ctx.issues. Built-in checks such as z.gt(0) can be passed too:

def positive(ctx):
    if ctx.value <= 0:
        ctx.issues.push({"code": "custom", "message": "Must be positive", "input": ctx.value})


z.int().check(positive).safeParse(-1).error.issues[0]["message"]  # => "Must be positive"
z.number().check(z.gt(0), z.lt(10)).safeParse(42).success  # => False

Async checks (coroutine functions) need parseAsync / safeParseAsync — see Async.

Errors

A failed parse() raises z.ZodError; its issues are dicts that also allow attribute access. Messages are built only when the issues are read, so a failure you don't inspect is cheap:

try:
    Player.parse({"username": 1, "xp": 0})
except z.ZodError as error:
    issue = error.issues[0]
    issue["path"]  # => ["username"]
    issue.message  # => "Invalid input: expected string, received number"

Issues leave out the input unless you pass {"reportInput": True} to the parse.

Customizing errors

Every schema and every check takes {"error": ...}:

z.string({"error": "Not a string!"}).safeParse(1).error.issues[0]["message"]  # => "Not a string!"
z.string().min(5, {"error": "Too short!"}).safeParse("ab").error.issues[0]["message"]  # => "Too short!"

Set them per field to build form-friendly messages:

class SignupFields(z.Object):
    email = z.email({"error": "Enter a valid email address"})
    age = z.int({"error": "Age must be a whole number"}).min(18, {"error": "You must be 18 or older"})


result = SignupFields.safeParse({"email": "nope", "age": 16})
[(issue["path"], issue["message"]) for issue in result.error.issues]
# => [(["email"], "Enter a valid email address"), (["age"], "You must be 18 or older")]

Error functions

error can be a function of the issue. It receives the issue dict — code, input, path and the code-specific fields such as minimum or expected — and returns the message:

MinFive = z.string().min(
    5,
    {"error": lambda issue: f"Need {issue['minimum']} characters, got {len(issue['input'])}"},
)
MinFive.safeParse("ab").error.issues[0]["message"]  # => "Need 5 characters, got 2"

Tell a missing value from a wrong type with issue["input"]:

RequiredString = z.string({"error": lambda issue: "Required" if issue["input"] is None else "Not a string"})
RequiredString.safeParse(None).error.issues[0]["message"]  # => "Required"
RequiredString.safeParse(1).error.issues[0]["message"]  # => "Not a string"

Return None to fall back to the next error in the precedence order (for an error function on a schema, that is the default message):

OnlyTooSmall = z.int({"error": lambda issue: "Too small!" if issue["code"] == "too_small" else None})
OnlyTooSmall.min(3).safeParse(1).error.issues[0]["message"]  # => "Too small!"

Per parse

Pass {"error": ...} to a single parse / safeParse call:

class Counter(z.Object):
    n = z.int()


Counter.safeParse({"n": "x"}, {"error": lambda issue: f"bad {issue['code']}"}).error.issues[0]["message"]
# => "bad invalid_type"

Globally

z.config({"customError": ...}) sets an error function for every schema — for example to translate messages. Return None for the issues you leave to the default messages:

def japanese(issue):
    if issue["code"] == "invalid_type":
        return f"{issue['expected']} を入力してください"
    return None  # other codes: the default message


z.config({"customError": japanese})
z.int().safeParse("x").error.issues[0]["message"]  # => "number を入力してください"
z.int().min(3).safeParse(1).error.issues[0]["message"]  # => "Too small: expected number to be >=3"
z.config({"customError": None})  # back to the default messages

Precedence

The first error that gives a message wins:

  1. the error of the check (.min(5, {"error": ...}))
  2. the error of the schema (z.string({"error": ...}))
  3. the per-parse error (.safeParse(data, {"error": ...}))
  4. z.config({"customError": ...})
  5. z.config({"localeError": ...}) — the default English messages
SchemaError = z.int({"error": "schema wins"})
SchemaError.safeParse("x", {"error": lambda issue: "per-parse"}).error.issues[0]["message"]
# => "schema wins"

Async

Async refinements and transforms take coroutine functions and need the async methods (parseAsync, safeParseAsync), which work with asyncio:

import asyncio


async def available(name: str) -> bool:
    await asyncio.sleep(0)  # e.g. a database lookup
    return name != "root"


Username = z.string().min(3).refine(available, {"error": "Username is taken"})
asyncio.run(Username.parseAsync("ada"))  # => "ada"

.parse() on a schema with async parts raises z.ZodAsyncError.

Engines

A schema is compiled on first use and validated by the Rust engine (zopy._core, built with PyO3). Parts it does not run itself — Python callbacks such as refine / transform, async schemas — are handed to the pure-Python engine, with identical results.

To run everything on the pure-Python engine (for debugging, or without the extension), set ZOD_ENGINE=python, or call zopy._compile.set_engine("python") at runtime.

Development

The repository is a uv + Cargo workspace:

.
├── packages/zopy/     # the library (maturin: Rust in src/, Python in python/zopy/, tests/)
└── apps/examples/      # runnable demos (`just examples`)

Requires uv and a Rust toolchain.

uv sync         # install everything (builds the Rust extension; also installs `just`)
just            # list the tasks
just check      # lint + typecheck + test (what CI runs)
just fmt        # format Python and Rust
just build      # rebuild the extension after changing Rust code
just examples   # run the demos

The code blocks in this README are executed by the test suite, so the examples stay accurate.

License

MIT

zopy

Metadata

Release files for zopy 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for zopy 0.1.2
File Size Uploaded Provenance Actions
zopy-0.1.2.tar.gz 145.3 kB With provenance Details for zopy-0.1.2.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for zopy 0.1.2
File Actions
zopy-0.1.2-cp314-cp314-win_amd64.whl CPython 3.14 CPython 3.14 Windows x86-64 Details for zopy-0.1.2-cp314-cp314-win_amd64.whl
zopy-0.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.17+ x86-64 Details for zopy-0.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
zopy-0.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.14 CPython 3.14 Linux glibc 2.17+ ARM64 Details for zopy-0.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
zopy-0.1.2-cp314-cp314-macosx_11_0_arm64.whl CPython 3.14 CPython 3.14 macOS 11.0+ ARM64 Details for zopy-0.1.2-cp314-cp314-macosx_11_0_arm64.whl
zopy-0.1.2-cp314-cp314-macosx_10_12_x86_64.whl CPython 3.14 CPython 3.14 macOS 10.12+ x86-64 Details for zopy-0.1.2-cp314-cp314-macosx_10_12_x86_64.whl
zopy-0.1.2-cp313-cp313-win_amd64.whl CPython 3.13 CPython 3.13 Windows x86-64 Details for zopy-0.1.2-cp313-cp313-win_amd64.whl
zopy-0.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ x86-64 Details for zopy-0.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
zopy-0.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.13 CPython 3.13 Linux glibc 2.17+ ARM64 Details for zopy-0.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
zopy-0.1.2-cp313-cp313-macosx_11_0_arm64.whl CPython 3.13 CPython 3.13 macOS 11.0+ ARM64 Details for zopy-0.1.2-cp313-cp313-macosx_11_0_arm64.whl
zopy-0.1.2-cp313-cp313-macosx_10_12_x86_64.whl CPython 3.13 CPython 3.13 macOS 10.12+ x86-64 Details for zopy-0.1.2-cp313-cp313-macosx_10_12_x86_64.whl
zopy-0.1.2-cp312-cp312-win_amd64.whl CPython 3.12 CPython 3.12 Windows x86-64 Details for zopy-0.1.2-cp312-cp312-win_amd64.whl
zopy-0.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ x86-64 Details for zopy-0.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
zopy-0.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.12 CPython 3.12 Linux glibc 2.17+ ARM64 Details for zopy-0.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
zopy-0.1.2-cp312-cp312-macosx_11_0_arm64.whl CPython 3.12 CPython 3.12 macOS 11.0+ ARM64 Details for zopy-0.1.2-cp312-cp312-macosx_11_0_arm64.whl
zopy-0.1.2-cp312-cp312-macosx_10_12_x86_64.whl CPython 3.12 CPython 3.12 macOS 10.12+ x86-64 Details for zopy-0.1.2-cp312-cp312-macosx_10_12_x86_64.whl
zopy-0.1.2-cp311-cp311-win_amd64.whl CPython 3.11 CPython 3.11 Windows x86-64 Details for zopy-0.1.2-cp311-cp311-win_amd64.whl
zopy-0.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ x86-64 Details for zopy-0.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
zopy-0.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl CPython 3.11 CPython 3.11 Linux glibc 2.17+ ARM64 Details for zopy-0.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
zopy-0.1.2-cp311-cp311-macosx_11_0_arm64.whl CPython 3.11 CPython 3.11 macOS 11.0+ ARM64 Details for zopy-0.1.2-cp311-cp311-macosx_11_0_arm64.whl
zopy-0.1.2-cp311-cp311-macosx_10_12_x86_64.whl CPython 3.11 CPython 3.11 macOS 10.12+ x86-64 Details for zopy-0.1.2-cp311-cp311-macosx_10_12_x86_64.whl

Total release size: 26.4 MB

Release files / zopy-0.1.2.tar.gz

Download URL zopy-0.1.2.tar.gz
Size 145.3 kB
Tags Source
SHA-256 checksum
How to use checksums
837b7625ea77a9b268abdd287447c6a00c04ee7c0f593aa80d992c33a4aa1b08
BLAKE2b-256 checksum
How to use checksums
6dc53ee875874dd91f6aba761fc2c84ff31fe77329546dd24ce39b3f00f08e8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp314-cp314-win_amd64.whl

Download URL zopy-0.1.2-cp314-cp314-win_amd64.whl
Size 1.2 MB
Tags CPython 3.14 Windows x86-64
SHA-256 checksum
How to use checksums
4ddd84cc8b144a9e0c0fd74dde04bcad4ad81f30af1059f156388920883b73d9
BLAKE2b-256 checksum
How to use checksums
4ad6d6f4feae49d6c9a09bf1d2de6ef4199019cab74d64900ad1cbe2fa554881
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL zopy-0.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 1.4 MB
Tags CPython 3.14 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
bca289ca4840cb804cf1895503c599d2cb6c7987ce086101dccdaf69abac27f0
BLAKE2b-256 checksum
How to use checksums
f59165aaee3d7748751b29af4220e358a1427af832c59238c9259a4d0ffc5638
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL zopy-0.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 1.3 MB
Tags CPython 3.14 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
ce82b3902784fb0af438d83dfc2a149ebc937daea4cee62d6662c7e5d44de856
BLAKE2b-256 checksum
How to use checksums
ce05816023233848280325feab91f60725fb61b0ee202799afb22c44fecead07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp314-cp314-macosx_11_0_arm64.whl

Download URL zopy-0.1.2-cp314-cp314-macosx_11_0_arm64.whl
Size 1.3 MB
Tags CPython 3.14 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
f3fd7c6ff5e5e18e551f69220ffaf68a3d58b830798e44a46db7ece35fff4fc3
BLAKE2b-256 checksum
How to use checksums
c4056316ac339d1e924573141a70516380a45e8e3ab53e308e1f91b569853a72
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp314-cp314-macosx_10_12_x86_64.whl

Download URL zopy-0.1.2-cp314-cp314-macosx_10_12_x86_64.whl
Size 1.3 MB
Tags CPython 3.14 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
2e29622ca88c3535d37932c951224782e1ceaf8529498852dfc2e411bceb0623
BLAKE2b-256 checksum
How to use checksums
5ad9acb64ac5f400c958c99ae750958b8c343074a53b0cc8e4eb5890bfc26cde
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp313-cp313-win_amd64.whl

Download URL zopy-0.1.2-cp313-cp313-win_amd64.whl
Size 1.2 MB
Tags CPython 3.13 Windows x86-64
SHA-256 checksum
How to use checksums
f1a2a01e9b554f25ea9ef77ebf123cc271cda98f209257c9099c13cb71955227
BLAKE2b-256 checksum
How to use checksums
79a6469e816a51adf0a0688d04fb1621f7e850e08ab1ecdb24da15dd907d26f8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL zopy-0.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 1.4 MB
Tags CPython 3.13 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
4fc1c23076ccf320c76ebbf8ec8a06050d5dd5870858880aeb3a030ee5a3b8a3
BLAKE2b-256 checksum
How to use checksums
9bcc5b3dac9ec7533dbe900953afc82c5157d2d595679d8481d09998f4129e68
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL zopy-0.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 1.3 MB
Tags CPython 3.13 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
b7ecc2fcebca30f88c3c4d20bdf99166b13c318e1b4f746657cc6722eaf66aa6
BLAKE2b-256 checksum
How to use checksums
8404984d52a107005f489b463d26fa018d7e1c571e4af3de5238186339979072
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp313-cp313-macosx_11_0_arm64.whl

Download URL zopy-0.1.2-cp313-cp313-macosx_11_0_arm64.whl
Size 1.3 MB
Tags CPython 3.13 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1018c59ba00bf546281aaabe6d0b4a1debbd27edf55aece4676e8d9de672e8e3
BLAKE2b-256 checksum
How to use checksums
d60841a0651653ceaed3300455b6f01b152e84fb111779fb9345015af83ca8da
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp313-cp313-macosx_10_12_x86_64.whl

Download URL zopy-0.1.2-cp313-cp313-macosx_10_12_x86_64.whl
Size 1.3 MB
Tags CPython 3.13 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
cda978bd23028b35376573defe3cbac9be12cea65028b2127084560051f876ac
BLAKE2b-256 checksum
How to use checksums
a5d25814dc0aefdcbc94bbc25bf504108b28b1564020d3f501f7b1a9a3009f00
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp312-cp312-win_amd64.whl

Download URL zopy-0.1.2-cp312-cp312-win_amd64.whl
Size 1.2 MB
Tags CPython 3.12 Windows x86-64
SHA-256 checksum
How to use checksums
c97f76619cf991a165e5df4d8565a0c5a172eef73818adffb10cab0965882c50
BLAKE2b-256 checksum
How to use checksums
b9dc9c193672387ded956fc04a6d5967ab556a9e8fe93134dcae2bcc515f8123
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL zopy-0.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 1.4 MB
Tags CPython 3.12 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
37cfa5298c25605c155e36c9ae5dec5723a17ed011112beaa25aa038ec1c2e5b
BLAKE2b-256 checksum
How to use checksums
deaf676d89c0087629f210eebfa20e4cfd50de70c219fa68747b60b8c972b5a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL zopy-0.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 1.3 MB
Tags CPython 3.12 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
4a6b0f851fe72faf9190a550cb796e004db5e0dd9aa1c5a1734add5321fdaae2
BLAKE2b-256 checksum
How to use checksums
22713345051ba6486b88d662caab645fe242b9c7d577d03ca3ccf1deac0e44a9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp312-cp312-macosx_11_0_arm64.whl

Download URL zopy-0.1.2-cp312-cp312-macosx_11_0_arm64.whl
Size 1.3 MB
Tags CPython 3.12 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
cc6dfc8daaf91476c5f910eb986a81c26173d0a678fcbbe4308ce3b4702dd17e
BLAKE2b-256 checksum
How to use checksums
63c6954f7ff23a65c676112c0ef78610b8227daf6c832ea9596bdd6a636eee55
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp312-cp312-macosx_10_12_x86_64.whl

Download URL zopy-0.1.2-cp312-cp312-macosx_10_12_x86_64.whl
Size 1.3 MB
Tags CPython 3.12 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
e2756728b35a68b071c11b19fb5c5d45368282a3fd7032db53d18167300333ed
BLAKE2b-256 checksum
How to use checksums
e94d65d73c1291c85b02890cc39a7223c3a3192d2b6249c3ec5b12f51a0267bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp311-cp311-win_amd64.whl

Download URL zopy-0.1.2-cp311-cp311-win_amd64.whl
Size 1.3 MB
Tags CPython 3.11 Windows x86-64
SHA-256 checksum
How to use checksums
e3cf8b880737e5457f6b9fe77686bfc455671954a2c7b8cc33ec3127f54fc2d3
BLAKE2b-256 checksum
How to use checksums
0d7b0f90fb7d8534643dfd092573db6dde07b72f5aeb4009ce82522bef252a1c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl

Download URL zopy-0.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl
Size 1.4 MB
Tags CPython 3.11 Linux glibc 2.17+ x86-64
SHA-256 checksum
How to use checksums
f1605612c3c46187ad570b1eb747482b914d3b4e9c1361c5f236b608ccb14eeb
BLAKE2b-256 checksum
How to use checksums
995c9dda84d4fd2c855bf7981951373263399d1e44d2a07a2565d0ad165c6960
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl

Download URL zopy-0.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl
Size 1.3 MB
Tags CPython 3.11 Linux glibc 2.17+ ARM64
SHA-256 checksum
How to use checksums
5b147a93bc223a8d21b7836fd11043257fbf431e6a61eaa7b04abf2d7b6ac273
BLAKE2b-256 checksum
How to use checksums
2fa7becfdfa36cbe8cbb5f4ca38e1a18496c965cb66ea22eb9b1cda4c57168a8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp311-cp311-macosx_11_0_arm64.whl

Download URL zopy-0.1.2-cp311-cp311-macosx_11_0_arm64.whl
Size 1.3 MB
Tags CPython 3.11 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
52b8a1905e672fae2606a36fd61917abd09e87ab4f2fd9a5ae0792d91efb95e4
BLAKE2b-256 checksum
How to use checksums
420c759a312ea9943af76643fd18cd718743074444bd867e85a36634373a068a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Release files / zopy-0.1.2-cp311-cp311-macosx_10_12_x86_64.whl

Download URL zopy-0.1.2-cp311-cp311-macosx_10_12_x86_64.whl
Size 1.3 MB
Tags CPython 3.11 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
0701b385a4fac85ae715ce21914f6e82ee96b8673809d25ff172ae23a159ee3c
BLAKE2b-256 checksum
How to use checksums
7d109fdedf1f15d17159e276d5eee1f4e11d1953f7744aa7d43bb7c419e7f6a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

Security for zopy 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 21 files in this release have been uploaded with provenance metadata.

Select from 21 files

Attestation for zopy-0.1.2.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page