finbrain-python 0.2.4__py3-none-any.whl → 0.2.6__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/aio/client.py +5 -3
- finbrain/aio/endpoints/_utils.py +4 -29
- finbrain/aio/endpoints/house_trades.py +13 -1
- finbrain/aio/endpoints/patent_filings.py +48 -0
- finbrain/aio/endpoints/screener.py +26 -2
- finbrain/aio/endpoints/senate_trades.py +13 -1
- finbrain/client.py +18 -3
- finbrain/endpoints/_utils.py +7 -1
- finbrain/endpoints/house_trades.py +24 -0
- finbrain/endpoints/patent_filings.py +74 -0
- finbrain/endpoints/screener.py +26 -2
- finbrain/endpoints/senate_trades.py +24 -0
- finbrain/exceptions.py +24 -0
- finbrain/plotting.py +381 -164
- {finbrain_python-0.2.4.dist-info → finbrain_python-0.2.6.dist-info}/METADATA +62 -1
- {finbrain_python-0.2.4.dist-info → finbrain_python-0.2.6.dist-info}/RECORD +19 -17
- {finbrain_python-0.2.4.dist-info → finbrain_python-0.2.6.dist-info}/WHEEL +1 -1
- {finbrain_python-0.2.4.dist-info → finbrain_python-0.2.6.dist-info}/licenses/LICENSE +0 -0
- {finbrain_python-0.2.4.dist-info → finbrain_python-0.2.6.dist-info}/top_level.txt +0 -0
finbrain/aio/client.py
CHANGED
|
@@ -25,10 +25,11 @@ from .endpoints.recent import AsyncRecentAPI
|
|
|
25
25
|
from .endpoints.corporate_lobbying import AsyncCorporateLobbyingAPI
|
|
26
26
|
from .endpoints.reddit_mentions import AsyncRedditMentionsAPI
|
|
27
27
|
from .endpoints.government_contracts import AsyncGovernmentContractsAPI
|
|
28
|
+
from .endpoints.patent_filings import AsyncPatentFilingsAPI
|
|
28
29
|
|
|
29
30
|
|
|
30
|
-
# Which status codes merit a retry
|
|
31
|
-
_RETRYABLE_STATUS = {500}
|
|
31
|
+
# Which status codes merit a retry (transient server / gateway errors)
|
|
32
|
+
_RETRYABLE_STATUS = {500, 502, 503, 504}
|
|
32
33
|
# How long to wait between retries (2, 4, 8 … seconds)
|
|
33
34
|
_BACKOFF_BASE = 2
|
|
34
35
|
|
|
@@ -86,6 +87,7 @@ class AsyncFinBrainClient:
|
|
|
86
87
|
self.corporate_lobbying = AsyncCorporateLobbyingAPI(self)
|
|
87
88
|
self.reddit_mentions = AsyncRedditMentionsAPI(self)
|
|
88
89
|
self.government_contracts = AsyncGovernmentContractsAPI(self)
|
|
90
|
+
self.patent_filings = AsyncPatentFilingsAPI(self)
|
|
89
91
|
|
|
90
92
|
async def __aenter__(self) -> "AsyncFinBrainClient":
|
|
91
93
|
"""Context manager entry."""
|
|
@@ -164,7 +166,7 @@ class AsyncFinBrainClient:
|
|
|
164
166
|
|
|
165
167
|
# ── Error path ───────────────────────────────────
|
|
166
168
|
if resp.status_code in _RETRYABLE_STATUS and attempt < self.retries:
|
|
167
|
-
#
|
|
169
|
+
# Transient server/gateway error – exponential back-off then retry
|
|
168
170
|
await asyncio.sleep(_BACKOFF_BASE**attempt)
|
|
169
171
|
continue
|
|
170
172
|
|
finbrain/aio/endpoints/_utils.py
CHANGED
|
@@ -1,37 +1,12 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Shared utility functions for async endpoint modules.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
These helpers are identical to the synchronous ones, so they are re-exported
|
|
5
|
+
from :mod:`finbrain.endpoints._utils` to keep a single source of truth.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from __future__ import annotations
|
|
9
|
-
import datetime as _dt
|
|
10
9
|
|
|
10
|
+
from ...endpoints._utils import to_datestr
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
Convert datetime.date to ISO format string (YYYY-MM-DD).
|
|
15
|
-
|
|
16
|
-
If the input is already a string, it is returned unchanged.
|
|
17
|
-
This allows flexible date parameter handling in API calls.
|
|
18
|
-
|
|
19
|
-
Parameters
|
|
20
|
-
----------
|
|
21
|
-
value : datetime.date or str
|
|
22
|
-
Date value to convert.
|
|
23
|
-
|
|
24
|
-
Returns
|
|
25
|
-
-------
|
|
26
|
-
str
|
|
27
|
-
ISO format date string (YYYY-MM-DD).
|
|
28
|
-
|
|
29
|
-
Examples
|
|
30
|
-
--------
|
|
31
|
-
>>> from datetime import date
|
|
32
|
-
>>> to_datestr(date(2025, 1, 15))
|
|
33
|
-
'2025-01-15'
|
|
34
|
-
>>> to_datestr("2025-01-15")
|
|
35
|
-
'2025-01-15'
|
|
36
|
-
"""
|
|
37
|
-
return value.isoformat() if isinstance(value, _dt.date) else value
|
|
12
|
+
__all__ = ["to_datestr"]
|
|
@@ -24,7 +24,19 @@ class AsyncHouseTradesAPI:
|
|
|
24
24
|
limit: int | None = None,
|
|
25
25
|
as_dataframe: bool = False,
|
|
26
26
|
) -> Dict[str, Any] | pd.DataFrame:
|
|
27
|
-
"""
|
|
27
|
+
"""
|
|
28
|
+
Fetch House-member trades for a symbol (async).
|
|
29
|
+
|
|
30
|
+
Each row in ``trades`` carries ``date`` (the transaction date),
|
|
31
|
+
``politician``, ``transactionType``, ``amount`` and
|
|
32
|
+
``disclosureDate`` — the date the trade was publicly disclosed in the
|
|
33
|
+
member's periodic transaction report. ``disclosureDate`` is ``None``
|
|
34
|
+
for historical rows collected before the field was captured (``NaN``
|
|
35
|
+
in the DataFrame branch on newer pandas — use ``pandas.isna``).
|
|
36
|
+
|
|
37
|
+
``date_from``/``date_to`` bound the transaction date, not the
|
|
38
|
+
disclosure date.
|
|
39
|
+
"""
|
|
28
40
|
params: Dict[str, str] = {}
|
|
29
41
|
if date_from:
|
|
30
42
|
params["startDate"] = to_datestr(date_from)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
5
|
+
|
|
6
|
+
from ._utils import to_datestr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..client import AsyncFinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AsyncPatentFilingsAPI:
|
|
13
|
+
"""Async wrapper for /patent-filings and /screener/patent-filings endpoints."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, client: "AsyncFinBrainClient") -> None:
|
|
16
|
+
self._c = client
|
|
17
|
+
|
|
18
|
+
async def ticker(
|
|
19
|
+
self,
|
|
20
|
+
symbol: str,
|
|
21
|
+
*,
|
|
22
|
+
date_from: _dt.date | str | None = None,
|
|
23
|
+
date_to: _dt.date | str | None = None,
|
|
24
|
+
limit: int | None = None,
|
|
25
|
+
as_dataframe: bool = False,
|
|
26
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
27
|
+
"""Fetch USPTO granted patents for a symbol (async)."""
|
|
28
|
+
params: Dict[str, str] = {}
|
|
29
|
+
if date_from:
|
|
30
|
+
params["startDate"] = to_datestr(date_from)
|
|
31
|
+
if date_to:
|
|
32
|
+
params["endDate"] = to_datestr(date_to)
|
|
33
|
+
if limit is not None:
|
|
34
|
+
params["limit"] = str(limit)
|
|
35
|
+
|
|
36
|
+
path = f"patent-filings/{symbol.upper()}"
|
|
37
|
+
|
|
38
|
+
data: Dict[str, Any] = await self._c._request("GET", path, params=params)
|
|
39
|
+
|
|
40
|
+
if as_dataframe:
|
|
41
|
+
rows: List[Dict[str, Any]] = data.get("patents", [])
|
|
42
|
+
df = pd.DataFrame(rows)
|
|
43
|
+
if not df.empty and "patentDate" in df.columns:
|
|
44
|
+
df["patentDate"] = pd.to_datetime(df["patentDate"])
|
|
45
|
+
df.set_index("patentDate", inplace=True)
|
|
46
|
+
return df
|
|
47
|
+
|
|
48
|
+
return data
|
|
@@ -107,7 +107,13 @@ class AsyncScreenerAPI:
|
|
|
107
107
|
limit: int | None = None,
|
|
108
108
|
as_dataframe: bool = False,
|
|
109
109
|
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
110
|
-
"""
|
|
110
|
+
"""
|
|
111
|
+
Screen House trades across all tickers.
|
|
112
|
+
|
|
113
|
+
Rows carry ``symbol``, ``name``, ``date``, ``politician``,
|
|
114
|
+
``transactionType``, ``amount`` and ``disclosureDate`` (the public
|
|
115
|
+
disclosure date, ``None`` on historical rows).
|
|
116
|
+
"""
|
|
111
117
|
params = self._build_params(limit=limit)
|
|
112
118
|
return await self._get("screener/congress/house", params, as_dataframe)
|
|
113
119
|
|
|
@@ -119,7 +125,13 @@ class AsyncScreenerAPI:
|
|
|
119
125
|
limit: int | None = None,
|
|
120
126
|
as_dataframe: bool = False,
|
|
121
127
|
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
122
|
-
"""
|
|
128
|
+
"""
|
|
129
|
+
Screen Senate trades across all tickers.
|
|
130
|
+
|
|
131
|
+
Rows carry ``symbol``, ``name``, ``date``, ``politician``,
|
|
132
|
+
``transactionType``, ``amount`` and ``disclosureDate`` (the public
|
|
133
|
+
disclosure date, ``None`` on historical rows).
|
|
134
|
+
"""
|
|
123
135
|
params = self._build_params(limit=limit)
|
|
124
136
|
return await self._get("screener/congress/senate", params, as_dataframe)
|
|
125
137
|
|
|
@@ -234,3 +246,15 @@ class AsyncScreenerAPI:
|
|
|
234
246
|
"""Screen government contracts across all tickers (async)."""
|
|
235
247
|
params = self._build_params(limit=limit)
|
|
236
248
|
return await self._get("screener/government-contracts", params, as_dataframe)
|
|
249
|
+
|
|
250
|
+
# ── patent filings ────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
async def patent_filings(
|
|
253
|
+
self,
|
|
254
|
+
*,
|
|
255
|
+
limit: int | None = None,
|
|
256
|
+
as_dataframe: bool = False,
|
|
257
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
258
|
+
"""Screen USPTO patent filings across all tickers (async)."""
|
|
259
|
+
params = self._build_params(limit=limit)
|
|
260
|
+
return await self._get("screener/patent-filings", params, as_dataframe)
|
|
@@ -24,7 +24,19 @@ class AsyncSenateTradesAPI:
|
|
|
24
24
|
limit: int | None = None,
|
|
25
25
|
as_dataframe: bool = False,
|
|
26
26
|
) -> Dict[str, Any] | pd.DataFrame:
|
|
27
|
-
"""
|
|
27
|
+
"""
|
|
28
|
+
Fetch Senate-member trades for a symbol (async).
|
|
29
|
+
|
|
30
|
+
Each row in ``trades`` carries ``date`` (the transaction date),
|
|
31
|
+
``politician``, ``transactionType``, ``amount`` and
|
|
32
|
+
``disclosureDate`` — the date the trade was publicly disclosed in the
|
|
33
|
+
member's periodic transaction report. ``disclosureDate`` is ``None``
|
|
34
|
+
for historical rows collected before the field was captured (``NaN``
|
|
35
|
+
in the DataFrame branch on newer pandas — use ``pandas.isna``).
|
|
36
|
+
|
|
37
|
+
``date_from``/``date_to`` bound the transaction date, not the
|
|
38
|
+
disclosure date.
|
|
39
|
+
"""
|
|
28
40
|
params: Dict[str, str] = {}
|
|
29
41
|
if date_from:
|
|
30
42
|
params["startDate"] = to_datestr(date_from)
|
finbrain/client.py
CHANGED
|
@@ -26,10 +26,11 @@ from .endpoints.recent import RecentAPI
|
|
|
26
26
|
from .endpoints.corporate_lobbying import CorporateLobbyingAPI
|
|
27
27
|
from .endpoints.reddit_mentions import RedditMentionsAPI
|
|
28
28
|
from .endpoints.government_contracts import GovernmentContractsAPI
|
|
29
|
+
from .endpoints.patent_filings import PatentFilingsAPI
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
# Which status codes merit a retry
|
|
32
|
-
_RETRYABLE_STATUS = {500}
|
|
32
|
+
# Which status codes merit a retry (transient server / gateway errors)
|
|
33
|
+
_RETRYABLE_STATUS = {500, 502, 503, 504}
|
|
33
34
|
# How long to wait between retries (2, 4, 8 … seconds)
|
|
34
35
|
_BACKOFF_BASE = 2
|
|
35
36
|
|
|
@@ -81,6 +82,20 @@ class FinBrainClient:
|
|
|
81
82
|
self.corporate_lobbying = CorporateLobbyingAPI(self)
|
|
82
83
|
self.reddit_mentions = RedditMentionsAPI(self)
|
|
83
84
|
self.government_contracts = GovernmentContractsAPI(self)
|
|
85
|
+
self.patent_filings = PatentFilingsAPI(self)
|
|
86
|
+
|
|
87
|
+
# ---------- lifecycle ----------
|
|
88
|
+
def __enter__(self) -> "FinBrainClient":
|
|
89
|
+
"""Context manager entry."""
|
|
90
|
+
return self
|
|
91
|
+
|
|
92
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
|
93
|
+
"""Context manager exit — close the underlying HTTP session."""
|
|
94
|
+
self.close()
|
|
95
|
+
|
|
96
|
+
def close(self) -> None:
|
|
97
|
+
"""Close the underlying ``requests.Session``."""
|
|
98
|
+
self.session.close()
|
|
84
99
|
|
|
85
100
|
# ---------- private helpers ----------
|
|
86
101
|
def _request(
|
|
@@ -131,7 +146,7 @@ class FinBrainClient:
|
|
|
131
146
|
|
|
132
147
|
# ── Error path ───────────────────────────────────
|
|
133
148
|
if resp.status_code in _RETRYABLE_STATUS and attempt < self.retries:
|
|
134
|
-
#
|
|
149
|
+
# Transient server/gateway error – exponential back-off then retry
|
|
135
150
|
time.sleep(_BACKOFF_BASE**attempt)
|
|
136
151
|
continue
|
|
137
152
|
|
finbrain/endpoints/_utils.py
CHANGED
|
@@ -28,10 +28,16 @@ def to_datestr(value: _dt.date | str) -> str:
|
|
|
28
28
|
|
|
29
29
|
Examples
|
|
30
30
|
--------
|
|
31
|
-
>>> from datetime import date
|
|
31
|
+
>>> from datetime import date, datetime
|
|
32
32
|
>>> to_datestr(date(2025, 1, 15))
|
|
33
33
|
'2025-01-15'
|
|
34
|
+
>>> to_datestr(datetime(2025, 1, 15, 9, 30))
|
|
35
|
+
'2025-01-15'
|
|
34
36
|
>>> to_datestr("2025-01-15")
|
|
35
37
|
'2025-01-15'
|
|
36
38
|
"""
|
|
39
|
+
# ``datetime`` subclasses ``date``; take the date part so the time
|
|
40
|
+
# component never leaks into the ``YYYY-MM-DD`` API parameter.
|
|
41
|
+
if isinstance(value, _dt.datetime):
|
|
42
|
+
return value.date().isoformat()
|
|
37
43
|
return value.isoformat() if isinstance(value, _dt.date) else value
|
|
@@ -49,6 +49,30 @@ class HouseTradesAPI:
|
|
|
49
49
|
Returns
|
|
50
50
|
-------
|
|
51
51
|
dict | pandas.DataFrame
|
|
52
|
+
The raw dict has a ``trades`` list whose rows carry ``date``
|
|
53
|
+
(the transaction date), ``politician``, ``transactionType``,
|
|
54
|
+
``amount`` and ``disclosureDate`` — the date the trade was
|
|
55
|
+
publicly disclosed in the member's periodic transaction report.
|
|
56
|
+
The gap between the two dates is the reporting lag.
|
|
57
|
+
|
|
58
|
+
``disclosureDate`` is ``None`` for historical rows collected
|
|
59
|
+
before the field was captured upstream. In the DataFrame branch
|
|
60
|
+
``date`` becomes the index and ``disclosureDate`` is a column
|
|
61
|
+
whose missing values read as ``None`` or ``NaN`` depending on the
|
|
62
|
+
pandas version — test them with :func:`pandas.isna`.
|
|
63
|
+
|
|
64
|
+
Notes
|
|
65
|
+
-----
|
|
66
|
+
``date_from`` and ``date_to`` bound the **transaction** date, not the
|
|
67
|
+
disclosure date. A trade executed inside the window is returned even
|
|
68
|
+
if it was disclosed after ``date_to``.
|
|
69
|
+
|
|
70
|
+
Example row::
|
|
71
|
+
|
|
72
|
+
{"date": "2026-06-15", "politician": "Jane Doe",
|
|
73
|
+
"transactionType": "Purchase", "amount": "$1,001 - $15,000",
|
|
74
|
+
"disclosureDate": "2026-07-01"}
|
|
75
|
+
|
|
52
76
|
"""
|
|
53
77
|
params: Dict[str, str] = {}
|
|
54
78
|
if date_from:
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import datetime as _dt
|
|
4
|
+
from typing import TYPE_CHECKING, Dict, Any, List
|
|
5
|
+
|
|
6
|
+
from ._utils import to_datestr
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING: # imported only by type-checkers
|
|
9
|
+
from ..client import FinBrainClient
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PatentFilingsAPI:
|
|
13
|
+
"""
|
|
14
|
+
Endpoints
|
|
15
|
+
---------
|
|
16
|
+
``/patent-filings/<TICKER>`` - USPTO granted patents mapped to a ticker.
|
|
17
|
+
``/screener/patent-filings`` - cross-ticker patent filings screener.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# ------------------------------------------------------------------ #
|
|
21
|
+
def __init__(self, client: "FinBrainClient") -> None:
|
|
22
|
+
self._c = client # reference to the parent client
|
|
23
|
+
|
|
24
|
+
# ------------------------------------------------------------------ #
|
|
25
|
+
def ticker(
|
|
26
|
+
self,
|
|
27
|
+
symbol: str,
|
|
28
|
+
*,
|
|
29
|
+
date_from: _dt.date | str | None = None,
|
|
30
|
+
date_to: _dt.date | str | None = None,
|
|
31
|
+
limit: int | None = None,
|
|
32
|
+
as_dataframe: bool = False,
|
|
33
|
+
) -> Dict[str, Any] | pd.DataFrame:
|
|
34
|
+
"""
|
|
35
|
+
Fetch USPTO granted patents for *symbol*.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
symbol :
|
|
40
|
+
Ticker symbol; auto-upper-cased.
|
|
41
|
+
date_from, date_to :
|
|
42
|
+
Optional ISO dates (``YYYY-MM-DD``) bounding the returned rows by
|
|
43
|
+
grant date (``patentDate``).
|
|
44
|
+
limit :
|
|
45
|
+
Maximum number of records to return (1-500).
|
|
46
|
+
as_dataframe :
|
|
47
|
+
If *True*, return a **pandas.DataFrame** indexed by ``patentDate``;
|
|
48
|
+
otherwise return the raw JSON dict.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
dict | pandas.DataFrame
|
|
53
|
+
"""
|
|
54
|
+
params: Dict[str, str] = {}
|
|
55
|
+
if date_from:
|
|
56
|
+
params["startDate"] = to_datestr(date_from)
|
|
57
|
+
if date_to:
|
|
58
|
+
params["endDate"] = to_datestr(date_to)
|
|
59
|
+
if limit is not None:
|
|
60
|
+
params["limit"] = str(limit)
|
|
61
|
+
|
|
62
|
+
path = f"patent-filings/{symbol.upper()}"
|
|
63
|
+
|
|
64
|
+
data: Dict[str, Any] = self._c._request("GET", path, params=params)
|
|
65
|
+
|
|
66
|
+
if as_dataframe:
|
|
67
|
+
rows: List[Dict[str, Any]] = data.get("patents", [])
|
|
68
|
+
df = pd.DataFrame(rows)
|
|
69
|
+
if not df.empty and "patentDate" in df.columns:
|
|
70
|
+
df["patentDate"] = pd.to_datetime(df["patentDate"])
|
|
71
|
+
df.set_index("patentDate", inplace=True)
|
|
72
|
+
return df
|
|
73
|
+
|
|
74
|
+
return data
|
finbrain/endpoints/screener.py
CHANGED
|
@@ -112,7 +112,13 @@ class ScreenerAPI:
|
|
|
112
112
|
limit: int | None = None,
|
|
113
113
|
as_dataframe: bool = False,
|
|
114
114
|
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
115
|
-
"""
|
|
115
|
+
"""
|
|
116
|
+
Screen House trades across all tickers.
|
|
117
|
+
|
|
118
|
+
Rows carry ``symbol``, ``name``, ``date``, ``politician``,
|
|
119
|
+
``transactionType``, ``amount`` and ``disclosureDate`` (the public
|
|
120
|
+
disclosure date, ``None`` on historical rows).
|
|
121
|
+
"""
|
|
116
122
|
params = self._build_params(limit=limit)
|
|
117
123
|
return self._get("screener/congress/house", params, as_dataframe)
|
|
118
124
|
|
|
@@ -124,7 +130,13 @@ class ScreenerAPI:
|
|
|
124
130
|
limit: int | None = None,
|
|
125
131
|
as_dataframe: bool = False,
|
|
126
132
|
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
127
|
-
"""
|
|
133
|
+
"""
|
|
134
|
+
Screen Senate trades across all tickers.
|
|
135
|
+
|
|
136
|
+
Rows carry ``symbol``, ``name``, ``date``, ``politician``,
|
|
137
|
+
``transactionType``, ``amount`` and ``disclosureDate`` (the public
|
|
138
|
+
disclosure date, ``None`` on historical rows).
|
|
139
|
+
"""
|
|
128
140
|
params = self._build_params(limit=limit)
|
|
129
141
|
return self._get("screener/congress/senate", params, as_dataframe)
|
|
130
142
|
|
|
@@ -239,3 +251,15 @@ class ScreenerAPI:
|
|
|
239
251
|
"""Screen government contracts across all tickers."""
|
|
240
252
|
params = self._build_params(limit=limit)
|
|
241
253
|
return self._get("screener/government-contracts", params, as_dataframe)
|
|
254
|
+
|
|
255
|
+
# ── patent filings ────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
def patent_filings(
|
|
258
|
+
self,
|
|
259
|
+
*,
|
|
260
|
+
limit: int | None = None,
|
|
261
|
+
as_dataframe: bool = False,
|
|
262
|
+
) -> List[Dict[str, Any]] | pd.DataFrame:
|
|
263
|
+
"""Screen USPTO patent filings across all tickers."""
|
|
264
|
+
params = self._build_params(limit=limit)
|
|
265
|
+
return self._get("screener/patent-filings", params, as_dataframe)
|
|
@@ -49,6 +49,30 @@ class SenateTradesAPI:
|
|
|
49
49
|
Returns
|
|
50
50
|
-------
|
|
51
51
|
dict | pandas.DataFrame
|
|
52
|
+
The raw dict has a ``trades`` list whose rows carry ``date``
|
|
53
|
+
(the transaction date), ``politician``, ``transactionType``,
|
|
54
|
+
``amount`` and ``disclosureDate`` — the date the trade was
|
|
55
|
+
publicly disclosed in the member's periodic transaction report.
|
|
56
|
+
The gap between the two dates is the reporting lag.
|
|
57
|
+
|
|
58
|
+
``disclosureDate`` is ``None`` for historical rows collected
|
|
59
|
+
before the field was captured upstream. In the DataFrame branch
|
|
60
|
+
``date`` becomes the index and ``disclosureDate`` is a column
|
|
61
|
+
whose missing values read as ``None`` or ``NaN`` depending on the
|
|
62
|
+
pandas version — test them with :func:`pandas.isna`.
|
|
63
|
+
|
|
64
|
+
Notes
|
|
65
|
+
-----
|
|
66
|
+
``date_from`` and ``date_to`` bound the **transaction** date, not the
|
|
67
|
+
disclosure date. A trade executed inside the window is returned even
|
|
68
|
+
if it was disclosed after ``date_to``.
|
|
69
|
+
|
|
70
|
+
Example row::
|
|
71
|
+
|
|
72
|
+
{"date": "2026-06-10", "politician": "Jane Doe",
|
|
73
|
+
"transactionType": "Purchase", "amount": "$1,001 - $15,000",
|
|
74
|
+
"disclosureDate": "2026-06-25"}
|
|
75
|
+
|
|
52
76
|
"""
|
|
53
77
|
params: Dict[str, str] = {}
|
|
54
78
|
if date_from:
|
finbrain/exceptions.py
CHANGED
|
@@ -14,6 +14,9 @@ Docs-based mapping
|
|
|
14
14
|
405 Method Not Allowed → MethodNotAllowed
|
|
15
15
|
429 Rate Limit Exceeded → RateLimitError
|
|
16
16
|
500 Internal Server Error → ServerError
|
|
17
|
+
502 Bad Gateway → BadGateway
|
|
18
|
+
503 Service Unavailable → ServiceUnavailable
|
|
19
|
+
504 Gateway Timeout → GatewayTimeout
|
|
17
20
|
"""
|
|
18
21
|
|
|
19
22
|
from __future__ import annotations
|
|
@@ -29,6 +32,9 @@ __all__ = [
|
|
|
29
32
|
"MethodNotAllowed",
|
|
30
33
|
"RateLimitError",
|
|
31
34
|
"ServerError",
|
|
35
|
+
"BadGateway",
|
|
36
|
+
"ServiceUnavailable",
|
|
37
|
+
"GatewayTimeout",
|
|
32
38
|
#
|
|
33
39
|
"InvalidResponse",
|
|
34
40
|
"http_error_to_exception",
|
|
@@ -96,6 +102,18 @@ class ServerError(FinBrainError):
|
|
|
96
102
|
"""500 - Internal error on FinBrain's side. Retrying later may help."""
|
|
97
103
|
|
|
98
104
|
|
|
105
|
+
class BadGateway(FinBrainError):
|
|
106
|
+
"""502 - Invalid response from an upstream server. Usually transient."""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ServiceUnavailable(FinBrainError):
|
|
110
|
+
"""503 - Service temporarily unavailable (overloaded or in maintenance)."""
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class GatewayTimeout(FinBrainError):
|
|
114
|
+
"""504 - Upstream server did not respond in time. Usually transient."""
|
|
115
|
+
|
|
116
|
+
|
|
99
117
|
# ─────────────────────────────────────────────────────────────
|
|
100
118
|
# Transport / decoding guard
|
|
101
119
|
# ─────────────────────────────────────────────────────────────
|
|
@@ -167,6 +185,12 @@ def http_error_to_exception(resp) -> FinBrainError: # expects requests.Response
|
|
|
167
185
|
return RateLimitError(message, **kwargs)
|
|
168
186
|
if status == 500:
|
|
169
187
|
return ServerError(message, **kwargs)
|
|
188
|
+
if status == 502:
|
|
189
|
+
return BadGateway(message, **kwargs)
|
|
190
|
+
if status == 503:
|
|
191
|
+
return ServiceUnavailable(message, **kwargs)
|
|
192
|
+
if status == 504:
|
|
193
|
+
return GatewayTimeout(message, **kwargs)
|
|
170
194
|
|
|
171
195
|
# Fallback for undocumented codes (future-proofing)
|
|
172
196
|
return FinBrainError(message, **kwargs)
|