summaryrefslogtreecommitdiffstats
path: root/openbb_platform/core/openbb_core/app/model/metadata.py
blob: e5ea6a0f5bf2fece586cbfdaa6c201988db7adb6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from datetime import datetime
from inspect import isclass
from typing import Any, Dict

import numpy as np
import pandas as pd
from pydantic import BaseModel, Field, field_validator

from openbb_core.provider.abstract.data import Data


class Metadata(BaseModel):
    arguments: Dict[str, Any] = Field(
        default_factory=dict,
        description="Arguments of the command.",
    )
    duration: int = Field(
        description="Execution duration in nano second of the command."
    )
    route: str = Field(description="Route of the command.")
    timestamp: datetime = Field(description="Execution starting timestamp.")

    def __repr__(self) -> str:
        return f"{self.__class__.__name__}\n\n" + "\n".join(
            f"{k}: {v}" for k, v in self.model_dump().items()
        )

    @field_validator("arguments")
    @classmethod
    def scale_arguments(cls, v):
        """Scale arguments.
        This function is meant to limit the size of the input arguments of a command.
        If the type is one of the following: `Data`, `List[Data]`, `DataFrame`, `List[DataFrame]`,
        `Series`, `List[Series]` or `ndarray`, the value of the argument is swapped by a dictionary
        containing the type and the columns. If the type is not one of the previous, the
        value is kept or trimmed to 80 characters.
        """
        for arg, arg_val in v.items():
            new_arg_val = None

            # Data
            if isclass(type(arg_val)) and issubclass(type(arg_val), Data):
                new_arg_val = {
                    "type": f"{type(arg_val).__name__}",
                    "columns": list(arg_val.model_dump().keys()),
                }

            # List[Data]
            if isinstance(arg_val, list) and issubclass(type(arg_val[0]), Data):
                columns = [list(d.model_dump().keys()) for d in arg_val]
                columns = (item for sublist in columns for item in sublist)  # flatten
                new_arg_val = {
                    "type": f"List[{type(arg_val[0]).__name__}]",
                    "columns": list(set(columns)),
                }

            # DataFrame
            elif isinstance(arg_val, pd.DataFrame):
                columns = (
                    list(arg_val.index.names) + arg_val.columns.tolist()
                    if any(index is not None for index in list(arg_val.index.names))
                    else arg_val.columns.tolist()
                )
                new_arg_val = {
                    "type": f"{type(arg_val).__name__}",
                    "columns": columns,
                }

            # List[DataFrame]
            elif isinstance(arg_val, list) and issubclass(
                type(arg_val[0]), pd.DataFrame
            ):
                columns = [
                    (
                        list(df.index.names) + df.columns.tolist()
                        if any(index is not None for index in list(df.index.names))
                        else df.columns.tolist()
                    )
                    for df in arg_val
                ]
                new_arg_val = {
                    "type": f"List[{type(arg_val[0]).__name__}]",
                    "columns": columns,
                }

            # Series
            elif isinstance(arg_val, pd.Series):
                new_arg_val = {
                    "type": f"{type(arg_val).__name__}",
                    "columns": list(arg_val.index.names) + [arg_val.name],
                }

            # List[Series]
            elif isinstance(arg_val, list) and isinstance(arg_val[0], pd.Series):
                columns = [
                    (
                        list(series.index.names) + [series.name]
                        if any(index is not None for index in list(series.index.names))
                        else series.name
                    )
                    for series in arg_val
                ]
                new_arg_val = {
                    "type": f"List[{type(arg_val[0]).__name__}]",
                    "columns": columns,
                }

            # ndarray
            elif isinstance(arg_val, np.ndarray):
                new_arg_val = {
                    "type": f"{type(arg_val).__name__}",
                    "columns": list(arg_val.dtype.names or []),
                }

            else:
                str_repr_arg_val = str(arg_val)
                if len(str_repr_arg_val) > 80:
                    new_arg_val = str_repr_arg_val[:80]

            v[arg] = new_arg_val or arg_val

        return v