Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: CodSpeed

permissions: {}

on:
workflow_dispatch:
pull_request:
push:
branches:
- main

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
FORCE_COLOR: 3

jobs:
benchmarks:
name: Run benchmarks
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # for OpenID Connect authentication with CodSpeed
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Install uv
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
with:
python-version: "3.13"

- name: Install
run: uv sync --no-default-groups --group test --locked

- name: Run benchmarks
uses: CodSpeedHQ/action@9a94d4f4af407c62dfa28ecf6a32e8e914cef4cf # v4.18.5
with:
mode: simulation
run: uv run --frozen pytest benchmarks/ --codspeed -p no:sybil
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<p align="center">
<a href="https://github.com/GalacticDynamics/dataclassish/actions"> <img alt="CI status" src="https://github.com/GalacticDynamics/dataclassish/workflows/CI/badge.svg" /> </a>
<a href="https://codecov.io/gh/GalacticDynamics/dataclassish"> <img alt="codecov" src="https://codecov.io/gh/GalacticDynamics/dataclassish/graph/badge.svg" /> </a>
<a href="https://app.codspeed.io/GalacticDynamics/dataclassish?utm_source=badge"><img alt="CodSpeed" src="https://img.shields.io/endpoint?url=https://codspeed.io/badge.json" /> </a>
<a href="https://scientific-python.org/specs/spec-0000/"> <img alt="ruff" src="https://img.shields.io/badge/SPEC-0-green?labelColor=%23004811&color=%235CA038" /> </a>
<a href="https://docs.astral.sh/ruff/"> <img alt="ruff" src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v2.json" /> </a>
<a href="https://pre-commit.com"> <img alt="pre-commit" src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit" /> </a>
Expand Down
1 change: 1 addition & 0 deletions benchmarks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Performance benchmarks for `dataclassish`."""
145 changes: 145 additions & 0 deletions benchmarks/test_benchmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
"""Benchmarks for `dataclassish`.

These benchmarks exercise the public, multiple-dispatch-based API of
``dataclassish`` across the different object types it supports (mappings and
dataclasses). They are collected and run by ``pytest-codspeed``.
"""

from dataclasses import dataclass

import pytest

from dataclassish import (
asdict,
astuple,
field_items,
field_keys,
field_values,
fields,
get_field,
replace,
)
from dataclassish.converters import (
Optional,
Unless,
dataclass as converter_dataclass,
field as converter_field,
)


@dataclass(frozen=True)
class Point:
"""A small dataclass used across the benchmarks."""

x: int
y: int
z: int


def _make_mapping() -> dict[str, object]:
return {"a": 1, "b": 2.0, "c": "3", "d": 4, "e": 5.0}


def _make_point() -> Point:
return Point(1, 2, 3)


# ============================================================================
# Mapping dispatches


@pytest.mark.benchmark
def test_replace_mapping(benchmark: object) -> None:
"""Benchmark `replace` on a mapping."""
d = _make_mapping()
benchmark(lambda: replace(d, c=3 + 0j, d=40))


@pytest.mark.benchmark
def test_asdict_mapping(benchmark: object) -> None:
"""Benchmark `asdict` on a mapping."""
d = _make_mapping()
benchmark(lambda: asdict(d))


@pytest.mark.benchmark
def test_field_items_mapping(benchmark: object) -> None:
"""Benchmark `field_items` on a mapping."""
d = _make_mapping()
benchmark(lambda: field_items(d))


# ============================================================================
# Dataclass dispatches


@pytest.mark.benchmark
def test_replace_dataclass(benchmark: object) -> None:
"""Benchmark `replace` on a dataclass."""
p = _make_point()
benchmark(lambda: replace(p, x=10, z=30))


@pytest.mark.benchmark
def test_fields_dataclass(benchmark: object) -> None:
"""Benchmark `fields` on a dataclass."""
p = _make_point()
benchmark(lambda: fields(p))


@pytest.mark.benchmark
def test_asdict_dataclass(benchmark: object) -> None:
"""Benchmark `asdict` on a dataclass."""
p = _make_point()
benchmark(lambda: asdict(p))


@pytest.mark.benchmark
def test_astuple_dataclass(benchmark: object) -> None:
"""Benchmark `astuple` on a dataclass."""
p = _make_point()
benchmark(lambda: astuple(p))


@pytest.mark.benchmark
def test_get_field_dataclass(benchmark: object) -> None:
"""Benchmark `get_field` on a dataclass."""
p = _make_point()
benchmark(lambda: get_field(p, "y"))


@pytest.mark.benchmark
def test_field_keys_dataclass(benchmark: object) -> None:
"""Benchmark `field_keys` on a dataclass."""
p = _make_point()
benchmark(lambda: field_keys(p))


@pytest.mark.benchmark
def test_field_values_dataclass(benchmark: object) -> None:
"""Benchmark `field_values` on a dataclass."""
p = _make_point()
benchmark(lambda: field_values(p))


@pytest.mark.benchmark
def test_field_items_dataclass(benchmark: object) -> None:
"""Benchmark `field_items` on a dataclass."""
p = _make_point()
benchmark(lambda: field_items(p))


# ============================================================================
# Converters


@pytest.mark.benchmark
def test_converter_dataclass_construction(benchmark: object) -> None:
"""Benchmark constructing a converter-enabled dataclass."""

@converter_dataclass(frozen=True, slots=True)
class Model:
a: int | None = converter_field(converter=Optional(int))
b: float = converter_field(converter=Unless(float, float))

benchmark(lambda: Model(a="1", b=2))
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ nox = ["nox>=2024.10.9", "nox-uv>=0.6.3"]
test = [
"attrs>=25.4.0",
"pytest>=8.4.2",
"pytest-codspeed>=5.0.0",
"pytest-cov>=7.0.0",
"sybil[pytest]>=9.2.0",
]
Expand Down
38 changes: 38 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.