theOGognf / finagg

A Python package for aggregating and normalizing historical data from popular and free financial APIs.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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 Generics.

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:

  1. 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.
  2. 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.
  3. 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.