Memory-mapped columnar binary format for fast random-access I/O on structured arrays.
Project description
ColStore
A memory-mapped columnar binary format for fast, memory-efficient I/O on
structured arrays. colstore lets you write a tabular dataset to a single
.cstore file (in one shot or streamed across many records) and then load
arbitrary row/column subsets without materializing the rest. Internally,
columns are stored back-to-back as raw NumPy bytes, reads use np.memmap,
and fancy-index gathers run through a parallel C++ kernel (OpenMP + software
prefetching) bound via Cython. Process memory stays bounded by the size of
the output you ask for; the source file is never fully read into RAM.
What you get
- Single-file or many. Write one
.cstore, or open many same-schema files — or a directory of shards — as one logical table. - Bounded memory. Reads are memory-mapped, so process memory tracks the output you ask for, not the file size; files larger than RAM are fine.
- Fast random access. Fancy-index and boolean gathers run through a parallel C++/OpenMP kernel, dispatched per access pattern.
- Lazy reads and edits. Indexing returns lazy views;
edit()derives a new file from an existing one without touching the source. - Zero-copy where the layout permits. A compacted, native-byte-order store hands back read-only views of the page cache (
copy=False).
Table of contents
Guide
Install
pip install colstore
Building from source needs a C++17 compiler and CMake ≥ 3.18. On macOS install
libomp (brew install libomp) to get the parallel kernel; without it the
build still succeeds but the kernel runs single-threaded.
Quick start
import colstore
# One-shot write + open. `.cstore` is the canonical extension.
ds = colstore.store(df, "data.cstore")
# Or open an existing file for read.
ds = colstore.open("data.cstore")
# Indexing returns lazy views; no data is read yet.
ds['price'] # ColumnView
ds[100:200] # TableView
ds[100:200, 'price'] # ColumnView
ds[100:200, ['price', 'qty']] # TableView
ds[[1, 5, 9], ['price', 'qty']] # TableView (fancy rows + cols)
# Views compose -- re-index one by a column or by rows, and it equals the direct form.
ds[100:200]['price'] # ColumnView == ds[100:200, 'price']
ds['price'][:100] # ColumnView (first 100) == ds[:100, 'price']
ds[100:200, ['price', 'qty']][:10] # TableView (first 10 rows of the selection)
# Materialize through one of the materialization methods.
ds['price'].array() # 1D ndarray
ds.array('price') # 1D ndarray (shortcut for ds['price'].array())
ds[indices, ['price', 'qty']].dict() # dict of 1D arrays
ds[indices, ['price', 'qty']].recarray() # structured ndarray
ds[indices, ['price', 'qty']].frame() # pandas DataFrame
ds[100:200].dict(copy=False) # read-only views, no copy (see Zero-copy reads)
ds.head() # peek: a table that renders in notebook + terminal
Reader, writer, frame
colstore has three objects, one per job. Most code only ever needs
ColStoreReader.
ColStoreReaderis the read interface.colstore.open(path)returns one; index it for lazy views (ds[rows, cols]) and materialize with.array(),.dict(),.recarray(), or.frame(). This is what you use to get data out.ColStoreWriteris the write interface for new data. You use it through thecolstore.create/recreate/updatecontext managers to stream records into a file. For a single in-memory dataset there is no need to touch it directly —colstore.store(data, path)writes a dict, record array, or DataFrame in one shot. (See Writing, below.)ColStoreFrameis the edit interface.reader.edit()returns one; update, add, drop, rename, cast (astype({"pt": "float32"})), or transform its columns — each edit returns a new frame (inplace=Trueto edit in place), so edits branch cheaply off a shared base. Then materialize in memory (array(name)for one column,dict()/recarray()/frame()for all) or.write(path)to stream the result to a new file. It never modifies the source store.
Which to use:
| Job | Get one from | Output | |
|---|---|---|---|
ColStoreReader |
Read an existing file | colstore.open(path) |
NumPy arrays / DataFrames, in memory |
ColStoreWriter |
Write a new file from data you hold | colstore.create / recreate / update (context manager) |
a .cstore on disk |
ColStoreFrame |
Derive a new file from an existing one | reader.edit() |
a new .cstore on disk, plus a reader for it |
The distinction people miss is writer vs. frame: a writer persists data you
are already holding in memory, while a frame derives a new file from one already
on disk by transforming its columns. Starting from raw arrays, reach for a writer
(or store); starting from a .cstore you want a modified copy of, reach for
edit(), which gives you a frame.
Eager and lazy operations
A reader/dataset and a frame are complementary surfaces, and three rules cover when colstore reads data and when it defers the work:
- Reading is lazy. Indexing, filtering, and projecting a reader return a view
(
ColumnView/TableView) that holds no data;edit()returns a frame, a deferred column-expression graph. Neither reads until you materialize (array()/dict()/recarray()/frame(), ornp.asarray) orwrite(). - The reader is eager; the frame is lazy. On a reader/view, work happens now:
select rows by a column predicate with
col()orquery()(or positionally withds[rows]) — each gives a lazy view — and compute on a column with operators, NumPy ufuncs, or membership (ds['a'] * 2,np.log(ds['a']),ds['a'].isin(keep)), which materialize the selected rows into anndarray(a boolean mask, forisin, that you index with:ds[ds['id'].isin(keep)]). A frame (reader.edit()) defers instead:frame['a'] * 2andframe['a'].isin(keep)build an expression realized on materialize /write(), and it filters withwhere()and projects withselect()rather than indexing rows (frame[rows]raises). - Reductions are eager everywhere.
sum/mean/min/max/std/var/count, and the NumPy spellingnp.sum(column)/np.std(column, ddof=1), run one bounded-memory pass and return a scalar right away, on a view or a frame alike;count()is also the row total on a reader, dataset, or view (ds.count(),ds[mask].count()).
So pick the surface by the job: read and compute now on the reader
(ds[col('x') > 0, 'a'] * 2 selects rows by a predicate, then computes, eagerly), or
build a deferred transform that composes and writes without materializing on a
frame (reader.edit()). Either way, a reduction (column.sum()) gives a scalar now.
Filtering and projection
Filter rows with a predicate string or a composable col()
expression — both return a lazy view. Nothing is read until you
materialize: the predicate columns are read (and the row mask computed) on the
first .evaluate() / .frame() / .dict() / .recarray() / .array(), and the
selected columns only when you ask for them.
from colstore import col
# String form (parsed and validated eagerly, evaluated lazily):
hot = ds.query("energy > 100 and -2.5 < eta < 2.5") # lazy TableView
hot.frame() # read + materialize now
ds.query("pt > @cut and region == 'SR'", params={"cut": 30}).dict()
# col() form (composable; stacks with & | ~ and stays lazy):
ds[(col("pt") > 30) & (col("region") == "SR")].frame()
ds.where(col("pt").isin([30, 40, 50])) # the explicit verb
ds[col("pt") > 30, ["pt", "eta"]].recarray() # project columns
# select() / drop() project columns by exact name -- chainable, stays lazy:
ds.select("pt", "eta") # lazy view of those columns
ds.drop("weight") # all columns except weight
ds.query("pt > 30").select("eta") # filter rows, then project
Lazy by default; evaluate() to peek. query() and ds[expr] defer
everything; call .evaluate() (or query(..., lazy=False)) to resolve the row
mask now — it reads the predicate columns and returns a view whose rows are
fixed, so a following .frame() / .dict() doesn't recompute the selection. The
selected columns are still materialized only on demand.
Projection. select(*names) / drop(*names) choose columns by exact name
— no wildcards, so column selection stays explicit and unambiguous. Both return a
lazy TableView, preserve the row selection, and chain with query() / col();
select keeps the order you give and rejects duplicates, and an unknown name
raises KeyError immediately. A single select("x") is still a one-column
TableView — use ds["x"] for a 1-D ColumnView.
The string grammar is a strict whitelist evaluated without eval: column
names, numeric/string/bool literals, comparisons (including chained a < x < b),
the boolean operators (and / or / not and & | ~ — parenthesize the
bitwise forms, which bind tighter than comparison), arithmetic, and in /
not in. @name resolves from params (the calling frame is never inspected),
and a bool column is a predicate on its own (ds.query("is_signal")). col()
expressions combine with & | ~ and .isin(...) (Python's and / or / not
can't be overloaded). An unknown column, an unsupported construct, or a
non-boolean condition raises colstore.QueryError immediately, reading no data.
Everything behaves the same on a single file and a multi-file dataset.
Editing
reader.edit() returns a ColStoreFrame: a lazy specification of a new file,
read only when you materialize it. A frame has two independent parts — the
columns to produce (each an expression over the source columns) and the
rows to keep — and nothing touches disk until you ask for the result, in
memory with array(name) / dict() / recarray(), or as a new file with
write(). Every edit returns a new frame, so frames branch cheaply from a shared
base; pass inplace=True to edit one in place.
import numpy as np
from colstore import col
cf = ds.edit() # a frame over every column, all rows
cf = cf.assign(ratio=col("close") / col("open")) # col() builds a column value...
cf = cf.where(col("ratio") > 1.0, "gains") # ...and the same col() selects rows (lazy; name the cut)
cf = cf.where("volume > @v", params={"v": 1e6}) # a query string works too; where()s compose
cf = cf.astype({"open": "float32"}).drop("noise")
cf = cf.select("close", "open", "ratio") # project columns; filter() aliases where()
cf.n_rows # how many rows the frame selects (resolves it)
cf.report() # cutflow per named cut (raw / weighted / both)
cf.dict(); cf.recarray(); cf.array("ratio") # materialize the selected rows in memory
cf.sum("ratio"); cf.mean("pt"); cf.std("pt"); cf.max("eta"); cf.count() # full-pass scalar reductions
for batch in cf.iter_batches("256 MiB"): ... # or stream bounded in-memory frames (any format)
reader = cf.write("derived.cstore") # or write a new .cstore (returns a reader)
# A filtered or gathered view continues straight into an edit:
hot = ds.query("pt > 30").edit() # a frame over only the matching rows
hot.assign(logpt=np.log(hot["pt"])).write("hot.cstore")
ds[indices].select("pt", "eta").edit() # a fancy index, slice, or mask carries over
The columns form an expression graph: stored columns, expressions over them, and
constants combined by elementwise operators and NumPy ufuncs. Only
row-independent transforms are representable — each output row depends only on
the input row at the same position — so reductions and sorts are rejected when the
frame is built. The same col() expression builds a value (assign) or a row
predicate (where); either way its names resolve against the frame's columns in
order, so a column derived or renamed earlier is what a later step sees. Every
column derives from the same base and stays the same length, so assign takes an
expression (or a column from another frame); a raw array attaches only at the base,
before any selection.
where (alias filter) is lazy — it records the predicate and evaluates it
only on materialization. A frame doesn't slice or index rows/columns — that's the
reader's job (view a written file); the frame filters with where, projects with
select, and edits with assign. n_rows is the source or selected count; a pending
predicate is resolved on access (an O(n) scan). Materializing evaluates each column
over the selected rows, sharing a subexpression once. Both iter_batches and write()
are bounded in memory: they resolve the selection once, then process the selected
rows one batch at a time (batch_size / memory_budget — an int row count or an IEC
string like "256 MiB"), so a filtered frame streams its survivors rather than
materializing them whole. iter_batches yields each batch as a materialized,
in-memory frame (columns detached from the source), so the consumer converts it to any
format — dict(), recarray(), write(), or further edits.
A where/filter cut can be named and weighted — where(col("pt") > 30, "trigger", weight="w") — and report() returns the cutflow: the rows entering and passing each cut,
in order, with efficiencies, and the summed weight too when one is in effect (a weight is sticky
across later cuts). report(show=...) prints "raw", "weighted", or "both"; the report
renders as a table in a terminal and as HTML in a notebook, and report().records() returns
the cutflow as a list of per-cut dicts for saving (JSON, CSV, …). Unnamed cuts appear by
position.
The expression-graph diagram details the
shared-subexpression reuse; the edit lifecycle and
streaming commit diagrams trace a frame from
edit() to a written file.
Writing
colstore.store(data, path) is the one-shot path; it dispatches on the
input type:
import colstore
import numpy as np
# From a dict of 1D arrays.
colstore.store(
{"x": np.arange(100, dtype=np.float32), "y": np.arange(100, dtype=np.int64)},
"data.cstore",
)
# From a structured (record) array.
records = np.empty(100, dtype=[("price", np.float32), ("qty", np.int32)])
colstore.store(records, "data.cstore", mode="recreate")
# From a pandas DataFrame.
colstore.store(df, "data.cstore", mode="recreate")
mode="create" (default) refuses to overwrite; mode="recreate" truncates
an existing file.
For multi-record streaming writes (data arriving in batches, or appending
to an existing file), use colstore.create / colstore.recreate /
colstore.update:
# Append-only stream into a new file.
with colstore.create("data.cstore") as f:
for batch in source:
f.write({"x": batch.x, "y": batch.y})
# Resume appending to an existing file. Schema is loaded from the manifest
# and every write must match it. Bytes from a crashed prior writer are
# truncated on open.
with colstore.update("data.cstore") as f:
f.write({"x": more_x, "y": more_y})
The writer commits records atomically on close() by rewriting a 64-byte
counters block. A reader opening the file mid-write sees only what the
last successful close committed.
The streaming writers (create / recreate / update) and compact take an
advisory flock for the duration of the write, so a second writer on the same
file fails fast with a clear error instead of corrupting it. On filesystems that
don't implement flock (some networked or parallel filesystem mounts), the lock is skipped
with a one-time warning and the write proceeds — there is no lock to contend for
on such a mount, so concurrent-writer detection is simply unavailable there.
Export a selection. saveas writes any reader / dataset / view selection to a file,
choosing the format by the destination extension:
ds[rows, cols].saveas("subset.cstore") # a new .cstore (its own native format)
dataset.saveas("merged.cstore") # whole multi-file dataset -> one file (like concat)
ds[rows, cols].saveas("subset.parquet") # or Parquet / Feather / HDF5 / JSON / NPZ / ROOT
Writing a .cstore streams in bounded memory and raw-copies unchanged columns (so a whole
store or dataset is copied / merged, not materialized); the foreign formats convert the
selection. See Format interop for the full set and convert() /
from_* to read them back.
Convert files. convert moves files between colstore's format and another, inferring the
direction from the extensions (one endpoint must be .cstore). A foreign file imports into a
new .cstore and returns the opened reader; a .cstore exports to a foreign format. source
may be a single path, a glob, or a list:
ds = colstore.convert("in.parquet", "out.cstore") # import -> a reader (also from_parquet/...)
colstore.convert("data.h5") # auto-name -> data.cstore
colstore.convert("*.h5") # one .cstore per file
colstore.convert("*.h5", "all.cstore") # a literal -o path merges into one file
colstore.convert("*.h5", "run_{index}.cstore") # template: run_0.cstore, run_1.cstore, ...
colstore.convert("*.h5", rename=lambda stem: stem + "_v2") # or an arbitrary name mapping
colstore.convert("events.cstore", "events.parquet") # export -> a Path
An existing output raises unless overwrite=True; output_dir= redirects the outputs and
on_mismatch="drop" reconciles schemas when merging. For a large conversion, batch_size=
(an int row count or a "256 MiB" byte budget) streams it in bounded memory in either
direction: on import it reads the foreign file in row-batches (ROOT / Parquet / Feather / HDF5
with a fixed-width numeric schema); on export it writes the foreign file in row-batches
(Parquet row groups, Feather record batches, resizable HDF5 datasets, a ROOT Snapshot, or a
.cstore editing-frame write). A target with no appendable path (JSON / NPZ, the pandas HDF5
backend, Feather with write options) is written whole with a warning. columns=[...] converts
only the named columns, in either direction.
Converting many files? max_workers runs them concurrently on a thread pool ("auto" picks
the throughput plateau): a batch import from a parallel filesystem goes ~3–5× faster. Peak
memory scales with the worker count (one file's working set per worker), so pair it with
batch_size to bound the total.
colstore.convert("*.h5", output_dir="out/", max_workers="auto") # convert a directory in parallel
colstore.convert("*.parquet", "all.cstore", max_workers=8, batch_size="256 MiB") # merge, bounded
convert is also a CLI command, so the same conversions run from a shell — with --dry-run
to preview the resolved input → output plan first:
colstore convert a.h5 b.h5 c.h5 -o all.cstore # merge named files into one .cstore
colstore convert "*.h5" -o all.cstore --on-mismatch drop # or a glob (quoted, or shell-expanded)
colstore convert big.cstore -o big.parquet --columns id,x --batch-size "256 MiB"
colstore convert "*.h5" --output-dir out/ --max-workers auto # convert each file in parallel
Pass dtypes={name: dtype} to coerce named columns as they are read — useful to give a
column the same dtype across files whose schemas differ (e.g. a flag that is bool in some
files and all-null in others, which would otherwise mismatch as b1 vs f8 on a multi-file
open):
ds = colstore.convert("in.h5", "out.cstore", dtypes={"flag": "bool"})
The coercion rule is exact. A column with real values is cast with NumPy astype, so a
too-narrow target truncates (1.9 → 1) or overflows (300 → int8) without error — pick a
target that fits. A missing value (a NaN in a float column, which is how an all-null or
in-band-null column arrives) maps to the target's natural empty value:
| Target dtype | A missing (NaN) value becomes |
|---|---|
float32 / float64 |
NaN (kept — float carries its own missing) |
bool |
False |
integer / unsigned (int64, uint16, …) |
0 |
fixed string (U, S) |
"" (empty string) |
datetime64 / timedelta64 |
NaT |
This holds whether the column is entirely missing or only partly so. A dtypes entry naming
a column the file does not have raises KeyError. (The override applies to the column-based
formats — Parquet / Feather / JSON / HDF5 / NPZ — not ROOT.)
Compaction
A streaming write produces one record per write() call. Reads of
slice and sorted-fancy index patterns scale near-flat with record count,
but unsorted-fancy reads degrade as records accumulate. colstore.compact
collapses all records into one, so every read pattern takes the
single-record fast path:
colstore.compact("data.cstore") # in-place
colstore.compact("data.cstore", out="new.cstore") # leave source untouched
In-place compaction writes to a sibling temp file and atomically renames
into place; the source is untouched on failure. The byte copy uses
os.sendfile on Linux (kernel-space copy, no Python-side surfacing)
and shutil.copyfileobj on macOS/Windows; on both paths memory
footprint is bounded by the kernel/I/O buffer (tens of KB) regardless
of file size — files much larger than RAM compact fine.
Multiple files and datasets
A run is often split across many same-schema .cstore files. Open them as one
logical table — every read decomposes across the files and is stitched back
together, with no data copied:
ds = colstore.open(["jan.cstore", "feb.cstore", "mar.cstore"]) # a ColStoreDataset
ds.n_rows # sum of the files
ds[1_000:2_000, ["price", "qty"]].dict() # slices span the files transparently
ds[[5, 1_000_000, 7], "price"].array() # fancy and boolean selection too
By default the files must share one schema — the same column names and dtypes — or open
raises a ValueError. Column order need not match: reads are by name, so files that store
the same columns in a different order open as one table (the dataset takes the first file's
order). To open a set of files whose schemas drifted further (a column that is bool in
some files and all-null float64 in others, say), pass on_mismatch="drop": the dataset
then exposes only the columns common to every file with one consistent dtype, warning about
the rest. Reads stay zero-copy — only the dataset's exposed schema narrows.
ds = colstore.open("run_*.cstore", on_mismatch="drop") # drop columns that disagree
The result is a ColStoreDataset. It is empty-constructible and growable, and
takes a mix of paths (which it opens and owns) and already-open readers or
datasets (which it borrows and leaves open):
from colstore import ColStoreDataset
ds = ColStoreDataset() # empty; grow it later
ds.append("jan.cstore") # opens and owns this file
ds.append(existing_reader) # borrows an open reader
ds |= another_reader # in-place combine (borrows)
combined = reader_a | reader_b | reader_c # combine open readers into one dataset
A dataset supports everything a single-file reader does — indexing, the lazy
views, dict()/recarray()/frame(), and edit() — by delegating to the
per-file readers, so the tuned single-file gather path is reused unchanged; a
one-file dataset costs the same as the bare reader. Closing a dataset closes
only the files it opened, so readers you passed in stay open and remain yours to
close.
To materialize the combination as one physical file, use concat:
# Lazy: a dataset over the files, no copy — the same as open([...]).
ds = colstore.concat(["jan.cstore", "feb.cstore"])
# Eager: stream the combined data into one new file, in bounded memory.
reader = colstore.concat(["jan.cstore", "feb.cstore"], out="q1.cstore")
The written file reads back on the single-record fast path. See the dataset read decomposition diagram for how a read is split across files and reassembled.
Growing a dataset by appending
A dataset can also be a directory, grown one piece at a time. Each new piece is
written as its own immutable .cstore shard, and the directory's contents are
the dataset — open(dir) reads every shard in order, the same as open([...])
over a list:
colstore.append("trades/", jan) # writes trades/shard_00000.cstore
colstore.append("trades/", feb) # writes trades/shard_00001.cstore
ds = colstore.open("trades/") # the whole directory as one table
ds.n_rows # sum across every shard
append writes only the new rows, so extending a large dataset stays cheap no
matter how big it already is — unlike re-running concat to a single file, which
rewrites everything each time. There is nothing to keep in sync by hand: add a
shard and it is in the dataset; remove one and it is gone. New shards take the
next free shard_{index:05d}.cstore name; pass name= to choose another
template (any {index} field) or a fixed filename.
To stream many batches without holding them all in memory, use appender, which
rolls a new shard each time the buffer reaches a size budget (a row count, a byte
string like "256 MiB", or None to roll only on an explicit flush()):
with colstore.appender("trades/", shard_size="256 MiB") as out:
for batch in source:
out.write(batch) # buffered; rolls a shard at the budget
# the remainder is flushed and the shard committed on exit
Every shard must share the schema of the ones already there, and is committed
atomically — a reader only ever sees a complete shard, and an interrupted write
leaves nothing behind. One writer holds the directory at a time. Pass
statistics=True (to either append or appender) to record per-record
min/max bounds in each shard, so a later filtered read can skip shards that
cannot match.
Introspection
i = colstore.info("data.cstore")
# ColStoreInfo(path='data.cstore', n_rows=1_000_000, n_records=42,
# columns=[a:<f4, b:<i8], file_size=8_001_232B,
# needs_compaction=True)
colstore.schema("data.cstore")
# [{'name': 'a', 'dtype': '<f4', 'encoding': 'raw', 'nullable': False}, ...]
Both info and schema read only the file header (no record bodies are
scanned), so they're cheap on multi-GB files.
Performance & tuning
Zero-copy reads
Materializing a whole store normally copies every column out of the mapping into
fresh arrays — doubling peak memory and reading+writing every byte before you
touch it. When the layout allows it, copy=False instead returns read-only
views over the page cache itself:
ds = colstore.open("data.cstore") # ideally compacted first
d = ds.dict(copy=False) # read-only ndarrays backed by the mmap
total = d["energy"].sum() # computed straight from page-cache bytes
ds["price"].array(copy=False) # one read-only column
ds[100:200, ["price", "qty"]].frame(copy=False) # a read-only DataFrame aliasing the mmap
copy=False is a guarantee, not a hint: it returns a real view or raises —
never a silent copy. It is supported exactly when the store is single-record
(colstore.compact produces these), the dtype is native byte order, and the
selector is whole-store / an int / a slice of any step. A fancy or boolean
selector needs a gather, which by definition copies, so it raises ValueError
with the remedy; the whole-table forms are all-or-nothing and never return a mix
of views and copies.
array, dict, and frame all take copy=False; recarray always repacks
(it interleaves the columns into one record buffer) and so ignores it. View
creation is O(1) in column size and halves peak resident memory — the data
stays page-cache-backed and reclaimable instead of committed to a second buffer.
Views pin the mapping, so they stay valid after ds.close(). Because the views
are read-only they cannot corrupt the store; use the default copy=True when you
need to mutate. The full contract is in
Performance & internals §7.
Configuration
from colstore import (
set_max_workers,
set_default_madvise,
set_default_backend,
set_gather_thread_cap,
calibrate,
)
from colstore import config
set_max_workers(8) # parallel gathers across columns
set_default_madvise("sequential") # OS read-ahead hint for sorted-index reads
set_default_backend("cpp") # gather kernel: cpp | numpy
set_gather_thread_cap(16) # threads per gather (default scales with socket count)
config.set_numa_policy("auto") # page placement: auto (interleave on multi-node) | local
config.set_write_method("auto") # write fill: auto (pwrite where available) | pwrite | mmap
config.set_preview_rows(20) # rows shown by head()/tail() and the Jupyter repr (default 10)
config.set_preview_precision(4) # decimal places shown for floats in a preview (default 6)
config.set_preview_memory_limit(512 * 1024 * 1024) # warn before a larger head()/tail()
calibrate() # one-time: measure the thread/prefetch knees for this host
In a notebook, displaying a reader, dataset, or concrete view renders a small
preview table (_repr_html_); a still-lazy query() / col() view shows a
compact card instead, since previewing it would have to read its predicate
columns. ds.head(n) / ds.tail(n) return a Preview that renders as an HTML
table in a notebook and a plain-text table in a terminal; indexing and array
attributes (.shape, .values, head["pt"]) delegate to the underlying numpy
data. On a filtered view, ds.query(...).head() previews the matching rows. The
default row count and the size at which a preview warns are the config
settings above.
How reads parallelize
A gather's thread count is decided in two stages. A single-column read runs at
the full gather thread cap; a multi-column read (dict / recarray / frame)
either splits the cap across a per-column pool or runs the columns sequentially
at the full cap, depending on the route taken. Either way the kernel's
resolve_thread_count has the final say: it scales the actual thread count with
the number of indices and clamps it to the cap, so small reads stay serial and
only large ones spend the whole budget.
The cap itself defaults to half the physical cores, bounded by a per-socket
allowance so multi-socket hosts (with more memory channels) get a higher
default; colstore.calibrate() refines it per host by measuring the saturation
knee directly.
NUMA placement
On a multi-node (multi-socket) Linux host, the default auto policy interleaves
a store's pages across nodes so the memory controllers share the load; on a
single-node host, or under local, it leaves the kernel's first-touch placement
that keeps pages near the reading thread. Placement is decided at the first page
fault, so warm pages cannot be moved — only a cold read (pages not yet resident)
is placed according to the policy.
Whether the best cold-read placement depends on the access pattern was the one
case the warm sweep never covered. benchmark/check_cold_read_placement.py
measured it on a multi-node host: cold, across contiguous, sorted-fancy, and
random-scatter selectors, interleave was at least as fast as local for every
pattern (1.10× on the contiguous scan, within noise otherwise) and never
slower. The winner never flips to local, so there is nothing for a per-pattern
mechanism to exploit and the single auto default stands. Revisit only if a
host or access pattern is found where local beats interleave for cold reads.
Pinning the gather threads (rather than placing pages) is a separate, opt-in
lever that ships off: a placement × binding × cap sweep measured spread
binding 24–51% slower on a multi-node host, so gather_binding defaults to off
and the realized path is the unbound default pool.
How writes reach disk
Every write — frame.write(), concat(..., out=...), write_dataset — chooses
a path and a fill method:
A write takes the merge copy route only when every output column is an
unchanged on-disk passthrough — a same-schema concat with no edits, say; any
transform, added, dropped, or renamed column, in-memory or constant column, or
single source routes through the streaming write instead.
Why the fill method matters. An mmap'd output is dirtied one page at a
time; a parallel filesystem serves that pattern poorly — a 1 GB write faults
~250k pages and runs at a fraction of the device's bandwidth. pwrite issues
large contiguous writes instead, which such filesystems serve well: measured ~2×
faster for the streaming write and ~4× for the merge copy on a parallel
filesystem (zero page faults vs ~250k), and faster node-local too. pwrite is
therefore the default wherever os.pwrite exists; mmap remains the fallback on
Windows.
Controlling it. The default (auto) is right on every filesystem measured so
far. Override only to force a method — to reproduce the mmap path, or on a
platform where it is faster:
from colstore import config
config.set_write_method("pwrite") # auto (default) | pwrite | mmap
The merge copy reuses the gather thread budget to copy its byte ranges in parallel; the streaming write fills one batch at a time within the configured memory budget. Output is byte-identical across every method.
Format & internals
On-disk format
[magic 8B = b"CSTORE\x00\x01"]
[counters 64B: n_records(8) + committed_rows(8) + stats_offset(8) + crc32(4) + reserved(36)]
[manifest_len 8B (u64 little-endian)]
[manifest_json: format_version + columns + manifest_crc32]
[zero-padding to 64-byte alignment]
[record_0 header 32B][record_0 body, padded to 8B]
[record_1 header 32B][record_1 body, padded to 8B]
...
The JSON manifest is immutable and carries only the schema; the mutable record/row counters live in their own fixed-position 64-byte block (with its own CRC) right after the magic. Each record body holds the columns back-to-back as raw bytes. A one-shot write produces a single-record file that reads via a per-column memmap fast path; a streamed write produces a multi-record file with per-pattern dispatch (contiguous range, sorted fancy, unsorted fancy).
The segment table
A logical column is one contiguous index space to the reader, but on disk its bytes are scattered: a multi-record file holds one run of the column per record (interleaved with the other columns), and a multi-file dataset spreads those runs across files. Reads bridge that gap with a segment table, the one structure the mask and fancy-gather kernels address through.
A segment is one column's run inside one record of one file — the smallest
unit that is contiguous on disk. The table is two int64 arrays per column:
start_rows— then_segments + 1cumulative row boundaries, so segmentsowns global rows[start_rows[s], start_rows[s + 1]).segment_base— one folded absolute byte address per segment. It bakes in the file's mapping base, the record's body offset, this column's prefix within the record, and the segment's row origin, so that the value at global rowiis simplysegment_base[s] + i * itemsize. Thestart_rows[s]shift is folded into the base, so the global row index plugs in directly — no per-segment rebasing in the inner loop.
To read global row i: binary-search start_rows for its segment s, then read
at segment_base[s] + i * itemsize.
The encoding is the same whichever way the rows are split, which is why one
kernel serves every case: a single-file multi-record store is the records of
one file; a multi-file dataset stitches every file's records into one global
table (ColStoreDataset memoizes it per column, since it depends only on the
column and the mappings, not the rows a read requests). A single-record file is
the degenerate case — one segment over the column's own memmap.
Supported dtypes
All fixed-size NumPy dtypes are supported: float32/float64,
int8/16/32/64, uint8/16/32/64, bool, datetime64, timedelta64,
and fixed-width strings (S bytes and U unicode, with the width baked
into the dtype, e.g. S16 or U8). Object dtype (variable-length
strings, Python objects) is rejected at write time — the design point is
zero-copy random access, which requires a fixed stride per row.
About
Design philosophy
A few choices shape everything above:
- Load and write, not stream-compute. colstore persists a structured array and reads arbitrary row/column subsets back fast; it is not a query engine. Reads are memory-mapped, so process memory stays bounded by the output you ask for, never the file size.
- Speed first, on the hardware that matters. The hot paths — every
access-pattern gather, the SoA→AoS record interleave, the contiguous and merge
copies — run in C++/OpenMP kernels bound through Cython, dispatched per pattern
(contiguous range, strided, sorted/unsorted fancy, boolean mask). Performance is
judged on multi-socket, multi-NUMA compute nodes; wheels stay portable (no
-march=native), with per-host calibration of the thread cap and prefetch distance. - No optimization ships without measurement. Every change is gated by an
interleaved A/B against the path it replaces, asserted output-identical, and
carries a committed
benchmark/check_*.py. Rejected and deferred alternatives are recorded with the measurement that closed them and a named reopen condition (see the optimization series), so an idea is not relitigated without new evidence. - Zero-copy where the layout permits. A compacted, native-byte-order store is
contiguous on disk, so whole-column reads can hand back read-only views of the
page cache (
copy=False) instead of copying. The format is built around a fixed stride per row to keep this possible — which is why variable-length object dtypes are refused at write time. - Correctness is not traded for speed. Misaligned packed columns load safely
(UBSan-verified), writes commit atomically via a fixed counters block, a reader
opening mid-write sees only the last committed state, and
copy=Falseis a hard guarantee that raises rather than silently copying.
Documentation
The full documentation — the guides below plus the complete API reference — is
published at alkaidcheng.github.io/colstore.
The guides also render in-repo under docs/:
- Performance & internals — the file layout, the kernel behind each access pattern, how reads parallelize, NUMA placement, and zero-copy.
- Format interop — convert to and from Parquet, Feather, JSON, HDF5, NPZ, and ROOT, plus the zero-copy Arrow bridge.
- Gather diagnostics — re-measure the thread, binding, and placement knobs on your own hardware.
- Optimization series — the cumulative record of every optimization and the measurement behind it.
- Valgrind leak checking — the native-leak
Memcheck harness under
scripts/.
License
MIT License - see LICENSE file for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file colstore-0.5.1.tar.gz.
File metadata
- Download URL: colstore-0.5.1.tar.gz
- Upload date:
- Size: 656.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e806d5d87922d2b5647bb5c73ddefa9d5296515dd6b498dac2b4a49db3155768
|
|
| MD5 |
98783a9cb8cc67f6cfd1efafdd7fc302
|
|
| BLAKE2b-256 |
e40dfecd7ffb95e41bc3e1098e156299eb3b0bd1fcae3df0330458db7163b3cd
|
Provenance
The following attestation bundles were made for colstore-0.5.1.tar.gz:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1.tar.gz -
Subject digest:
e806d5d87922d2b5647bb5c73ddefa9d5296515dd6b498dac2b4a49db3155768 - Sigstore transparency entry: 2040025007
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: colstore-0.5.1-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 362.4 kB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c053ed37f7d75eb3626be017e4df054e08646838c8851f4f702fd2360e95f71d
|
|
| MD5 |
6ca2b728fe5055203effcb0954ca603c
|
|
| BLAKE2b-256 |
8625840f8fd4652659eb670ad5261048ed74cd801526a1baa48d0544b7c657cd
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp313-cp313-win_amd64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp313-cp313-win_amd64.whl -
Subject digest:
c053ed37f7d75eb3626be017e4df054e08646838c8851f4f702fd2360e95f71d - Sigstore transparency entry: 2040026304
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp313-cp313-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: colstore-0.5.1-cp313-cp313-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 527.5 kB
- Tags: CPython 3.13, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1a7c03927d30eabe3fce9a57d4a84d4e4946502ff746d2ea6602182ffa3e7b55
|
|
| MD5 |
6371cbd4860d906fc95ee22cfd51e0dc
|
|
| BLAKE2b-256 |
503945ee2549d1a2386a44f99c607b3fef46959c9404a8d08dd95ceae7fccb83
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp313-cp313-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp313-cp313-manylinux_2_28_x86_64.whl -
Subject digest:
1a7c03927d30eabe3fce9a57d4a84d4e4946502ff746d2ea6602182ffa3e7b55 - Sigstore transparency entry: 2040026108
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: colstore-0.5.1-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 363.6 kB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d82333a90890c223c08ffe5642337a38b473cc3728daf8aca53e86a26385746e
|
|
| MD5 |
d0d23d908cba7f3c87291f15cdb31160
|
|
| BLAKE2b-256 |
0906a9ce9a6000a145c92f21f7553f339b5f6eaa3ade67f11738a1a7b75546e6
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp313-cp313-macosx_11_0_arm64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp313-cp313-macosx_11_0_arm64.whl -
Subject digest:
d82333a90890c223c08ffe5642337a38b473cc3728daf8aca53e86a26385746e - Sigstore transparency entry: 2040025491
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: colstore-0.5.1-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 362.3 kB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf0c8b97d9768e6a933ae852a74d5f04f27be12c917b22c3b1331beace7c0887
|
|
| MD5 |
1b11d65808283dc286b783c7fc3f0106
|
|
| BLAKE2b-256 |
48568ebfa3301a3b4973d6db5c63c3e76d9e7d7c3f3dc3eeec4c6e2cc0a98af4
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp312-cp312-win_amd64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp312-cp312-win_amd64.whl -
Subject digest:
cf0c8b97d9768e6a933ae852a74d5f04f27be12c917b22c3b1331beace7c0887 - Sigstore transparency entry: 2040025424
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp312-cp312-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: colstore-0.5.1-cp312-cp312-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 528.7 kB
- Tags: CPython 3.12, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
892969938e06d8601a02575d41e5fbeb9e7f24ea82d32fa9a91c14c8fc52608a
|
|
| MD5 |
5ce187aaca2793a11a868a986096d074
|
|
| BLAKE2b-256 |
7815111db98f4acd6299ab8ae148c6bf4b6e40efb8f73fd14966ebd42c1fcb28
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp312-cp312-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp312-cp312-manylinux_2_28_x86_64.whl -
Subject digest:
892969938e06d8601a02575d41e5fbeb9e7f24ea82d32fa9a91c14c8fc52608a - Sigstore transparency entry: 2040026012
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: colstore-0.5.1-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 363.9 kB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
50e4f4e74af5dd5176730a4ff4551adb9b12491a0f975e689ffc8b9cd2fd16a9
|
|
| MD5 |
2c9c8d0a641e05916e51240ba17cd133
|
|
| BLAKE2b-256 |
0988901e3f7a91520fb01148cd68a2a35a66cbc6f40788804a347aed7f0940af
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp312-cp312-macosx_11_0_arm64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp312-cp312-macosx_11_0_arm64.whl -
Subject digest:
50e4f4e74af5dd5176730a4ff4551adb9b12491a0f975e689ffc8b9cd2fd16a9 - Sigstore transparency entry: 2040026450
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp311-cp311-win_amd64.whl.
File metadata
- Download URL: colstore-0.5.1-cp311-cp311-win_amd64.whl
- Upload date:
- Size: 363.0 kB
- Tags: CPython 3.11, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9cd1d6ae7e1d91f5ad88eaa0fbfe8f4943f989ff314ab52438235ed5ef24f8d
|
|
| MD5 |
7869af5684ebc010b5ab687d92043329
|
|
| BLAKE2b-256 |
c37b9a76617dde8ac1dad1decb623102f0c595e4dc7d6c3aebeb1648b44609e2
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp311-cp311-win_amd64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp311-cp311-win_amd64.whl -
Subject digest:
a9cd1d6ae7e1d91f5ad88eaa0fbfe8f4943f989ff314ab52438235ed5ef24f8d - Sigstore transparency entry: 2040025139
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp311-cp311-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: colstore-0.5.1-cp311-cp311-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 533.0 kB
- Tags: CPython 3.11, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60a8e302d9a92acc97a9aa06d71f4bc15d432e02e0f790c998e695f45ef9a4f5
|
|
| MD5 |
e0fa916d2a70598a43873f4967fa5fea
|
|
| BLAKE2b-256 |
da0335e84c25995d786bd11eaeb961be9db07cedeb1cb9475ca8439ec8d7e3f5
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp311-cp311-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp311-cp311-manylinux_2_28_x86_64.whl -
Subject digest:
60a8e302d9a92acc97a9aa06d71f4bc15d432e02e0f790c998e695f45ef9a4f5 - Sigstore transparency entry: 2040025312
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp311-cp311-macosx_11_0_arm64.whl.
File metadata
- Download URL: colstore-0.5.1-cp311-cp311-macosx_11_0_arm64.whl
- Upload date:
- Size: 361.4 kB
- Tags: CPython 3.11, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90a98e84d35fb7c8b51f6a16c20059799ae14479bff41f7bcc6d973621d67d8c
|
|
| MD5 |
3f3f386514db48f9ea961bd9bcd6992a
|
|
| BLAKE2b-256 |
b717b426fe3cca72e1de04dfe27b9c5ba11db89c5b154c10139236a4228779d9
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp311-cp311-macosx_11_0_arm64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp311-cp311-macosx_11_0_arm64.whl -
Subject digest:
90a98e84d35fb7c8b51f6a16c20059799ae14479bff41f7bcc6d973621d67d8c - Sigstore transparency entry: 2040025676
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp310-cp310-win_amd64.whl.
File metadata
- Download URL: colstore-0.5.1-cp310-cp310-win_amd64.whl
- Upload date:
- Size: 363.2 kB
- Tags: CPython 3.10, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c5fc1b92da881f0ea9f7c6c3afade9b8078ea8c864c135070d928ee4e6bc470
|
|
| MD5 |
c8c333a61c09e646e28c798baf9802b4
|
|
| BLAKE2b-256 |
e699a9a0ef042cfea39fede6ef4e232fb92990b2cb5a17ecf5a5c67729d7d80a
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp310-cp310-win_amd64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp310-cp310-win_amd64.whl -
Subject digest:
6c5fc1b92da881f0ea9f7c6c3afade9b8078ea8c864c135070d928ee4e6bc470 - Sigstore transparency entry: 2040025802
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp310-cp310-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: colstore-0.5.1-cp310-cp310-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 532.9 kB
- Tags: CPython 3.10, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
67878ba074534b2834e7e466b1c85a5ee8175283af748c4e59b4a316fe09edf8
|
|
| MD5 |
cd206fb9150429b12db3e60de8f27308
|
|
| BLAKE2b-256 |
f82b4b8e607642f651022212662e906461aebdaa7f30e5111e119e401b395f5c
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp310-cp310-manylinux_2_28_x86_64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp310-cp310-manylinux_2_28_x86_64.whl -
Subject digest:
67878ba074534b2834e7e466b1c85a5ee8175283af748c4e59b4a316fe09edf8 - Sigstore transparency entry: 2040025864
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type:
File details
Details for the file colstore-0.5.1-cp310-cp310-macosx_11_0_arm64.whl.
File metadata
- Download URL: colstore-0.5.1-cp310-cp310-macosx_11_0_arm64.whl
- Upload date:
- Size: 362.0 kB
- Tags: CPython 3.10, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a40340f91227c47aecc3e6861a995eb974cc58dc6f4dfe867c00af9bfcd07b79
|
|
| MD5 |
91d98dc3c93f43f055b4617c3f4447f7
|
|
| BLAKE2b-256 |
b1c042f0f33aafec9e9b5d4e81420b0792826539eefd38b734f6c921b918e237
|
Provenance
The following attestation bundles were made for colstore-0.5.1-cp310-cp310-macosx_11_0_arm64.whl:
Publisher:
release.yml on AlkaidCheng/colstore
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
colstore-0.5.1-cp310-cp310-macosx_11_0_arm64.whl -
Subject digest:
a40340f91227c47aecc3e6861a995eb974cc58dc6f4dfe867c00af9bfcd07b79 - Sigstore transparency entry: 2040025585
- Sigstore integration time:
-
Permalink:
AlkaidCheng/colstore@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Branch / Tag:
refs/tags/v0.5.1 - Owner: https://github.com/AlkaidCheng
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@35d60c5f3ac4b1f6627d8f17b1af85f6ea7cbb38 -
Trigger Event:
release
-
Statement type: