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
- Quick start
- Differences from Zod
- Schemas
- Objects are classes
- Type inference
- Python types
- Missing keys and None
- Readonly
- Brands
- Refinements
- Errors
- Async
- Engines
- Development
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:
- the
errorof the check (.min(5, {"error": ...})) - the
errorof the schema (z.string({"error": ...})) - the per-parse
error(.safeParse(data, {"error": ...})) z.config({"customError": ...})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
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)
| 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)
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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logRelease 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 logSecurity 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.
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 logA 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.