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
34 changes: 34 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CodSpeed

on:
pull_request:
branches:
- "main"
- "dev"
workflow_dispatch:

permissions:
contents: read
id-token: write

jobs:
codspeed:
name: Run benchmarks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Install the project
run: uv sync --dev

- name: Run benchmarks
uses: CodSpeedHQ/action@v4
with:
mode: simulation
run: uv run pytest tests/test_benchmarks.py --codspeed
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# danom

[![PyPI Downloads](https://static.pepy.tech/personalized-badge/danom?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=BLUE&left_text=downloads)](https://pepy.tech/projects/danom) ![coverage](./coverage.svg)
[![PyPI Downloads](https://static.pepy.tech/personalized-badge/danom?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=BLUE&left_text=downloads)](https://pepy.tech/projects/danom) ![coverage](./coverage.svg) [![CodSpeed](https://img.shields.io/endpoint?url=https://codspeed.io/badge.json)](https://codspeed.io/second-ed/danom?utm_source=badge)

# API Reference

Expand Down Expand Up @@ -275,6 +275,7 @@ Alternatively the map method can be used to return a new type instance with the
│ ├── __init__.py
│ ├── conftest.py
│ ├── test_api.py
│ ├── test_benchmarks.py
│ ├── test_monad_laws.py
│ ├── test_new_type.py
│ ├── test_result.py
Expand Down
32 changes: 32 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,42 @@
REPO_ROOT = Path(__file__).parents[1]


def is_positive(x: float) -> bool:
return x > 0


def lt_100(x: float) -> bool:
return x < 100 # noqa: PLR2004


def add[T: (str, float, int)](a: T, b: T) -> T:
return a + b # ty: ignore[unsupported-operator]


def triple(x: float) -> float:
return x * 3


def is_gt_ten(x: float) -> float:
return x > 10 # noqa: PLR2004


def min_two(x: float) -> float:
return x - 2


def is_even_num(x: float) -> bool:
return x % 2 == 0


def square(x: float) -> float:
return x * x


def is_lt_400(x: float) -> float:
return x < 400 # noqa: PLR2004


def has_len(value: str) -> bool:
return len(value) > 0

Expand Down
186 changes: 186 additions & 0 deletions tests/test_benchmarks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
from __future__ import annotations

from danom import Err, Ok, Result, Stream, all_of, any_of, compose, identity, invert, new_type
from tests.conftest import (
add_one,
double,
has_len,
is_even,
is_even_num,
is_gt_ten,
is_lt_400,
is_positive,
lt_100,
min_two,
square,
triple,
)


def test_stream_map_small(benchmark) -> None:
@benchmark
def _() -> None:
Stream.from_iterable(range(100)).map(add_one).collect()


def test_stream_map_medium(benchmark) -> None:
@benchmark
def _() -> None:
Stream.from_iterable(range(10_000)).map(add_one).collect()


def test_stream_filter_small(benchmark) -> None:
@benchmark
def _() -> None:
Stream.from_iterable(range(100)).filter(is_even).collect()


def test_stream_filter_medium(benchmark) -> None:
@benchmark
def _() -> None:
Stream.from_iterable(range(10_000)).filter(is_even).collect()


def test_stream_map_filter_chain(benchmark) -> None:
@benchmark
def _() -> None:
(
Stream.from_iterable(range(100_000))
.map(triple)
.filter(is_gt_ten) # ty: ignore[invalid-argument-type]
.map(min_two)
.filter(is_even_num)
.map(square)
.filter(is_lt_400) # ty: ignore[invalid-argument-type]
.collect()
)


def test_stream_fold(benchmark) -> None:
@benchmark
def _() -> None:
Stream.from_iterable(range(1_000)).fold(0, lambda a, b: a + b)


def test_stream_partition(benchmark) -> None:
@benchmark
def _() -> None:
Stream.from_iterable(range(1_000)).partition(is_even)


def test_ok_creation(benchmark) -> None:
@benchmark
def _() -> None:
for i in range(1_000):
Ok(i)


def test_err_creation(benchmark) -> None:
@benchmark
def _() -> None:
for i in range(1_000):
Err(error=i)


def test_ok_map_chain(benchmark) -> None:
@benchmark
def _() -> None:
Ok(1).map(add_one).map(double).map(add_one)


def test_ok_and_then_chain(benchmark) -> None:
def safe_add_one(x: float) -> Result:
return Ok(x + 1)

@benchmark
def _() -> None:
Ok(1).and_then(safe_add_one).and_then(safe_add_one).and_then(safe_add_one)


def test_err_map_short_circuit(benchmark) -> None:
@benchmark
def _() -> None:
Err(error=TypeError()).map(add_one).map(double).map(add_one)


def test_result_unwrap(benchmark) -> None:
ok = Ok(42)

@benchmark
def _() -> None:
ok.unwrap()


def test_stream_of_results(benchmark) -> None:
@benchmark
def _() -> None:
(
Stream.from_iterable([Ok(i) for i in range(100)])
.filter(Result.result_is_ok)
.map(Result.result_unwrap)
.collect()
)


def test_compose(benchmark) -> None:
fn = compose(add_one, double, add_one)

@benchmark
def _() -> None:
fn(5)


def test_all_of(benchmark) -> None:
fn = all_of(is_even, is_positive, lt_100)

@benchmark
def _() -> None:
fn(42)


def test_any_of(benchmark) -> None:
fn = any_of(is_even, is_positive, lt_100)

@benchmark
def _() -> None:
fn(42)


def test_identity(benchmark) -> None:
@benchmark
def _() -> None:
identity(42)


def test_invert(benchmark) -> None:
fn = invert(is_even)

@benchmark
def _() -> None:
fn(3)


positive_float_type = new_type("PositiveFloat", float, validators=[is_positive])


def test_new_type_creation(benchmark) -> None:
@benchmark
def _() -> None:
positive_float_type(42.0)


def test_new_type_map(benchmark) -> None:
val = positive_float_type(10.0)

@benchmark
def _() -> None:
val.map(double)


str_type = new_type("StrType", str, validators=[has_len], converters=[str])


def test_new_type_with_converter(benchmark) -> None:
@benchmark
def _() -> None:
str_type(12345)
4 changes: 2 additions & 2 deletions tests/test_safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_traceback():
"Traceback (most recent call last):",
' File "./src/danom/_safe.py", line 34, in __call__',
" return Ok(self.func(*args, **kwargs))",
' File "./tests/conftest.py", line 85, in div_zero',
' File "./tests/conftest.py", line 117, in div_zero',
" return x / 0",
"ZeroDivisionError: division by zero",
]
Expand All @@ -79,4 +79,4 @@ def test_traceback():

missing_lines = [line for line in expected_lines if line not in tb_lines]

assert missing_lines == []
assert missing_lines == [], f"lines don't match {tb_lines = }"
Loading