Skip to content

Commit

Permalink
release: v0.1.6 (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
eonu authored Dec 26, 2023
2 parents 783d925 + a104509 commit 481e812
Show file tree
Hide file tree
Showing 18 changed files with 439 additions and 84 deletions.
19 changes: 4 additions & 15 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ body:
description: |
Please describe the unexpected behaviour that you observed.
Make sure to provide as much information as possible, so that we can investigate as thoroughly as we can!
Make sure to provide as much information as possible, so that we can investigate as thoroughly as we can.
validations:
required: true

Expand All @@ -43,31 +43,20 @@ body:
making sure that it is [minimal and reproducible](https://stackoverflow.com/help/minimal-reproducible-example).
placeholder: |
"""To reproduce my bug, run the following script with:
python command.py --bug
"""
# command.py
import feud
def command(*, bug: bool = False):
"""Command that demonstrates the bug."""
if bug:
raise ValueError("Woops, this is buggy!")
...
if __name__ == "__main__":
feud.run(command)
feud.run()
render: Python

- type: textarea
id: version
attributes:
label: Version details
description: |
To help us get to the root of the problem as fast as possible,
please run the following command to display version information about:
To help us get to the root of the problem as fast as possible, please run the following command to display version information about:
- Python
- Feud
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

All notable changes to this project will be documented in this file.

## [v0.1.6](https://github.com/eonu/feud/releases/tag/v0.1.6) - 2023-12-26

### Bug Fixes

- improve docstring parsing ([#121](https://github.com/eonu/feud/issues/121))

## [v0.1.5](https://github.com/eonu/feud/releases/tag/v0.1.5) - 2023-12-26

### Documentation
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Consider the following example command for serving local files on a HTTP server.
- def serve(port, watch, env):
+ def serve(port: int, *, watch: bool = True, env: Literal["dev", "prod"] = "dev"):
- """Start a local HTTP server."""
+ """Start a local HTTP server.\f
+ """Start a local HTTP server.
+
+ Parameters
+ ----------
Expand Down Expand Up @@ -293,26 +293,26 @@ from datetime import date
def create_post(id: int, *, title: str, desc: str | None = None):
"""Create a blog post."""

def delete_post(*ids: int):
def delete_posts(*ids: int):
"""Delete blog posts."""

def list_posts(*, between: tuple[date, date] | None = None):
"""View all blog posts, optionally filtering by date range."""

if __name__ == "__main__":
feud.run([create_post, delete_post, list_posts])
feud.run([create_post, delete_posts, list_posts])
```

You can also use a `dict` to rename the generated commands:

```python
feud.run({"create": create_post, "delete": delete_post, "list": list_posts})
feud.run({"create": create_post, "delete": delete_posts, "list": list_posts})
```

For more complex applications, you can also nest commands in sub-groups:

```python
feud.run({"list": list_posts, "modify": [create_post, delete_post]})
feud.run({"list": list_posts, "modify": [create_post, delete_posts]})
```

If commands are defined in another module, you can also
Expand Down Expand Up @@ -688,7 +688,7 @@ from pydantic import constr

@click.password_option("--password", help="The user's password (≥ 10 characters).")
def login(*, username: str, password: constr(min_length=10)):
"""Log in as a user.\f
"""Log in as a user.
Parameters
----------
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
project = "feud"
copyright = "2023-2025, Feud Developers" # noqa: A001
author = "Edwin Onuonga (eonu)"
release = "0.1.5"
release = "0.1.6"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
2 changes: 1 addition & 1 deletion docs/source/sections/typing/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Feud relies on type hints to determine the type for command-line argument/option
from feud import typing as t
def serve(port: t.PositiveInt, *, watch: bool = True, env: t.Literal["dev", "prod"] = "dev"):
"""Start a local HTTP server.\f
"""Start a local HTTP server.
Parameters
----------
Expand Down
8 changes: 8 additions & 0 deletions feud/_internal/_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class CommandState:
default_factory=dict
)
options: dict[str, ParameterSpec] = dataclasses.field(default_factory=dict)
description: str | None = None

def decorate(self: CommandState, func: t.Callable) -> click.Command:
meta_vars: dict[str, str] = {}
Expand Down Expand Up @@ -124,6 +125,13 @@ def decorate(self: CommandState, func: t.Callable) -> click.Command:
if command_rename := self.names["command"]:
self.click_kwargs = {**self.click_kwargs, "name": command_rename}

# set help to docstring description if not provided
if self.is_group:
if "help" in self.click_kwargs:
self.click_kwargs["help"] = self.description
elif help_ := self.click_kwargs.get("help", self.description):
self.click_kwargs["help"] = help_

command = _decorators.validate_call(
func,
name=self.click_kwargs["name"],
Expand Down
72 changes: 72 additions & 0 deletions feud/_internal/_docstring.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright (c) 2023-2025 Feud Developers.
# Distributed under the terms of the MIT License (see the LICENSE file).
# SPDX-License-Identifier: MIT
# This source code is part of the Feud project (https://feud.wiki).

from __future__ import annotations

import typing as t

import docstring_parser

from feud import click


def get_description(
obj: docstring_parser.Docstring | click.Command | t.Callable | str,
/,
) -> str | None:
"""Retrieve the description section of a docstring.
Modified from https://github.com/rr-/docstring_parser/pull/83.
The MIT License (MIT)
Copyright (c) 2018 Marcin Kurczewski
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
doc: docstring_parser.Docstring | None = None

if isinstance(obj, str):
doc = docstring_parser.parse(obj)
elif isinstance(obj, click.Command):
if func := getattr(obj, "__func__", None):
doc = docstring_parser.parse_from_object(func)
elif isinstance(obj, docstring_parser.Docstring):
doc = obj
elif isinstance(obj, t.Callable):
doc = docstring_parser.parse_from_object(obj)

ret = None
if doc:
ret = []
if doc.short_description:
ret.append(doc.short_description)
if doc.blank_after_short_description:
ret.append("")
if doc.long_description:
ret.append(doc.long_description)

if ret is None or ret == []:
return None

return "\n".join(ret).strip()
6 changes: 4 additions & 2 deletions feud/_internal/_inflect.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def transliterate(string: str) -> str:
>>> transliterate("Ærøskøbing")
"rskbing"
Source code from inflection package (https://github.com/jpvanhal/inflection).
Source code from inflection package
(https://github.com/jpvanhal/inflection).
Copyright (C) 2012-2020 Janne Vanhala
Expand Down Expand Up @@ -58,7 +59,8 @@ def parameterize(string: str, separator: str = "-") -> str:
>>> parameterize(u"Donald E. Knuth")
"donald-e-knuth"
Source code from inflection package (https://github.com/jpvanhal/inflection).
Source code from inflection package
(https://github.com/jpvanhal/inflection).
Copyright (C) 2012-2020 Janne Vanhala
Expand Down
4 changes: 3 additions & 1 deletion feud/core/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
import click

import feud.exceptions
from feud._internal import _command, _types
from feud._internal import _command, _docstring, _types
from feud.config import Config
from feud.typing import custom

Expand Down Expand Up @@ -128,6 +128,8 @@ def build_command_state( # noqa: PLR0915
else:
doc = docstring_parser.parse_from_object(func)

state.description: str | None = _docstring.get_description(doc)

sig: inspect.Signature = inspect.signature(func)

for param, spec in sig.parameters.items():
Expand Down
24 changes: 11 additions & 13 deletions feud/core/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ def from_dict(
----------
obj:
:py:obj:`dict` of runnable function, command or group objects.
**kwargs:
Click keywords or Feud configuration to apply:
Expand Down Expand Up @@ -481,10 +482,7 @@ def from_dict(
subgroups[name] = types.new_class(
"__feud_group__",
bases=(subgroup,),
kwds={
**subgroup.__feud_click_kwargs__,
"name": name,
},
kwds={"name": name},
exec_body=(
lambda body: body.update(
{"__doc__": subgroup.__doc__}, # noqa: B023
Expand Down Expand Up @@ -524,6 +522,7 @@ def from_iter(
----------
obj:
:py:obj:`dict` of runnable function, command or group objects.
**kwargs:
Click keywords or Feud configuration to apply:
Expand Down Expand Up @@ -584,6 +583,7 @@ def from_module(
----------
obj:
Module of runnable function, command or group objects.
**kwargs:
Click keywords or Feud configuration to apply:
Expand All @@ -602,10 +602,9 @@ def is_command(item: t.Any) -> bool:
return inspect.getmodule(item) == obj
return isinstance(item, click.Command)

def get_descendants(group: type[Group]) -> t.Generator[type[Group]]:
for name, child in group.descendants().items():
yield name
yield from map(get_descendants, child.keys())
# function to get the name of a command or function
def get_name(o: click.Command | t.Callable) -> str:
return o.name if isinstance(o, click.Command) else o.__name__

# split function/click.Command/click.Group from feud.Group
commands: list[t.Callable | click.Command] = []
Expand All @@ -616,10 +615,6 @@ def get_descendants(group: type[Group]) -> t.Generator[type[Group]]:
elif inspect.isclass(item) and issubclass(item, Group):
groups.append(item)

# function to get the name of a command or function
def get_name(o: click.Command | t.Callable) -> str:
return o.name if isinstance(o, click.Command) else o.__name__

# group members
members: dict[str, click.Command | t.Callable] = {
get_name(cmd): cmd for cmd in commands
Expand All @@ -633,7 +628,10 @@ def get_name(o: click.Command | t.Callable) -> str:

# discard non-root groups
non_root: set[type[Group]] = set(
chain.from_iterable(map(get_descendants, groups))
chain.from_iterable(
group._descendants() # noqa: SLF001
for group in groups
)
)
subgroups = [group for group in groups if group not in non_root]

Expand Down
2 changes: 1 addition & 1 deletion feud/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@

__all__ = ["VERSION", "version_info"]

VERSION = "0.1.5"
VERSION = "0.1.6"


def version_info() -> str:
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "feud"
version = "0.1.5"
version = "0.1.6"
license = "MIT"
authors = ["Edwin Onuonga <[email protected]>"]
maintainers = ["Edwin Onuonga <[email protected]>"]
Expand Down
8 changes: 4 additions & 4 deletions tests/unit/test_core/fixtures/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@


def func1(*, opt: bool) -> bool:
"""This is the first function.\f
"""This is the first function.
Parameters
----------
Expand All @@ -24,7 +24,7 @@ def func1(*, opt: bool) -> bool:

@feud.command
def command(*, opt: bool) -> bool:
"""This is a command.\f
"""This is a command.
Parameters
----------
Expand All @@ -38,7 +38,7 @@ class Group(feud.Group, name="feud-group"):
"""This is a Feud group."""

def func(*, opt: bool) -> bool:
"""This is a function in the group.\f
"""This is a function in the group.
Parameters
----------
Expand All @@ -52,7 +52,7 @@ class Subgroup(feud.Group, name="feud-subgroup"):
"""This is a subgroup."""

def func(*, opt: bool) -> bool:
"""This is a function in the subgroup.\f
"""This is a function in the subgroup.
Parameters
----------
Expand Down
Loading

0 comments on commit 481e812

Please sign in to comment.