Skip to content

Commit 7546d95

Browse files
authored
Merge pull request #175 from Daraan/ignore-tapify
TapIgnore for to_tap_class / tapify
2 parents ac813d3 + f46ecd2 commit 7546d95

File tree

3 files changed

+79
-6
lines changed

3 files changed

+79
-6
lines changed

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,16 +399,14 @@ args = MyTap().parse_args(["--help"])
399399
```
400400

401401
```txt
402-
usage: ipython --package PACKAGE [--stars STARS] [-h]
402+
usage: main.py --package PACKAGE [--stars STARS] [-h]
403403
404404
options:
405405
--package PACKAGE (str, required)
406406
--stars STARS (int, default=5)
407407
-h, --help show this help message and exit
408408
```
409409

410-
Note: this is currently only for `Tap` objects, not `tapify`.
411-
412410
### Argument processing
413411

414412
With complex argument parsing, arguments often end up having interdependencies. This means that it may be necessary to disallow certain combinations of arguments or to modify some arguments based on other arguments.

src/tap/tapify.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,8 @@ def _configure(self):
304304
annotation.__metadata__ = (*annotation.__metadata__, *arg_data.pydantic_metadata)
305305
self._annotations_with_extras[variable] = annotation
306306
self._annotations[variable] = _remove_extras_from_annotation(annotation)
307+
if self._is_ignored_argument(variable):
308+
continue
307309
self.class_variables[variable] = {"comment": arg_data.description or ""}
308310
if arg_data.is_required:
309311
kwargs = {}
@@ -371,13 +373,15 @@ def tapify(
371373
raise ValueError(f"Unknown keyword arguments: {func_kwargs}")
372374

373375
# Parse command line arguments
374-
command_line_args: Tap = tap.parse_args(args=command_line_args, known_only=known_only)
376+
parsed_command_line_args: Tap = tap.parse_args(args=command_line_args, known_only=known_only)
375377

376378
# Prepare command line arguments for class_or_function, respecting positional-only args
377379
class_or_function_args: list[Any] = []
378380
class_or_function_kwargs: dict[str, Any] = {}
379-
command_line_args_dict = command_line_args.as_dict()
381+
command_line_args_dict = parsed_command_line_args.as_dict()
380382
for arg_data in tap_data.args_data:
383+
if tap._is_ignored_argument(arg_data.name):
384+
continue
381385
arg_value = command_line_args_dict[arg_data.name]
382386
if arg_data.is_positional_only:
383387
class_or_function_args.append(arg_value)

tests/test_tap_ignore.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import sys
22
import unittest
33
from typing import Annotated
4-
from tap import Tap, TapIgnore
4+
from tap import Tap, TapIgnore, tapify
5+
from dataclasses import dataclass
6+
7+
8+
try:
9+
import pydantic
10+
except ModuleNotFoundError:
11+
_IS_PYDANTIC_V1 = None
12+
else:
13+
_IS_PYDANTIC_V1 = pydantic.VERSION.startswith("1.")
514

615

716
# Suppress prints from SystemExit
@@ -262,6 +271,68 @@ def configure(self):
262271
args = Args().parse_args(["--a", "1"])
263272
self.assertEqual(args.b, 3)
264273

274+
def test_tapify_function_with_tap_ignore(self):
275+
"""Test that tapify handles TapIgnore annotations on function arguments."""
276+
277+
def my_func(a: int, b: TapIgnore[int] = 2, c: str = "hello") -> str:
278+
return f"{a} {b} {c}"
279+
280+
# b is ignored, so it shouldn't be parsed from CLI - should use default
281+
output = tapify(my_func, command_line_args=["--a", "1", "--c", "world"])
282+
self.assertEqual(output, "1 2 world")
283+
284+
# Passing --b should fail because it's not a recognized argument
285+
with self.assertRaises(SystemExit):
286+
tapify(my_func, command_line_args=["--a", "1", "--b", "99", "--c", "world"])
287+
288+
def test_tapify_function_with_tap_ignore_known_only(self):
289+
"""Test tapify with TapIgnore and known_only=True."""
290+
291+
def my_func(a: int, b: TapIgnore[int] = 2, c: str = "hello") -> str:
292+
return f"{a} {b} {c}"
293+
294+
# With known_only=True, --b should be ignored (not cause an error)
295+
output = tapify(
296+
my_func,
297+
command_line_args=["--a", "1", "--b", "99", "--c", "world"],
298+
known_only=True
299+
)
300+
# b should still be 2 (the default), not 99
301+
self.assertEqual(output, "1 2 world")
302+
303+
def test_tapify_class_with_tap_ignore(self):
304+
"""Test that tapify handles TapIgnore annotations on class __init__ arguments."""
305+
306+
class MyClass:
307+
def __init__(self, a: int, b: TapIgnore[int] = 2, c: str = "hello"):
308+
self.result = f"{a} {b} {c}"
309+
310+
# Passing --b should fail because it's not a recognized argument
311+
with self.assertRaises(SystemExit):
312+
tapify(MyClass, command_line_args=["--a", "1", "--b", "99", "--c", "world"])
313+
314+
# test with known_only
315+
myclass = tapify(MyClass, known_only=True, command_line_args=["--a", "1", "--b", "99", "--c", "world"])
316+
# b should still be 2 (the default), not 99
317+
self.assertEqual(myclass.result, "1 2 world")
318+
319+
def test_tapify_ignore_dataclass(self):
320+
@dataclass
321+
class DataclassConfig:
322+
x: int
323+
y: TapIgnore[str] = "ignore_me"
324+
325+
class PydanticModel(pydantic.BaseModel):
326+
x: int
327+
y: TapIgnore[str] = "ignore_me"
328+
329+
for struct_cls in (DataclassConfig, PydanticModel):
330+
with self.subTest(struct_cls=struct_cls.__name__):
331+
model = tapify(struct_cls, command_line_args=["--x", "20"])
332+
self.assertEqual(model.x, 20)
333+
self.assertEqual(model.y, "ignore_me")
334+
with self.assertRaises(SystemExit):
335+
tapify(struct_cls, command_line_args=["--x", "10", "--y", "should_fail"])
265336

266337
if __name__ == "__main__":
267338
unittest.main()

0 commit comments

Comments
 (0)