From 591d57dddae4897b7defc28488eec5370659eb56 Mon Sep 17 00:00:00 2001 From: oir Date: Sun, 22 Feb 2026 17:29:55 -0500 Subject: [PATCH] share logic between recursive tests --- tests/test_recursive/__init__.py | 0 tests/test_recursive/defs.py | 215 +++++++++++++++ .../test_recursive_parse.py | 251 ++---------------- .../test_recursive_start.py | 242 ++--------------- 4 files changed, 254 insertions(+), 454 deletions(-) create mode 100644 tests/test_recursive/__init__.py create mode 100644 tests/test_recursive/defs.py rename tests/{ => test_recursive}/test_recursive_parse.py (83%) rename tests/{ => test_recursive}/test_recursive_start.py (83%) diff --git a/tests/test_recursive/__init__.py b/tests/test_recursive/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_recursive/defs.py b/tests/test_recursive/defs.py new file mode 100644 index 0000000..8442cbe --- /dev/null +++ b/tests/test_recursive/defs.py @@ -0,0 +1,215 @@ +from dataclasses import dataclass, field +from typing import Literal, TypedDict + + +@dataclass +class DieConfig: + """ + Configuration for the dice program. + + Attributes: + sides: The number of sides on the dice. + kind: Whether to throw a single die or a pair of dice. + """ + + sides: int = 6 + kind: Literal["single", "pair"] = "single" + + +@dataclass +class DieConfig2: + """ + Configuration for the dice program. + + Attributes: + sides: The number of sides on the dice. + kind: Whether to throw a single die or a pair of dice. + """ + + sides: int + kind: Literal["single", "pair"] + + +class DieConfig2TD(TypedDict): + """ + Configuration for the dice program. + + Attributes: + sides: The number of sides on the dice. + kind: Whether to throw a single die or a pair of dice. + """ + + sides: int + kind: Literal["single", "pair"] + + +class ConfigWithVarArgs: + def __init__(self, *values: int) -> None: + self.values = list(values) + + +class ConfigWithVarKwargs: + def __init__(self, **settings: int) -> None: + self.settings = settings + + +@dataclass +class NestedConfigWithVarArgs: + config: ConfigWithVarArgs + + +class NestedTypedDictWithVarArgs(TypedDict): + config: ConfigWithVarArgs + + +@dataclass +class FusionConfig: + """ + Fusion config. + + Attributes: + left_path: Path to the first monster. + right_path: Path to the second monster. + output_path [p]: Path to store the fused monster. + components: Components to fuse. + alpha: Weighting factor for the first monster. + """ + + left_path: str + right_path: str + output_path: str + components: list[str] = field(default_factory=lambda: ["fang", "claw"]) + alpha: float = 0.5 + + +@dataclass +class InputPaths: + """ + Input paths for fusion. + + Attributes: + left_path: Path to the first monster. + right_path: Path to the second monster. + """ + + left_path: str + right_path: str + + +@dataclass +class IOPaths: + """ + Input and output paths for fusion. + + Attributes: + input_paths: Input paths for the fusion. + output_path [p]: Path to store the fused monster. + """ + + input_paths: InputPaths + output_path: str + + +@dataclass +class FusionConfig2: + """ + Fusion config with separate input and output paths. + + Attributes: + io_paths: Input and output paths for the fusion. + components: Components to fuse. + alpha: Weighting factor for the first monster. + """ + + io_paths: IOPaths + components: list[str] = field(default_factory=lambda: ["fang", "claw"]) + alpha: float = 0.5 + + +class FusionConfig2TD(TypedDict): + """ + Fusion config with separate input and output paths. + + Attributes: + io_paths: Input and output paths for the fusion. + components: Components to fuse. + alpha: Weighting factor for the first monster. + """ + + io_paths: IOPaths + components: list[str] + alpha: float + + +@dataclass +class IOPaths2: + """ + Input and output paths for fusion. + + Attributes: + input_paths: Input paths for the fusion. + output_path [l]: Path to store the fused monster. + """ + + input_paths: InputPaths + output_path: str + + +@dataclass +class FusionConfig3: + """ + Fusion config with separate input and output paths. + + Attributes: + io_paths: Input and output paths for the fusion. + components: Components to fuse. + alpha: Weighting factor for the first monster. + """ + + io_paths: IOPaths2 + components: list[str] = field(default_factory=lambda: ["fang", "claw"]) + alpha: float = 0.5 + + +@dataclass +class FusionConfig4: + """ + Fusion config with separate input and output paths. + + Attributes: + io_paths: Input and output paths for the fusion. + components: Components to fuse. + alpha: Weighting factor for the first monster. + """ + + io_paths: IOPaths2 | tuple[str, str] + components: list[str] = field(default_factory=lambda: ["fang", "claw"]) + alpha: float = 0.5 + + +@dataclass(kw_only=True) +class AppleConfig: + """ + Configuration for apple. + + Attributes: + color: The color of the apple. + heavy: Whether the apple is heavy. + """ + + color: str = "red" + heavy: bool = False + + +@dataclass(kw_only=True) +class BananaConfig: + """ + Configuration for banana. + + Attributes: + length: The length of the banana. + ripe: Whether the banana is ripe. + """ + + length: float = 6.0 + ripe: bool = False diff --git a/tests/test_recursive_parse.py b/tests/test_recursive/test_recursive_parse.py similarity index 83% rename from tests/test_recursive_parse.py rename to tests/test_recursive/test_recursive_parse.py index d304af0..ba0cf2a 100644 --- a/tests/test_recursive_parse.py +++ b/tests/test_recursive/test_recursive_parse.py @@ -1,34 +1,32 @@ import re from dataclasses import dataclass, field -from typing import Any, Literal, TypedDict +from typing import Any, Literal from pytest import mark, raises from startle import parse from startle.error import ParserConfigError, ParserOptionError -from .test_help._utils import ( - NS, - OS, - TS, - VS, - check_help_from_class, +from ..test_help._utils import NS, OS, TS, VS, check_help_from_class +from .defs import ( + AppleConfig, + BananaConfig, + ConfigWithVarArgs, + ConfigWithVarKwargs, + DieConfig, + DieConfig2, + DieConfig2TD, + FusionConfig, + FusionConfig2, + FusionConfig2TD, + FusionConfig3, + FusionConfig4, + InputPaths, + IOPaths, + NestedConfigWithVarArgs, + NestedTypedDictWithVarArgs, ) -@dataclass -class DieConfig: - """ - Configuration for the dice program. - - Attributes: - sides: The number of sides on the dice. - kind: Whether to throw a single die or a pair of dice. - """ - - sides: int = 6 - kind: Literal["single", "pair"] = "single" - - @dataclass class MainConfig: """ @@ -82,8 +80,6 @@ def test_recursive_w_defaults( if count is not None: cli_args += [count_opt, str(count)] - # expected_cfg = DieConfig(**config_kwargs) # type: ignore[arg-type] - # expected_count = count if count is not None else 1 cfg = parse(MainConfig, args=cli_args, recurse=True) assert cfg == MainConfig( @@ -132,20 +128,6 @@ def test_recursive_w_defaults_nested( parse(MainConfigUnion, args=cli_args, recurse=True, naming="nested") -@dataclass -class DieConfig2: - """ - Configuration for the dice program. - - Attributes: - sides: The number of sides on the dice. - kind: Whether to throw a single die or a pair of dice. - """ - - sides: int - kind: Literal["single", "pair"] - - @dataclass class MainConfig2: """ @@ -160,19 +142,6 @@ class MainConfig2: count: int -class DieConfig2TD(TypedDict): - """ - Configuration for the dice program. - - Attributes: - sides: The number of sides on the dice. - kind: Whether to throw a single die or a pair of dice. - """ - - sides: int - kind: Literal["single", "pair"] - - @dataclass class MainConfig2TD: """ @@ -399,25 +368,6 @@ def test_recursive_w_inner_required_nested() -> None: assert cfg == MainConfig4(count=2, cfg=None) -class ConfigWithVarArgs: - def __init__(self, *values: int) -> None: - self.values = list(values) - - -class ConfigWithVarKwargs: - def __init__(self, **settings: int) -> None: - self.settings = settings - - -@dataclass -class NestedConfigWithVarArgs: - config: ConfigWithVarArgs - - -class NestedTypedDictWithVarArgs(TypedDict): - config: ConfigWithVarArgs - - @mark.parametrize("naming", ["flat", "nested"]) def test_recursive_unsupported(naming: Literal["flat", "nested"]) -> None: @dataclass @@ -621,95 +571,6 @@ class C6d: ) -@dataclass -class FusionConfig: - """ - Fusion config. - - Attributes: - left_path: Path to the first monster. - right_path: Path to the second monster. - output_path [p]: Path to store the fused monster. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - left_path: str - right_path: str - output_path: str - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - -@dataclass -class InputPaths: - """ - Input paths for fusion. - - Attributes: - left_path: Path to the first monster. - right_path: Path to the second monster. - """ - - left_path: str - right_path: str - - -@dataclass -class IOPaths: - """ - Input and output paths for fusion. - - Attributes: - input_paths: Input paths for the fusion. - output_path [p]: Path to store the fused monster. - """ - - input_paths: InputPaths - output_path: str - - -@dataclass -class FusionConfig2: - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - -class FusionConfig2TD(TypedDict): - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths - components: list[str] - alpha: float - - -def fuse1(cfg: FusionConfig) -> None: - """ - Fuse two monsters with polymerization. - - Args: - cfg: The fusion configuration. - """ - pass - - @dataclass class Fuse1: """ @@ -881,36 +742,6 @@ def test_recursive_dataclass_nested(cls: type[Fuse2] | type[Fuse2TD]) -> None: assert parsed == expected -@dataclass -class IOPaths2: - """ - Input and output paths for fusion. - - Attributes: - input_paths: Input paths for the fusion. - output_path [l]: Path to store the fused monster. - """ - - input_paths: InputPaths - output_path: str - - -@dataclass -class FusionConfig3: - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths2 - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - @dataclass class Fuse3: """ @@ -948,22 +779,6 @@ def test_recursive_dataclass_help_2() -> None: ) -@dataclass -class FusionConfig4: - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths2 | tuple[str, str] - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - @dataclass class Fuse4: """ @@ -984,34 +799,6 @@ def test_recursive_dataclass_non_class() -> None: parse(Fuse4, args=[], recurse=True, naming="nested") -@dataclass(kw_only=True) -class AppleConfig: - """ - Configuration for apple. - - Attributes: - color: The color of the apple. - heavy: Whether the apple is heavy. - """ - - color: str = "red" - heavy: bool = False - - -@dataclass(kw_only=True) -class BananaConfig: - """ - Configuration for banana. - - Attributes: - length: The length of the banana. - ripe: Whether the banana is ripe. - """ - - length: float = 6.0 - ripe: bool = False - - @dataclass class FruitSaladConfig: """ diff --git a/tests/test_recursive_start.py b/tests/test_recursive/test_recursive_start.py similarity index 83% rename from tests/test_recursive_start.py rename to tests/test_recursive/test_recursive_start.py index 6be5a1f..dd5852c 100644 --- a/tests/test_recursive_start.py +++ b/tests/test_recursive/test_recursive_start.py @@ -1,35 +1,32 @@ import re from collections.abc import Callable -from dataclasses import dataclass, field -from typing import Any, Literal, TypedDict +from typing import Any, Literal from pytest import mark, raises from startle.error import ParserConfigError, ParserOptionError -from ._utils import check_args -from .test_help._utils import ( - NS, - OS, - TS, - VS, - check_help_from_func, +from .._utils import check_args +from ..test_help._utils import NS, OS, TS, VS, check_help_from_func +from .defs import ( + AppleConfig, + BananaConfig, + ConfigWithVarArgs, + ConfigWithVarKwargs, + DieConfig, + DieConfig2, + DieConfig2TD, + FusionConfig, + FusionConfig2, + FusionConfig2TD, + FusionConfig3, + FusionConfig4, + InputPaths, + IOPaths, + NestedConfigWithVarArgs, + NestedTypedDictWithVarArgs, ) -@dataclass -class DieConfig: - """ - Configuration for the dice program. - - Attributes: - sides: The number of sides on the dice. - kind: Whether to throw a single die or a pair of dice. - """ - - sides: int = 6 - kind: Literal["single", "pair"] = "single" - - def throw_dice(cfg: DieConfig, count: int = 1) -> None: """ Throw dice according to the configuration. @@ -134,20 +131,6 @@ def test_recursive_w_defaults_nested( ) -@dataclass -class DieConfig2: - """ - Configuration for the dice program. - - Attributes: - sides: The number of sides on the dice. - kind: Whether to throw a single die or a pair of dice. - """ - - sides: int - kind: Literal["single", "pair"] - - def throw_dice2(cfg: DieConfig2, count: int) -> None: """ Throw dice according to the configuration. @@ -159,19 +142,6 @@ def throw_dice2(cfg: DieConfig2, count: int) -> None: pass -class DieConfig2TD(TypedDict): - """ - Configuration for the dice program. - - Attributes: - sides: The number of sides on the dice. - kind: Whether to throw a single die or a pair of dice. - """ - - sides: int - kind: Literal["single", "pair"] - - def throw_dice2_td(cfg: DieConfig2TD, count: int) -> None: """ Throw dice according to the configuration. @@ -401,25 +371,6 @@ def test_recursive_w_inner_required_nested() -> None: ) -class ConfigWithVarArgs: - def __init__(self, *values: int) -> None: - self.values = list(values) - - -class ConfigWithVarKwargs: - def __init__(self, **settings: int) -> None: - self.settings = settings - - -@dataclass -class NestedConfigWithVarArgs: - config: ConfigWithVarArgs - - -class NestedTypedDictWithVarArgs(TypedDict): - config: ConfigWithVarArgs - - @mark.parametrize("naming", ["flat", "nested"]) def test_recursive_unsupported(naming: Literal["flat", "nested"]) -> None: def f1(cfg: ConfigWithVarArgs) -> None: @@ -626,85 +577,6 @@ def f8d(cfg: DieConfig2TD, cfg2: DieConfig2TD) -> None: ) -@dataclass -class FusionConfig: - """ - Fusion config. - - Attributes: - left_path: Path to the first monster. - right_path: Path to the second monster. - output_path [p]: Path to store the fused monster. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - left_path: str - right_path: str - output_path: str - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - -@dataclass -class InputPaths: - """ - Input paths for fusion. - - Attributes: - left_path: Path to the first monster. - right_path: Path to the second monster. - """ - - left_path: str - right_path: str - - -@dataclass -class IOPaths: - """ - Input and output paths for fusion. - - Attributes: - input_paths: Input paths for the fusion. - output_path [p]: Path to store the fused monster. - """ - - input_paths: InputPaths - output_path: str - - -@dataclass -class FusionConfig2: - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - -class FusionConfig2TD(TypedDict): - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths - components: list[str] - alpha: float - - def fuse1(cfg: FusionConfig) -> None: """ Fuse two monsters with polymerization. @@ -853,36 +725,6 @@ def test_recursive_dataclass_nested(fuse: Callable[..., Any]) -> None: ) -@dataclass -class IOPaths2: - """ - Input and output paths for fusion. - - Attributes: - input_paths: Input paths for the fusion. - output_path [l]: Path to store the fused monster. - """ - - input_paths: InputPaths - output_path: str - - -@dataclass -class FusionConfig3: - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths2 - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - def fuse3(cfg: FusionConfig3) -> None: """ Fuse two monsters with polymerization. @@ -912,22 +754,6 @@ def test_recursive_dataclass_help_2() -> None: check_help_from_func(fuse3, "fuse.py", expected, recurse=True) -@dataclass -class FusionConfig4: - """ - Fusion config with separate input and output paths. - - Attributes: - io_paths: Input and output paths for the fusion. - components: Components to fuse. - alpha: Weighting factor for the first monster. - """ - - io_paths: IOPaths2 | tuple[str, str] - components: list[str] = field(default_factory=lambda: ["fang", "claw"]) - alpha: float = 0.5 - - def fuse4(cfg: FusionConfig4) -> None: """ Fuse two monsters with polymerization. @@ -946,34 +772,6 @@ def test_recursive_dataclass_non_class() -> None: check_help_from_func(fuse4, "fuse.py", "", recurse=True) -@dataclass(kw_only=True) -class AppleConfig: - """ - Configuration for apple. - - Attributes: - color: The color of the apple. - heavy: Whether the apple is heavy. - """ - - color: str = "red" - heavy: bool = False - - -@dataclass(kw_only=True) -class BananaConfig: - """ - Configuration for banana. - - Attributes: - length: The length of the banana. - ripe: Whether the banana is ripe. - """ - - length: float = 6.0 - ripe: bool = False - - def make_fruit_salad( apple_cfg: AppleConfig, banana_cfg: BananaConfig,