Clunky API implementations
theOGognf opened this issue · comments
Just a thought I had: the API implementations are a bit clunky and could be slimmed down by writing a simple decorator that instantiates an API-specific class that has the respective get
method and url
attribute instead of writing standalone classes for each endpoint.
So instead of:
class Series
url = ...
def get():
...
we have:
@secapi(url)
def series():
...
I finally understood how to do this using Python 3.10's ParamSpec
with Generic
s.
import pandas as pd
from typing import Callable, Generic, ParamSpec
P = ParamSpec("P")
def partial(f: Callable[Concatenate[str, P], pd.DataFrame], url: str) -> Callable[P, pd.DataFrame]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> pd.DataFrame:
return f(url, *args, **kwargs)
return wrapper
class API(Generic[P]):
def __init__(self, f: Callable[Concatenate[str, P], pd.DataFrame], url: str) -> None:
self.get = partial(f, url)
self.url = url
def api(url: str) -> Callable[Callable[Concatenate[str, P], pd.DataFrame], API[P]]:
def wrapped(f: Callable[Concatenate[str, P], pd.DataFrame]) -> API[P]:
return API(f, url)
return wrapped
Although this passes strict mypy and has all the correct type-hints for any getter function passed to the api
decorator:
- This is arguably more clunky than the current API implementation. Similar code will need to be repeated for each API submodule since they have different helper functions/methods/attributes.
- This does not enable the
finagg.fred.api.series.observations
-style API calls. Adding attributes to these generics/wrappers just requires more boilerplate code that'll be more verbose than the original implementation. - This is pretty hard to look at for someone new jumping into the package. I'd prefer to keep stuff as magic-free as possible so it's easier for people to jump in.
Going to close for now unless something else comes up that motivates a change like this.