finbrain-python 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- finbrain/__init__.py +12 -0
- finbrain/aio/__init__.py +0 -0
- finbrain/aio/client.py +0 -0
- finbrain/client.py +112 -0
- finbrain/endpoints/__init__.py +0 -0
- finbrain/endpoints/analyst_ratings.py +83 -0
- finbrain/endpoints/app_ratings.py +102 -0
- finbrain/endpoints/available.py +83 -0
- finbrain/endpoints/house_trades.py +82 -0
- finbrain/endpoints/insider_transactions.py +68 -0
- finbrain/endpoints/linkedin_data.py +85 -0
- finbrain/endpoints/options.py +86 -0
- finbrain/endpoints/predictions.py +130 -0
- finbrain/endpoints/sentiments.py +108 -0
- finbrain/exceptions.py +140 -0
- finbrain_python-0.1.1.dist-info/METADATA +238 -0
- finbrain_python-0.1.1.dist-info/RECORD +20 -0
- finbrain_python-0.1.1.dist-info/WHEEL +5 -0
- finbrain_python-0.1.1.dist-info/licenses/LICENSE +21 -0
- finbrain_python-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
import datetime as _dt
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # imported only by type-checkers
|
|
8
|
+
from ..client import FinBrainClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class OptionsAPI:
|
|
12
|
+
"""
|
|
13
|
+
Options data endpoints
|
|
14
|
+
----------------------
|
|
15
|
+
|
|
16
|
+
Currently implemented
|
|
17
|
+
~~~~~~~~~~~~~~~~~~~~~
|
|
18
|
+
• **put_call** - ``/putcalldata/<MARKET>/<TICKER>``
|
|
19
|
+
|
|
20
|
+
Future additions (open interest, IV, strikes, …) can live in this same class.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# ────────────────────────────────────────────────────────────────────
|
|
24
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
25
|
+
self._c = client # reference to the parent client
|
|
26
|
+
|
|
27
|
+
# ────────────────────────────────────────────────────────────────────
|
|
28
|
+
def put_call(
|
|
29
|
+
self,
|
|
30
|
+
market: str,
|
|
31
|
+
symbol: str,
|
|
32
|
+
*,
|
|
33
|
+
date_from: _dt.date | str | None = None,
|
|
34
|
+
date_to: _dt.date | str | None = None,
|
|
35
|
+
as_dataframe: bool = False,
|
|
36
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
37
|
+
"""
|
|
38
|
+
Put/Call ratio data for *symbol* in *market*.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
market :
|
|
43
|
+
Market name **exactly as FinBrain lists it**
|
|
44
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
45
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
46
|
+
automatically.
|
|
47
|
+
symbol :
|
|
48
|
+
Ticker symbol; converted to upper-case.
|
|
49
|
+
date_from, date_to :
|
|
50
|
+
Optional ISO dates (``YYYY-MM-DD``) bounding the returned rows.
|
|
51
|
+
as_dataframe :
|
|
52
|
+
If *True*, return a **pandas.DataFrame** indexed by ``date``;
|
|
53
|
+
otherwise return the raw JSON dict.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
dict | pandas.DataFrame
|
|
58
|
+
"""
|
|
59
|
+
params: Dict[str, str] = {}
|
|
60
|
+
if date_from:
|
|
61
|
+
params["dateFrom"] = _to_datestr(date_from)
|
|
62
|
+
if date_to:
|
|
63
|
+
params["dateTo"] = _to_datestr(date_to)
|
|
64
|
+
|
|
65
|
+
market_slug = quote(market, safe="")
|
|
66
|
+
path = f"putcalldata/{market_slug}/{symbol.upper()}"
|
|
67
|
+
|
|
68
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
69
|
+
|
|
70
|
+
if as_dataframe:
|
|
71
|
+
rows: List[Dict[str, Any]] = data.get("putCallData", [])
|
|
72
|
+
df = pd.DataFrame(rows)
|
|
73
|
+
if not df.empty and "date" in df.columns:
|
|
74
|
+
df["date"] = pd.to_datetime(df["date"])
|
|
75
|
+
df.set_index("date", inplace=True)
|
|
76
|
+
return df
|
|
77
|
+
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ────────────────────────────────────────────────────────────────────────
|
|
82
|
+
# helper
|
|
83
|
+
# ────────────────────────────────────────────────────────────────────────
|
|
84
|
+
def _to_datestr(value: _dt.date | str) -> str:
|
|
85
|
+
"""Convert :class:`datetime.date` → ``YYYY-MM-DD``; leave strings untouched."""
|
|
86
|
+
return value.isoformat() if isinstance(value, _dt.date) else value
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from urllib.parse import quote
|
|
6
|
+
from typing import TYPE_CHECKING, Literal, Dict, Any, List
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..client import FinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# ------------------------------------------------------------------------- #
|
|
13
|
+
_PType = Literal["daily", "monthly"]
|
|
14
|
+
_ALLOWED: set[str] = {"daily", "monthly"}
|
|
15
|
+
_DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PredictionsAPI:
|
|
19
|
+
"""
|
|
20
|
+
Price-prediction endpoints
|
|
21
|
+
|
|
22
|
+
• `/market/<MARKET>/predictions/<TYPE>`
|
|
23
|
+
• `/ticker/<TICKER>/predictions/<TYPE>`
|
|
24
|
+
|
|
25
|
+
where **TYPE** ∈ { `daily`, `monthly` }.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
29
|
+
self._c = client
|
|
30
|
+
|
|
31
|
+
# ------------------------------------------------------------------ #
|
|
32
|
+
def ticker(
|
|
33
|
+
self,
|
|
34
|
+
symbol: str,
|
|
35
|
+
*,
|
|
36
|
+
prediction_type: _PType = "daily",
|
|
37
|
+
as_dataframe: bool = False,
|
|
38
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
39
|
+
"""
|
|
40
|
+
Single-ticker predictions.
|
|
41
|
+
|
|
42
|
+
Parameters
|
|
43
|
+
----------
|
|
44
|
+
symbol :
|
|
45
|
+
Symbol such as ``AAPL`` (case-insensitive).
|
|
46
|
+
prediction_type :
|
|
47
|
+
``"daily"`` (10-day horizon) or ``"monthly"`` (12-month horizon).
|
|
48
|
+
as_dataframe :
|
|
49
|
+
Return a **DataFrame** (index =`date`, cols =`main, lower, upper`)
|
|
50
|
+
instead of raw JSON.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
dict | pandas.DataFrame
|
|
55
|
+
"""
|
|
56
|
+
_validate(prediction_type)
|
|
57
|
+
path = f"ticker/{symbol.upper()}/predictions/{prediction_type}"
|
|
58
|
+
data: Dict[str, Any] = self._c._request("GET", path)
|
|
59
|
+
|
|
60
|
+
if as_dataframe:
|
|
61
|
+
pred = data.get("prediction", {})
|
|
62
|
+
rows: list[dict[str, float]] = []
|
|
63
|
+
for k, v in pred.items():
|
|
64
|
+
if _DATE_RE.fullmatch(k):
|
|
65
|
+
main, low, high = map(float, v.split(","))
|
|
66
|
+
rows.append({"date": k, "main": main, "lower": low, "upper": high})
|
|
67
|
+
df = pd.DataFrame(rows).set_index(
|
|
68
|
+
pd.to_datetime(pd.Series([r["date"] for r in rows]))
|
|
69
|
+
)
|
|
70
|
+
df.index.name = "date"
|
|
71
|
+
df.drop(columns="date", inplace=True)
|
|
72
|
+
return df
|
|
73
|
+
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
# ------------------------------------------------------------------ #
|
|
77
|
+
def market(
|
|
78
|
+
self,
|
|
79
|
+
market: str,
|
|
80
|
+
*,
|
|
81
|
+
prediction_type: _PType = "daily",
|
|
82
|
+
as_dataframe: bool = False,
|
|
83
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
84
|
+
"""
|
|
85
|
+
Predictions for **all** tickers in a market.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
market :
|
|
90
|
+
Market name **exactly as FinBrain lists it**
|
|
91
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``). Spaces/`&` are OK.
|
|
92
|
+
prediction_type :
|
|
93
|
+
``"daily"`` or ``"monthly"``.
|
|
94
|
+
as_dataframe :
|
|
95
|
+
If *True* return a DataFrame (index =`ticker`) with
|
|
96
|
+
``expectedShort``, ``expectedMid``, ``expectedLong``, and optional
|
|
97
|
+
``sentimentScore``.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
list[dict] | pandas.DataFrame
|
|
102
|
+
"""
|
|
103
|
+
_validate(prediction_type)
|
|
104
|
+
slug = quote(market, safe="")
|
|
105
|
+
path = f"market/{slug}/predictions/{prediction_type}"
|
|
106
|
+
data: List[Dict[str, Any]] = self._c._request("GET", path)
|
|
107
|
+
|
|
108
|
+
if as_dataframe:
|
|
109
|
+
rows: list[dict[str, Any]] = []
|
|
110
|
+
for rec in data:
|
|
111
|
+
p = rec.get("prediction", {})
|
|
112
|
+
rows.append(
|
|
113
|
+
{
|
|
114
|
+
"ticker": rec["ticker"],
|
|
115
|
+
"expectedShort": float(p["expectedShort"]),
|
|
116
|
+
"expectedMid": float(p["expectedMid"]),
|
|
117
|
+
"expectedLong": float(p["expectedLong"]),
|
|
118
|
+
"sentimentScore": float(rec.get("sentimentScore", "nan")),
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
df = pd.DataFrame(rows).set_index("ticker")
|
|
122
|
+
return df
|
|
123
|
+
|
|
124
|
+
return data
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------- #
|
|
128
|
+
def _validate(value: str) -> None:
|
|
129
|
+
if value not in _ALLOWED:
|
|
130
|
+
raise ValueError("prediction_type must be 'daily' or 'monthly'")
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
from typing import TYPE_CHECKING, Dict, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING: # imported only by static type-checkers
|
|
8
|
+
from ..client import FinBrainClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SentimentsAPI:
|
|
12
|
+
"""
|
|
13
|
+
Wrapper for **/sentiments/<MARKET>/<TICKER>** endpoints.
|
|
14
|
+
|
|
15
|
+
Example
|
|
16
|
+
-------
|
|
17
|
+
>>> fb.sentiments.ticker(
|
|
18
|
+
... market="S&P 500",
|
|
19
|
+
... symbol="AMZN",
|
|
20
|
+
... date_from="2024-01-01",
|
|
21
|
+
... date_to="2024-02-02",
|
|
22
|
+
... )
|
|
23
|
+
{
|
|
24
|
+
"ticker": "AMZN",
|
|
25
|
+
"name": "Amazon.com Inc.",
|
|
26
|
+
"sentimentAnalysis": {
|
|
27
|
+
"2024-01-15": "0.123",
|
|
28
|
+
...
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
# --------------------------------------------------------------------- #
|
|
34
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
35
|
+
self._c = client
|
|
36
|
+
|
|
37
|
+
# --------------------------------------------------------------------- #
|
|
38
|
+
def ticker(
|
|
39
|
+
self,
|
|
40
|
+
market: str,
|
|
41
|
+
symbol: str,
|
|
42
|
+
*,
|
|
43
|
+
date_from: _dt.date | str | None = None,
|
|
44
|
+
date_to: _dt.date | str | None = None,
|
|
45
|
+
days: int | None = None,
|
|
46
|
+
as_dataframe: bool = False,
|
|
47
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
48
|
+
"""
|
|
49
|
+
Retrieve sentiment scores for a *single* ticker.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
market :
|
|
54
|
+
Market name **exactly as FinBrain lists it**
|
|
55
|
+
(e.g. ``"S&P 500"``, ``"Germany DAX"``, ``"HK Hang Seng"``).
|
|
56
|
+
Spaces and special characters are accepted; they are URL-encoded
|
|
57
|
+
automatically.
|
|
58
|
+
symbol :
|
|
59
|
+
Stock/crypto symbol (``AAPL``, ``AMZN`` …) *uppercase recommended*.
|
|
60
|
+
date_from, date_to :
|
|
61
|
+
Optional start / end dates (``YYYY-MM-DD``). If omitted, FinBrain
|
|
62
|
+
defaults to its internal window or to ``days``.
|
|
63
|
+
days :
|
|
64
|
+
Alternative to explicit dates - integer 1…120 for "past *n* days".
|
|
65
|
+
Ignored if either ``date_from`` or ``date_to`` is supplied.
|
|
66
|
+
as_dataframe :
|
|
67
|
+
If *True*, return a **DataFrame** with a ``date`` index and a single
|
|
68
|
+
``sentiment`` column.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
dict | pandas.DataFrame
|
|
73
|
+
"""
|
|
74
|
+
# Build query parameters
|
|
75
|
+
params: Dict[str, str] = {}
|
|
76
|
+
|
|
77
|
+
if date_from:
|
|
78
|
+
params["dateFrom"] = _to_datestr(date_from)
|
|
79
|
+
if date_to:
|
|
80
|
+
params["dateTo"] = _to_datestr(date_to)
|
|
81
|
+
if days is not None and "dateFrom" not in params and "dateTo" not in params:
|
|
82
|
+
params["days"] = str(days)
|
|
83
|
+
|
|
84
|
+
market_slug = quote(market, safe="")
|
|
85
|
+
path = f"sentiments/{market_slug}/{symbol.upper()}"
|
|
86
|
+
|
|
87
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
88
|
+
|
|
89
|
+
if as_dataframe:
|
|
90
|
+
sa: Dict[str, str] = data.get("sentimentAnalysis", {})
|
|
91
|
+
df = (
|
|
92
|
+
pd.Series(sa, name="sentiment")
|
|
93
|
+
.astype(float)
|
|
94
|
+
.rename_axis("date")
|
|
95
|
+
.to_frame()
|
|
96
|
+
)
|
|
97
|
+
df.index = pd.to_datetime(df.index)
|
|
98
|
+
return df
|
|
99
|
+
|
|
100
|
+
return data
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# ------------------------------------------------------------------------- #
|
|
104
|
+
# Helpers #
|
|
105
|
+
# ------------------------------------------------------------------------- #
|
|
106
|
+
def _to_datestr(value: _dt.date | str) -> str:
|
|
107
|
+
"""Convert ``datetime.date`` → 'YYYY-MM-DD' but pass strings through."""
|
|
108
|
+
return value.isoformat() if isinstance(value, _dt.date) else value
|
finbrain/exceptions.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""
|
|
2
|
+
finbrain.exceptions
|
|
3
|
+
~~~~~~~~~~~~~~~~~~~
|
|
4
|
+
|
|
5
|
+
Canonical exception hierarchy for the FinBrain Python SDK.
|
|
6
|
+
Every public error subclasses :class:`FinBrainError`.
|
|
7
|
+
|
|
8
|
+
Docs-based mapping
|
|
9
|
+
------------------
|
|
10
|
+
400 Bad Request → BadRequest
|
|
11
|
+
401 Unauthorized → AuthenticationError
|
|
12
|
+
403 Forbidden → PermissionDenied
|
|
13
|
+
404 Not Found → NotFound
|
|
14
|
+
405 Method Not Allowed → MethodNotAllowed
|
|
15
|
+
500 Internal Server Error → ServerError
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from typing import Any, Dict, Union
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"FinBrainError",
|
|
24
|
+
"BadRequest",
|
|
25
|
+
"AuthenticationError",
|
|
26
|
+
"PermissionDenied",
|
|
27
|
+
"NotFound",
|
|
28
|
+
"MethodNotAllowed",
|
|
29
|
+
"ServerError",
|
|
30
|
+
#
|
|
31
|
+
"InvalidResponse",
|
|
32
|
+
"http_error_to_exception",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
# ─────────────────────────────────────────────────────────────
|
|
36
|
+
# Base class
|
|
37
|
+
# ─────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FinBrainError(Exception):
|
|
41
|
+
"""Root of the SDK's exception tree."""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
message: str,
|
|
46
|
+
*,
|
|
47
|
+
status_code: int | None = None,
|
|
48
|
+
payload: Any | None = None,
|
|
49
|
+
):
|
|
50
|
+
super().__init__(message)
|
|
51
|
+
self.status_code: int | None = status_code
|
|
52
|
+
self.payload: Any | None = payload # raw JSON/text for debugging
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# ─────────────────────────────────────────────────────────────
|
|
56
|
+
# 4xx family
|
|
57
|
+
# ─────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BadRequest(FinBrainError):
|
|
61
|
+
"""400 - The request is malformed or contains invalid parameters."""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class AuthenticationError(FinBrainError):
|
|
65
|
+
"""401 - API key missing or invalid."""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class PermissionDenied(FinBrainError):
|
|
69
|
+
"""403 - Authenticated, but not authorised to perform this action."""
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class NotFound(FinBrainError):
|
|
73
|
+
"""404 - Requested data or endpoint not found."""
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class MethodNotAllowed(FinBrainError):
|
|
77
|
+
"""405 - Endpoint exists, but the HTTP method is not supported."""
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# ─────────────────────────────────────────────────────────────
|
|
81
|
+
# 5xx family
|
|
82
|
+
# ─────────────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class ServerError(FinBrainError):
|
|
86
|
+
"""500 - Internal error on FinBrain's side. Retrying later may help."""
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ─────────────────────────────────────────────────────────────
|
|
90
|
+
# Transport / decoding guard
|
|
91
|
+
# ─────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class InvalidResponse(FinBrainError):
|
|
95
|
+
"""Response couldn't be parsed as JSON or is missing required fields."""
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# ─────────────────────────────────────────────────────────────
|
|
99
|
+
# Helper: map HTTP response ➜ exception
|
|
100
|
+
# ─────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _extract_message(payload: Any, default: str) -> str:
|
|
104
|
+
if isinstance(payload, dict):
|
|
105
|
+
# FinBrain usually returns {"message": "..."}
|
|
106
|
+
return payload.get("message", default)
|
|
107
|
+
return default
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def http_error_to_exception(resp) -> FinBrainError: # expects requests.Response
|
|
111
|
+
"""
|
|
112
|
+
Convert a non-2xx ``requests.Response`` into a typed FinBrainError.
|
|
113
|
+
|
|
114
|
+
Usage
|
|
115
|
+
-----
|
|
116
|
+
>>> raise http_error_to_exception(resp)
|
|
117
|
+
"""
|
|
118
|
+
status = resp.status_code
|
|
119
|
+
try:
|
|
120
|
+
payload: Union[Dict[str, Any], str] = resp.json()
|
|
121
|
+
except ValueError:
|
|
122
|
+
payload = resp.text
|
|
123
|
+
|
|
124
|
+
message = _extract_message(payload, f"{status} {resp.reason}")
|
|
125
|
+
|
|
126
|
+
if status == 400:
|
|
127
|
+
return BadRequest(message, status_code=status, payload=payload)
|
|
128
|
+
if status == 401:
|
|
129
|
+
return AuthenticationError(message, status_code=status, payload=payload)
|
|
130
|
+
if status == 403:
|
|
131
|
+
return PermissionDenied(message, status_code=status, payload=payload)
|
|
132
|
+
if status == 404:
|
|
133
|
+
return NotFound(message, status_code=status, payload=payload)
|
|
134
|
+
if status == 405:
|
|
135
|
+
return MethodNotAllowed(message, status_code=status, payload=payload)
|
|
136
|
+
if status == 500:
|
|
137
|
+
return ServerError(message, status_code=status, payload=payload)
|
|
138
|
+
|
|
139
|
+
# Fallback for undocumented codes (future-proofing)
|
|
140
|
+
return FinBrainError(message, status_code=status, payload=payload)
|