From 8e5d8774f4d293fd405dadee7d11b9ac4aee3910 Mon Sep 17 00:00:00 2001 From: "Edwin (Ed) Onuonga" Date: Wed, 27 Dec 2023 04:02:00 +0000 Subject: [PATCH 1/2] feat: add `feud.build` + move `rich_click` styling to `feud.Config` (#123) --- README.md | 8 - docs/source/index.rst | 2 +- docs/source/sections/core/group.rst | 1 - docs/source/sections/core/run.rst | 11 +- feud/_internal/_command.py | 17 +- feud/_internal/_decorators.py | 5 +- feud/_internal/_metaclass.py | 6 +- feud/config.py | 16 ++ feud/core/__init__.py | 193 +++++++++++---------- feud/core/command.py | 9 + feud/core/group.py | 49 +++--- tests/unit/test_config.py | 2 + tests/unit/test_core/fixtures/module.py | 18 +- tests/unit/test_core/test_group.py | 54 +++--- tests/unit/test_internal/test_docstring.py | 18 +- tests/unit/test_internal/test_metaclass.py | 4 +- 16 files changed, 221 insertions(+), 192 deletions(-) diff --git a/README.md b/README.md index ab183e3..cfbc62e 100644 --- a/README.md +++ b/README.md @@ -821,14 +821,6 @@ Without Rich-formatted output -> [!TIP] -> -> [Settings for `rich-click`](https://github.com/ewels/rich-click/blob/main/src/rich_click/rich_click.py) can be provided to `feud.run`, e.g.: -> -> ```python -> feud.run(command, rich_settings={"SHOW_ARGUMENTS": False}) -> ``` - ## Build status | `master` | `dev` | diff --git a/docs/source/index.rst b/docs/source/index.rst index 1bb269c..0b8c17d 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -40,7 +40,7 @@ Feud ---- -Designing a *good* CLI can spiral into chaos without the help of +Designing a *good* CLI can quickly spiral into chaos without the help of an intuitive CLI builder. **Feud builds on** `Click `__ **for diff --git a/docs/source/sections/core/group.rst b/docs/source/sections/core/group.rst index 3ade12f..17fa96d 100644 --- a/docs/source/sections/core/group.rst +++ b/docs/source/sections/core/group.rst @@ -33,5 +33,4 @@ API reference :members: :exclude-members: from_dict, from_iter, from_module -.. autofunction:: feud.core.group.compile \ No newline at end of file diff --git a/docs/source/sections/core/run.rst b/docs/source/sections/core/run.rst index e618a57..bb183a7 100644 --- a/docs/source/sections/core/run.rst +++ b/docs/source/sections/core/run.rst @@ -1,5 +1,5 @@ -Running commands/groups -======================= +Running and building CLIs +========================= .. contents:: Table of Contents :class: this-will-duplicate-information-and-it-is-still-useful-here @@ -7,9 +7,14 @@ Running commands/groups :backlinks: none :depth: 3 +- :py:func:`.run`: **Build and run** runnable objects as a :py:class:`click.Command` or :py:class:`click.Group`. +- :py:func:`.build`: **Build** runnable object(s) into a + :py:class:`click.Command`, :py:class:`click.Group` (or :py:class:`.Group`). + ---- API reference ------------- -.. autofunction:: feud.core.run +.. automodule:: feud.core + :members: run, build diff --git a/feud/_internal/_command.py b/feud/_internal/_command.py index 371442c..da53dc1 100644 --- a/feud/_internal/_command.py +++ b/feud/_internal/_command.py @@ -12,9 +12,13 @@ try: import rich_click as click + + RICH = True except ImportError: import click + RICH = False + from feud._internal import _decorators, _inflect, _types from feud.config import Config @@ -56,7 +60,10 @@ class CommandState: options: dict[str, ParameterSpec] = dataclasses.field(default_factory=dict) description: str | None = None - def decorate(self: CommandState, func: t.Callable) -> click.Command: + def decorate( # noqa: PLR0915 + self: CommandState, + func: t.Callable, + ) -> click.Command: meta_vars: dict[str, str] = {} sensitive_vars: dict[str, bool] = {} positional: list[str] = [] @@ -146,6 +153,14 @@ def decorate(self: CommandState, func: t.Callable) -> click.Command: if self.pass_context: command = click.pass_context(command) + if RICH: + # apply rich-click styling + command = click.rich_config( + help_config=click.RichHelpConfiguration( + **self.config.rich_click_kwargs + ) + )(command) + constructor = click.group if self.is_group else click.command command = constructor(**self.click_kwargs)(command) diff --git a/feud/_internal/_decorators.py b/feud/_internal/_decorators.py index 3d7cad5..def9a45 100644 --- a/feud/_internal/_decorators.py +++ b/feud/_internal/_decorators.py @@ -13,10 +13,7 @@ import pydantic as pyd import pydantic_core as pydc -try: - import rich_click as click -except ImportError: - import click +from feud import click def validate_call( diff --git a/feud/_internal/_metaclass.py b/feud/_internal/_metaclass.py index 2e0bccb..2db7f4c 100644 --- a/feud/_internal/_metaclass.py +++ b/feud/_internal/_metaclass.py @@ -8,15 +8,11 @@ import abc import typing as t +from feud import click from feud._internal import _command from feud.config import Config from feud.core.command import command -try: - import rich_click as click -except ImportError: - import click - class GroupBase(abc.ABCMeta): def __new__( diff --git a/feud/config.py b/feud/config.py index 087935a..0ef5954 100644 --- a/feud/config.py +++ b/feud/config.py @@ -44,6 +44,13 @@ class Config(pyd.BaseModel): #: :py:func:`pydantic.validate_call_decorator.validate_call`. pydantic_kwargs: dict[str, Any] = {} + #: Styling settings for ``rich-click``. + #: + #: See all available options + #: `here `__ + #: (as of ``rich-click`` v1.7.2). + rich_click_kwargs: dict[str, Any] = {"show_arguments": True} + def __init__(self: Config, **kwargs: Any) -> Config: caller: str = inspect.currentframe().f_back.f_code.co_name if caller != Config._create.__name__: @@ -73,6 +80,7 @@ def config( show_help_datetime_formats: bool | None = None, show_help_envvars: bool | None = None, pydantic_kwargs: dict[str, Any] | None = None, + rich_click_kwargs: dict[str, Any] | None = None, ) -> Config: """Create a reusable configuration for :py:func:`.command` or :py:class:`.Group` objects. @@ -97,6 +105,13 @@ def config( Validation settings for :py:func:`pydantic.validate_call_decorator.validate_call`. + rich_click_kwargs: + Styling settings for ``rich-click``. + + See all available options + `here `__ + (as of ``rich-click`` v1.7.2). + Returns ------- The reusable :py:class:`.Config`. @@ -132,4 +147,5 @@ def config( show_help_datetime_formats=show_help_datetime_formats, show_help_envvars=show_help_envvars, pydantic_kwargs=pydantic_kwargs, + rich_click_kwargs=rich_click_kwargs, ) diff --git a/feud/core/__init__.py b/feud/core/__init__.py index f9c3920..77625e1 100644 --- a/feud/core/__init__.py +++ b/feud/core/__init__.py @@ -7,30 +7,19 @@ from __future__ import annotations -import contextlib import inspect import sys import types import typing as t import warnings -try: - import rich_click as click - - RICH = True -except ImportError: - import click - - RICH = False - import feud.exceptions +from feud import click from feud.config import Config from feud.core.command import * from feud.core.group import * -__all__ = ["Group", "compile", "command", "run"] - -RICH_DEFAULTS = {"SHOW_ARGUMENTS": True} +__all__ = ["Group", "build", "command", "run"] Runner = t.Union[ click.Command, @@ -54,13 +43,10 @@ def run( help: str | None = None, # noqa: A002 epilog: str | None = None, config: Config | None = None, - rich_settings: dict[str, t.Any] | None = None, warn: bool = True, **click_kwargs: t.Any, ) -> t.Any: - """ - - Run a function, :py:class:`click.Command`, :py:class:`.Group`, + """Run a function, :py:class:`click.Command`, :py:class:`.Group`, or :py:class:`click.Group`. Multiple functions/commands can also be provided as a :py:obj:`dict`, @@ -106,14 +92,6 @@ def run( If a :py:obj:`dict`, iterable or module ``obj`` is provided, the configuration will be forwarded to the nested runnable objects within. - rich_settings: - Styling options for ``rich-click``, e.g. - ``{"SHOW_ARGUMENTS": False, "MAX_WIDTH": 60}``. - - See all available options - `here `__ - (as of ``rich-click`` v1.7.2). - warn: Silences warnings that are produced if ``name``, ``help``, ``epilog`` or ``config`` are provided when ``obj`` is a :py:class:`click.Command`, @@ -127,11 +105,6 @@ def run( ------- Output of the called object. - Raises - ------ - feud.exceptions.CompilationError - If no runnable object or current module can be determined. - Examples -------- Running an undecorated function. @@ -167,7 +140,7 @@ def run( >>> class CLI(feud.Group): ... def func(*, opt: int) -> int: ... return opt - >>> group: click.Group = feud.compile(CLI) + >>> group: click.Group = CLI.compile() >>> feud.run(group, ["func", "--opt", "3"], standalone_mode=False) 3 @@ -222,20 +195,8 @@ def run( args = obj obj = None - # use current module if no runner provided - if obj is None: - frame = inspect.stack()[1] - obj = inspect.getmodule(frame[0]) or sys.modules.get("__main__") - - if obj is None: - msg = ( - "Unable to build command - no runnable object was provided " - "and no current module can be determined in the present context." - ) - raise feud.CompilationError(msg) - # get runner - runner: click.Command | type[Group] = get_runner( + runner: click.Command | click.Group = build( obj, name=name, help=help, @@ -244,13 +205,12 @@ def run( warn=warn, ) - # set (and unset) feud/user-specified rich-click settings - with rich_styler(rich_settings or {}): - return runner(args, **click_kwargs) + return runner(args, **click_kwargs) def get_runner( obj: Runner, + /, *, name: str | None = None, help: str | None = None, # noqa: A002 @@ -355,56 +315,103 @@ def get_runner( return obj -@contextlib.contextmanager -def rich_styler( - kwargs: dict[str, t.Any] | None, +def build( + obj: Runner | None = None, /, -) -> t.Generator[dict[str, t.Any]]: - """Temporarily applies Feud and user-specified rich-click settings if - rich-click is installed and rich-click keywords were provided to feud.run. - """ - # avoid mutating - kwargs: dict[str, t.Any] = kwargs.copy() + *, + name: str | None = None, + help: str | None = None, # noqa: A002 + epilog: str | None = None, + config: Config | None = None, + warn: bool = True, + compile: bool = True, # noqa: A002 +) -> click.Command | click.Group | Group: + """Build a :py:class:`click.Command` or :py:class:`click.Group` from + a runnable object. - try: - if RICH: - # alias click.rich_click - rich = click.rich_click + See :py:func:`.run` for details on runnable objects. - # check for screaming snake case kwargs that are invalid - invalid_kwargs: dict[str, t.Any] = { - k: kwargs.pop(k) for k in kwargs.copy() if not hasattr(rich, k) - } + .. warning:: - # warn if any invalid kwargs - if invalid_kwargs: - msg = ( - "The following invalid rich-click settings will be " - f"ignored: {invalid_kwargs}." - ) - warnings.warn(msg, stacklevel=1) + ``name``, ``help``, ``epilog`` and ``config`` are ignored if a + :py:class:`click.Command`, :py:class:`click.Group` or + :py:class:`.Group` is provided. - # override: true defaults -> feud defaults -> specified settings - true_defaults = {k: getattr(rich, k) for k in kwargs} - kwargs = {**true_defaults, **RICH_DEFAULTS, **kwargs} + Parameters + ---------- + obj: + Runnable group, command or function to run, or :py:obj:`dict`, + iterable or module of runnable objects. - # set rich_click settings - for k, v in kwargs.items(): - setattr(rich, k, v) + name: + CLI command or group name. - yield - else: - if kwargs: - msg = ( - "rich-click settings were provided to feud.run, " - "but rich-click is not installed - these settings " - "will be ignored." - ) - warnings.warn(msg, stacklevel=1) - - yield - finally: - # restore rich_click defaults - if RICH: - for k, v in true_defaults.items(): - setattr(rich, k, v) + help: + CLI command or group description, displayed when ``--help`` + is called. If not set, the docstring of the object will be used if + available. + + epilog: + CLI command or group epilog. Appears at the bottom of ``--help``. + + config: + Configuration for the command or group. + + If a :py:obj:`dict`, iterable or module ``obj`` is provided, the + configuration will be forwarded to the nested runnable objects within. + + warn: + Silences warnings that are produced if ``name``, ``help``, ``epilog`` + or ``config`` are provided when ``obj`` is a :py:class:`click.Command`, + :py:class:`click.Group` or :py:class:`.Group`. + + compile: + Whether or not to compile :py:class:`.Group` objects into + :py:class:`click.Group` objects. + + Returns + ------- + :py:class:`click.Command`, :py:class:`click.Group` or :py:class:`.Group` + + Raises + ------ + feud.exceptions.CompilationError + If no runnable object or current module can be determined. + + Examples + -------- + >>> import feud + >>> from feud import click + >>> def func1(*, opt: int) -> int: + ... return opt + >>> def func2(*, opt: float) -> float: + ... return opt + >>> group: click.Group = feud.build([func1, func2]) + >>> isinstance(group, click.Group) + True + """ + # use current module if no runner provided + if obj is None: + frame = inspect.stack()[1] + obj = inspect.getmodule(frame[0]) or sys.modules.get("__main__") + + if obj is None: + msg = ( + "Unable to build command - no runnable object was provided " + "and no current module can be determined in the present context." + ) + raise feud.CompilationError(msg) + + runner: click.Command | Group = get_runner( + obj, + name=name, + help=help, + epilog=epilog, + config=config, + warn=warn, + ) + + if compile and inspect.isclass(runner) and issubclass(runner, Group): + return runner.compile() + + return runner diff --git a/feud/core/command.py b/feud/core/command.py index 4412f97..3b2e165 100644 --- a/feud/core/command.py +++ b/feud/core/command.py @@ -40,6 +40,7 @@ def command( show_help_datetime_formats: bool | None = None, show_help_envvars: bool | None = None, pydantic_kwargs: dict[str, typing.Any] | None = None, + rich_click_kwargs: dict[str, typing.Any] | None = None, config: Config | None = None, **click_kwargs: typing.Any, ) -> click.Command: @@ -68,6 +69,13 @@ def command( Validation settings for :py:func:`pydantic.validate_call_decorator.validate_call`. + rich_click_kwargs: + Styling settings for ``rich-click``. + + See all available options + `here `__ + (as of ``rich-click`` v1.7.2). + config: Configuration for the command. @@ -112,6 +120,7 @@ def decorate(__func: typing.Callable, /) -> typing.Callable: show_help_datetime_formats=show_help_datetime_formats, show_help_envvars=show_help_envvars, pydantic_kwargs=pydantic_kwargs, + rich_click_kwargs=rich_click_kwargs, ) # decorate function return get_command(__func, config=cfg, click_kwargs=click_kwargs) diff --git a/feud/core/group.py b/feud/core/group.py index 8f90c26..e6b709a 100644 --- a/feud/core/group.py +++ b/feud/core/group.py @@ -18,15 +18,13 @@ from collections import OrderedDict from itertools import chain -import pydantic as pyd - import feud.exceptions from feud import click from feud._internal import _command, _metaclass from feud.config import Config from feud.core.command import build_command_state -__all__ = ["Group", "compile"] +__all__ = ["Group"] class Group(metaclass=_metaclass.GroupBase): @@ -63,6 +61,7 @@ class Group(metaclass=_metaclass.GroupBase): The following function names should **NOT** be used in a group: + - :py:func:`~compile` - :py:func:`~deregister` - :py:func:`~descendants` - :py:func:`~register` @@ -166,6 +165,25 @@ def __compile__( return click_group + @classmethod + def compile(cls: type[Group]) -> click.Group: # noqa: A003 + """Compile the group into a :py:class:`click.Group`. + + Returns + ------- + The generated :py:class:`click.Group`. + + Examples + -------- + >>> import feud, click + >>> class CLI(feud.Group): + ... def func(*, opt: int) -> int: + ... return opt + >>> isinstance(CLI.compile(), click.Group) + True + """ + return cls.__compile__() + @classmethod def subgroups(cls: type[Group]) -> list[type[Group]]: """Registered subgroups. @@ -650,31 +668,6 @@ def get_name(o: click.Command | t.Callable) -> str: return group -@pyd.validate_call(config=pyd.ConfigDict(arbitrary_types_allowed=True)) -def compile(group: type[Group], /) -> click.Group: # noqa: A001 - """Compile a :py:class:`.Group` into a :py:class:`click.Group`. - - Parameters - ---------- - group: - Group to compile into a :py:class:`click.Group`. - - Returns - ------- - The generated :py:class:`click.Group`. - - Examples - -------- - >>> import feud - >>> class CLI(feud.Group): - ... def func(*, opt: int) -> int: - ... return opt - >>> isinstance(feud.compile(CLI), click.Group) - True - """ - return group.__compile__() - - def get_group(__cls: type[Group], /) -> click.Group: func: callable = __cls.__main__ if isinstance(func, staticmethod): diff --git a/tests/unit/test_config.py b/tests/unit/test_config.py index ce3f5e2..14d4021 100644 --- a/tests/unit/test_config.py +++ b/tests/unit/test_config.py @@ -16,6 +16,7 @@ def test_create_no_base_no_kwargs() -> None: assert config.show_help_datetime_formats is False assert config.show_help_envvars is True assert config.pydantic_kwargs == {} + assert config.rich_click_kwargs == {"show_arguments": True} def test_create_no_base_none_kwargs() -> None: @@ -32,6 +33,7 @@ def test_create_no_base_none_kwargs() -> None: assert config.show_help_datetime_formats is False assert config.show_help_envvars is True assert config.pydantic_kwargs == {} + assert config.rich_click_kwargs == {"show_arguments": True} def test_create_no_base_with_kwargs_default() -> None: diff --git a/tests/unit/test_core/fixtures/module.py b/tests/unit/test_core/fixtures/module.py index a96e305..9cd5642 100644 --- a/tests/unit/test_core/fixtures/module.py +++ b/tests/unit/test_core/fixtures/module.py @@ -64,13 +64,11 @@ def func(*, opt: bool) -> bool: Group.register(Subgroup) -click_group: click.Group = feud.compile( - types.new_class( - "ClickGroup", - bases=(Group,), - kwds={ - "name": "click-group", - "help": "This is a Click group.", - }, - ) -) +click_group: click.Group = types.new_class( + "ClickGroup", + bases=(Group,), + kwds={ + "name": "click-group", + "help": "This is a Click group.", + }, +).compile() diff --git a/tests/unit/test_core/test_group.py b/tests/unit/test_core/test_group.py index d0ce946..f89e7d1 100644 --- a/tests/unit/test_core/test_group.py +++ b/tests/unit/test_core/test_group.py @@ -72,7 +72,7 @@ def f(*, arg1: int) -> None: def g(*, arg1: int, arg2: bool) -> None: pass - group = feud.compile(Test) + group = Test.compile() assert isinstance(group, click.Group) assert len(group.commands) == 2 @@ -169,7 +169,7 @@ def f(*, arg1: int = 1) -> None: def g(*, arg1: int = 2, arg2: bool = False) -> None: pass - group = feud.compile(Test) + group = Test.compile() for command in group.commands.values(): for param in command.params: assert param.show_default is False @@ -181,7 +181,7 @@ def f(*, arg1: int = 1) -> None: def g(*, arg1: int = 2, arg2: bool = False) -> None: pass - group = feud.compile(Test) + group = Test.compile() for command in group.commands.values(): for param in command.params: assert param.show_default is True @@ -195,7 +195,7 @@ def f(*, arg1: int = 1) -> None: def g(*, arg1: int = 2, arg2: bool = False) -> None: pass - group = feud.compile(Test) + group = Test.compile() for command in group.commands.values(): for param in command.params: assert param.show_default is False @@ -207,7 +207,7 @@ def f(*, arg1: int = 1) -> None: def g(*, arg1: int = 2, arg2: bool = False) -> None: pass - group = feud.compile(Test) + group = Test.compile() for command in group.commands.values(): for param in command.params: assert param.show_default is True @@ -219,7 +219,7 @@ class Test(feud.Group, show_help_defaults=False): def f(*, arg1: int = 1) -> None: pass - group = feud.compile(Test) + group = Test.compile() for command in group.commands.values(): for param in command.params: assert param.show_default is True @@ -244,7 +244,7 @@ def g(*, arg: int) -> None: assert Parent.subgroups() == [Child] assert Child.subgroups() == [] - parent = feud.compile(Parent) + parent = Parent.compile() f, child = itemgetter("f", "child")(parent.commands) assert isinstance(f, click.Command) @@ -293,7 +293,7 @@ def h(*, arg: int) -> int: assert Child1.subgroups() == [] assert Child2.subgroups() == [] - parent = feud.compile(Parent) + parent = Parent.compile() f, child1, child2 = itemgetter("f", "child1", "child2")(parent.commands) assert isinstance(f, click.Command) @@ -409,7 +409,7 @@ def j(*, arg: int) -> int: assert Child3.subgroups() == [] assert Child4.subgroups() == [] - parent = feud.compile(Parent) + parent = Parent.compile() assert len(parent.commands) == 3 f, child1, child2 = itemgetter("f", "child1", "child2")(parent.commands) @@ -519,7 +519,7 @@ def i(*, arg: int) -> int: assert Child1.subgroups() == [Child2] assert Child2.subgroups() == [Child3] - parent = feud.compile(Parent) + parent = Parent.compile() assert len(parent.commands) == 3 f, child1, child2 = itemgetter("f", "child1", "child2")(parent.commands) @@ -573,7 +573,7 @@ def i(*, arg: int) -> int: assert Child1.subgroups() == [Child2] assert Child2.subgroups() == [] - parent = feud.compile(Parent) + parent = Parent.compile() assert len(parent.commands) == 3 f, child1, child2 = itemgetter("f", "child1", "child2")(parent.commands) @@ -673,7 +673,7 @@ class Grandparent(feud.Group, name="older", negate_flags=False): def f(*, arg: int) -> int: return arg - grandparent = feud.compile(Grandparent) + grandparent = Grandparent.compile() assert grandparent.name == "older" assert list(grandparent.commands) == ["f"] @@ -681,7 +681,7 @@ class Parent(Grandparent, name="old", show_help_defaults=False): def g(*, arg: int) -> int: return arg - parent = feud.compile(Parent) + parent = Parent.compile() assert parent.name == "old" assert list(parent.commands) == ["f", "g"] assert all( @@ -693,7 +693,7 @@ class Child(Parent, name="young", show_help_datetime_formats=True): def h(*, arg: int) -> int: return arg - child = feud.compile(Child) + child = Child.compile() assert child.name == "young" assert list(child.commands) == ["f", "g", "h"] assert all( @@ -714,7 +714,7 @@ class Mother( def f(*, arg: int) -> int: return arg - mother = feud.compile(Mother) + mother = Mother.compile() assert mother.name == "mother" assert list(mother.commands) == ["f"] @@ -724,7 +724,7 @@ class Father( def g(*, arg: int) -> int: return arg - father = feud.compile(Father) + father = Father.compile() assert father.name == "father" assert list(father.commands) == ["g"] @@ -732,7 +732,7 @@ class Child(Mother, Father, show_help_datetime_formats=True): def h(*, arg: int) -> int: return arg - child = feud.compile(Child) + child = Child.compile() assert list(child.commands) == ["f", "g", "h"] assert Child.__feud_config__ == feud.config( negate_flags=False, @@ -748,7 +748,7 @@ class Child(Father, Mother, show_help_datetime_formats=True): def h(*, arg: int) -> int: return arg - child = feud.compile(Child) + child = Child.compile() assert list(child.commands) == ["g", "f", "h"] assert Child.__feud_config__ == feud.config( negate_flags=False, @@ -796,29 +796,29 @@ def g(*, arg: int) -> int: class Child(Parent): pass - child = feud.compile(Child) + child = Child.compile() assert list(child.commands) == ["f", "subgroup"] Child.deregister(Subgroup) - child = feud.compile(Child) + child = Child.compile() assert list(child.commands) == ["f"] class Child(Parent): pass - parent = feud.compile(Parent) + parent = Parent.compile() assert list(parent.commands) == ["f", "subgroup"] - child = feud.compile(Child) + child = Child.compile() assert list(child.commands) == ["f", "subgroup"] Parent.deregister(Subgroup) - parent = feud.compile(Parent) + parent = Parent.compile() assert list(parent.commands) == ["f"] - child = feud.compile(Child) + child = Child.compile() assert list(child.commands) == ["f", "subgroup"] @@ -1201,7 +1201,7 @@ def command(ctx: click.Context, path: Path) -> Path: """ return ctx.obj["root"] / path - group = feud.compile(Test) + group = Test.compile() assert_help( group, @@ -1268,7 +1268,7 @@ def command(ctx: click.Context, path: Path) -> Path: class Child(Test): pass - group = feud.compile(Child) + group = Child.compile() assert_help( group, @@ -1341,7 +1341,7 @@ class Child(Test): Root directory """ - group = feud.compile(Child) + group = Child.compile() assert_help( group, diff --git a/tests/unit/test_internal/test_docstring.py b/tests/unit/test_internal/test_docstring.py index 0d3ce81..e6f56df 100644 --- a/tests/unit/test_internal/test_docstring.py +++ b/tests/unit/test_internal/test_docstring.py @@ -178,14 +178,14 @@ def test_get_description_class_single_line_no_doc() -> None: class Group(feud.Group): pass - assert feud.compile(Group).help is None + assert Group.compile().help is None def test_get_description_class_single_line_doc() -> None: class Group(feud.Group): """Line 1.""" - assert feud.compile(Group).help == "Line 1." + assert Group.compile().help == "Line 1." def test_get_description_class_single_line_doc_with_examples() -> None: @@ -197,7 +197,7 @@ class Group(feud.Group): >>> # Hello World! """ - assert feud.compile(Group).help == "Line 1." + assert Group.compile().help == "Line 1." def test_get_description_class_multi_line_doc() -> None: @@ -207,7 +207,7 @@ class Group(feud.Group): Line 2. """ - assert feud.compile(Group).help == "Line 1.\n\nLine 2." + assert Group.compile().help == "Line 1.\n\nLine 2." def test_get_description_class_multi_line_doc_with_examples() -> None: @@ -221,7 +221,7 @@ class Group(feud.Group): >>> # Hello World! """ - assert feud.compile(Group).help == "Line 1.\n\nLine 2." + assert Group.compile().help == "Line 1.\n\nLine 2." def test_get_description_class_multi_line_doc_with_f() -> None: @@ -231,7 +231,7 @@ class Group(feud.Group): Line 2.\f """ - assert feud.compile(Group).help == "Line 1.\n\nLine 2." + assert Group.compile().help == "Line 1.\n\nLine 2." def test_get_description_class_multi_line_doc_with_examples_and_f() -> None: @@ -245,7 +245,7 @@ class Group(feud.Group): >>> # Hello World! """ - assert feud.compile(Group).help == "Line 1.\n\nLine 2." + assert Group.compile().help == "Line 1.\n\nLine 2." def test_get_description_class_main() -> None: @@ -260,7 +260,7 @@ def __main__() -> None: >>> # Hello World! """ - assert feud.compile(Group).help == "Line 1.\n\nLine 2." + assert Group.compile().help == "Line 1.\n\nLine 2." def test_get_description_class_override() -> None: @@ -274,4 +274,4 @@ class Group(feud.Group, help="Override."): >>> # Hello World! """ - assert feud.compile(Group).help == "Override." + assert Group.compile().help == "Override." diff --git a/tests/unit/test_internal/test_metaclass.py b/tests/unit/test_internal/test_metaclass.py index 1e17710..ab5de38 100644 --- a/tests/unit/test_internal/test_metaclass.py +++ b/tests/unit/test_internal/test_metaclass.py @@ -86,7 +86,7 @@ def command(*, opt: bool) -> None: assert Test.__feud_config__ == feud.config() assert Test.__feud_click_kwargs__ == {"name": name, "epilog": epilog} - group = feud.compile(Test) + group = Test.compile() assert group.name == name assert group.epilog == epilog @@ -115,7 +115,7 @@ def command(*, opt: bool) -> None: assert Test.__feud_config__ == feud.config(negate_flags=True) assert Test.__feud_click_kwargs__ == {"name": name, "epilog": epilog} - group = feud.compile(Test) + group = Test.compile() assert group.name == name assert group.epilog == epilog From fbeb613077c6d3099eb7c03f02d9122fa3f52cf1 Mon Sep 17 00:00:00 2001 From: eonu Date: Wed, 27 Dec 2023 04:03:09 +0000 Subject: [PATCH 2/2] release: v0.2.0 --- CHANGELOG.md | 6 ++++++ docs/source/conf.py | 2 +- feud/version.py | 2 +- pyproject.toml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244e732..a2db07c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project will be documented in this file. +## [v0.2.0](https://github.com/eonu/feud/releases/tag/v0.2.0) - 2023-12-27 + +### Features + +- add `feud.build` + move `rich_click` styling to `feud.Config` ([#123](https://github.com/eonu/feud/issues/123)) + ## [v0.1.6](https://github.com/eonu/feud/releases/tag/v0.1.6) - 2023-12-26 ### Bug Fixes diff --git a/docs/source/conf.py b/docs/source/conf.py index 44d91ee..eb0393d 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -20,7 +20,7 @@ project = "feud" copyright = "2023-2025, Feud Developers" # noqa: A001 author = "Edwin Onuonga (eonu)" -release = "0.1.6" +release = "0.2.0" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/feud/version.py b/feud/version.py index a3f5c6f..fc687f8 100644 --- a/feud/version.py +++ b/feud/version.py @@ -33,7 +33,7 @@ __all__ = ["VERSION", "version_info"] -VERSION = "0.1.6" +VERSION = "0.2.0" def version_info() -> str: diff --git a/pyproject.toml b/pyproject.toml index d63bcc4..2123de2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "feud" -version = "0.1.6" +version = "0.2.0" license = "MIT" authors = ["Edwin Onuonga "] maintainers = ["Edwin Onuonga "]