]> git.rocketbowman.com Git - proto.git/commitdiff
Refactor: Single dispatch type-based argspecs.
authorKyle Bowman <kyle+github@rocketbowman.com>
Sun, 10 Mar 2024 15:38:36 +0000 (11:38 -0400)
committerKyle Bowman <kyle+github@rocketbowman.com>
Sun, 10 Mar 2024 15:38:36 +0000 (11:38 -0400)
findings.md
src/proto/utils.py

index ef191912e34c93f750200dcff5b465f265c68981..0ca9873ceb6871adeedce188129e1093ad656f74 100644 (file)
@@ -57,6 +57,15 @@ if __name__ == "__main__":
 ## About Signatures
 * Argnames = Signature - Kwargs; this is a good way to report missing required arg
 
+## About Singledispatch
+
+* Use `@singledispatch` decorator to mark function`foo` a generic function.
+* Use `@foo.register` decorator to mark a function as an implemntation of `foo`.
+* Dispatch is based on the type of the first argument.
+* Method resolution is from specific to generic.
+* Use `foo.dispatch(<type>)` to determine which function is dispatched by `<type>`.
+* Use `foo.registry` to see all registered functions.
+
 # About MainCLI
 Purpose: 
 1. Search for other commands to register with it
index 2116ea9bff500ff1c5e3c56849110ff32b901987..fe58b23d4002cbd7b75ee5a5cda54eb3045fc798 100644 (file)
@@ -1,6 +1,9 @@
+import argparse
 from collections.abc import Callable
+from functools import singledispatch
 import inspect
-import argparse
+from typing import Union
+
 
 def get_defaults(fn: Callable)->tuple[list, dict]:
     """ Returns a dict of required arguments and a dict of kwarg defaults. """
@@ -57,13 +60,29 @@ def get_parser(fn: Callable)->argparse.ArgumentParser:
             argname = prm.name
 
         # NOTE: If you don't specify type in add_argument(), it will be parsed as a string.
-        # HACK: Whenever you see <class 'int'> you can use it as an initializer.
-        # It works consistently, but I haven't seen it as defined/supported behavior.
-        # Ex: type(42)('36') creates an integer 36.
-        if prm.annotation in (int, float, str):
-            arg_specs['type'] = prm.annotation
-        else:
-            pass
+        # Use get_argspecs() to add type-specific information to the arg_spec.
+        arg_specs | get_argspecs(prm.annotation(), arg_specs)
         
         parser.add_argument(argname, **arg_specs)
     return parser
+
+# NOTE: When a single dispatch function is invoked, the the first arg is inspected.
+# Based on the type of the first argument, a corresponding implementation is dispatched.
+# Use @foo.register to register an implementation to the single dispatch function foo.
+# The following two hacks are used throughout the get_argspecs implementations:
+# HACK: type(annotation) == type. But type(annotation()) == str | int | whatever. 
+# This hack works because of the following hack.
+# HACK: The 'type' type is callable. It behaves like a constructor for it's type.
+# For example: `type(42)('36')` creates an integer 36.
+# It works consistently, but I haven't seen it as defined/supported behavior.
+@singledispatch
+def get_argspecs(annotation: type, arg_specs: dict)->dict:
+    """ Creates a partial argspec dictionary from a parameter annotation. """
+    return arg_specs
+
+@get_argspecs.register
+def scalar_argspecs(annotation: Union[int, float, str], arg_specs)->dict:
+    """ Implements get_argspecs for integers, floats, and strings. """
+    arg_specs['type'] = type(annotation)
+    return arg_specs
+    
\ No newline at end of file