Skip to content

Commit

Permalink
Fixes #5 - Clearly define boolean handling (#6)
Browse files Browse the repository at this point in the history
* Shift to a custom "type"
    * Clearly specify truth table for boolean parameters
    * Change handling to align with truth table
    * Update tests where applicable
  • Loading branch information
inno authored May 19, 2024
1 parent 64e3471 commit ab93820
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 14 deletions.
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

0 comments on commit ab93820

Please sign in to comment.