Skip to main content

pytest bonsai

Read the Docs

pytest-bonsai is a plugin that brings elegant, declarative, and composable test data to your test suite.

pytest-bonsai helps you grow minimal, yet expressive dependency trees using Python dataclasses, fixtures, and dynamic parameter resolution.

Installation

$ pip install pytest-bonsai

Showcase

import random
from dataclasses import dataclass, field
from enum import Enum

import pytest

from pytest_bonsai import FixtureRequest, expand, parametrized_fixture


class Rank(Enum):
    GENIN = "Genin"
    CHUNIN = "Chunin"
    JONIN = "Jonin"


class Weapon(Enum):
    KATANA = "Katana"
    KUSARIGAMA = "Kusarigama"
    NUNCHAKU = "Nunchaku"
    SHURIKEN = "Shuriken"
    TANTO = "Tanto"


@dataclass
class Uniform:
    emblem: str
    color: str


@dataclass
class Dojo:
    name: str
    weapons: list[Weapon]
    uniform: Uniform


@dataclass
class Ninja:
    name: str
    rank: Rank
    dojo: Dojo
    weapon: Weapon
    uniform: Uniform

    def __repr__(self):
        return (
            f"{self.name} {self.rank.value}, "
            f"armed with {self.weapon.value}, "
            f"wearing {self.uniform.color} uniform with {self.uniform.emblem}"
        )


# parametrized fixtures act as factories
@dataclass
class DojoParam:
    name: str = field(default_factory=lambda: random.choice(["Konoha", "Kiri", "Kumo"]))
    weapons: list[Weapon] = field(default_factory=lambda: random.sample(list(Weapon), 2))
    uniform_color: str = "black"


@parametrized_fixture(DojoParam)
def dojo(request: FixtureRequest[DojoParam]) -> Dojo:
    return Dojo(
        name=request.param.name,
        weapons=request.param.weapons,
        uniform=Uniform(emblem=f"emblem of {request.param.name}-ryu", color=request.param.uniform_color),
    )


@dataclass
class NinjaParam:
    name: str = field(default_factory=lambda: random.choice(["Hattori", "Goemon", "Kotarou"]))
    rank: Rank = field(default_factory=lambda: random.choice(list(Rank)))
    dojo: Dojo = field(default_factory=dojo)
    weapon: Weapon | None = None
    uniform: Uniform | None = None


@parametrized_fixture(NinjaParam)
def ninja(request: FixtureRequest[NinjaParam]) -> Ninja:
    return Ninja(
        name=request.param.name,
        rank=request.param.rank,
        dojo=request.param.dojo,
        weapon=request.param.weapon or random.choice(request.param.dojo.weapons),
        uniform=request.param.uniform or request.param.dojo.uniform,
    )


# by default, ninjas use dojo's uniform and weapon
def test_ninja_uses_approved_equipment(ninja, dojo):
    assert ninja.weapon in dojo.weapons
    assert ninja.uniform is dojo.uniform


# but can have personal preferences
@ninja.parametrize(weapon=Weapon.KATANA)
def test_sword_ninja(ninja, dojo):
    assert ninja.weapon == Weapon.KATANA


# some are allowed to wear special uniforms
@pytest.fixture
def red_uniform(dojo):
    return Uniform(emblem=dojo.uniform.emblem, color="red")


@ninja.parametrize(uniform=red_uniform)
def test_red_ninja(ninja, dojo):
    assert ninja.uniform.color == "red"


# or choose the color on the fly
@ninja.parametrize(uniform=lambda dojo: Uniform(emblem=dojo.uniform.emblem, color="green"))
def test_green_ninja(ninja, dojo):
    assert ninja.uniform.color == "green"


# some may even choose a diffrent uniform for each assignment
@parametrized_fixture
def color(request): ...


@ninja.parametrize(uniform=lambda dojo, color: Uniform(emblem=dojo.uniform.emblem, color=color))
@color.parametrize(expand(["blue", "pink"]))
def test_rainbow_ninja(ninja, dojo, color):
    assert ninja.uniform.color == color
    assert ninja.uniform.emblem == dojo.uniform.emblem
    assert ninja.weapon in dojo.weapons


# masters have the highest rank, but specialize in weapon of choice
@parametrized_fixture
def master_weapon(request): ...


@ninja.parametrize(rank=Rank.JONIN, weapon=master_weapon)
class TestMasters:
    @master_weapon.parametrize(Weapon.KATANA)
    def test_katana_master(self, ninja, master_weapon):
        assert ninja.rank == Rank.JONIN
        assert ninja.weapon == Weapon.KATANA

    @master_weapon.parametrize(Weapon.TANTO)
    def test_tanto_master(self, ninja, master_weapon):
        assert ninja.rank == Rank.JONIN
        assert ninja.weapon == Weapon.TANTO

Metadata

Release files for pytest-bonsai 0.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-bonsai 0.0.2
File Size Uploaded
pytest_bonsai-0.0.2.tar.gz 11.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-bonsai 0.0.2
File Interpreter ABI Platform
pytest_bonsai-0.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 17.6 kB

Release files / pytest_bonsai-0.0.2.tar.gz

Download URL pytest_bonsai-0.0.2.tar.gz
Size 11.1 kB
Tags Source
SHA-256 checksum
How to use checksums
fc252f8d1402ef01db1de11d93a90651dbb5c4ba55ef97a556dda9dd24723233
BLAKE2b-256 checksum
How to use checksums
c30b8dc0459b4b6dd6987f0cc30d891b4750f870eb5d3994d0feab49afecc560
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 8, 2025.

Transparency log

Release files / pytest_bonsai-0.0.2-py3-none-any.whl

Download URL pytest_bonsai-0.0.2-py3-none-any.whl
Size 6.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
76784ed09cb038ba46f786aec4e17553b39d74cc68a3f82357cfe14d2ebb08ed
BLAKE2b-256 checksum
How to use checksums
e528bfff3ae643b284c6ea4ad2bdd10c4ba780edc2c01181cd6f60c39fc5fed9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.12.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 8, 2025.

Transparency log

Release history Release notifications | RSS feed

This release

0.0.2 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page