graphersal
An in-memory graph database and traversal engine for Python, queried with Gremlin, the query language of Apache TinkerPop. The engine is Graphersal, written in Rust and embedded into the Python process: no server, no JVM.
Book · Python Quick Start · Playground (try the queries in the browser) · Repository
You write Gremlin as script text (the same DSL as the playground and the command line tool) and
run it against a Graph; results come back as native Python values
(list/dict/int/float/str/bool/None/uuid.UUID). Graph files are GraphSON 3.0 (the
primary exchange format for Gremlin users) and GraphML; graphs can also be kept on disk with
durable commits (the Store).
Installing
pip install graphersal
Wheels exist for Linux (x86_64, aarch64), macOS (x86_64, Apple silicon) and Windows (x86_64). One
wheel per platform covers Python 3.10 and every newer version (the stable ABI, abi3). On another
platform pip builds the package from source, which needs a Rust toolchain (1.88 or newer).
Quick start
import graphersal
graph = graphersal.Graph()
graph.execute('g.addV("person").property("name", "Alice").next()')
names = graph.query('g.V().values("name").to_list()')
print(names) # ['Alice']
Script text follows this project's Gremlin/Rhai syntax: string literals use double quotes
(single quotes are Rhai character literals), and both snake_case (has_label) and Gremlin
camelCase (hasLabel) step spellings work. Inside a script a traversal only runs when it ends
in a terminal — .next(), .to_list() or .iterate() (runs for the side effects, returns
None) — including mutations: a statement g.addV("person"); in the middle of a script builds a
traversal object and does nothing; add .iterate(). A traversal
the script returns without a terminal (also inside a returned list or map) is run for you, as
if it ended in .to_list(): graph.execute('g.V().values("name")') returns the list of names.
execute() and query() are exactly the same method under two names; query is offered for
read-oriented call sites (it does not restrict the script by itself, see
Access policy). Both take the keywords atomic= (Units of
atomicity) and policy= (Access policy); type stubs
(graphersal.pyi) ship with the package.
The DSL terminals .profile() and .execute() come back as dicts with snake_case keys
(durations as integer nanoseconds, duration_ns); see the book page "Profiling and execute()":
metrics = graph.execute('g.V().out().profile()')
metrics["metrics"][0]["counts"]["traverser_count"]
run = graph.execute('g.V().values("name").sum().execute(#{profile: true})')
run["error"]["step_id"] # "2": the failing step, numbered like metrics[..]["id"]
run["profile"] # the profile up to the failing step
A DSL .execute() never raises: its error is part of the dict. A failing .profile() raises
GraphersalError like any other terminal.
Creating a graph
import graphersal
# An empty graph.
graph = graphersal.Graph()
# The standard TinkerPop "modern" toy graph (6 vertices, 6 edges), useful for trying things out.
graph = graphersal.Graph.tinkerpop_modern()
Loading and exporting GraphSON and GraphML
GraphSON 3.0 is what TinkerPop's g.io("graph.json").write() produces (one vertex per line):
import graphersal
graph = graphersal.Graph.from_graphson("path/to/graph.json")
graph.export_graphson("path/to/out.json") # TinkerGraph loads it with g.io(path).read()
GraphSON values carry their types (uuid, lists and maps come back as such). Data Graphersal
cannot hold yet (dates such as g:Date, other unsupported types) is skipped, never converted,
and named in one UserWarning. Multi-valued properties become lists, meta-properties go into the
_meta property; see the book's GraphSON page for every mapping.
GraphML:
graph = graphersal.Graph.from_graphml("path/to/graph.xml")
# ... query or mutate ...
graph.export_graphml("path/to/out.graphml")
GraphML carries no schema. Without one, values come back with their GraphML types: a UUID, a list
or a map is a string. To restore declared types (uuid, array, object), pass the schema to the
load: it is set on the new, empty graph first, then the file is imported into it as one unit (in
open/closed mode a violation fails the load with the usual report):
typed = graphersal.Graph.from_graphml(
"path/to/out.graphml",
schema=open("path/to/schema.json").read(), # a dict or JSON text
mode="closed", # optional: replaces the schema's own mode
)
Graph.from_graphson takes the same schema=/mode= keywords. To add a file to a graph you
already have, import it into the live graph; each import is one unit (a file that fails to parse
or violates the schema leaves the graph unchanged):
graph.import_graphml("path/to/more.graphml") # declared types restored from graph's schema
graph.import_graphson("path/to/more.json") # skipped data: one UserWarning
Saving, journal and recovery (packed snapshots)
Graphersal's own binary format keeps everything (every value type, the schema, the graph's commit position) in one file, and a journal makes every committed change durable before it is final (book chapter "Keeping a Graph on Disk", page "Python"):
graph.save("g.gsnap") # {"commit_seq", "vertex_count", "edge_count"}
graph = graphersal.Graph.load("g.gsnap") # no schema re-validation, checksummed
graph.start_journal("g.wal") # durability="every_commit" | "os" | seconds
graph.execute('g.addV("person").property("name", "ann").next()')
graph.mark("after-ann") # a named point in the journal
graph, report = graphersal.Graph.recover("g.gsnap", ["g.wal"]) # to the end
graph, report = graphersal.Graph.recover("g.gsnap", ["g.wal"], mark="after-ann")
Graph.recover also takes commit_seq= or time= (microseconds since the Unix epoch). An
interrupted last write is ignored (report["torn_tail"]); damage raises GraphersalError with
its location. The recovered graph has no journal: save and start_journal again to continue.
Keeping a graph on disk: the Store
A Store is one directory (or one .gstore file) holding one graph: every commit is durable before
it returns, and the store keeps marks, snapshots, point-in-time forks and verified backups (book
page "Persistence: Python"):
with graphersal.Store.create("data") as store: # or Store.open("data"); "x.gstore": one file
store.graph.execute('g.addV("person").property("name", "ann").next()') # durable when it returns
store.mark("after-ann")
store.checkpoint("nightly")
store.fork("branch", mark="after-ann") # a new store from that state
store.backup("bk") # full, then incremental
Schemas
A schema is a dict (or JSON text) in the library's schema format: an envelope with mode
("none", "open", "closed"), vertices, edges and a free meta, around standard JSON
Schema 2020-12 label schemas, so a Pydantic Model.model_json_schema() can be used as a label's
schema (drop date-time formats and dict[str, T] fields first: no date or map type before
0.2.0). The full reference is the book page "Schemas".
graph = graphersal.Graph.tinkerpop_modern()
graph.set_schema(graph.infer_schema(), mode="closed") # freeze the structure the graph has
schema = graph.infer_schema() # what the data satisfies; stores nothing
schema["mode"] = "closed"
report = graph.validate_schema(schema) # dry run: {"violations", "diff", "compatible"}
graph.set_schema(schema) # validates the data first; on violations nothing changes
graph.patch_schema({"vertices": {"person": {"schema": {"properties": {"email": {"type": "string"}}}}}})
graph.diff_schema(schema) # {"changes": [...], "compatible": bool}
graph.get_schema() # the stored schema, or None
graph.set_schema({}) # removes it
| Method | Returns |
|---|---|
get_schema() |
the stored schema, or None (never inferred) |
set_schema(schema, mode=None) |
None; replaces the stored schema (mode: "none"/"open"/"closed" replaces the schema's own mode) |
patch_schema(patch) |
the new schema (JSON Merge Patch, RFC 7396) |
validate_schema(schema, mode=None), validate_schema_patch(patch) |
{"violations": {...}, "diff": [...], "compatible": bool}; applies nothing |
infer_schema() |
the schema the data satisfies (mode "none") |
diff_schema(schema) |
{"changes": [...], "compatible": bool} from the stored schema |
Every method takes policy= like execute() (read methods need ("read", "schema"), set/patch
need ("update", "schema")). A violation raises GraphersalError with the aggregated report and a
help. A schema default is an annotation only: it is never written into the data nor
substituted on read. To make a key required that existing elements lack, backfill first in one
atomic script (patch to optional, write the values, patch to required):
graph.execute('''
g.patch_schema(#{vertices: #{person: #{schema: #{properties: #{email: #{type: "string"}}}}}});
g.V().hasLabel("person").property("email", "unknown").toList();
g.patch_schema(#{vertices: #{person: #{schema: #{required: ["name", "age", "email"]}}}});
''', atomic=True)
The schema methods are also in the script DSL (g.get_schema(), g.set_schema(..), ...), and a
run reports what it changed: execute() of a script ending in .execute() returns
changes: {"data": bool, "schema": bool, "definitions": bool}.
Saved queries
Named, parameterized, read-only queries stored in the graph (book page "Saved Queries"):
graph.define_query({
"name": "older_than",
"folder": "people",
"params": {"age": {"schema": {"type": "integer", "minimum": 0}, "default": 30}},
"body": 'fn older_than(age) { g.v().has_label("person").has("age", P.gt(age)).values("name") }',
})
graph.call_query("older_than", {"age": 29}) # ["josh", "peter"]; graph.query stays execute's alias
graph.queries("people") # [{"name", "folder", "description", "params", "status", ...}]
graph.get_query("older_than")["body"]
graph.drop_query("older_than") # True
Each wraps the DSL call (g.define_query, g.query, g.queries, g.get_query,
g.drop_query) and takes policy= like execute().
Creating vertices and edges
import graphersal
graph = graphersal.Graph()
# addE()'s from()/to() take step labels (set with as(...)), not nested traversals.
graph.execute(
'g.addV("person").property("name", "Alice").property("age", 30).as("a")'
'.addV("person").property("name", "Bob").as("b")'
'.addE("knows").from("a").to("b").next()'
)
Query examples
import graphersal
graph = graphersal.Graph.tinkerpop_modern()
# A plain list of scalar values.
names = graph.query('g.V().hasLabel("person").values("name").to_list()')
# ['marko', 'vadas', 'josh', 'peter']
# elementMap() turns each vertex/edge into a dict.
people = graph.query('g.V().hasLabel("person").elementMap().to_list()')
# [{'id': '1', 'label': 'person', 'name': 'marko', 'age': 29}, ...]
# next() returns a single value (or None if the traversal yields nothing).
count = graph.query('g.V().hasLabel("person").count().next()')
# 4
# A one-hop traversal.
who_marko_knows = graph.query(
'g.V().has("name", "marko").out("knows").values("name").to_list()'
)
# ['josh', 'vadas']
Parameterized queries
Pass a params dict of int, float, str, bool, None or uuid.UUID values. Each one is
bound as both a script variable of the same name and an entry of the params map, so user input
never needs to be spliced into the script string:
import graphersal
graph = graphersal.Graph.tinkerpop_modern()
names = graph.query(
'g.V().hasLabel("person").has("age", params.min_age).values("name").to_list()',
params={"min_age": 32},
)
# ['josh']
list/dict values in params are not supported and raise GraphersalError, as does an int
outside the int64 range (it is never widened to a float). A key that is not a str raises
TypeError.
Error handling
Every script/engine failure — parse errors, a failed cast, a broken GraphML file — raises
graphersal.GraphersalError or one of its subclasses. When the underlying error carries
actionable guidance, it is available as .help (None otherwise):
import graphersal
graph = graphersal.Graph.tinkerpop_modern()
try:
graph.execute('UUID.fromString("not-a-uuid")')
except graphersal.GraphersalError as err:
print(err) # Error: Cast exception: expected type [uuid], got type 'string' ...
print(err.help) # A UUID is written in canonical form: 8-4-4-4-12 lowercase hex digits, ...
The failures a program handles on purpose have their own subclasses (all of them are
GraphersalErrors and keep .help):
| Exception | Raised when |
|---|---|
PermissionDeniedError |
the script makes a request (an action on a resource) its policy does not allow, or uses a file outside the file root; the denied traversal did not run |
ReturnedClosureError |
a script run as one unit (atomic=True, dry_run) returns a closure; everything it changed was rolled back |
CommitRejectedError |
a commit hook vetoed the commit (hooks can only be registered from Rust in 0.1.0); everything was rolled back |
try:
graph.execute('g.addV("x").next()', policy=graphersal.AccessPolicy.read_only())
except graphersal.PermissionDeniedError as err:
print(err) # Error: Permission denied for 'add_v' (Create Data(vertex "x")): ...
Units of atomicity
A failing script never leaves a half-applied traversal behind. By default every traversal (every
terminal such as .next() or .to_list()) is its own unit: a failing traversal rolls back only
itself, and the statements before it stay — the TinkerPop behaviour:
graph = graphersal.Graph.tinkerpop_modern()
script = 'g.addV("x").next(); g.V().values("name").sum().next()' # sum() over strings fails
try:
graph.execute(script)
except graphersal.GraphersalError:
pass
graph.query('g.V().count().next()') # 7: the first statement stayed, the second one left nothing
atomic=True makes the whole script one unit: when it fails, everything it changed is rolled
back; when it succeeds, all of it stays. While it runs, no other reader or writer of the graph
does. Inside it, each traversal is a savepoint, so a script that catches a failure itself
(try { ... } catch (err) { }) goes on with only that traversal undone.
try:
graph.execute(script, atomic=True)
except graphersal.GraphersalError:
pass
graph.query('g.V().count().next()') # still 7: nothing of the failing script stayed
A script run with atomic=True may not return a closure (ReturnedClosureError): return data.
A traversal it returns without a terminal runs after the commit, as its own unit.
Auto-generated ids are never reused, also not after a rollback.
Dry run: preview the changes of a script
graph.dry_run(script, params=None, *, policy=None) runs the script as one unit and then
always rolls it back. It returns a graphersal.DryRunResult, an immutable object with two
attributes: .result, what execute would have returned, and .changes, the would-be changes as
plain Python data. The graph is left exactly as it was.
graph = graphersal.Graph.tinkerpop_modern()
run = graph.dry_run(
'g.V().has("name", "marko").property("age", 30).next();'
'g.V().has("name", "lop").drop().to_list();'
'g.V().count().next()'
)
run # DryRunResult(result=5, changes=<5 mutations>)
run.result # 5
changes = run.changes
changes["commit_seq"] # 0: never committed
changes["committed_at"] # 0: never committed (a commit's time: microseconds since the epoch)
changes["node_sequence"] # 6: the vertex auto-id sequence after the run (ids are never reused)
[m["op"] for m in changes["mutations"]]
# ['set_property', 'drop_edge', 'drop_edge', 'drop_edge', 'drop_vertex']
changes["mutations"][0]
# {'op': 'set_property', 'element': {'kind': 'vertex', 'id': '1'}, 'key': 'age', 'before': 29, 'after': 30}
graph.query('g.V().count().next()') # 6: untouched
The change set is the serialized form of the engine's ChangeSet (book page "Transactions"),
JSON-compatible as it is (json.dumps(changes) works):
commit_seqandcommitted_atare0(never committed);node_sequenceandedge_sequenceare the auto-id sequences after the run: auto ids a dry run handed out are not handed out again.mutationslists the changes in execution order, each a dict with an"op"key:add_vertex,drop_vertex,add_edge,drop_edge,set_property,remove_property,add_label,remove_label,set_schema, with the values before and after. Dropping a vertex lists the drops of its incident edges first.- Values JSON cannot hold carry a type tag instead of being guessed from their content: a
uuid.UUIDis{"@type": "g:UUID", "@value": "550e8400-e29b-41d4-a716-446655440000"}. A user map that itself has an"@type"key is wrapped as{"@type": "graphersal:Object", "@value": {...}}.
A failing script raises like execute and returns no change set; a returned closure raises
ReturnedClosureError. No commit hook runs. A traversal the script returns without a terminal
runs inside the dry run, so its result reflects the previewed changes. The change set is data only:
Python cannot register commit hooks (callbacks on every commit) in 0.1.0.
Access policy
policy= limits what a script may do. Every step and file operation of a script makes a request,
an action on a resource; a graphersal.AccessPolicy allows or denies them by kind, plus
an optional file root:
| Resource | Requested by |
|---|---|
"data" |
read: every step that reads the graph (V(), out(), values(), count(), ...); create: addV, addE, mergeV, mergeE, importing GraphML; update: property, removeProperty, mergeV/mergeE; delete: drop |
"schema" |
read: get_schema, infer_schema, diff_schema, validate_schema(_patch) (methods and DSL); update: set_schema, patch_schema |
"file" |
read: import_graphml, GraphSource::file; create: export_graphml; execute: Rhai import |
"option" |
update: administrative g.with() options (evaluationTimeout, repeat.max_loops, optimizer switches, ...) |
"custom" |
never requested by the engine (for servers embedding the Rust library) |
from graphersal import Graph, AccessPolicy
graph = Graph.tinkerpop_modern()
graph.query('g.V().count().next()', policy=AccessPolicy.read_only()) # 6
graph.execute('g.addV("x").next()', policy=AccessPolicy.read_only()) # PermissionDeniedError
AccessPolicy.deny_all() # nothing: the script can only compute
AccessPolicy.allow_all() # everything
AccessPolicy(("read", "data"), ("create", "data")) # exactly these (action, resource) pairs
AccessPolicy.read_only().allow("*", "data") # a new value; policies are immutable
AccessPolicy.allow_all().deny("*", "file").deny("update", "option")
# File access limited to one directory: relative paths resolve under it, paths that leave it
# ("..", absolute paths elsewhere, symbolic links pointing out) are denied.
uploads = AccessPolicy.read_only().allow("*", "file").with_file_root("/srv/graphs")
graph.execute('g.export_graphml("snapshot.graphml")', policy=uploads) # /srv/graphs/snapshot.graphml
A denied script fails with PermissionDeniedError before the denied traversal runs; the message
names the step and the request (Permission denied for 'add_v' (Create Data(vertex "x"))). A file
root does not allow file access by itself.
The default allows everything: without policy=, a script may do everything, like the Rust
library's own default. A Python program is a trusted local host — it already has the graph
object, file access and Graph.export_graphml. Whenever a script comes from somewhere else (a
user, a request, an LLM), pass an explicit policy, e.g. AccessPolicy.read_only(), together with
resource limits (graph.set_limits(...)).
Resource limits
A new Graph runs scripts under the library's SAFE limits: 10 million script operations, 16 MiB
strings, 100 000 list items and map entries, and 128 levels of nested lists and maps in one value.
graph.set_limits(...) changes them for every later script of that graph; each keyword overrides
one limit, 0 meaning unlimited, and unlimited=True starts from no limits:
graph.set_limits(max_operations=1_000_000, max_string_size=1 << 20)
graph.set_limits(unlimited=True, max_value_depth=256)
max_value_depth is never unlimited (0 means its default, 128): deeper values are read
recursively and would overflow the native stack. Scripts run on a worker thread with a 64 MiB
stack (one per calling Python thread), so the scripting engine's own recursion over nested values
has room. A limit hit raises GraphersalError whose help names the limit to raise. Traversal
limits (evaluationTimeout, traversal.max_materialized_bytes, traversal.max_string_bytes, ...)
are set per query with g.with(key, value); see the book's "Resource Limits" page.
A graph can carry a memory budget (off by default): every traversal on it that would take the
memory in use over the budget raises GraphersalError and is rolled back, instead of the process
being killed for lack of memory. Pass a number of bytes, or "auto" for the cgroup memory limit
of the process (a Linux container) minus a headroom (memory_headroom, default a tenth of the
limit, at least 64 MiB):
graph = graphersal.Graph(memory_limit=2 << 30)
graph = graphersal.Graph.from_graphml("data.graphml", memory_limit="auto")
The extension installs Graphersal's tracking allocator as its own global allocator, so the budget
compares the exact live heap of the extension's Rust code (every graph and query of the process,
read with one atomic load, no walk of the graph). That count does not include Python objects:
with memory_limit="auto" the Rust heap is compared with a limit for the whole process, so
memory_headroom must also cover the Python side (the interpreter, your own data, the converted
results). The same allocator makes Memory profiles exact:
g.v().out().profile_with(ProfileType.Memory) reports "source": "tracked" bytes per step
(a bare profile() collects no memory data). The cost is a few counter increments per
allocation of the extension.
GraphRAG example
examples/graphrag.py ingests a small knowledge graph of text entities and
relations, extracts the 2-hop neighbourhood of a question entity with
repeat(__.out()).times(2) and prints one JSON line per path (entity, type, text, path,
hops), ready to paste into an LLM prompt:
python examples/graphrag.py "Marie Curie"
Concurrency
execute()/query()/dry_run() release the GIL for the duration of the script, so a
long-running query on one Graph does not stall other Python threads (e.g. a thread-pool server,
or asyncio driving this through run_in_executor). The underlying graph still serializes
concurrent mutations from multiple threads; an atomic=True script and a dry run hold the graph's
write lock for the whole script.
What's out of scope (for now)
- Commit hooks from Python (a Python callback on every commit); change sets are available only
as data through
dry_run. - Positional parameters (
$1,$2, ...). list/dictvalues inparams.- Graph file formats other than GraphSON and GraphML (and Graphersal's own snapshots).
Building from source
For development in the repository.
You need a Rust toolchain and Python 3.10+ (the binding targets abi3-py310). The macOS system
python3 is 3.9 and too old; use e.g. Homebrew's python3.12. Keep the virtualenv outside the
repository or at bindings/py-graphersal/.venv (ignored by version control). One-time setup, from
the repository root:
/opt/homebrew/bin/python3.12 -m venv bindings/py-graphersal/.venv
bindings/py-graphersal/.venv/bin/pip install maturin pytest
Build the extension into that virtualenv as graphersal (repeat after every Rust change in the
binding or the engine), then run the tests:
cd bindings/py-graphersal
VIRTUAL_ENV=$PWD/.venv .venv/bin/maturin develop # add --release for an optimized build
.venv/bin/python -m pytest tests/ # Python suite, including examples/
cd ../..
PYO3_PYTHON=$PWD/bindings/py-graphersal/.venv/bin/python cargo test -p py-graphersal # Rust unit tests
PYO3_PYTHON must name a Python >= 3.10 for every Cargo command that builds this crate
(cargo test -p py-graphersal, workspace-wide cargo check/cargo clippy); otherwise exclude it
with --exclude py-graphersal. import graphersal is then available in the virtualenv
(source bindings/py-graphersal/.venv/bin/activate).
A wheel to install elsewhere
cd bindings/py-graphersal
maturin build --release # writes a wheel to target/wheels/
pip install ../../target/wheels/graphersal-*.whl
The wheel targets the stable ABI (abi3-py310), so one wheel per platform covers Python 3.10 and
newer — no per-Python-version rebuild is needed.
License
Apache License, Version 2.0. Graphersal is compatible with, and inspired by, Apache TinkerPop Gremlin. It is an independent project: it is not part of Apache TinkerPop and is not endorsed by the Apache Software Foundation.
Metadata
Release files for graphersal 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| graphersal-0.1.0.tar.gz | 1.5 MB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| graphersal-0.1.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| graphersal-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| graphersal-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| graphersal-0.1.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| graphersal-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 38.5 MB
Release files / graphersal-0.1.0.tar.gz
| Download URL | graphersal-0.1.0.tar.gz |
|---|---|
| Size | 1.5 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
61469c23c538db3d4cb0d86299a15d588909de1c5217d07db6ca0888b11becb0
|
|
BLAKE2b-256 checksum How to use checksums |
dba3c87a85acc62bb59b8cadb32149d4e6809ffedda6d0a7140b26222739ea68
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 12, 2026.
Transparency logRelease files / graphersal-0.1.0-cp310-abi3-win_amd64.whl
| Download URL | graphersal-0.1.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 7.7 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
1292b5b2075b23aea4d639534b490b0564882f51b0d08f9dc55d7dc28676d96b
|
|
BLAKE2b-256 checksum How to use checksums |
ddd65cf97f5da2a20a0f38f4870378514a9ef4e1985db6812601afe824c73cd9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 12, 2026.
Transparency logRelease files / graphersal-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | graphersal-0.1.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 7.7 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
13e9080ee5d73fb61899ca5bc238542794e9feacc996575dfc2dfeb0590d359f
|
|
BLAKE2b-256 checksum How to use checksums |
5207150c53cbca7bd5f9dcd4bfe63ecdabfb9f1691d874c983557d92ba1fc076
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 12, 2026.
Transparency logRelease files / graphersal-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | graphersal-0.1.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 7.4 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
69307bfc979e1b6995ed3c80e65c083758ff24231ec22430315c7bfe277af6cf
|
|
BLAKE2b-256 checksum How to use checksums |
452a7bbadca811b90d3564b67b6e61781f6b08fc9a26acfa1452046c51417564
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 12, 2026.
Transparency logRelease files / graphersal-0.1.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | graphersal-0.1.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 6.9 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
dc6447956db998a8c6deb3779800387645ecf0ae8af7de11c271086529005a69
|
|
BLAKE2b-256 checksum How to use checksums |
dee7ead438d046e4aa7f0faa501ff9515bcd106bbf16c3a794862381e05aaedc
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 12, 2026.
Transparency logRelease files / graphersal-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | graphersal-0.1.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 7.3 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
0ac5f73488c24272ed11377ac458e72bf05d7021dbf2dd479d86a5aa62a1b6d1
|
|
BLAKE2b-256 checksum How to use checksums |
cb7ed5ceefe35e56af257ad1e38f04ab7db97171e16c1b7fd0da409610c28b38
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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 Oct 12, 2026.
Transparency log