1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
|
from dataclasses import dataclass
from textwrap import dedent
from typing import Annotated, Optional
import pytest
from attrs import define
from cyclopts import Parameter
from cyclopts.exceptions import UnknownOptionError
@pytest.mark.parametrize("decorator", [dataclass, define])
def test_parameter_decorator_dataclass(app, assert_parse_args, decorator):
@Parameter(name="*") # Flatten namespace.
@decorator
class User:
name: str
age: int
@app.command
def create(*, user: Optional[User] = None):
pass
assert_parse_args(create, "create")
assert_parse_args(create, "create --name=Bob --age=100", user=User("Bob", 100)) # pyright: ignore[reportCallIssue]
@pytest.mark.parametrize("decorator", [dataclass, define])
def test_parameter_decorator_dataclass_nested_1(app, decorator, console):
"""
https://github.com/BrianPugh/cyclopts/issues/320
"""
@decorator
class S3Path:
bucket: Annotated[str, Parameter()]
key: Annotated[str, Parameter()]
@Parameter(name="*") # Flatten namespace.
@decorator
class S3CliParams:
path: Annotated[S3Path, Parameter(name="*")]
region: Annotated[str, Parameter(name="region")]
@app.command
def action(*, s3_path: S3CliParams):
pass
with console.capture() as capture:
app("action --help", console=console)
actual = capture.get()
expected = dedent(
"""\
Usage: test_parameter_decorator action [OPTIONS]
╭─ Parameters ───────────────────────────────────────────────────────╮
│ * --bucket [required] │
│ * --key [required] │
│ * --region [required] │
╰────────────────────────────────────────────────────────────────────╯
"""
)
assert actual == expected
def test_parameter_decorator_dataclass_inheritance(app, assert_parse_args):
@Parameter(name="person") # Should override the name="u" below.
@Parameter(name="u", negative_bool=[])
@dataclass
class User:
name: str
age: int
privileged: bool = False
@Parameter(name="a", negative_bool=None) # Should revert to Cyclopts defaults
@dataclass
class Admin(User):
privileged: bool = True
@app.command
def create(*, user: Optional[User] = None, admin: Optional[Admin] = None):
pass
assert_parse_args(create, "create --person.name=Bob --person.age=100", user=User("Bob", 100))
with pytest.raises(UnknownOptionError):
app("create --person.no-privileged", exit_on_error=False)
assert_parse_args(create, "create --a.name=Bob --a.age=100", admin=Admin("Bob", 100))
assert_parse_args(
create, "create --a.name=Bob --a.age=100 --a.no-privileged", admin=Admin("Bob", 100, privileged=False)
)
|