Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #5 - Clearly define boolean handling #6

Merged
merged 1 commit into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ A parameter becomes "required" if it is an `int`, `float` or `str` and does not

With more than one decorator, it's impossible to tell which function you'd like to wrap. Because of this, we enforce a single `@wrap` per file. Importing modules using `pysimplecli` is supported, as is calling said wrapped functions.

### Truth table for boolean parameters

| Parameter Default | Without Argument | With Argument |
| --- | --- | --- |
| `True` | `True` | `False` |
| `False` | `False` | `True` |
| no default | `False` | `True` |

## How It Works

The `wrap` decorator takes the annotated parameters of a given function and maps them to corresponding command-line arguments. It relies heavily on Python's `inspect` and `tokenize` modules to gather parameters, parse comments for parameter descriptions, determine default functionality, etc... In fact, a core part of this module is a are heavily extended `inspect.Parameter` objects.
Expand Down
26 changes: 16 additions & 10 deletions simplecli/simplecli.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ class Empty:
pass


class DefaultIfBool:
pass


_wrapped = False
# Overloaded placeholder for a potential boolean
DefaultIfBool = "Crazy going slowly am I, 6, 5, 4, 3, 2, 1, switch!"
ValueType = Union[type[Empty], bool, float, int, str]
ValueType = Union[type[DefaultIfBool], type[Empty], bool, float, int, str]
ArgDict = dict[str, ValueType]
ArgList = list[str]

Expand Down Expand Up @@ -208,6 +210,7 @@ def validate(self, value: ValueType) -> bool:
return passed

def set_value(self, value: ValueType) -> None:
result = value
if self.validate(value) is False:
raise ValueError(
f"'{self.help_name}' must be of type {self.help_type}"
Expand All @@ -225,13 +228,10 @@ def set_value(self, value: ValueType) -> None:
if value is DefaultIfBool:
if bool not in self.datatypes:
raise ValueError(f"'{self.help_name}' requires a value")
if self.default != Empty:
self._value = self.annotation(self.default)
return
else:
self._value = self.annotation(True)
return
self._value = self.annotation(value)
result = self.default if self.default is not Empty else True
elif bool in self.datatypes and self.default is Empty:
result = value
self._value = self.annotation(result)


def tokenize_string(string: str) -> Generator[TokenInfo, None, None]:
Expand Down Expand Up @@ -315,6 +315,12 @@ def params_to_kwargs(
if pos_args:
param.set_value(pos_args.pop(0))
elif param.name in kw_args:
if kw_args[param.name] is DefaultIfBool:
# Invert the default value
param.set_value(
True if param.default is Empty else not param.default
)
continue
param.set_value(kw_args[param.name])
continue
elif param.required:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_param.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def test_set_value():
with pytest.raises(ValueError, match="[int, float]"):
p1.set_value("this is the value")

with pytest.raises(ValueError, match="[int, float]"):
with pytest.raises(TypeError, match="[int, float]"):
p1.set_value(DefaultIfBool)
assert p1.value is Empty
p1.set_value(3)
Expand Down
22 changes: 19 additions & 3 deletions tests/test_wrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,22 +181,38 @@ def code(a: int, b: int, c: typing.Union[int, float]):


def test_wrap_boolean_false(monkeypatch):
monkeypatch.setattr(sys, "argv", ["filename", "--is-false"])
monkeypatch.setattr(sys, "argv", ["filename"])

@simplecli.wrap
def code(is_false: bool = False):
assert is_false is False


def test_wrap_boolean_false_invert(monkeypatch):
monkeypatch.setattr(sys, "argv", ["filename", "--invert"])

@simplecli.wrap
def code(invert: bool = False):
assert invert is True


def test_wrap_boolean_true(monkeypatch):
monkeypatch.setattr(sys, "argv", ["filename", "--is-true"])
monkeypatch.setattr(sys, "argv", ["filename"])

@simplecli.wrap
def code(is_true: bool = True):
assert is_true is True


def test_wrap_boolean_true_no_default(monkeypatch):
def test_wrap_boolean_true_invert(monkeypatch):
monkeypatch.setattr(sys, "argv", ["filename", "--invert"])

@simplecli.wrap
def code(invert: bool = True):
assert invert is False


def test_wrap_boolean_true_no_default_invert(monkeypatch):
monkeypatch.setattr(sys, "argv", ["filename", "--is-something"])

@simplecli.wrap
Expand Down