Skip to main content

C++ accelerated data preparation for pandas and the Python data stack

Project description


Arnio



Fast data preparation for the Python data stack.


Arnio is a compiled C++ data preparation engine for messy CSV and pandas workflows.
It parses, infers types, strips whitespace, deduplicates, validates, and profiles data —
then hands clean results back to the tools you already use.
Use Arnio before and alongside pandas, NumPy, scikit-learn, DuckDB, and Arrow.


PyPI  Python  CI  Coverage  MIT  GSSoC 2026  Join Discord PyPI Downloads



pip install arnio

Colab install smoke test: COLAB_SMOKE_TEST.md


Quickstart · Integrations · Why Arnio · Architecture · Benchmarks · Community · Contribute




⚡ Quickstart

A simple workflow in just a few steps.

New to Arnio? Start with the pandas workflow example below before exploring advanced pipelines.

import arnio as ar

# Load CSV directly through C++ — no Python parsing overhead
frame = ar.read_csv("messy_sales_data.csv")

# Strict mode (default) fails on inconsistent row widths
frame = ar.read_csv("messy_sales_data.csv", mode="strict")

# Permissive mode fills missing trailing values with nulls
frame = ar.read_csv("messy_sales_data.csv", mode="permissive")

# Declare what clean data looks like — arnio handles the rest
clean = ar.pipeline(frame, [
    ("strip_whitespace",),
    ("normalize_case", {"case_type": "lower"}),
    ("fill_nulls", {"value": 0.0, "subset": ["revenue"]}),
    ("drop_nulls",),
    ("drop_duplicates",),
])



# Out comes a standard pandas DataFrame — use it like you always have
df = ar.to_pandas(clean)

# Use copy=True when you need defensive pandas-owned buffers
safe_df = ar.to_pandas(clean, copy=True)

Dry Run Validation

Use dry_run=True to validate pipeline configuration and step execution without returning transformed output.

ar.pipeline(
    frame,
    [
        ("drop_nulls",),
    ],
    dry_run=True,
)

Need step timings for debugging? Opt in without changing the default pipeline return type:

clean, metadata = ar.pipeline(
    frame,
    [("strip_whitespace",), ("drop_duplicates",)],
    return_metadata=True,
)

print(metadata["step_timings"])
print(metadata["applied_steps"])
print(metadata["row_counts"])

Quick Example

import arnio

frame = arnio.read_csv("sample.csv")

# Preview first 5 rows
frame.preview(5)

# Generate and view scannable summary statistics
print(frame.describe())

Pipeline validation behavior

Pipeline step specifications are validated before execution begins.

Malformed step tuples, invalid kwargs structures, or unknown step names fail early before any pipeline steps execute.

ar.pipeline(
    frame,
    [
        ("strip_whitespace",),
        ("bad_step", "oops", "extra"),
    ],
)

This prevents partial pipeline execution when later pipeline steps are invalid.

Already have a pandas DataFrame? Use Arnio in-place in your existing pandas workflow:

import pandas as pd
import arnio as ar

df = pd.read_csv("messy_sales_data.csv")

clean_df = df.arnio.clean([
    ("strip_whitespace",),
    ("normalize_case", {"case_type": "lower"}),
    ("drop_duplicates",),
])

report = clean_df.arnio.profile()

Cross-field validation rules

Pass a rules list to Schema for checks that span multiple columns. Each rule receives the full pandas DataFrame and must return a list[ValidationIssue] — an empty list means the rule passed.

import arnio as ar

def end_after_start(df):
    return [
        ar.ValidationIssue(
            column="end_date",
            rule="cross_field",
            message="end_date must be >= start_date",
            row_index=int(i) + 1,
        )
        for i, row in df.iterrows()
        if row["end_date"] < row["start_date"]
    ]

schema = ar.Schema(
    {"start_date": ar.String(), "end_date": ar.String()},
    rules=[end_after_start],
)

result = schema.validate(ar.read_csv("events.csv"))
print(result.passed)

Row index convention: ValidationIssue.row_index values are 1-based and count data rows only. The header row is excluded. row_index=1 is the first data row in the file.

Schema diff reports

Use diff_schema() to compare expected and observed data contracts across datasets, releases, or generated schemas.

import arnio as ar

expected = ar.Schema({
    "id": ar.Int64(nullable=False, unique=True),
    "email": ar.Email(nullable=False),
})

observed = ar.Schema({
    "id": ar.Int64(nullable=False),
    "created_at": ar.DateTime(format="%Y-%m-%d"),
})

diff = ar.diff_schema(expected, observed)
print(diff.summary())
print(diff.to_markdown())

CI data contracts (GitHub Actions)

If you want to block schema drift or invalid rows in pull requests, see DATA_CONTRACT_CI.md for an inert copy-paste GitHub Actions workflow example.

Example contract files are included under examples/contracts/.

Select specific columns

Use select_columns() to create a new ArFrame with only the required columns before converting to pandas.

selected = ar.select_columns(frame, ["name", "revenue"])

print(selected.columns)
# ['name', 'revenue']
  • Preserves the requested column order.
  • Returns a new ArFrame.
  • Raises ValueError if any requested column does not exist.
  • Raises TypeError if columns is not a sequence of strings.

Handling missing values

Arnio supports configuring which strings are treated as null during CSV parsing using the null_values parameter in read_csv and scan_csv. By default, Arnio preserves its existing behavior and treats only empty cells as null. Custom matching is case-insensitive and applies to cell values only (not headers).

# Default behavior: empty cells are null
frame = ar.read_csv("data.csv")

# Provide a custom list of sentinels (overrides the empty-cell default)
frame = ar.read_csv("data.csv", null_values=["", "MISSING", "UNKNOWN"])

# Disable null sentinel handling completely
frame = ar.read_csv("data.csv", null_values=[])

Handling decimal separators

Use decimal_separator when numeric CSV data uses a separator other than the default dot. This is explicit by design: Arnio does not auto-detect decimal formats because a comma can also be the CSV delimiter.

# Semicolon-delimited CSV with unquoted European decimals
frame = ar.read_csv("prices.csv", delimiter=";", decimal_separator=",")

# Comma-delimited CSV still needs quoted comma-decimal values
frame = ar.read_csv("prices.csv", decimal_separator=",")

The default remains decimal_separator=".", so existing dot-decimal files keep their current behavior. If you also use thousands_separator, it must differ from decimal_separator.

Handling invalid UTF-8 bytes

Use encoding_errors to control how invalid UTF-8 bytes are handled during CSV parsing.

# Raise an error on invalid UTF-8 bytes (default)
frame = ar.read_csv(
    "data.csv",
    encoding_errors="strict",
)

# Replace invalid bytes with the Unicode replacement character (�)
frame = ar.read_csv(
    "data.csv",
    encoding_errors="replace",
)

# Ignore invalid bytes completely
frame = ar.read_csv(
    "data.csv",
    encoding_errors="ignore",
)

Supported values:

  • "strict" (default)
  • "replace"
  • "ignore"

Every step above executes in C++. Your Python code is a configuration — not the execution engine.

Explore more in the examples/ folder — ready-to-run recipes for sales, customers, survey, logs, and finance datasets.


Security note: CSV formula injection

Arnio preserves cell values when reading CSV files. It does not rewrite strings that begin with spreadsheet formula prefixes such as =, +, -, or @.

If you export Arnio-cleaned data back to CSV and expect users to open that file in Excel, Google Sheets, LibreOffice, or another spreadsheet application, treat untrusted text fields as potentially executable spreadsheet formulas. Before exporting, escape or neutralize formula-like strings in user-controlled columns, for example by prefixing a single quote or another project-approved escape marker.

This is especially important for customer names, notes, comments, imported form fields, and any other free-text values that may come from outside your trust boundary. Arnio focuses on parsing, validation, profiling, and cleanup; final CSV export policy should stay explicit in the application that writes the file.


Error Handling

read_csv and scan_csv

Input Raises Message
File not found CsvReadError Cannot open file: <path>
Zero-byte file CsvReadError CSV file is empty: '<path>'
Blank header line CsvReadError CSV header contains an empty column name
Binary / NUL bytes CsvReadError CSV input contains NUL bytes and appears to be binary or corrupted

Schema Validation

ar.validate() returns a ValidationResult; it does not raise for validation failures. Check result.passed and result.issues for dtype or required_column rule violations.

validate() currently operates on a single in-memory ArFrame. Chunked validation via read_csv_chunked() iterators is not yet supported directly. Validate each chunk individually or materialize the data before validation when working with streamed/chunked inputs.

Pipeline Step Errors

Unknown step names raise UnknownStepError before execution begins.

📸 Peek at a 100 GB file without loading it

scan_csv reads only the header + a sample to infer the schema. Zero data loaded.

# Pass sample_size to control how many rows are evaluated for type inference
schema = ar.scan_csv("100GB_file.csv", sample_size=500)
# {'id': 'int64', 'name': 'string', 'is_active': 'bool', 'revenue': 'float64'}

Useful for exploring datasets before committing memory.

📄 Read JSON Lines (JSONL / NDJSON) files

read_jsonl parses one JSON object per line into an ArFrame. Blank lines are skipped, missing keys become nulls, and mixed-type columns are coerced to string — the same rules as from_pandas.

# events.jsonl
# {"user": "alice", "score": 9.5, "active": true}
# {"user": "bob",   "score": 8.1, "active": false}

frame = ar.read_jsonl("events.jsonl")

# Limit rows
frame = ar.read_jsonl("large.jsonl", nrows=1000)

# Non-UTF-8 encoding
frame = ar.read_jsonl("data.ndjson", encoding="latin-1")

# Plug straight into the cleaning pipeline
clean = ar.pipeline(frame, [("strip_whitespace",), ("drop_nulls",)])

Raises ar.JsonlReadError with the 1-based line number if a line contains invalid JSON.

📦 Export to Parquet for columnar analytics pipelines

write_parquet exports an ArFrame to a Parquet file via pyarrow. Install the optional extra first:

pip install arnio[parquet]
# Basic export
ar.write_parquet(frame, "output.parquet")

# Choose compression codec: "snappy" (default), "gzip", "zstd", "brotli", "none"
ar.write_parquet(frame, "output.parquet", compression="zstd")

# Control row group size for large files
ar.write_parquet(frame, "output.parquet", row_group_size=50_000)

# .pq extension also accepted
ar.write_parquet(frame, "output.pq")

Raises ImportError with an install hint if pyarrow is not available.

👀 Preview rows without pandas conversion or full-column Python list materialization

preview() reads only the first n rows directly from the C++ frame — no pandas conversion triggered.

frame = ar.read_csv("huge_file.csv")

print(frame.preview())      # first 5 rows (default)
print(frame.preview(n=10))  # first 10 rows

Raises ValueError for invalid n (zero, negative, or non-integer).

💰 Financial Decimal Support

arnio provides support for converting Python decimal.Decimal objects.

  • Behavior: Python Decimal objects are automatically preserved as high-precision strings during serialization/binding to prevent floating-point precision loss.
  • Caveat: When reading back into Pandas, to_pandas() returns these as string (object dtype) columns. You will need to explicitly cast them back to Decimal objects on the resulting DataFrame if you want to resume exact math.

Example:

from decimal import Decimal

import pandas as pd

import arnio as ar

df = pd.DataFrame({
    "price": [Decimal("19.99"), Decimal("29.95")]
})

frame = ar.from_pandas(df)  # Decimal values safely preserved as exact strings
result = ar.to_pandas(frame)
# result["price"] will be string objects ["19.99", "29.95"]
🧩 Add custom steps without touching C++

Register any Python function as a pipeline step. It receives a DataFrame, returns a DataFrame.

def remove_outliers(df, column="revenue", threshold=100_000):
    return df[df[column] <= threshold]

ar.register_step("remove_outliers", remove_outliers)
ar.register_step("team:drop_nulls", remove_outliers)  # namespaced custom step

# Use builtin: for an explicit built-in step, and your own prefixes
# like team: or plugin_name: to avoid name collisions.

# Introspect built-in and custom step names without reaching into internals.
print(ar.list_steps())

# Opt in to a context object only when you need execution metadata.
def capture_context(df, context=None):
    print(context.step_name, context.step_index, context.total_steps)
    return df

# Now use it in any pipeline alongside native C++ steps
clean = ar.pipeline(frame, [
    ("builtin:strip_whitespace",),
    ("remove_outliers", {"column": "revenue", "threshold": 50000}),
    ("drop_duplicates",),
])

Need to inspect the built-in kwargs a step accepts before assembling a pipeline?

signatures = ar.get_builtin_step_signatures()
print(list(signatures["drop_nulls"].parameters))  # ["subset"]
print(list(signatures["filter_rows"].parameters))  # ["column", "op", "value"]

Need to restore the registry back to built-in steps only during tests?

ar.reset_steps()

print(ar.list_steps())
# Only built-in steps remain

Custom steps run through a pandas↔ArFrame conversion bridge. Prototype in Python, then optionally migrate hot paths to C++ for full speed.

🔄 Custom Step Overwrite Policy

By default, trying to register a custom step with a name that is already taken by another custom Python step will raise a ValueError to prevent silent overwriting.

To intentionally replace an existing custom Python step, pass overwrite=True:

def custom_logging(df):
    print("Running step v1")
    return df

ar.register_step("log_data", custom_logging)

# This will succeed and safely overwrite the original logic
def custom_logging_v2(df):
    print("Running step v2")
    return df

ar.register_step("log_data", custom_logging_v2, overwrite=True)

Note: Built-in C++ pipeline steps (like "drop_nulls") can never be overwritten, even if overwrite=True is explicitly supplied.

✂️ Slice rows with head() and tail()

head() and tail() return the first or last n rows as a new ArFrame.

frame = ar.read_csv("data.csv")

frame.head()     # first 5 rows (default)
frame.head(10)   # first 10 rows
frame.tail(3)    # last 3 rows

# n larger than row count returns all rows safely
frame.head(1000)

# n=0 returns an empty ArFrame
frame.head(0)

Raises ValueError for negative or boolean n.

Pipeline verbose diagnostics

Enable lightweight pipeline diagnostics with verbose=True:

result = ar.pipeline(
    frame,
    [
        ("strip_whitespace",),
        ("drop_nulls",),
    ],
    verbose=True,
)

This logs step execution order, execution path, elapsed time, and row-count changes through the arnio logger.




🔗 Integrations

Arnio is designed to make the rest of the Python data stack more productive, not to replace it.

Workflow How Arnio helps
pandas Clean, validate, and profile messy DataFrames through df.arnio.
NumPy Prepare typed numeric data before array/modeling workflows.
scikit-learn Use Arnio cleaning as a preprocessing layer before model training.
DuckDB / Arrow Validate and prepare data before analytics and columnar exchange. Export ArFrame to pyarrow.Table via ar.to_arrow(frame).
notebooks Inspect quality issues and cleaning suggestions before analysis.

DuckDB registration

Use ar.register_duckdb(frame, conn, "table_name") to register an ArFrame directly as a DuckDB relation without writing pandas conversion glue yourself. DuckDB is an optional dependency — install it with pip install duckdb when needed.

import duckdb
import arnio as ar

frame = ar.read_csv("data.csv")
conn = duckdb.connect()
ar.register_duckdb(frame, conn, "my_table")
result = conn.execute("SELECT * FROM my_table").fetchdf()

Row-dropping pipeline behavior

Some pipeline steps such as drop_nulls or drop_duplicates can change the number of rows returned during transform.

By default, ArnioCleaner raises a ValueError if a pipeline changes row count during transform because many scikit-learn workflows expect input and output sample counts to remain aligned.

If row-dropping behavior is intentional, pass allow_row_count_change=True when constructing ArnioCleaner.

cleaner = ArnioCleaner(
    steps=[
        ("drop_nulls",),
        ("strip_whitespace",),
    ],
    allow_row_count_change=True,
)

Pandas accessor

df = pd.read_csv("raw_customers.csv")

clean_df = df.arnio.clean(drop_duplicates=True)
quality = clean_df.arnio.profile()
validation = clean_df.arnio.validate({
    "email": ar.Email(nullable=False),
    "user_code": ar.Regex(r"^USR-\d{4}$", nullable=False),
    "age": ar.Int64(nullable=True, min=0),
    "score": ar.Custom("positive"),
})

This keeps pandas as the analysis tool while Arnio handles the preparation, quality, and validation layer.

Product direction: PROJECT_DIRECTION.md

📘 Examples

These examples demonstrate how Arnio integrates with the Python data ecosystem.

They follow a simple workflow:

clean/validate data with Arnio → analyze with other tools

🔹 Interoperability Examples

  • Arnio + pandas Clean and normalize messy tabular data using Arnio, then analyze it using pandas. Run:
  python examples/arnio_with_pandas.py
  • Arnio + NumPy Prepare numeric data safely using Arnio, then perform computations using NumPy. Run:
  python examples/arnio_with_numpy.py
  • Arnio + scikit-learn Prepare messy data with Arnio, then train a model with scikit-learn. Run:
  python examples/arnio_with_sklearn.py
  • Arnio + DuckDB Clean data with Arnio, then run SQL queries using DuckDB. Run:
  python examples/arnio_with_duckdb.py
  • Arnio + Arrow Export ArFrame to pyarrow.Table using ar.to_arrow() for zero-copy interop with Arrow-native tools. Run:
  python examples/arnio_with_arrow.py



🔍 Why Arnio exists

Every data project starts the same way:

df = pd.read_csv("data.csv")              # 💥 RAM spike — entire file as raw strings
df.columns = df.columns.str.strip()        # Why is this not automatic?
df["name"] = df["name"].str.strip()        # Python loop over every cell
df["name"] = df["name"].str.lower()        # Another Python loop
df = df.dropna()                           # Another pass
df = df.drop_duplicates()                  # Another pass

Six lines. Four full-data passes. All in interpreted Python. This is fine for a Jupyter demo — but it doesn't scale, it doesn't compose, and it definitely doesn't belong in production.

Arnio intercepts this entire pattern. It moves the preparation layer into a predictable pipeline, accelerates supported operations in C++, and gives you clean data for pandas, NumPy, scikit-learn, DuckDB, or notebooks.

Without Arnio

df = pd.read_csv(path)
df.columns = df.columns.str.strip()
for col in str_cols:
    df[col] = df[col].str.strip()
    df[col] = df[col].str.lower()
df = df.dropna(subset=["revenue"])
df = df.drop_duplicates()
# 6+ lines, multiple passes, pure Python

With Arnio

frame = ar.read_csv(path)
df = ar.to_pandas(ar.pipeline(frame, [
    ("strip_whitespace",),
    ("normalize_case", {"case_type": "lower"}),
    ("drop_nulls", {"subset": ["revenue"]}),
    ("drop_duplicates",),
]))
# Declarative. Single pipeline. C++ execution.



🏗️ Architecture

Arnio is not a pandas wrapper. It's a separate runtime with its own data model.

flowchart LR
  subgraph python["Your Python Code"]
    PY["frame = ar.read_csv('data.csv')\nclean = ar.pipeline(frame, [...])\ndf = ar.to_pandas(clean)"]
  end

  python -->|"pybind11 boundary"| cpp

  subgraph cpp["C++ Runtime (_arnio_cpp)"]
    direction TB
    CSV["CsvReader\n• RFC 4180\n• BOM strip\n• Type inference\n• Quoted fields"]
    FRAME["Frame / Column\n• Columnar\n• std::variant\n• Bool null masks\n• O(1) column lookup"]
    CLEAN["Cleaning Engine\n• drop_nulls\n• fill_nulls\n• drop_dupes\n• strip_ws\n• normalize\n• rename/cast"]
    CSV --> FRAME --> CLEAN
  end

  cpp -->|"to_pandas() → zero-copy NumPy buffer (numerics/bools)"| OUT["pandas DataFrame"]

Design decisions that matter

Decision What it means
Columnar storage Data lives in typed std::vectors — vector<int64_t>, vector<double>, vector<string> — not rows of variants. Cache-friendly and SIMD-ready.
Boolean null masks Nulls are tracked in a separate vector<bool>, keeping data vectors dense. No sentinel values, no NaN tricks.
Two-pass CSV read Pass 1 infers types across all rows. Pass 2 parses values directly into the correct typed column. No string→object→cast overhead.
Zero-copy bridge to_pandas() exposes C++ memory directly via NumPy's buffer protocol where supported. Numeric columns preserve the fast zero-copy path by default, while copy=True requests defensive pandas-owned buffers.
Step registry Pipeline steps map to C++ function pointers. Adding a new cleaning primitive is a single function + one registry entry.

Full architecture documentation: ARCHITECTURE.md API reference guide: Arnio API Reference




🏎️ Benchmarks

Reference environment: Ubuntu, Python 3.12, synthetic messy CSV inputs.
Reproduce: make benchmark — generates deterministic tall and wide datasets and runs both engines.

To reproduce the published numbers from a fresh checkout:

python -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e .
python benchmarks/generate_data.py
python benchmarks/benchmark_vs_pandas.py

benchmarks/generate_data.py uses deterministic NumPy seeds, so every run creates the same benchmarks/benchmark_1m.csv tall input and benchmarks/benchmark_wide.csv wide input. The benchmark then executes three pandas runs and three arnio runs for each case, printing average wall-clock time from time.perf_counter() and peak Python allocation from tracemalloc. For cleaner comparisons, close other memory-heavy processes and run the script from the repository root after installing the same Python, pandas, NumPy, compiler, and arnio commit you want to compare.

Expected output format:

Tall CSV (1,000,000 rows x 12 columns)
Metric                     pandas        arnio
────────────────────────────────────────────
Exec Time (avg)       4.73s         5.75s
Peak RAM               211MB         212MB
Speed: 0.8x | RAM: -1% reduction

Wide CSV (5,000 rows x 256 columns)
Metric                     pandas        arnio
────────────────────────────────────────────
Exec Time (avg)       ...s          ...s
Peak RAM              ...MB         ...MB
Speed: ...x | RAM: ...% reduction

Small differences are expected across CPUs, operating systems, compilers, Python builds, and pandas/NumPy versions. If you share benchmark results in an issue or PR, include your OS, Python version, CPU model, pandas/NumPy versions, arnio commit, and the full command output so maintainers can compare like for like.

Arnio is near memory parity in the reference benchmark while replacing ad-hoc Python string loops with a compiled, declarative pipeline. Validate memory and speed on your own workload. The execution time gap is a known, active optimization target — the current drop_duplicates and strip_whitespace implementations use unoptimized row-key serialization.

What's already won 🎯 What's being optimized
  • Native C++ parsing eliminates Python memory spikes
  • Columnar storage matches pandas' internal efficiency
  • Declarative API eliminates .apply() spaghetti
  • Zero-copy bridge for numeric conversions
  • drop_duplicates — replace string serialization with hash-based comparisons
  • strip_whitespace — in-place mutation instead of copy-on-write
  • Parallel column processing via std::thread
  • Help close the gap →

🧠 Auto Clean Memory Benchmark

To measure the peak memory and execution time of the auto_clean pipeline using realistic dataset sizes:

python benchmarks/benchmark_auto_clean_memory.py --rows 100000

This script generates a reproducible synthetic dataset with mixed column types (strings, ints, floats, booleans, nulls, and duplicates) and measures:

  • ar.read_csv performance
  • ar.auto_clean(mode="safe") performance (low-risk cleanup like whitespace trimming)
  • ar.auto_clean(mode="strict") performance (includes type casting and deduplication)

The dataset is regenerated deterministically unless --reuse-file is provided. Each auto_clean benchmark run reloads the dataset to avoid mutation or caching effects between runs.

Options:

  • --repeat N runs each operation multiple times and reports average (and min/max range).
  • --seed N changes the deterministic dataset seed.
  • --reuse-file reuses an existing dataset file instead of regenerating it.
  • --keep-file keeps the generated CSV (otherwise it is removed at the end).

Expected output format:

Operation                    Time(s)     Peak Py(MiB)
--------------------------------------------------------------------
ar.read_csv           0.042 (0.041-0.044)    4.52 (4.50-4.60)
ar.auto_clean(safe)   0.012 (0.011-0.013)    0.15 (0.14-0.16)
ar.auto_clean(strict) 0.035 (0.034-0.036)    1.20 (1.18-1.22)
--------------------------------------------------------------------
Total avg (Read+Strict)       0.077             4.52



🧰 Cleaning primitives

Most operations below run natively in C++. Currently, filter_rows, replace_values and standardize_missing_tokens run via the Python (pandas) backend and may be optimized in C++ later.

Primitive What it does Example
drop_nulls Remove rows with null/empty values ar.drop_nulls(frame, subset=["age"])
drop_columns Remove selected columns while preserving the remaining order frame = ar.drop_columns(frame, ["debug_col"])
drop_empty_columns Remove columns whose values are all null/empty frame = ar.drop_empty_columns(frame)
keep_rows_with_nulls Keep only rows that contain at least one null ar.keep_rows_with_nulls(frame, subset=["age"])
validate_columns_exist Fail early when required columns are missing ar.validate_columns_exist(frame, ["age"])
filter_rows Filter rows using comparison operators ar.filter_rows(frame, column="age", op=">", value=18)
fill_nulls Replace nulls with a scalar ar.fill_nulls(frame, 0, subset=["revenue"])
drop_duplicates Deduplicate rows (first/last/none) ar.drop_duplicates(frame, keep="first")
drop_constant_columns Remove columns with only one unique value ar.drop_constant_columns(frame)
clip_numeric Clip numeric values to lower and/or upper bounds ar.clip_numeric(frame, lower=0, upper=100)
coalesce_columns Select the first non-null value from a list of columns ar.coalesce_columns(frame, subset=["phone", "mobile"], output_column="contact")
combine_columns Combine multiple columns into a single output column ar.combine_columns(frame, subset=["first", "last"], separator=" ", output_column="name")
strip_whitespace Trim leading/trailing spaces from strings ar.strip_whitespace(frame)
standardize_missing_tokens Replace common missing-value strings with NaN ar.standardize_missing_tokens(frame)
normalize_case Force lower/upper/title case ar.normalize_case(frame, case_type="title")
rename_columns Rename columns via mapping ar.rename_columns(frame, {"old": "new"})
cast_types Cast column types ar.cast_types(frame, {"age": "int64"})
round_numeric_columns Round numeric columns (non-numeric columns in subset ignored safely) ar.round_numeric_columns(frame, decimals=2)
replace_values Replace values using a mapping (column or whole-frame). Handles None/NaN. ar.replace_values(frame, {"active": "A", "inactive": "I"}, column="status")
clean Convenience shorthand ar.clean(frame, drop_nulls=True)
safe_divide_columns Divide one column by another, handling zero/null denominators ar.safe_divide_columns(frame, numerator="revenue", denominator="cost", output_column="ratio")
drop_columns_matching Drop columns whose names match a regex pattern ar.drop_columns_matching(frame, pattern="^temp_")
trim_column_names Strip leading/trailing whitespace from column names ar.trim_column_names(frame)
select_columns Return a new frame containing only selected columns ar.select_columns(frame, ["id", "name"])

ArFrame.select_dtypes — type-based column selection

Returns a new ArFrame containing only the columns whose dtype matches the filter. Raises ValueError if no columns match.

frame = ar.read_csv("data.csv")

# Keep only numeric columns
numeric = frame.select_dtypes(include=["int64", "float64"])

# Drop string columns
without_strings = frame.select_dtypes(exclude="string")

Valid dtype strings: "int64", "float64", "string", "bool", "null"

  • At least one of include or exclude must be given — raises ValueError otherwise.
  • include and exclude must not overlap — raises ValueError if they share a dtype.
  • Unknown dtype strings raise ValueError with a list of valid options.
  • Raises ValueError when no columns match (never returns an empty frame silently).
  • Column order in the result always matches the original frame.

Or compose them all into a pipeline:

clean = ar.pipeline(frame, [
    ("validate_columns_exist", {"columns": ["name", "city", "revenue"]}),
    ("drop_columns", {"columns": ["debug_notes"]}),
    ("strip_whitespace",),
    ("standardize_missing_tokens",),
    ("normalize_case", {"case_type": "lower"}),
    ("fill_nulls", {"value": "unknown", "subset": ["city"]}),
    ("drop_duplicates", {"keep": "first"}),
])

Winsorize outliers

winsorize_outliers() clips extreme numeric values using lower and upper quantiles. Non-numeric columns are ignored unless explicitly selected in subset.

frame = ar.read_csv("data.csv")

result = ar.winsorize_outliers(
    frame,
    lower=0.05,
    upper=0.95,
)

It can also be used inside ar.pipeline() as ("winsorize_outliers", {"lower": 0.05, "upper": 0.95}).

🔁 Replace values

Use replace_values to substitute values using a mapping. It works as a pipeline step (Python backend) and can operate on a single column or the whole frame when column is omitted. It also understands null semantics: using None (or np.nan) as a mapping key targets existing nulls, and mapping a value to None creates real nulls.

Column-specific example:

clean = ar.pipeline(frame, [
    ("replace_values", {"mapping": {"active": "A", "inactive": "I"}, "column": "status"}),
])

Whole-frame example (no column):

clean = ar.pipeline(frame, [
    ("replace_values", {"mapping": {None: "MISSING", "active": "A", "inactive": "I"}}),
])

Direct API:

frame2 = ar.replace_values(frame, {"active": "A", "inactive": "I"})

🔎 Filter rows inside pipelines

Use filter_rows to keep only rows matching a condition.

clean = ar.pipeline(frame, [
    ("filter_rows", {
        "column": "revenue",
        "op": ">=",
        "value": 1000
    }),
])

Supported operators:

  • >
  • <
  • >=
  • <=
  • ==
  • !=

Works with:

  • integers
  • floats
  • strings
  • booleans

🔎 Isolate rows with null values

Use keep_rows_with_nulls to audit incomplete data — keep only rows that have at least one null.

frame = ar.read_csv("data.csv")

# Keep all rows that have at least one null anywhere
nulls = ar.keep_rows_with_nulls(frame)

# Keep rows where specifically 'age' or 'score' is null
nulls = ar.keep_rows_with_nulls(frame, subset=["age", "score"])

# Works inside a pipeline too
result = ar.pipeline(frame, [
    ("keep_rows_with_nulls", {"subset": ["age"]}),
])

Useful for data auditing — inspect what's missing before deciding how to fill or drop.

Boolean string normalization

clean = ar.parse_bool_strings(frame)

This normalizes values such as "yes", "no", "true", "false", "y", "n", "1", and "0" into boolean values while preserving unsupported values unchanged.

Columns containing both parsed boolean values and unsupported string values may round-trip as strings because of ArFrame column typing semantics.


### 🔢 Safe column division

Divide one column by another while handling division by zero and null denominators explicitly:

result = ar.safe_divide_columns(
    frame,
    numerator="revenue",
    denominator="cost",
    output_column="ratio",
    fill_value=0.0,  # used when denominator is zero or null
)

When the denominator is zero or null, the result is replaced with fill_value (default 0.0) instead of raising an error or producing NaN/Inf.



📊 Pandas Dtype Support Matrix

This table helps users understand which pandas dtypes and workflows are fully supported, partially supported, unsupported, or planned.

If a dtype is partially supported, users may need conversion before processing. Unsupported dtypes should raise clear errors where applicable.

Pandas Dtype Support Status Notes / Fix Hints
int64 / Int64 ✅ Supported Fully supported with native C++ columnar storage. Nulls mapped to pd.NA.
float64 / Float64 ✅ Supported Fully supported with zero-copy conversion. Nulls mapped to np.nan or pd.NA.
bool / boolean ✅ Supported Native booleans supported with C++ backing. Nulls mapped to pd.NA.
string / string[python] ✅ Supported Native string extension type. Recommended for text. Nulls mapped to pd.NA.
object (strings / scalars) ✅ Supported Handled as text or coerced to common type if mixed.
object (nested / lists / dicts) ❌ Unsupported Nested structures not allowed in flat columnar storage. Raises TypeError.
category ❌ Unsupported Raises TypeError with fix hint. Convert to string: df["col"].astype(str)
datetime64[ns] / timezone-aware ❌ Unsupported Raises TypeError with fix hint. Use df["col"].astype(str) or string timestamps.
timedelta64[ns] ❌ Unsupported Raises TypeError with fix hint. Use df["col"].dt.total_seconds().
complex64 / complex128 ❌ Unsupported Raises TypeError with fix hint. Split into real/imag columns or convert to strings.

Notes

  • Zero-copy Optimization: Numeric columns (int64, float64) are optimized for fast zero-copy conversion between C++ and pandas where supported.
  • Defensive Buffers: Pass copy=True to to_pandas() when downstream pandas code needs defensive pandas-owned column buffers.
  • Boolean Buffers: Boolean conversion is copied because std::vector<bool> cannot be exposed as a zero-copy NumPy buffer.
  • Null Handling: Columns with null masks are automatically converted to pandas nullable Extension dtypes (Int64, BooleanDtype, StringDtype).
  • Index Drop: pandas DataFrame indexes are currently not preserved during from_pandas() conversion; converted frames receive a default RangeIndex when converted back via to_pandas().
  • Validation: Attempting to convert any unsupported type will raise a clear, user-friendly TypeError detailing the column name and how to fix/preprocess it.



🧠 Data quality engine

Arnio now includes built-in dataset understanding before you analyze in pandas.

report = ar.profile(frame)
print(report.summary())

suggestions = ar.suggest_cleaning(frame)
clean = ar.pipeline(frame, suggestions)

For production data contracts:

# Register a custom validator once, then reference it by name in any schema
ar.register_validator("positive", lambda v: v > 0)

schema = ar.Schema({
    "id": ar.Int64(nullable=False, unique=True),
    "email": ar.Email(nullable=False),
    "phone": ar.PhoneNumber(nullable=False),

    "user_type": ar.String(nullable=False),

    # country becomes required when user_type == "international"
    "country": ar.String(
        nullable=True,
        required_if=("user_type", "international"),
    ),

    # CurrencyCode validates 3-letter uppercase formats (e.g., USD, EUR, INR).
    "currency": ar.CurrencyCode(),

    "username": ar.String(min_length=3, max_length=20),
    "user_code": ar.Regex(r"^USR-\d{4}$", nullable=False),
    "revenue": ar.Custom("positive", nullable=True),
    "signup_date": ar.Date(nullable=False),
    "created_at": ar.DateTime(nullable=False, format="%Y-%m-%d"),

})

result = ar.validate(frame, schema)

if not result.passed:
    summary = result.summary()
    print(summary["issues_by_rule"])
    print(summary["issues_by_column"])
    print(summary["issues_by_column_and_rule"])
    print(result.to_pandas())
    print(result.to_markdown(max_issues=10))

Numeric string compatibility hints

Validation messages indicate when string values appear safely convertible to numeric dtypes.

frame = ar.from_pandas(
    pd.DataFrame(
        {
            "age": ["1", "2", "3"],
        }
    )
)

schema = ar.Schema(
    {
        "age": ar.Int64(),
    }
)

result = ar.validate(frame, schema)

print(result.issues[0].message)
# Column 'age' has dtype 'string'; expected 'int64'.
# Values appear safely convertible to 'int64'

In this example, country becomes required only when user_type == "international".

Date validates strict YYYY-MM-DD calendar dates.

Phone number validation

PhoneNumber() validates common international and formatted phone number strings.

schema = ar.Schema({
    "phone": ar.PhoneNumber(nullable=False),
})

result = ar.validate(frame, schema)
print(result.passed)

Accepted examples include:

  • +1-555-123-4567
  • +91 9876543210
  • 5551234567

Warning-only validation

schema = ar.Schema(
    {
        "age": ar.Int64(
            min=18,
            severity="warning",
        )
    }
)

result = ar.validate(frame, schema)

print(result.passed)  # True
print(result.issue_count)  # Warning issues are still reported

Warning-level issues remain visible in validation results without failing the overall validation status.

URL validation

URL() validates that values are well-formed URLs. By default, both http and https schemes are accepted.

schema = ar.Schema({
    "website": ar.URL(nullable=False),
})
result = ar.validate(frame, schema)
print(result.passed)

Use allowed_schemes to restrict which URL schemes are valid:

# https only
schema = ar.Schema({
    "website": ar.URL(allowed_schemes=["https"]),
})

# multiple schemes
schema = ar.Schema({
    "endpoint": ar.URL(allowed_schemes=["https", "ftp"]),
})

Any URL with a scheme not in allowed_schemes will fail validation.

Schema JSON round-trips

schema = ar.Schema(
    {
        "id": ar.String(nullable=False),
        "created_at": ar.DateTime(format="%Y-%m-%dT%H:%M:%S"),
    },
    strict=True,
    unique=["id"],
)

payload = schema.to_json()
restored = ar.Schema.from_json(payload)

See examples/schema_validation.py for a complete runnable tutorial covering Schema, field types, invalid-row reporting, and ValidationResult output.

ValidationResult.to_markdown() is useful in CI logs, GitHub comments, or data quality reports because it renders a compact validation summary plus a GitHub-friendly issue table.

For multi-column uniqueness (composite keys):

schema = ar.Schema({
    "user_id": ar.Int64(nullable=False),
    "course_id": ar.Int64(nullable=False),
}, unique=["user_id", "course_id"])

result = ar.validate(frame, schema)

For low-risk automatic cleanup:

clean, report = ar.auto_clean(frame, mode="strict", return_report=True)

This is the layer pandas does not try to own: profiling, data contracts, row-level validation issues, and safe cleaning suggestions for messy incoming datasets.


Beginner-friendly auto-clean tutorial

Use this workflow when you receive a small messy dataset and want to inspect what Arnio will change before applying it.

import arnio as ar
import pandas as pd

raw = pd.DataFrame(
    {
        "order_id": [1001, 1002, 1002, 1003, 1004],
        "customer": [" Ishan ", " Prasoon ", " Prasoon ", " Pranay ", " Dhruv "],
        "city": [" Paris ", "London", "London", " New York ", " Tokyo "],
    }
)

frame = ar.from_pandas(raw)

report = ar.profile(frame)
summary = report.summary()
print(summary)

suggestions = ar.suggest_cleaning(frame)
print(suggestions)
# [('strip_whitespace', {'subset': ['customer', 'city']}), ('drop_duplicates', {'keep': 'first'})]

safe = ar.auto_clean(frame)
strict = ar.auto_clean(frame, mode="strict")

Messy input:

order_id customer city
1001 Ishan Paris
1002 Prasoon London
1002 Prasoon London
1003 Pranay New York
1004 Dhruv Tokyo

Expected cleaned output with mode="strict":

order_id customer city
1001 Ishan Paris
1002 Prasoon London
1003 Pranay New York
1004 Dhruv Tokyo

mode="safe" only trims whitespace. Use mode="strict" when you also want deterministic built-in cleanup such as exact duplicate removal.

See examples/auto_clean_tutorial.py for a runnable version of this walkthrough, and examples/schema_validation.py for a focused validation tutorial.

For strict mode data-loss risks and safe workflow, see AUTO_CLEAN_GUIDE.md.


Data Quality Reports

Arnio provides detailed profiling for datasets via ar.profile(). To generate the report shown in these examples, the following code was used:

import arnio as ar
import pandas as pd

# Sample dataset used for these examples
data = {
    "user_id": [101, 102, 103, 104],
    "email": ["test@arnio.ai", "invalid-email", None, "test@arnio.ai"],
    "score": [85.5, 90.0, None, 88.2]
}
df = ar.from_pandas(pd.DataFrame(data))
# Bounded profiling for large datasets (controls how many sample values are kept)
report = ar.profile(df, sample_size=5)
safe_report = report.to_dict(redact_sample_values=True)

Profiling privacy and redaction

Profiling helps you understand data, but some report fields can still expose real emails, names, IDs, or other sensitive values. Before you paste output into GitHub issues, Slack, public notebooks, or shared logs, check whether you are sharing aggregate statistics only or raw/sample cell values.

What is aggregate-only vs may expose raw values

Field or export Aggregate-only? May expose raw / sample data?
row_count, column_count, duplicate_rows, duplicate_ratio, quality_score, score_components Yes No
null_count, null_ratio, unique_count, unique_ratio, whitespace / empty-string counts Yes No
Numeric min / max / mean / std / q25q95 Statistics only Uncommon on large datasets; small tables can still be identifying
semantic_type, suggested_dtype, warnings Metadata / hints Can imply PII type (for example email-like), not redaction
ColumnProfile.sample_values (in-memory) No Yes — first N non-null values (sample_size on ar.profile())
ColumnProfile.top_values Includes counts / ratios Yes — frequent actual values (exact or approximate; see below)
report.to_dict() Mixed Yes — includes sample_values and top_values unless you redact samples
report.to_dict(redact_sample_values=True) Mixed sample_values"[REDACTED]" (same list length); top_values unchanged
report.to_markdown(), report.summary() Yes No raw cell values in output
report.to_html() / notebook display of report Partial Shows top_values chips; does not list sample_values
report.to_pandas() Partial Includes top_values, not sample_values
ProfileComparison.to_dict() Nested profiles Yes — embeds left_profile / right_profile via default to_dict()

Arnio does not auto-mask emails, phone numbers, or IDs by column type. Use the controls below for safer sharing.

Safe sharing practices

  • JSON logs and artifacts: report.to_dict(redact_sample_values=True) before writing or uploading.
  • Collect fewer samples: ar.profile(frame, sample_size=0) skips sample_values (defaults still apply to top_values on string columns).
  • Text summaries for CI or comments: prefer report.to_markdown() or report.summary() when you do not need per-value examples.
  • Notebooks and HTML exports: avoid evaluating report or saving report.to_html() for sensitive data; HTML still shows top_values.
  • GitHub bug reports and examples: use synthetic data (user@example.com, ID-001), a minimal CSV, and redacted to_dict() output — not production dumps.
  • Pandas export: ar.to_pandas(frame) returns full table data; redaction applies to quality reports, not the underlying frame.
  • Profile comparison: ProfileComparison.to_dict() nests full profiles; build shared artifacts with profile.to_dict(redact_sample_values=True) if needed.
import arnio as ar
import pandas as pd

df = ar.from_pandas(pd.DataFrame({
    "email": ["user@example.com", "bad-email", None],
    "user_id": [101, 102, 103],
}))
report = ar.profile(df, sample_size=2)

# Safer JSON for sharing (sample_values only; top_values still present)
safe_json = report.to_dict(redact_sample_values=True)

# Safer text summary (no sample_values or top_values in output)
print(report.to_markdown())

When approx_top_values=True, high-cardinality string columns estimate top_values from a deterministic sample. Each column may set top_values_is_approximate, top_values_sample_count, and top_values_sample_ratio. Counts and ratios are sample-based, but displayed values are still real strings from your data — treat them like top_values for privacy.

# Optional: approximate top values for high-cardinality string columns
report = ar.profile(
    df,
    approx_top_values=True,
    approx_top_values_min_unique=1000,
    approx_top_values_min_ratio=0.2,
    approx_top_values_sample_size=2000,
)

Notebook dashboard (Jupyter / Colab)

DataQualityReport includes a notebook-friendly HTML dashboard. In a notebook, simply evaluate report in a cell to see a rich, static summary (quality score, duplicates, nulls, warnings, top values, and cleaning suggestions).

If you want to embed or save the HTML explicitly:

from IPython.display import HTML

HTML(report.to_html())
# or: report.to_html(file_path="data_quality_report.html")

Sample output now includes quantiles for numeric columns:

{
  "age": {
    "dtype": "float64",
    "mean": 35.2,
    "std": 10.1,
    "min": 18.0,
    "max": 60.0,
    "q25": 27.5,
    "q50": 35.0,
    "q75": 44.0,
    "q95": 57.0,
    "null_count": 0
  }
}

Compare Profiles

Use ar.compare_profiles() to compare two DataQualityReport profiles and flag per-column drift.

baseline = ar.profile(ar.read_csv("baseline.csv"))
current  = ar.profile(ar.read_csv("current.csv"))

comparison = ar.compare_profiles(baseline, current)
print(comparison.drift_report["score"]["status"])  # "ok", "warning", or "changed"
print(comparison.status_counts)  # {"ok": 2, "warning": 1, "changed": 0}

Use ar.check_quality_gates() when profile drift should become a pass/fail decision for CI, data releases, or monitoring.

result = ar.check_quality_gates(
    baseline,
    current,
    max_row_count_delta_ratio=0.10,
    max_null_ratio_delta=0.05,
    max_numeric_mean_delta_ratio=0.10,
)

if not result.passed:
    print(result.to_markdown())
    result.raise_for_failures()

Scoring Contract: The quality_score starts at 100.0 and subtracts capped penalties for duplicates, nulls, and suggested dtype mismatches. The score_components field exposes these penalties as negative values. (Note: Semantic-validity penalties are intentionally out of scope for the current implementation.)

1. Terminal Representation (Simplified Example)

A simplified view of the standard string representation of the report object:

DataQualityReport(
    row_count=4,
    column_count=3,
    memory_usage=733,
    duplicate_rows=0,
    quality_score=100.0,
    score_components={},
    columns={
        'user_id': ColumnProfile(dtype='int64', semantic_type='identifier', unique_count=4),
        'email': ColumnProfile(dtype='string', semantic_type='categorical', null_count=1, unique_ratio=0.666667, min=13, max=13, mean=13.0),
        'score': ColumnProfile(dtype='float64', semantic_type='numeric', mean=87.9, min=85.5, max=90.0)
    }
)

2. JSON Format (Excerpts from .to_dict())

Key fields from the structured JSON export for integration with APIs or dashboards:

{
  "row_count": 4,
  "column_count": 3,
  "memory_usage": 733,
  "duplicate_rows": 0,
  "duplicate_ratio": 0.0,
  "quality_score": 100.0,
  "score_components": {},
  "columns": {
    "user_id": {
      "dtype": "int64",
      "semantic_type": "identifier",
      "null_count": 0,
      "unique_ratio": 1.0
    },
    "email": {
      "dtype": "string",
      "semantic_type": "categorical",
      "null_count": 1,
      "unique_ratio": 0.666667,
      "min": 13,
      "max": 13,
      "mean": 13.0,
      "warnings": ["contains_nulls"]
    },
    "score": {
      "dtype": "float64",
      "semantic_type": "numeric",
      "null_count": 1,
      "mean": 87.9,
      "min": 85.5,
      "max": 90.0,
      "warnings": ["contains_nulls"],
      "histogram": [
        {"bucket_start": 85.5, "bucket_end": 85.95, "count": 1, "ratio": 0.333333},
        {"bucket_start": 85.95, "bucket_end": 86.4, "count": 0, "ratio": 0.0},
        {"bucket_start": 86.4, "bucket_end": 86.85, "count": 0, "ratio": 0.0},
        {"bucket_start": 86.85, "bucket_end": 87.3, "count": 0, "ratio": 0.0},
        {"bucket_start": 87.3, "bucket_end": 87.75, "count": 0, "ratio": 0.0},
        {"bucket_start": 87.75, "bucket_end": 88.2, "count": 0, "ratio": 0.0},
        {"bucket_start": 88.2, "bucket_end": 88.65, "count": 1, "ratio": 0.333333},
        {"bucket_start": 88.65, "bucket_end": 89.1, "count": 0, "ratio": 0.0},
        {"bucket_start": 89.1, "bucket_end": 89.55, "count": 0, "ratio": 0.0},
        {"bucket_start": 89.55, "bucket_end": 90.0, "count": 1, "ratio": 0.333333}
      ]
    },
    "city": {
      "dtype": "string",
      "semantic_type": "categorical",
      "null_count": 0,
      "top_values": [
        {"value": "London", "count": 3, "ratio": 0.5},
        {"value": "Paris", "count": 2, "ratio": 0.333}
      ]
    }
  },
  "suggestions": [
    {
      "step": "cast_types",
      "kwargs": {"score": "float64"},
      "confidence_score": 0.95,
      "confidence_reason": "Column 'score' conforms perfectly to float64 structure."
    }
  ]
}

Columns where a single non-null value represents at least 95% of rows are reported with a near_constant warning. Columns with a very high ratio of unique values are reported with a high_cardinality warning because they may represent identifiers, leakage risk, or modeling hazards.

Example near-constant distribution:

{
  "row_count": 100,
  "top_values": [
    {"value": "London", "count": 95, "ratio": 0.95},
    {"value": "Paris", "count": 5, "ratio": 0.05}
  ],
  "warnings": ["near_constant"]
}

3. Example Summary Table

A manually formatted Markdown table representing the core metrics:

Metric Value
Row Count 4
Column Count 3
Memory Usage 733 bytes
Duplicates 0 (0.0%)
Quality Score 100.0

Bootstrapping a Schema from a Quality Report

After profiling a dataset, you can automatically generate a validation schema directly from the report:

import arnio as ar

frame = ar.from_pandas(df)
report = ar.profile(frame)

schema = ar.Schema.bootstrap_from_report(report)
result = schema.validate(frame)

print(result.passed)
print(result.summary())

The inferred schema uses conservative defaults: column dtypes are mapped directly from the report, and a column is marked nullable=True if any null values were observed during profiling.

🗺️ Roadmap

Version Focus Status
v1.0 Stable release · cross-platform wheels · CI/CD · PyPI publishing · Google Colab support ✅ Shipped
v1.1 Production readiness · release hardening · docs/tooling ✅ Shipped
v1.2 C++ pipeline optimization · speed parity with pandas · hash-based deduplication 🔨 Active
v1.3 Chunked / streaming processing · Parquet & JSON readers 📋 Planned
v1.4 Parallel column processing · SIMD string operations 💭 Exploring

For CLI command reference and examples, see CLI_REFERENCE.md.




💬 Community

Join the Arnio Discord Community for quick setup help, contributor onboarding, GSSoC 2026 coordination, feature discussion, and community updates.

Discord is for fast conversation and support. GitHub remains the source of truth for issue assignment, PR reviews, bugs, roadmap decisions, and releases.

Join Arnio Discord




📚 Documentation

🤝 Contribute

Arnio is a GSSoC 2026 project with a structured contributor backlog across beginner, intermediate, and advanced tracks.

You don't need C++ to contribute

Most new features are pure Python pipeline steps:

# 1. Write a function that takes a DataFrame and returns a DataFrame
def remove_special_chars(df, columns=None):
    cols = columns or df.select_dtypes("object").columns
    for col in cols:
        df[col] = df[col].str.replace(r"[^a-zA-Z0-9\s]", "", regex=True)
    return df

# 2. Register it
ar.register_step("remove_special_chars", remove_special_chars)

# 3. Write tests, open a PR. That's it.

If Arnio renames a built-in or registered pipeline step in a future release, the old step name can stay temporarily available and will emit a DeprecationWarning while routing execution to the new canonical step.

If you do know C++

The biggest performance wins are in:

  • drop_duplicates — replacing std::ostringstream row serialization with proper hash-based comparisons
  • strip_whitespace — converting from copy-on-write to in-place mutation
  • Parallel column processingstd::thread across independent columns

Getting started

# macOS / Linux
git clone https://github.com/im-anishraj/arnio.git && cd arnio
make install   # pip install -e ".[dev]" + pre-commit
make test      # pytest with coverage
make lint      # ruff + black

# Windows
pip install -e ".[dev]"
pre-commit install
pytest tests/ -v

Building frames without a CSV

Use ArFrame.from_records (also available as ar.from_records) to build small frames inline — useful for tests, quick experiments, or feeding hand-crafted data into the pipeline without writing a CSV file.

import arnio as ar

# list-of-dicts — column names inferred from keys
frame = ar.from_records([
    {"id": 1, "name": "alice", "score": 95},
    {"id": 2, "name": "bob",   "score": 88},
])

# list-of-lists or tuples — columns must be supplied
frame2 = ar.from_records(
    [(1, "alice", 95), (2, "bob", 88)],
    columns=["id", "name", "score"],
)

Missing keys in dict records are filled with None. Nested values raise TypeError. An empty list raises ValueError.

Windows build troubleshooting

If pip install -e ".[dev]" fails on Windows, work through this checklist before retrying:

  1. Install Visual Studio Build Tools 2022 with the Desktop development with C++ workload.
  2. Upgrade packaging tools:
    python -m pip install --upgrade pip setuptools wheel
    
  3. Confirm the MSVC compiler is on PATH by running cl from a Developer Command Prompt.
  4. Retry the editable install:
    pip install -e ".[dev]"
    pre-commit install
    pytest tests/ -v
    

If you want a quick wheel-build smoke test before running the full suite, use:

pip wheel . --no-deps -w dist/
python tests/smoke_wheel_install.py --wheelhouse dist

Common symptoms:

  • Microsoft Visual C++ 14.x is required: install the Build Tools workload above, then reopen your shell.
  • 'cl' is not recognized: use a Developer Command Prompt or repair the Build Tools installation.
  • pip install -e ".[dev]" succeeds but pre-commit is missing: rerun python -m pip install -e ".[dev]" after upgrading pip, setuptools, and wheel.
  • The wheel build passes but tests fail: rerun pytest tests/ -v and debug the failing test output separately from the build step.

If you prefer a Linux-like toolchain on Windows, WSL is also supported.

PR titles must follow Conventional Commitsfeat:, fix:, docs:, chore:. Our release pipeline auto-generates changelogs from these.

For GSSoC contributors, please read GSSOC_GUIDE.md before asking to be assigned. It explains issue claiming, contribution levels, review expectations, and what maintainers look for in a strong PR. If you want a quick onboarding refresher, see the GSSoC FAQ. If you are new to Arnio terms, see the contributor glossary.

📖 Full Contributing Guide ·  GSSoC Guide ·  🐛 Open Issues ·  💬 Discussions ·  Discord

💖 Contributors

Thanks to everyone who contributes to Arnio and helps improve the project.



🚢 Release process

Arnio releases are automated through Release Please and GitHub Actions.

  1. Merge user-facing changes with Conventional Commit PR titles (feat:, fix:, docs:, or chore:) so Release Please can choose the version bump and changelog entries.
  2. Review and merge the Release Please PR on main; this updates release metadata and creates the GitHub release and tag.
  3. Confirm the Build & Publish Wheels workflow succeeds for the release tag. It builds the sdist and wheels, then publishes to PyPI through Trusted Publishing.
  4. Smoke test the published package in a clean environment:
python -m venv /tmp/arnio-smoke
source /tmp/arnio-smoke/bin/activate
python -m pip install -U pip
python -m pip install arnio
printf 'name,revenue\n Ada,10\n' > /tmp/arnio-smoke.csv
python - <<'PY'
import arnio as ar
print(ar.__version__)
print(ar.scan_csv("/tmp/arnio-smoke.csv"))
PY
  1. Verify the GitHub release, PyPI project page, and install command all show the expected version before announcing the release.

If any publish or smoke-test step fails, leave the failed tag and GitHub release in place until maintainers agree on the recovery plan.




📐 Project structure

arnio/
├── cpp/
│   ├── include/arnio/      # C++ headers — types, column, frame, csv_reader, cleaning
│   └── src/                 # C++ implementations (~30 KB of compiled logic)
├── bindings/
│   └── bind_arnio.cpp       # pybind11 module — the Python↔C++ bridge
├── arnio/
│   ├── __init__.py          # Public API surface
│   ├── io.py                # read_csv, read_jsonl, scan_csv, write_csv, write_parquet
│   ├── cleaning.py          # Python wrappers for C++ cleaning functions
│   ├── pipeline.py          # Step registry + pipeline executor
│   ├── convert.py           # to_pandas (zero-copy), from_pandas
│   ├── frame.py             # ArFrame — lightweight C++ Frame wrapper
│   └── exceptions.py        # ArnioError, UnknownStepError, CsvReadError, TypeCastError
├── tests/                   # pytest suite — CSV, cleaning, pipeline, conversions
├── benchmarks/              # Reproducible arnio vs pandas benchmark
├── examples/                # basic_usage.py, auto_clean_tutorial.py, custom_step.py and ready to run recipes for sales, customers, survey, logs, finance
└── website/                 # Project website — arnio.vercel.app



Arnio



Stop writing cleaning scripts. Declare clean data.


DownloadsStarsForksWebsiteDiscord


Built with C++ and pybind11 · Licensed under MIT · Maintained by @im-anishraj

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

arnio-1.18.0.tar.gz (2.2 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

arnio-1.18.0-cp314-cp314-win_amd64.whl (321.4 kB view details)

Uploaded CPython 3.14Windows x86-64

arnio-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (369.2 kB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

arnio-1.18.0-cp314-cp314-macosx_11_0_arm64.whl (304.5 kB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

arnio-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl (325.0 kB view details)

Uploaded CPython 3.14macOS 10.15+ x86-64

arnio-1.18.0-cp313-cp313-win_amd64.whl (314.9 kB view details)

Uploaded CPython 3.13Windows x86-64

arnio-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (368.9 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

arnio-1.18.0-cp313-cp313-macosx_11_0_arm64.whl (303.9 kB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

arnio-1.18.0-cp313-cp313-macosx_10_13_x86_64.whl (324.6 kB view details)

Uploaded CPython 3.13macOS 10.13+ x86-64

arnio-1.18.0-cp312-cp312-win_amd64.whl (314.9 kB view details)

Uploaded CPython 3.12Windows x86-64

arnio-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (368.8 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

arnio-1.18.0-cp312-cp312-macosx_11_0_arm64.whl (303.8 kB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

arnio-1.18.0-cp312-cp312-macosx_10_13_x86_64.whl (324.5 kB view details)

Uploaded CPython 3.12macOS 10.13+ x86-64

arnio-1.18.0-cp311-cp311-win_amd64.whl (312.8 kB view details)

Uploaded CPython 3.11Windows x86-64

arnio-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (365.0 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

arnio-1.18.0-cp311-cp311-macosx_11_0_arm64.whl (303.3 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

arnio-1.18.0-cp311-cp311-macosx_10_9_x86_64.whl (322.3 kB view details)

Uploaded CPython 3.11macOS 10.9+ x86-64

arnio-1.18.0-cp310-cp310-win_amd64.whl (311.8 kB view details)

Uploaded CPython 3.10Windows x86-64

arnio-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (363.3 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

arnio-1.18.0-cp310-cp310-macosx_11_0_arm64.whl (302.2 kB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

arnio-1.18.0-cp310-cp310-macosx_10_9_x86_64.whl (320.9 kB view details)

Uploaded CPython 3.10macOS 10.9+ x86-64

arnio-1.18.0-cp39-cp39-win_amd64.whl (320.6 kB view details)

Uploaded CPython 3.9Windows x86-64

arnio-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (363.5 kB view details)

Uploaded CPython 3.9manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

arnio-1.18.0-cp39-cp39-macosx_11_0_arm64.whl (302.3 kB view details)

Uploaded CPython 3.9macOS 11.0+ ARM64

arnio-1.18.0-cp39-cp39-macosx_10_9_x86_64.whl (321.1 kB view details)

Uploaded CPython 3.9macOS 10.9+ x86-64

File details

Details for the file arnio-1.18.0.tar.gz.

File metadata

  • Download URL: arnio-1.18.0.tar.gz
  • Upload date:
  • Size: 2.2 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arnio-1.18.0.tar.gz
Algorithm Hash digest
SHA256 90bfe119b9f2c269fceb3f12715b306f3c19192e4ac4bac49ff70b84b80d4950
MD5 027061269488921792c00da91cc918a4
BLAKE2b-256 ce9c93f28fba18436ad348d9106c09b3fd977a0aaa72ecf6c6e6b172bc42c43a

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp314-cp314-win_amd64.whl.

File metadata

  • Download URL: arnio-1.18.0-cp314-cp314-win_amd64.whl
  • Upload date:
  • Size: 321.4 kB
  • Tags: CPython 3.14, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arnio-1.18.0-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 8687d3ff3163c2dc9824560116cead7e5b341d1112c1627b634821ea9d1900c2
MD5 d20f59e6d3131428f886c5186f8a0cca
BLAKE2b-256 cefd311f5c0a85d4162b1cc3322bc6afa705d5383aeedafdf8835df59e76d38f

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4c0b49e67023fb763eac3449d1a8923a6303cd84b6cd04bda4fad0f944fc84f4
MD5 13862af8a68549e63783454abf356d51
BLAKE2b-256 17d86d20380a7cff1d7c7958b534df37b43338c4f0891b279756a1e31969da9e

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b47ba340bf92f569e7a9e1aaaac12afc8025bca4b0e85631bc5a203cb484223f
MD5 5aa298a3244f8798b3ff363c6a8f30fa
BLAKE2b-256 b9c0d17019714ef0dfc4e65a28b7542f6e2b0b3eae79c86c64032685218d0b8f

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 cfb00963a27dd1a0aadd4541ea121596b642944679ca576baff7b31312a2e10e
MD5 1407d636310bdc21c2ce0d2ed0b31c00
BLAKE2b-256 912d6ada1a34a615c43c1b13d4bdf847c76bdb8cc3c3392434792f8057fbfe5a

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: arnio-1.18.0-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 314.9 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

Hashes for arnio-1.18.0-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 29ca8e62315bff0a536fe2958d44893faedc819e162cf6ffb7a44eda0a29a93b
MD5 1e10420967a74b417f55db50cf2ffa6a
BLAKE2b-256 3452640f0d2ff5f72d9fc1563ca73666c8a89c22fd65334c37ea70aaef72b748

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 0ab6aed8361b4a19aad82c36f9c3738a55c433962ef63d0d9b3c123721af0cef
MD5 0b57cfc38e93356bfd77bae380acd269
BLAKE2b-256 4732795717a2f8054a4004d549902944b2954a44bff6585d2e1a96d163336fe9

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 8aa46be97acdf87bd80cb4f236458f356e072a424c5fefdb5070956b40155c26
MD5 159df282d4d5908e7bb2f804165f86b5
BLAKE2b-256 0625d626fef42d2cae25260693cb16c34a64afa46aa8856447a11f6d3f2c87d0

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp313-cp313-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp313-cp313-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 6614c50997d3354800a737d7c3b461f770c64f3f61fe7ea74cd2a10bd1d466f4
MD5 f2175d2a02e5909b4bb49f47cf7b06b9
BLAKE2b-256 b8406694b1a6bb4a4eaa0ecc6b54073cdfd1362fe1841d3fc2e14ec365264690

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: arnio-1.18.0-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 314.9 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

Hashes for arnio-1.18.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 2d6d96403f1af5b66a2ac935bbeb3e916f35ca45aaedb6bc0f9a1710257613a4
MD5 0e25df75c1f49deef47c5af013f4638c
BLAKE2b-256 48f0bcadf6aea92759bf0f66d5bfd86cd0ff2283eac4b4137bbd24fad0057298

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 e901d058d7bf75683c034f183b7cb93f343e6cc2d959a70d3f03d204c0b731de
MD5 1b77c765bb633fecab607bba987174e2
BLAKE2b-256 ccb9a482a856d43d6ce5c55d30b9adcdbeb14bfcaac1fe0eab8029bcdc5d2a37

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 1475a20bee1eb04a34505f882141c0f2671a5f2e90f9a95605e9cdac47c9c665
MD5 cde1fe88fa51eae0ce9f048e8514ca4e
BLAKE2b-256 30e2e7bc0d4bfbba3c236f89e520f30bced3d431c62182f7101c47e922dc15ba

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp312-cp312-macosx_10_13_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp312-cp312-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 da97dbfa6d2b012f696da08a42ed197c193b3e754b53bd4f9c708abead01f5ad
MD5 968dacd89e4a8eb3f6989ea189b08000
BLAKE2b-256 f585c1f58e430123c366a8065ed4e0fe7c3cef3042a78f843f885d156bc53d43

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: arnio-1.18.0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 312.8 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

Hashes for arnio-1.18.0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 f5503deece721d6bda79748eb45e70a8f33508440c453b7e1206cf3b573d75a3
MD5 4077825ed177d8c18876b379eabfb389
BLAKE2b-256 f448a7ef986e50bc2cd146fd0bb911641bf02a7586a136b54c1d5d783d24c381

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5a01c1529c788498ab9669f3944a5e857fa868e916fa2e6ddc56f3d91c4aa16b
MD5 b99aee4bef87a7eab95233bc983d3372
BLAKE2b-256 0dbf1acd11645922f19325d5a19c4eaaee645aa95584fb3362fc9ba054e5b3ba

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ff54de18c5988d47d501b4a209130132b95887b46ffdd1103669f4a885de7c47
MD5 1699a5fdb5f5b0de3c123bf42c3382c1
BLAKE2b-256 1968452ea3f0ac57a83010ba6c458dd7a3dcda2b10782b807fbd83540d1cfbcb

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp311-cp311-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp311-cp311-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 7ed69fc449ca33925c912ac9f14081fbcf93174782a18d08f7e3b72112be6597
MD5 fa8ebf960e7026cb301d2a037c9507e2
BLAKE2b-256 6a9d3dae0af9ee73ca23bcd748c8c58f4685db4ce21b2951802d948d729d097a

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: arnio-1.18.0-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 311.8 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

Hashes for arnio-1.18.0-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 e1f0b527f068b0c99971d210b2d4bf7080489d501adbeef327909b5fa40a0854
MD5 463128592b46d41509417d38c2f11813
BLAKE2b-256 c943bdf718839c0974fa7733bf3cdbf28f277c4096e062c256e4bcef896b0a41

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 51667cf06b02d97921ae1a12ac0908ed56eab0d43a978ba5003779e2e59617b7
MD5 46f7aaecee9f174ad760b31fcbda9843
BLAKE2b-256 9a38b3c379c2a8a8b8d1d53d0b8c82d4c9e605bf88f68edd7b59a45e219b3191

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 7544e8f8ea01b156e460b112fcc60cd2ced749a3c6d4602e0f9748e8e0e0f279
MD5 70312531ca23fe1c5fb3e37eac584cf3
BLAKE2b-256 a8a648e57dc5f0e39b70f1ad48e4a20d7ba7d91bf23a63c4bbaf93fc2d009df6

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp310-cp310-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp310-cp310-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 61402f7f9663025c226b3a75fa6992b08b7d9b49ef92aa4a7cc91767807d6678
MD5 e444cf32af50688aa747678a8c01f70e
BLAKE2b-256 31f2711d83a5970c3b8493a562abca9cd3b05f57e5b0f1f0a26cdf70eb230e12

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp39-cp39-win_amd64.whl.

File metadata

  • Download URL: arnio-1.18.0-cp39-cp39-win_amd64.whl
  • Upload date:
  • Size: 320.6 kB
  • Tags: CPython 3.9, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for arnio-1.18.0-cp39-cp39-win_amd64.whl
Algorithm Hash digest
SHA256 b58d2cedaa258856cad44dec48f2beecd3707d945df0f4d626dada758787813f
MD5 0a148f7cb28ede00815bd521ac4052f8
BLAKE2b-256 d538335e9ae39572673f568f987796f1b30e27d5b30c5a444b25aa2110e93691

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 5383fbd7b20f5d9fd58030cafba9ef6cd8c35a655d1eaafbac9be89cf37b8ec3
MD5 59c19fe9a7898d7489bb05b137d9f0e2
BLAKE2b-256 227f8bbbe665b8e5251200b6b8d1412d9d818b9a62828fc687cce6d627c7c9c5

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp39-cp39-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp39-cp39-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 386ab2d8b4234c0baaa0fa19354add769aefd21562ef02575dd77a0357b7f2d1
MD5 737d616a3658a8f88d7610821892369f
BLAKE2b-256 a13ab849e4e7c1bd9faf8d247982bf4edb1b67e14e0a4545e15701b2af108128

See more details on using hashes here.

File details

Details for the file arnio-1.18.0-cp39-cp39-macosx_10_9_x86_64.whl.

File metadata

File hashes

Hashes for arnio-1.18.0-cp39-cp39-macosx_10_9_x86_64.whl
Algorithm Hash digest
SHA256 d148473a25aff57905f9b13cd1f92e5a2a6dea5642e466a918f1ca5faf66d8c5
MD5 c6dbe493719902d8c778daa7f9e4fe31
BLAKE2b-256 6a2fc75ccc5829672f81abf8eb7f251aff0044c0c2096a0a23b334bde5eb99e5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page