]> git.rocketbowman.com Git - proto.git/commitdiff
Refactor: Change filenames to command/infer.
authorKyle Bowman <kyle+github@rocketbowman.com>
Sun, 24 Mar 2024 18:55:52 +0000 (14:55 -0400)
committerKyle Bowman <kyle+github@rocketbowman.com>
Sun, 24 Mar 2024 18:55:52 +0000 (14:55 -0400)
src/proto/__init__.py
src/proto/command.py [new file with mode: 0644]
src/proto/infer.py [new file with mode: 0644]
src/proto/prototype.py [deleted file]
src/proto/utils.py [deleted file]
tests/test_command.py [new file with mode: 0644]
tests/test_infer.py [new file with mode: 0644]
tests/test_proto.py [deleted file]
tests/test_utils.py [deleted file]

index 85846f29e48d5d296434c0d6afdf8c5ff2e75286..4e0fee23943d409af895547489ca2224e76f8580 100644 (file)
@@ -1,3 +1,3 @@
-from proto.prototype import Command, command
+from proto.command import Command, command
 
 __all__ = ['Command', 'command']
\ No newline at end of file
diff --git a/src/proto/command.py b/src/proto/command.py
new file mode 100644 (file)
index 0000000..29bfba9
--- /dev/null
@@ -0,0 +1,47 @@
+from argparse import ArgumentError
+from collections.abc import Callable
+from functools import partial
+from typing import Optional
+from proto.infer import get_defaults, get_types, get_parser
+
+
+class Command:
+    def __init__(self, fn: Callable):
+        self.name   = fn.__name__
+        self._run   = fn 
+        self.types  = get_types(fn)
+        self.parser = get_parser(fn)
+        self.required, self.defaults = get_defaults(fn)
+        self.args   = []
+        self.kwargs = {}
+
+    def parse(self, args: Optional[list[str]] = None):
+        """ Parse the list to populate args and kwargs attributes.
+        If no list is specified, sys.argv is used. """
+        # NOTE: Make parsing idemptotent.
+        self.args = []
+        self.kwargs={}
+
+        options = vars(self.parser.parse_args(args=args))
+        for k, v in options.items():
+            if k in self.required.keys():
+                self.args.append(v) 
+            elif k in self.defaults.keys():
+                self.kwargs[k] = v
+            else: 
+                # NOTE: Unrecognized args should be caught earlier in parse_args().
+                raise ArgumentError(k,"Unrecognized argument {k}")
+        return self.args, self.kwargs
+
+    def run(self, *args, **kwargs):
+        """ Invokes function using args/kwargs if specified. Otherwise, uses self. """
+        if not args and not kwargs:
+            return partial(self._run, *self.args, **self.kwargs)()
+        else:
+            args = args if args else self.args
+            kwargs = kwargs if kwargs else self.kwargs
+            return self._run(*args, **kwargs)
+
+def command(fn: Callable):
+    """ Defines a decorator that is used to turn a function into a Command. """
+    return Command(fn)
\ No newline at end of file
diff --git a/src/proto/infer.py b/src/proto/infer.py
new file mode 100644 (file)
index 0000000..98ff1be
--- /dev/null
@@ -0,0 +1,105 @@
+import argparse
+from collections.abc import Callable
+from functools import singledispatch
+import inspect
+from pathlib import Path
+from typing import Union
+
+
+def get_defaults(fn: Callable)->tuple[list, dict]:
+    """ Returns a dict of required arguments and a dict of kwarg defaults. """
+    sig = inspect.signature(fn)
+    args = {}
+    kwargs = {}
+    for name, prm in sig.parameters.items():
+        # ASSUME: It's a Pythonic standard to use self and cls, but it's convention, not rule. Beware.
+        if name == "self" or name == "cls":
+            continue
+        if prm.default == inspect._empty:
+            # NOTE: Don't use args[name]=None. None could be a valid argument 
+            # and we want to flag that a required argument is missing a value.
+            args[name] = inspect._empty
+        else:
+            kwargs[name] = prm.default
+    return args, kwargs
+
+def get_types(fn: Callable)->dict[str]:
+    """ Returns a dictionary of parameter names and their types as strings. """
+    sig = inspect.signature(fn)
+    types = {}
+    for name, prm in sig.parameters.items():
+        # ASSUME: It's a Pythonic standard to use self, but it's convention, not rule. Beware.
+        if name == "self" or name == "cls":
+            continue
+        if prm.annotation == inspect._empty: 
+            types[name] = None
+        else:
+            types[name] = prm.annotation
+    return types
+
+class _ArgSpec(dict):
+    """ _ArgSpec contains key-value pairs used for parser.add_argument(). """
+
+    def __init__(self, prm: inspect.Parameter):
+        super().__init__()
+        self.update(self._parse_default(prm))
+        self.update(self._parse_type(prm))
+
+    def _parse_default(self, prm):
+        # ASSUME: If a function specifies a default argument, we tell the parser to consider it optional.
+        dct = {}
+        if prm.default != prm.empty: 
+            # NOTE: Argparse requires optional args to start with '-' or '--'.
+            dct['argname']  = "--" + prm.name
+            dct['required'] = False
+            dct['default']  = prm.default
+        else: 
+            dct['argname']  = prm.name
+        return dct
+
+    def _parse_type(self, prm):
+        # NOTE: If you don't specify type in add_argument(), it will be parsed as a string.
+        # Use get_argspecs() to add type-specific information to the arg_spec.
+        if isinstance(prm.annotation, type):    # Basic types
+            return get_argspecs(prm.annotation())
+        elif hasattr(prm.annotation, '__args__'): # Unions
+            # ASSUME: Order of types in signatures indicate order of preference.
+            for type_ in prm.annotation.__args__:
+                try:
+                    return get_argspecs(type_())
+                except TypeError as e:
+                    raise e
+        else:
+            raise TypeError(f"Cannot instantiate. Check the type of {prm.annotation}")
+
+def get_parser(fn: Callable)->argparse.ArgumentParser:
+    """ Returns an argparse.ArgumentParser based on the function's signature. """
+    sig = inspect.signature(fn)
+    parser = argparse.ArgumentParser() 
+    for prm in sig.parameters.values():
+        # ASSUME: It's a Pythonic standard to use self, but it's convention, not rule. Beware.
+        if prm.name == "self" or prm.name == "cls":
+            continue
+        argspec = _ArgSpec(prm)
+        argname = argspec.pop('argname')
+        parser.add_argument(argname, **argspec)
+    return parser
+
+@singledispatch
+def get_argspecs(annotation: type)->dict:
+    """ Creates a partial argspec dictionary from a parameter annotation. """
+    return {'type': type(annotation)}
+
+@get_argspecs.register
+def empty_argspecs(annotation: inspect._empty)->dict:
+    """ Implements argspecs for unannotated parameters. """
+    return {}
+
+@get_argspecs.register
+def scalar_argspecs(annotation: Union[int, float, str])->dict:
+    """ Implements get_argspecs for integers, floats, and strings. """
+    return {'type': type(annotation)}
+
+@get_argspecs.register
+def path_argspecs(annotation: Path):
+    return {'type': type(annotation)}
diff --git a/src/proto/prototype.py b/src/proto/prototype.py
deleted file mode 100644 (file)
index 27609bb..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-from argparse import ArgumentError
-from collections.abc import Callable
-from functools import partial
-from typing import Optional
-from proto.utils import get_defaults, get_types, get_parser
-
-
-class Command:
-    def __init__(self, fn: Callable):
-        self.name   = fn.__name__
-        self._run   = fn 
-        self.types  = get_types(fn)
-        self.parser = get_parser(fn)
-        self.required, self.defaults = get_defaults(fn)
-        self.args   = []
-        self.kwargs = {}
-
-    def parse(self, args: Optional[list[str]] = None):
-        """ Parse the list to populate args and kwargs attributes.
-        If no list is specified, sys.argv is used. """
-        # NOTE: Make parsing idemptotent.
-        self.args = []
-        self.kwargs={}
-
-        options = vars(self.parser.parse_args(args=args))
-        for k, v in options.items():
-            if k in self.required.keys():
-                self.args.append(v) 
-            elif k in self.defaults.keys():
-                self.kwargs[k] = v
-            else: 
-                # NOTE: Unrecognized args should be caught earlier in parse_args().
-                raise ArgumentError(k,"Unrecognized argument {k}")
-        return self.args, self.kwargs
-
-    def run(self, *args, **kwargs):
-        """ Invokes function using args/kwargs if specified. Otherwise, uses self. """
-        if not args and not kwargs:
-            return partial(self._run, *self.args, **self.kwargs)()
-        else:
-            args = args if args else self.args
-            kwargs = kwargs if kwargs else self.kwargs
-            return self._run(*args, **kwargs)
-
-def command(fn: Callable):
-    """ Defines a decorator that is used to turn a function into a Command. """
-    return Command(fn)
\ No newline at end of file
diff --git a/src/proto/utils.py b/src/proto/utils.py
deleted file mode 100644 (file)
index 98ff1be..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-import argparse
-from collections.abc import Callable
-from functools import singledispatch
-import inspect
-from pathlib import Path
-from typing import Union
-
-
-def get_defaults(fn: Callable)->tuple[list, dict]:
-    """ Returns a dict of required arguments and a dict of kwarg defaults. """
-    sig = inspect.signature(fn)
-    args = {}
-    kwargs = {}
-    for name, prm in sig.parameters.items():
-        # ASSUME: It's a Pythonic standard to use self and cls, but it's convention, not rule. Beware.
-        if name == "self" or name == "cls":
-            continue
-        if prm.default == inspect._empty:
-            # NOTE: Don't use args[name]=None. None could be a valid argument 
-            # and we want to flag that a required argument is missing a value.
-            args[name] = inspect._empty
-        else:
-            kwargs[name] = prm.default
-    return args, kwargs
-
-def get_types(fn: Callable)->dict[str]:
-    """ Returns a dictionary of parameter names and their types as strings. """
-    sig = inspect.signature(fn)
-    types = {}
-    for name, prm in sig.parameters.items():
-        # ASSUME: It's a Pythonic standard to use self, but it's convention, not rule. Beware.
-        if name == "self" or name == "cls":
-            continue
-        if prm.annotation == inspect._empty: 
-            types[name] = None
-        else:
-            types[name] = prm.annotation
-    return types
-
-class _ArgSpec(dict):
-    """ _ArgSpec contains key-value pairs used for parser.add_argument(). """
-
-    def __init__(self, prm: inspect.Parameter):
-        super().__init__()
-        self.update(self._parse_default(prm))
-        self.update(self._parse_type(prm))
-
-    def _parse_default(self, prm):
-        # ASSUME: If a function specifies a default argument, we tell the parser to consider it optional.
-        dct = {}
-        if prm.default != prm.empty: 
-            # NOTE: Argparse requires optional args to start with '-' or '--'.
-            dct['argname']  = "--" + prm.name
-            dct['required'] = False
-            dct['default']  = prm.default
-        else: 
-            dct['argname']  = prm.name
-        return dct
-
-    def _parse_type(self, prm):
-        # NOTE: If you don't specify type in add_argument(), it will be parsed as a string.
-        # Use get_argspecs() to add type-specific information to the arg_spec.
-        if isinstance(prm.annotation, type):    # Basic types
-            return get_argspecs(prm.annotation())
-        elif hasattr(prm.annotation, '__args__'): # Unions
-            # ASSUME: Order of types in signatures indicate order of preference.
-            for type_ in prm.annotation.__args__:
-                try:
-                    return get_argspecs(type_())
-                except TypeError as e:
-                    raise e
-        else:
-            raise TypeError(f"Cannot instantiate. Check the type of {prm.annotation}")
-
-def get_parser(fn: Callable)->argparse.ArgumentParser:
-    """ Returns an argparse.ArgumentParser based on the function's signature. """
-    sig = inspect.signature(fn)
-    parser = argparse.ArgumentParser() 
-    for prm in sig.parameters.values():
-        # ASSUME: It's a Pythonic standard to use self, but it's convention, not rule. Beware.
-        if prm.name == "self" or prm.name == "cls":
-            continue
-        argspec = _ArgSpec(prm)
-        argname = argspec.pop('argname')
-        parser.add_argument(argname, **argspec)
-    return parser
-
-@singledispatch
-def get_argspecs(annotation: type)->dict:
-    """ Creates a partial argspec dictionary from a parameter annotation. """
-    return {'type': type(annotation)}
-
-@get_argspecs.register
-def empty_argspecs(annotation: inspect._empty)->dict:
-    """ Implements argspecs for unannotated parameters. """
-    return {}
-
-@get_argspecs.register
-def scalar_argspecs(annotation: Union[int, float, str])->dict:
-    """ Implements get_argspecs for integers, floats, and strings. """
-    return {'type': type(annotation)}
-
-@get_argspecs.register
-def path_argspecs(annotation: Path):
-    return {'type': type(annotation)}
diff --git a/tests/test_command.py b/tests/test_command.py
new file mode 100644 (file)
index 0000000..826cb9b
--- /dev/null
@@ -0,0 +1,58 @@
+from abc import abstractmethod
+from typing import Protocol
+import pytest
+from proto import command, Command
+
+class Stringable(Protocol):
+
+    @abstractmethod
+    def __str__(self)->str:
+        ...
+
+#def echo_fn(arg: Stringable)->str:
+def echo_fn(arg: str)->str:
+    return str(arg)
+
+@command
+#def echo(arg: Stringable)->str:
+def echo(arg: str)->str:
+    return echo_fn(arg)
+
+def test_attributes():
+    cmd = Command(echo_fn)
+    assert cmd.name == "echo_fn"
+    cmd2 = echo
+    assert cmd2.name == "echo"
+
+def test_parse():
+    echo.parse(args=['42'])
+    assert echo.kwargs == {}
+    # Okay for 42 to be a string because echo is defined on stringables
+    assert echo.args[0] == '42'
+
+def test_parse_argument_error():
+    with pytest.raises(SystemExit):
+        echo.parse(args=['echo_target','--unknown'])
+
+def test_parse_idempotency():
+    echo.parse(args=['42'])
+    echo.parse(args=['something else'])
+    assert '42' not in echo.args
+    assert 'something else' in echo.args
+
+def test_run_parsed_args():
+    echo.parse(args=['yippee!'])
+    assert "yippee!" in echo.run()
+
+def test_run_override_parsed():
+    echo.parse(args=['yippee!'])
+    assert "override" in echo.run("override")
+    assert '42' in echo.run(42)
+        
+
+if __name__ == "__main__":
+    @command 
+    def test(char: str, n: int = 42)->tuple[str,int]:
+        return char, n 
+    
+    test.parse(args=['--n', '36', 'char_field'])
\ No newline at end of file
diff --git a/tests/test_infer.py b/tests/test_infer.py
new file mode 100644 (file)
index 0000000..8e42f7b
--- /dev/null
@@ -0,0 +1,136 @@
+import inspect
+import os
+import sys
+from   typing import Optional
+import pytest
+
+from dummy import *
+from proto.infer import get_defaults, get_types, get_parser
+
+def test_get_default_equivalence():
+    """ Ensures that defaults are treated the same amongst Callables. """
+    callable_defaults = get_defaults(DummyCallable())
+    method_defaults   = get_defaults(DummyClass().dummy_method)
+    function_defaults = get_defaults(dummy_fn_full)
+    assert callable_defaults == method_defaults == function_defaults
+
+def test_get_types_equivalence():
+    """ Ensures that type signatures are treated the same amongst Callables. """
+    callable_types = get_types(DummyCallable())
+    method_types   = get_types(DummyClass().dummy_method)
+    function_types = get_types(dummy_fn_full)
+    assert callable_types == method_types == function_types
+
+@pytest.mark.skip("Equivalence is not defined for argparse.ArgumentParser")
+def test_get_parser_equivalence():
+    callable_parser = get_parser(DummyCallable())
+    method_parser   = get_parser(DummyClass().dummy_method)
+    function_parser = get_parser(dummy_fn_full)
+    assert callable_parser == method_parser == function_parser
+
+# ASSUME: Assuming the above tests are valid and pass, we only have to test one
+# kind of the Callable and the results should hold for the others.
+def test_get_defaults():
+    args, kwargs = get_defaults(dummy_fn_full)
+    assert args == {}
+    assert kwargs['string'] == "default"
+    assert kwargs['num'] == 42
+
+def test_get_defaults_unspecified():
+    args, kwargs = get_defaults(dummy_fn_no_signature)
+    assert args == {'string': inspect._empty, 'num': inspect._empty}
+    assert kwargs == {}
+
+def test_get_types():
+    types = get_types(dummy_fn_typed)
+    assert types['string'] == str
+    assert types['num'] == int
+
+def test_get_types_unspecified():
+    types = get_types(dummy_fn_no_signature)
+    assert types['string'] is None
+    assert types['num'] is None
+
+def test_get_types_optional():
+    types = get_types(dummy_fn_optional)
+    assert types['string'] == Optional[str]
+    assert types['num']  == Optional[int]
+
+def test_validating_optional():
+    types = get_types(dummy_fn_optional)
+    _, kwargs = get_defaults(dummy_fn_optional)
+    assert isinstance(kwargs['num'],types['num'])
+
+def test_get_parser_defaults():
+    """ If a fn default is specified, use keyword syntax (optional). """
+    string="not default"
+    num=42
+    parser=get_parser(dummy_fn_full)
+    args = parser.parse_args(args=[f"--string={string}", "--num", str(num)])
+    assert args.string == string
+    assert int(args.num) == num
+
+def test_get_parser_no_defaults():
+    """ If no function default is specified, use postional syntax (required). """
+    string="not default"
+    num=42
+    parser=get_parser(dummy_fn_no_signature)
+    args = parser.parse_args(args=[string, str(num)])
+    assert args.string == string
+
+def test_get_parser_method():
+    """ Ensures that 'self' and 'cls' are ignored. """
+    string="not default"
+    num=42
+    parser=get_parser(DummyClass().dummy_method)
+    args = parser.parse_args(args=[f"--string={string}", "--num", str(num)])
+    assert args.string == string
+    parser2=get_parser(DummyClass.dummy_classmethod)
+    args2 = parser2.parse_args([f"--string={string}", "--num", str(num)])
+    assert args2.string == string
+
+def test_get_parser_types_scalar():
+    """ Asserts ints, strings, and floats, are parsed from CLI as types. """
+    string="not default"
+    num="42"
+    parser=get_parser(dummy_fn_full)
+    args = parser.parse_args(args=[f"--string={string}", "--num", str(num)])
+    assert args.string == string
+    assert args.num == int(num)
+
+def test_get_parser_types_path():
+    parser=get_parser(dummy_fn_path)
+    args = parser.parse_args(args=['.'])
+    assert isinstance(args.path, Path)
+
+def test_get_parser_types_union():
+    parser=get_parser(dummy_fn_optional)
+    args=parser.parse_args(args=['--string','yay'])
+    assert args.string == "yay"
+
+def test_get_parser_types_union_defaults(): 
+    parser=get_parser(dummy_fn_optional)
+    args=parser.parse_args(args=[])
+    assert args.num == int(42)
+    assert args.string == None
+
+if __name__ == "__main__":
+    import inspect
+    sig = inspect.signature(dummy_fn_full)
+    p = sig.parameters['string']
+    p2 = sig.parameters['num']
+    def dummy_fn_file(filename: Optional[os.PathLike] = None):
+        if filename is None:
+            contents = sys.stdin.read()
+            return contents
+        else: 
+            return str(filename)
+    sig2=inspect.signature(dummy_fn_file)
+    p3 = sig2.parameters['filename']
+
+    from tests.test_command import Stringable
+    def echo(arg: Stringable):
+        return str(arg)
+    sig3 = inspect.signature(echo)
+    p4 = sig3.parameters['arg']
+    # p3.annotation.__args__ = (<class 'os.PathLike'>, <class 'NoneType'>)
\ No newline at end of file
diff --git a/tests/test_proto.py b/tests/test_proto.py
deleted file mode 100644 (file)
index 826cb9b..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-from abc import abstractmethod
-from typing import Protocol
-import pytest
-from proto import command, Command
-
-class Stringable(Protocol):
-
-    @abstractmethod
-    def __str__(self)->str:
-        ...
-
-#def echo_fn(arg: Stringable)->str:
-def echo_fn(arg: str)->str:
-    return str(arg)
-
-@command
-#def echo(arg: Stringable)->str:
-def echo(arg: str)->str:
-    return echo_fn(arg)
-
-def test_attributes():
-    cmd = Command(echo_fn)
-    assert cmd.name == "echo_fn"
-    cmd2 = echo
-    assert cmd2.name == "echo"
-
-def test_parse():
-    echo.parse(args=['42'])
-    assert echo.kwargs == {}
-    # Okay for 42 to be a string because echo is defined on stringables
-    assert echo.args[0] == '42'
-
-def test_parse_argument_error():
-    with pytest.raises(SystemExit):
-        echo.parse(args=['echo_target','--unknown'])
-
-def test_parse_idempotency():
-    echo.parse(args=['42'])
-    echo.parse(args=['something else'])
-    assert '42' not in echo.args
-    assert 'something else' in echo.args
-
-def test_run_parsed_args():
-    echo.parse(args=['yippee!'])
-    assert "yippee!" in echo.run()
-
-def test_run_override_parsed():
-    echo.parse(args=['yippee!'])
-    assert "override" in echo.run("override")
-    assert '42' in echo.run(42)
-        
-
-if __name__ == "__main__":
-    @command 
-    def test(char: str, n: int = 42)->tuple[str,int]:
-        return char, n 
-    
-    test.parse(args=['--n', '36', 'char_field'])
\ No newline at end of file
diff --git a/tests/test_utils.py b/tests/test_utils.py
deleted file mode 100644 (file)
index e1b8af0..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-import inspect
-import os
-import sys
-from   typing import Optional
-import pytest
-
-from dummy import *
-from proto.utils import get_defaults, get_types, get_parser
-
-def test_get_default_equivalence():
-    """ Ensures that defaults are treated the same amongst Callables. """
-    callable_defaults = get_defaults(DummyCallable())
-    method_defaults   = get_defaults(DummyClass().dummy_method)
-    function_defaults = get_defaults(dummy_fn_full)
-    assert callable_defaults == method_defaults == function_defaults
-
-def test_get_types_equivalence():
-    """ Ensures that type signatures are treated the same amongst Callables. """
-    callable_types = get_types(DummyCallable())
-    method_types   = get_types(DummyClass().dummy_method)
-    function_types = get_types(dummy_fn_full)
-    assert callable_types == method_types == function_types
-
-@pytest.mark.skip("Equivalence is not defined for argparse.ArgumentParser")
-def test_get_parser_equivalence():
-    callable_parser = get_parser(DummyCallable())
-    method_parser   = get_parser(DummyClass().dummy_method)
-    function_parser = get_parser(dummy_fn_full)
-    assert callable_parser == method_parser == function_parser
-
-# ASSUME: Assuming the above tests are valid and pass, we only have to test one
-# kind of the Callable and the results should hold for the others.
-def test_get_defaults():
-    args, kwargs = get_defaults(dummy_fn_full)
-    assert args == {}
-    assert kwargs['string'] == "default"
-    assert kwargs['num'] == 42
-
-def test_get_defaults_unspecified():
-    args, kwargs = get_defaults(dummy_fn_no_signature)
-    assert args == {'string': inspect._empty, 'num': inspect._empty}
-    assert kwargs == {}
-
-def test_get_types():
-    types = get_types(dummy_fn_typed)
-    assert types['string'] == str
-    assert types['num'] == int
-
-def test_get_types_unspecified():
-    types = get_types(dummy_fn_no_signature)
-    assert types['string'] is None
-    assert types['num'] is None
-
-def test_get_types_optional():
-    types = get_types(dummy_fn_optional)
-    assert types['string'] == Optional[str]
-    assert types['num']  == Optional[int]
-
-def test_validating_optional():
-    types = get_types(dummy_fn_optional)
-    _, kwargs = get_defaults(dummy_fn_optional)
-    assert isinstance(kwargs['num'],types['num'])
-
-def test_get_parser_defaults():
-    """ If a fn default is specified, use keyword syntax (optional). """
-    string="not default"
-    num=42
-    parser=get_parser(dummy_fn_full)
-    args = parser.parse_args(args=[f"--string={string}", "--num", str(num)])
-    assert args.string == string
-    assert int(args.num) == num
-
-def test_get_parser_no_defaults():
-    """ If no function default is specified, use postional syntax (required). """
-    string="not default"
-    num=42
-    parser=get_parser(dummy_fn_no_signature)
-    args = parser.parse_args(args=[string, str(num)])
-    assert args.string == string
-
-def test_get_parser_method():
-    """ Ensures that 'self' and 'cls' are ignored. """
-    string="not default"
-    num=42
-    parser=get_parser(DummyClass().dummy_method)
-    args = parser.parse_args(args=[f"--string={string}", "--num", str(num)])
-    assert args.string == string
-    parser2=get_parser(DummyClass.dummy_classmethod)
-    args2 = parser2.parse_args([f"--string={string}", "--num", str(num)])
-    assert args2.string == string
-
-def test_get_parser_types_scalar():
-    """ Asserts ints, strings, and floats, are parsed from CLI as types. """
-    string="not default"
-    num="42"
-    parser=get_parser(dummy_fn_full)
-    args = parser.parse_args(args=[f"--string={string}", "--num", str(num)])
-    assert args.string == string
-    assert args.num == int(num)
-
-def test_get_parser_types_path():
-    parser=get_parser(dummy_fn_path)
-    args = parser.parse_args(args=['.'])
-    assert isinstance(args.path, Path)
-
-def test_get_parser_types_union():
-    parser=get_parser(dummy_fn_optional)
-    args=parser.parse_args(args=['--string','yay'])
-    assert args.string == "yay"
-
-def test_get_parser_types_union_defaults(): 
-    parser=get_parser(dummy_fn_optional)
-    args=parser.parse_args(args=[])
-    assert args.num == int(42)
-    assert args.string == None
-
-if __name__ == "__main__":
-    import inspect
-    sig = inspect.signature(dummy_fn_full)
-    p = sig.parameters['string']
-    p2 = sig.parameters['num']
-    def dummy_fn_file(filename: Optional[os.PathLike] = None):
-        if filename is None:
-            contents = sys.stdin.read()
-            return contents
-        else: 
-            return str(filename)
-    sig2=inspect.signature(dummy_fn_file)
-    p3 = sig2.parameters['filename']
-
-    from test_proto import Stringable
-    def echo(arg: Stringable):
-        return str(arg)
-    sig3 = inspect.signature(echo)
-    p4 = sig3.parameters['arg']
-    # p3.annotation.__args__ = (<class 'os.PathLike'>, <class 'NoneType'>)
\ No newline at end of file