From f7d4e445ffeaab8741f6518243d8a42b43749a91 Mon Sep 17 00:00:00 2001 From: Kyle Bowman Date: Sun, 10 Mar 2024 11:38:36 -0400 Subject: [PATCH] Refactor: Single dispatch type-based argspecs. --- findings.md | 9 +++++++++ src/proto/utils.py | 35 +++++++++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/findings.md b/findings.md index ef19191..0ca9873 100644 --- a/findings.md +++ b/findings.md @@ -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()` to determine which function is dispatched by ``. +* Use `foo.registry` to see all registered functions. + # About MainCLI Purpose: 1. Search for other commands to register with it diff --git a/src/proto/utils.py b/src/proto/utils.py index 2116ea9..fe58b23 100644 --- a/src/proto/utils.py +++ b/src/proto/utils.py @@ -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 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 -- 2.39.5