Dynamically Setting the Output Type of Python Function

Question:

Okay, I have a set of dictionaries that contain a key/ID and a function. Said dictionaries are used as identifiers that point to a specific cache key and function to update said cache key’s associated data.

from typing import TypedDict, List

class CacheIdentifier(TypedDict):
   key: str
   func: Callable

def function_to_fetch_cache_data() -> List[str]:
   return ["a", "b", "c"]

IDENTIFIER: CacheIdentifier = {
   "key": "my_cache_key",
   "func": function_to_fetch_cache_data
}

Now, the thing is, I have a function called load_or_set_cache which takes an identifier object and, like the name says, checks if there’s data associated with a cache key and fetches the data associated with the cache key if it exists, otherwise it uses the func argument of the CacheIdentifier object provided to fetch new data.

def load_or_set_cache(identifier: CacheIdentifier) -> Any:
   # Logic to check if the data exists in the cache or not
   
   if not cached:
      cache_data = identifier["func"]()
      cache_key = identifier["key"]

      cache.set(cache_key, cache_data, TTL = 3600)

   else:
      cache_key = identifier["key"]
      cache_data = cache.get(cache_key)     

   return cache_data

cache_data = load_or_set_cache(IDENTIFIER)

The thing is, the load_or_set_function function returns the data that was fetched and stored in the cache, but as you can expect, the type of said data varies depending on the return type of the function of each identifier. In my example above, if the function_to_fetch_cache_data has a return type of List[str] then the load_or_set_cache function will have the same return type, causing cache_data to have a List[str] type.

Currently the output type of the load_or_set_cache function is just set to Any, is there any way I could dynamically change the output type of the function depending on the output type of the associated func argument found in each cache identifier?

I’ve tried playing with TypeVars but dont feel like they really suit what I want to do

Asked By: Eddysanoli

||

Answers:

I think Pedro has the right idea. Making your CacheIdentifier generic in terms of the func return type works, but not with a TypedDict:

from collections.abc import Callable
from typing import Generic, TypeVar


T = TypeVar("T")


class CacheIdentifier(Generic[T]):
    key: str
    func: Callable[..., T]

    def __init__(self, key: str, func: Callable[..., T]) -> None:
        self.key = key
        self.func = func


def load_or_set_cache(identifier: CacheIdentifier[T]) -> T:
    return identifier.func()


def main() -> None:
    def function_to_fetch_cache_data() -> list[str]:
        return ["a", "b", "c"]

    ident = CacheIdentifier("my_cache_key", func=function_to_fetch_cache_data)
    cache_data = load_or_set_cache(ident)
    reveal_type(cache_data)

Mypy output:

note: Revealed type is "builtins.list[builtins.str]"

Python currently does not support defining generic TypedDict subclasses.


I think your question was confusing some people, not least because you were talking about "dynamically" setting the output type, yet have that be understood by a static type checker. That makes no sense. And nothing about this is dynamic. The types are all known before the program is run.

I recommend you read through this section of PEP 484 (but really the entire thing).

Answered By: Daniil Fajnberg
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.