Giter Site home page Giter Site logo

janluke / cloup Goto Github PK

View Code? Open in Web Editor NEW
96.0 5.0 10.0 944 KB

Library to build command line interfaces based on Click. It extends click with: option groups, constraints (e.g. mutually exclusive params), command aliases, help themes, "did you mean ...?" suggestions and more.

Home Page: https://cloup.readthedocs.io/

License: BSD 3-Clause "New" or "Revised" License

Makefile 1.29% Python 98.71%
click cli python package library

cloup's Introduction

Click + option groups + constraints + aliases + help themes + ...

https://cloup.readthedocs.io/


Overview

Latest release on PyPI Supported versions PyPI - Downloads Tests status Coverage Status Documentation Status (master branch) Donate with PayPal

Cloup — originally from "Click + option groups" — enriches Click with several features that make it more expressive and configurable:

  • option groups and an (optional) help section for positional arguments
  • constraints, like mutually_exclusive, that can be applied to option groups or to any group of parameters, even conditionally
  • subcommand aliases
  • subcommands sections, i.e. the possibility of organizing the subcommands of a Group in multiple help sections
  • a themeable HelpFormatter that:
    • has more parameters for adjusting widths and spacing, which can be provided at the context and command level
    • use a different layout when the terminal width is below a certain threshold in order to improve readability
  • suggestions like "did you mean <subcommand>?" when you mistype a subcommand.

Moreover, Cloup improves on IDE support providing decorators with detailed type hints and adding the static methods Context.settings() and HelpFormatter.settings() for creating dictionaries of settings.

Cloup is statically type-checked with MyPy in strict mode and extensively tested against multiple versions of Python with nearly 100% coverage.

A simple example

from cloup import (
    HelpFormatter, HelpTheme, Style,
    command, option, option_group
)
from cloup.constraints import RequireAtLeast, mutually_exclusive

# Check the docs for all available arguments of HelpFormatter and HelpTheme.
formatter_settings = HelpFormatter.settings(
    theme=HelpTheme(
        invoked_command=Style(fg='bright_yellow'),
        heading=Style(fg='bright_white', bold=True),
        constraint=Style(fg='magenta'),
        col1=Style(fg='bright_yellow'),
    )
)

# In a multi-command app, you can pass formatter_settings as part
# of your context_settings so that they are propagated to subcommands.
@command(formatter_settings=formatter_settings)
@option_group(
    "Cool options",
    option('--foo', help='This text should describe the option --foo.'),
    option('--bar', help='This text should describe the option --bar.'),
    constraint=mutually_exclusive,
)
@option_group(
    "Other cool options",
    "This is the optional description of this option group.",
    option('--pippo', help='This text should describe the option --pippo.'),
    option('--pluto', help='This text should describe the option --pluto.'),
    constraint=RequireAtLeast(1),
)
def cmd(**kwargs):
    """This is the command description."""
    pass

if __name__ == '__main__':
    cmd(prog_name='invoked-command')

Basic example --help screenshot

If you don't provide --pippo or --pluto:

Usage: invoked-command [OPTIONS]
Try 'invoked-command --help' for help.

Error: at least 1 of the following parameters must be set:
  --pippo
  --pluto

This simple example just scratches the surface. Read more in the documentation (links below).

Supporting the project

Designing, testing and documenting a library takes a lot of time. The most concrete way to show your appreciation and to support future development is by donating. Any amount is appreciated.

Donate with PayPal

Apart from that, you can help the project by starring it on GitHub, reporting issues, proposing improvements and contributing with your code!

cloup's People

Contributors

alexreg avatar dependabot[bot] avatar janluke avatar jsonvillanueva avatar kdeldycke avatar kianmeng avatar nue-melexis avatar pocek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

cloup's Issues

In command decorators, use generics to get a precise return type based on the `cls` argument

Command decorators (@command, @group, Group.command and Group.group) allow to create commands of different subtypes passing a class as cls argument. Ideally, the following code would work:

from typing import TypeVar, Callable

C = TypeVar('C', bound=click.Command)

def command(
    name: Optional[str] = None,
    cls: Type[C] = Command,
    **kwargs
) -> Callable[[Callable], C]:
    ...

Unfortunately, this doesn't work because MyPy doesn't allow to set a default value for generic function arguments (cls in this case): python/mypy#3737. In the issue page, Guido describes a workaround using @overload but, unless I'm missing something, it's inapplicable in this case because of **kwargs.

As of v0.6.0, this is what I'm doing:

  • in @command and Group.command, use click.Command in place of C; this is okay, since cloup.Command doesn't have additional methods that clients need to call... typing is correct and auto-completion is fine
  • in @group, use cloup.Group in place of C; requiring a cloup.Group in input is not a problem in this case
  • in Group.group take click.Group and return Any (opt out of type checking);
    • using cloup.Group in place of C felt too restrictive to me would break the Liskov substitution principle because the cls argument would be more narrow than in the base class
    • using click.Group would be annoying because it would require clients to manually cast to cloup.Group in order to use grp.section(). I preferred opting out of static typing.

Remove MultiCommand

It doesn't serve any purpose. Formally it's a "breaking change" but a quite irrelevant one.

Default option group: make it globally configurable

This is a minor enhancement, a refinement, to implement (maybe) in the future.

Specs

  • The name and the help of the default OptionGroup should be globally configurable.
  • Important: as it is implemented now, if the command has no option groups defined, the default group name should be just "Options" and the help should be empty.

Notes

  • There should be an analogous issue relative to subcommand sections; these configurations should be released together. Subcommand sections don't strictly need this, because no command is added automatically by Cloup (as it happens in the case of options with --help), so if the user wants to change "Other commands" to something else, (s)he can define another a section and leave the default one empty.

Implement option groups' and subcommand sections' support as mixins

Currently:

  • option groups' support is hard-coded into cloup.Command
  • subcommand sections' support is hard-coded into cloup.Group.

It may be useful to implement it as a mixin so that:

  • they can be more cleanly mixed with other click extensions
  • they can be mixed with multiple click command classes, e.g. cloup.Group could extend OptionGroupMixin.

`Context.show_constraints` is overridden by `Command.show_constraint` wrong default (should be `None`, it's `False`)

Constraints are not shown if Context.show_constraints = True and no value is passed to Command. This is due to the fact I forgot to update the default value of Command.show_constraints which should be None but was mistakenly left to False. This means it always overrides the context value.

Lesson: I'm currently testing for all possible configurations of (Context.show_constraints, Command.show_constraints) but that didn't was enough to catch the bug. To ensure everything works as I expected I need to consider the case "nothing is provided".

I should modify the tests for all other similar context options.

In dark and light themes, leave epilog unstyled

Many people use the epilog to add extra documentation, not for credits something similar. It's better to leave it unstyled by default.

For now, one can do:

def unstyled(x: str) -> str:
    return x

HelpTheme.dark().with_(
    epilog=unstyled,
)

Nested option groups with constraints

  • cloup version: 0.7 (or latest)
  • Python version: 3.7
  • Operating System: OS X

Description

I am trying to create a group of options with a seemingly complex, but perhaps not, constraint system. I basically have 5 parameters that I want to house in one group, that are a combination of required and mutually exclusive, and at least x. For example, options: A, B, C, D, and E. D and E are an "all_or_none" group. C and D+E are "mutually_exclusive" group, but at least one is required. B is required with one of (C,D+E). And either A or group (B + (C or D+E)) is required. Is this possible? It's not clear to me if nested option groups are possible, i.e. if I can create one option_group with a constraint and apply a constraint on top of it as part of another option_group.

I just discovered this code and am digging into the docs, so perhaps the answer is buried in there.

something like the following...

OptionGroup(
  A,
  OptionGroup(
    B, required=True,
    OptionGroup(
      C,
      OptionGroup(
        D,
        E
        constraint=all_or_none),
    constraint=mutually_exclusive
    ),
  constraint=RequireAtLeast(1)
  ),
constraint=RequireAtLeast(1)
)

or would I do something like

OptionGroup(
  A,
  B,
  C,
  D,
  E,
  constraint="some defined complex conditional constraint"
)

Add method `format_subcommand_name` to `SectionMixin`

This method turns useful when you combine Group (which includes SectionMixin) with other click extensions that override format_commands(). Most of these extensios, like click-default-group and click-aliases, override format_command just to add something to the name of the subcommands, which is exactly what this method allows you to do without overriding bigger methods.

This method could be useful in case I decide to implement command aliases in Group.

Update and improve documentation for v0.8.0

Some stuff to do:

  • Improve existing documentation
  • Hidden option groups
  • Document command classes hierarchy
  • Document the new formatter and theming
  • Document Context settings

A base class for `Constraint` and `Predicate`?

Constraint and Predicate are very similar. Essentially, they are both predicates with an associated description. There's some common logic to them (e.g. operators). Could/should they be derived from the same base class?

Predicate operators (& and |) raising AttributeError

  • cloup version: 0.7
  • Python version: 3.9
  • Operating System: mac arm64

Description

Dear @janluke ,

I am currently working on a switchover for our library wetterdienst [1]. Therewith I'm currently moving our client accessor to click, however as we have certain mixing arguments for some functions I was happy to find that you are working on making this easier allowing to check for conditions. First of all thanks for your much needed work!

I'd like to add the following condition where the user can either supply rank or distance which in combination with provided longitude and latitude is used for geo filtering of some weather stations.

@cloup.option_group(
    "Latitude-Longitude rank filtering",
    cloup.option("--latitude", type=click.FLOAT),
    cloup.option("--longitude", type=click.FLOAT),
    cloup.option("--rank", type=click.INT),
    cloup.option("--distance", type=click.FLOAT),
    constraint=If(
        IsSet("latitude") & IsSet("longitude"),
        then=RequireExactly(3),
        else_=cloup.constraints.accept_none),
)

However as far as it goes it would only give me a nice help text but when running the command with arguments it ends up with

Traceback (most recent call last):
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/wetterdienst/ui/cli.py", line 736, in <module>
    wetterdienst()
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/click/core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/click/core.py", line 782, in main
    rv = self.invoke(ctx)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/click/core.py", line 1257, in invoke
    sub_ctx = cmd.make_context(cmd_name, args, parent=ctx)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/click/core.py", line 700, in make_context
    self.parse_args(ctx, args)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/cloup/constraints/_support.py", line 104, in parse_args
    constr.check_values(ctx)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/cloup/constraints/_support.py", line 49, in check_values
    self.constraint.check_values(self.params, ctx)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/cloup/constraints/_conditional.py", line 49, in check_values
    condition_is_true = condition(ctx)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/cloup/constraints/conditions.py", line 104, in __call__
    return all(c(ctx.params) for c in self.predicates)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/cloup/constraints/conditions.py", line 104, in <genexpr>
    return all(c(ctx.params) for c in self.predicates)
  File "/Users/benjamin/DEV/earth_observations/sources/wetterdienst/.venv/lib/python3.9/site-packages/cloup/constraints/conditions.py", line 136, in __call__
    if not isinstance(ctx.command, ConstraintMixin):
AttributeError: 'dict' object has no attribute 'command'

Is there a way to achieve what I'm planning with the current status of the library?

Again, thanks a lot for your work here! Much appreciated!

[1] https://github.com/earthobservations/wetterdienst

Edit
Got it working by making the first two arguments required like

@cloup.option_group(
    "Latitude-Longitude rank filtering",
    cloup.option("--latitude", type=click.FLOAT, required=True),
    cloup.option("--longitude", type=click.FLOAT, required=True),
    cloup.option("--rank", type=click.INT),
    cloup.option("--distance", type=click.FLOAT),
    constraint=RequireExactly(3)
)

Is this the recommend way of setting the constraints?

Alternative to click types

In click, I could specify the type of an option, like this: 1 with the types specified here: 2 3 .

This is expecially useful for files with the click.File class.
cloup.File does not exist; Also it looks to me as if the whole typing functionality from click was not adopted. Are there alternatives to achieve similar functionality in cloup?

Forbid '\n' at the end of `row_sep`

This integrates:

Motivations

  • Nobody should ever use a row separator ending with one or more '\n'.
  • It's very inconvenient that no error is raised by Cloup if someone that used row_sep='\n' updates Cloup to v0.9, where row_sep had another semantics.

After this, the formatter will consistently write a \n after row_sep when it's provided. And that's all.

Add Cloup-specific arguments (explicitly) to `@command` signature

Cloup-specific args were excluded from @command because it currently allows cls to be any click.Command. This is mostly because command is used internally by Group.command. As a consequence, only click.Command arguments are explicitly listed.

Decision

Given that the following has become possible using @overload

I will:

  • add Cloup-arguments to the @overload signature with cls=None
  • allow any click.Group in group
  • catch and augment errors caused by a Cloup-specific argument being passed to a non-supporting cls.

[OLD] Options previously considered

Leading option

Use a private @_command for the core implementation, leaving cls unbounded. Then, in the public @command limit cls to be a (subclass of) Command.

Pros:

  • it actually makes sense for something called @cloup.command to require cls to be a subclass of cloup.Command
  • the signature is 100% legit
  • you can't pass cls=cloup.Group, which is consistent with the fact that cloup.Group is not a cloup.Command in Cloup (both are cloup.BaseCommand).

Cons:

  • it's a (minor) breaking change
  • it's somehow inconsistent that @command require cls to be a cloup.Command while Group.command doesn't. But the same already applies to @group and Group.group.

Second option

Adds the arguments to the signature but do not forward them to cls if:

 arg is None and not issubclass(cls, cloup.Command)

Otherwise, they are forwarded to cls. If cls doesn't support Cloup args will raise an error. I can optionally catch that to provide more info.

This looks kinda dirty but I can't see any drawbacks.

Implement a custom HelpFormatter

Motivations

  1. Cloup relies on a hack (padding) to align multiple sections because click.HelpFormatter doesn't support it.

  2. Click 7 adds an extra empty line only after options with a long description taking multiple lines. This makes vertical spacing inconsistent, especially when using Cloup. This behaviour was removed in Click 8 on my request.

  3. Customizing the help is unnecessarily difficult in Click, especially in Click 7. There's work in progress but the new formatting system won't be released in Click 8.0.

  4. The formatting code (especially HelpFormatter.write_dl) has several issues and there's no point in fixing them in Click since they are going to redesign the entire formatting system.

Requirements

  • Should support alignment of multiple sections; this can be implemented (at the core) by adding col1_width=None arguments to write_dl
  • Should have more attributes for customization, like col1_max_width (called col_max in write_dl), col_spacing.
  • Optionally, implements a row_sep attribute that is written after each row of a definition list; this allows clients to separate rows with an empty line, for example.
  • If there's not enough space for a 2nd column (min 15-20 chars), write a definition list as a linear list with the option.help printed always under the opts, indented.
  • Improve help readability when the available width for the second column is small.
  • (#25) Help styling and theming.

Changes to support the feature

Using a custom help formatter will require other structural changes:

  • (#15) implement of a custom Context that uses the custom HelpFormatter; add a BaseCommand(click.Command) that uses the custom Context and make it the parent class of Command and MultiCommand.
  • make OptionGroupMixin and SectionMixin use the custom formatter
  • add an attribute formatter_settings to both Context and BaseCommand to support easy customization.

Constraints on parameters that have identical "internal" names

Discussed in #77

Originally posted by Mitmischer July 16, 2021
Hello,
not sure if that is a feature request or if it already exists, so I'm starting it as discussion :)
Consider the following minimal example:

import cloup
import cloup.constraints

@cloup.command()
@cloup.option_group(
    "group",
    cloup.option("--a", "a"),
    cloup.option("--b", "a"),
    constraint=cloup.constraints.mutually_exclusive
)
def main(**kw):
    pass

(As it is, the CLI does not make sense. My use case is that a should contain a list of files, but the user may also specify a folder. In that case, I'll convert the folder content to a list of files. I'd personally prefer two parameters, but I'm moving from an argparse CLI to click and I want to keep changes minimal in order to sell it)

Running the example gives the following error ...

python3 main.py --a "hello"
Usage: main.py [OPTIONS]
Try 'main.py --help' for help.

Error: the following parameters are mutually exclusive:
  --a
  --b

... which is not surprising if arguments are found by name. Are there plans to get this (corner case) into cloup?

Use constraints as decorators (or @constrained_params) to constrain contiguous parameters

Motivation

@constraint is currently the only way to apply a constraint to a group of parameters that is not defined as an @option_group. Unfortunately, it is not ideal for several reasons:

  • it requires to replicate (once again) the name of the involved parameters; replication is never good
  • it doesn't visually group the involved parameters together.

Despite being the most "powerful" (sometimes unavoidable) way of defining a constraint, it could be avoided when the parameters to constrain are contiguous, which is often the case. In that case we can use something similar to @option_group that internally uses @constraint.

Design

The main idea is to use constraints themselves as decorators, i.e. making Constraint.__call__ return a decorator rather than being equivalent to Constraint.check (breaking change):

@RequireAtLeast(1)(
    option('--one'),
    option('--two'),
    option('--three'),
)

Unfortunately, the above code is valid only in Python >= 3.9, because previously Python required the expressions on the right of @ to be a "dotted name, optionally followed by a single call" (see PEP 614). Thus:

  • @mutually_exclusive(...) is valid
  • @RequireAtLeast(1)(...) is not because makes two calls.

Besides, a long compound/conditional constraint may not be very readable.

For these reasons, I'll implement the core feature in a new decorator @constrained_params with an equivalent semantic to Constraint.__call__:

@constrained_params(
    RequireAtLeast(1),
    option('--one'),
    option('--two'),
    option('--three'),
)

# desugars to:

@option('--one')
@option('--two')
@option('--three'),
@constraint(RequireAtLeast(1), ['one', 'two', 'three])

Make it work with @option_group

@option_group(
    'Number options',
    RequireAtLeast(1)(
        option('--one'),
        option('--two')
    ),
    option('--three'),
)

Note that inside an @option_group the decorator problem disappears since there's no need to use @.

Command aliases

Decision

This feature is gonna be added. Only explicit aliases will be allowed. The design will be described in the docs/PR.


Reasons not to include

  • There's click-aliases.
  • It's well-documented how to implement yourself.
  • Several variants: simple aliases, configurable aliases (git-like), heuristic aliases (prefix).

Reasons to include

  • People have requested it multiple times to Click:
  • I'd like to have them built-in.
  • Argparse have them built-in.
  • click-aliases overrides format_commands, so it conflicts with all other extensions that do the same, including Cloup. It's trivial to fix the conflict (especially after #59) but not having to fix it is even better :]
  • The examples and the extension are actually buggy, proving that this feature is not so trivial to implement:
  • I'd implement it differently.

Existing implementations & examples

Let `row_sep` take a "policy" that decides for which definition lists a row separator must be used

Problems with row_sep

  1. It affects the "Commands" section, which generally doesn't need the extra empty line / separator.

  2. It affects all definition lists indiscriminately, including those that don't benefit from the extra empty line between definitions. Some people may like this consistency. Others, including me, may want to avoid the extra spacing when unneded.

  3. Probably nobody is going to use a row separator other than '\n' (an extra new line), and probably shouldn't. Horizontal lines obtained by repeating one of the unicode "Box drawing" characters like '─' seem too much for separating definitions. This is not a real issue, just something I want to keep in mind: I'm open to "sacrifice" this rather useless flexibility.

Chosen solution: RowSepPolicy

Allow to pass a policy as row_sep:

class RowSepPolicy(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def __call__(  # noqa E704
        self, rows: Sequence[Sequence[str]],
        col_widths: Sequence[int],
        col_spacing: int,
    ) -> Optional[str]:
        """Decides which row separator to use (eventually none) in the given
        definition list."""

In practice, the row separator should be the same for all definition lists that satisfy a given condition (see RowSepIf below). So why this class is designed as it is? Mainly for one reason: it leaves open the door to policies that can decide a row separator for each individual row (feature I'm personally against to for now), without breaking changes. This would allow to implement the old Click 7.2 behavior, which inserted an empty line only after options with a long help (which I don't like). Adding this feature would be possible without breaking changes, by extending the return type to Union[None, str, Sequence[str]].

The concrete implementation that the user will actually use is much more pragmatic:

RowSepIf(condition: RowSepCondition, sep: Union[str, SepGenerator] = '\n')

where:

  • RowSepCondition is a function (rows, col_widths, col_spacing) -> bool
  • SepGenerator is a function (width: int) -> str that generates a separator given a line width.

Cloup will provide:

  • a function multiline_rows_are_at_least(count_or_percentage) that returns a RowSepCondition

  • an Hline(pattern: str) class that implements SepGenerator and has different static members for different kinds of line: Hline.solid, Hline.dashed, Hline.densely_dashed, Hline.dotted

So, in the end I decided to allow separators other than '\n' and Hline makes it trivial to use one. Plus, it may be useful in other parts of the program (e.g. print(Hline.solid(width), end='')).

For consistency, row_sep will also accept a SepGenerator (#39). So after this is implemented, row_sep will accept either:

  • a string (e.g. '\n')
  • a SepGenerator (e.g. Hline.dotted)
  • a RowSepPolicy (e.g. RowSepIf(multiline_rows_are_at_least(2))).

Limitations

  • A policy can't take decisions based on all sections. This would require serious refactoring.
  • Intentionally, a policy can't decide a different row_sep for each definition. Nonetheless, the interface was designed to be flexible enough for extensions in this regard without breaking changes.

Evaluated solutions

The quick and easy one

I could just add row_sep to write_dl() so that the caller, e.g. format_commands, can set it to '' if opportune. This is also enough to allow format_options for eventually forcing row_sep to '' only for definition lists that don't need the extra spacing. Nonetheless this would either be hard-coded behaviour (difficult to override) or would require other formatting parameters in mixins, which I want to avoid.

Similar to chosen one, but allowing only for empty lines, not arbitrary strings

This would have required to replace row_sep with some other parameter.

Export some Click decorators from cloup

from click import (
    argument,
    confirmation_option,
    help_option,
    pass_context,
    pass_obj,
    password_option,
    version_option,
)

I'll decide later in time if it's the case to export other stuff, like Click types.

Set `row_sep=None` by default and write a '\n' after `row_sep` (when it is provided)

Motivation

  • The use of "" to indicate "no value" makes type checking less useful. Using None is more correct.
  • A separator not ending with \n doesn't make any sense. So the \n should be be enforced in some way.
  • I don't want to require SepGenerator to return a string ending with \n. If it needs to be there, why should I bother the user to add it? Furthermore, if one wants to use Hline in other parts of the program, s(he) may need to rstrip() the returned separator returned by Hline, which is not ideal.

Decision

  • Set row_sep=None by default.
  • Make the formatter add the \n after the row_sep string (when row_sep is not None). In other words, do not bother the user to include a '\n' at the end of row_sep.

This is a breaking change.

Command/OptionGroupMixin not working if `params` is not provided

Cloup doesn't work if a command has no parameters (edge case).

To Reproduce

Command(name='cmd')

# or

@command():
def f():
    pass
        self.option_groups, self.ungrouped_options = \
>           self._option_groups_from_params(kwargs['params'])
E       KeyError: 'params'

..\cloup\_option_groups.py:118: KeyError

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.