kensho-kfinance 2.0.1__py3-none-any.whl → 2.2.2__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.
Potentially problematic release.
This version of kensho-kfinance might be problematic. Click here for more details.
- {kensho_kfinance-2.0.1.dist-info → kensho_kfinance-2.2.2.dist-info}/METADATA +9 -1
- kensho_kfinance-2.2.2.dist-info/RECORD +42 -0
- {kensho_kfinance-2.0.1.dist-info → kensho_kfinance-2.2.2.dist-info}/WHEEL +1 -1
- kfinance/CHANGELOG.md +21 -0
- kfinance/batch_request_handling.py +32 -27
- kfinance/constants.py +23 -7
- kfinance/fetch.py +106 -40
- kfinance/kfinance.py +164 -89
- kfinance/meta_classes.py +118 -9
- kfinance/tests/conftest.py +32 -0
- kfinance/tests/test_batch_requests.py +46 -8
- kfinance/tests/test_client.py +54 -0
- kfinance/tests/test_example_notebook.py +194 -0
- kfinance/tests/test_fetch.py +31 -2
- kfinance/tests/test_group_objects.py +32 -0
- kfinance/tests/test_objects.py +40 -0
- kfinance/tests/test_tools.py +13 -61
- kfinance/tool_calling/__init__.py +2 -6
- kfinance/tool_calling/get_business_relationship_from_identifier.py +2 -1
- kfinance/tool_calling/get_capitalization_from_identifier.py +2 -1
- kfinance/tool_calling/get_cusip_from_ticker.py +2 -0
- kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +2 -0
- kfinance/tool_calling/get_financial_line_item_from_identifier.py +2 -1
- kfinance/tool_calling/get_financial_statement_from_identifier.py +2 -1
- kfinance/tool_calling/get_history_metadata_from_identifier.py +2 -1
- kfinance/tool_calling/get_info_from_identifier.py +3 -1
- kfinance/tool_calling/get_isin_from_ticker.py +2 -0
- kfinance/tool_calling/get_latest.py +2 -1
- kfinance/tool_calling/get_n_quarters_ago.py +2 -1
- kfinance/tool_calling/get_prices_from_identifier.py +2 -1
- kfinance/tool_calling/resolve_identifier.py +18 -0
- kfinance/tool_calling/shared_models.py +2 -0
- kfinance/version.py +2 -2
- kensho_kfinance-2.0.1.dist-info/RECORD +0 -40
- kfinance/tool_calling/get_company_id_from_identifier.py +0 -14
- kfinance/tool_calling/get_security_id_from_identifier.py +0 -14
- kfinance/tool_calling/get_trading_item_id_from_identifier.py +0 -14
- {kensho_kfinance-2.0.1.dist-info → kensho_kfinance-2.2.2.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.0.1.dist-info → kensho_kfinance-2.2.2.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.0.1.dist-info → kensho_kfinance-2.2.2.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kensho-kfinance
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.2
|
|
4
4
|
Summary: Python CLI for kFinance
|
|
5
5
|
Author-email: Luke Brown <luke.brown@kensho.com>, Michelle Keoy <michelle.keoy@kensho.com>, Keith Page <keith.page@kensho.com>, Matthew Rosen <matthew.rosen@kensho.com>, Nick Roshdieh <nick.roshdieh@kensho.com>
|
|
6
6
|
Project-URL: source, https://github.com/kensho-technologies/kfinance
|
|
@@ -12,6 +12,7 @@ Requires-Python: >=3.10
|
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
License-File: AUTHORS.md
|
|
15
|
+
Requires-Dist: cachetools<6,>=5.5
|
|
15
16
|
Requires-Dist: langchain-core>=0.3.15
|
|
16
17
|
Requires-Dist: langchain-google-genai<3,>=2.1.0
|
|
17
18
|
Requires-Dist: numpy>=1.22.4
|
|
@@ -27,12 +28,17 @@ Requires-Dist: requests<3,>=2.22.0
|
|
|
27
28
|
Requires-Dist: urllib3>=1.21.1
|
|
28
29
|
Provides-Extra: dev
|
|
29
30
|
Requires-Dist: coverage<8,>=7.6.10; extra == "dev"
|
|
31
|
+
Requires-Dist: ipykernel<7,>=6.29; extra == "dev"
|
|
30
32
|
Requires-Dist: mypy<2,>=1.15.0; extra == "dev"
|
|
33
|
+
Requires-Dist: nbconvert<8,>=7.16; extra == "dev"
|
|
34
|
+
Requires-Dist: nbformat<6,>5.10; extra == "dev"
|
|
35
|
+
Requires-Dist: nbqa<2,>1.9; extra == "dev"
|
|
31
36
|
Requires-Dist: pytest<7,>=6.1.2; extra == "dev"
|
|
32
37
|
Requires-Dist: pytest-cov<7,>=6.0.0; extra == "dev"
|
|
33
38
|
Requires-Dist: requests_mock<2,>=1.12; extra == "dev"
|
|
34
39
|
Requires-Dist: ruff<1,>=0.9.4; extra == "dev"
|
|
35
40
|
Requires-Dist: time_machine<3,>=2.1; extra == "dev"
|
|
41
|
+
Requires-Dist: types-cachetools<6,>=5.5; extra == "dev"
|
|
36
42
|
Dynamic: license-file
|
|
37
43
|
|
|
38
44
|
# kFinance
|
|
@@ -55,6 +61,8 @@ To receive access, please email [S&P Global Market Intelligence](market.intellig
|
|
|
55
61
|
|
|
56
62
|
Once access is obtained, get started using the [Authentication Guide](https://docs.kensho.com/llmreadyapi/kf-authentication) and [Usage Guide](https://docs.kensho.com/llmreadyapi/usage).
|
|
57
63
|
|
|
64
|
+
We also provide an [interactive notebook](usage_examples.ipynb) that demonstrates some usage examples.
|
|
65
|
+
|
|
58
66
|
# Versioning
|
|
59
67
|
The kFinance uses semantic versioning (major, minor, patch).
|
|
60
68
|
To bump the version, add a new entry in [CHANGELOG.md](kfinance%2FCHANGELOG.md).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
kensho_kfinance-2.2.2.dist-info/licenses/AUTHORS.md,sha256=0h9ClbI0pu1oKj1M28ROUsaxrbZg-6ukQGl6X4y9noI,68
|
|
2
|
+
kensho_kfinance-2.2.2.dist-info/licenses/LICENSE,sha256=bsY4blvSgq6o0FMQ3RXa2NCgco--nHCCchLXzxr6kms,83
|
|
3
|
+
kfinance/CHANGELOG.md,sha256=eX36Pj6mDG2B6AtnHpP5FAJ14X-8XqF0fNRTYOGClXw,1140
|
|
4
|
+
kfinance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
kfinance/batch_request_handling.py,sha256=p6p_G4_BL06GgeKlh7P1k9CUqOMahWCLEw1NoBwbLvU,5698
|
|
6
|
+
kfinance/constants.py,sha256=4vj0WA0xupEJexvnI-ap6LFHXEf6dn8jQwJlxz8cGTs,48767
|
|
7
|
+
kfinance/fetch.py,sha256=fguxYy5rjLQKNkmP7gsvZUJtmt9uDNxg6LN9FRJTAVQ,22936
|
|
8
|
+
kfinance/kfinance.py,sha256=_U69k0Dcwkw1B_lzaUZy8N2-c-v93ZKoUAVVZb6wBUM,52758
|
|
9
|
+
kfinance/meta_classes.py,sha256=nwqHRi8IFa4WLFAZpX4a6htR47doK3_qVLnYBOylQV0,20482
|
|
10
|
+
kfinance/prompt.py,sha256=PtVB8c_FcSlVdyGgByAnIFGzuUuBaEjciCqnBJl1hSQ,25133
|
|
11
|
+
kfinance/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
kfinance/server_thread.py,sha256=jUnt1YGoYDkqqz1MbCwd44zJs1T_Z2BCgvj75bdtLgA,2574
|
|
13
|
+
kfinance/version.py,sha256=YDBduKhVnvbZfZTbXbsjQMbxTwgIIXIZ646Mj9YV074,511
|
|
14
|
+
kfinance/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
kfinance/tests/conftest.py,sha256=voB-w8P_6L3Nel3rdgylXKe5WWaS1q7nCFt1O04uqoY,948
|
|
16
|
+
kfinance/tests/test_batch_requests.py,sha256=uXJF2IcRdyBm5SthwIUHMKtkGZ21MY84pg_k1JeSNOY,11430
|
|
17
|
+
kfinance/tests/test_client.py,sha256=O7icZCSDhlQ9WGhzoXlpiSvbuA-mQNJHBYVsilyP_dE,2209
|
|
18
|
+
kfinance/tests/test_example_notebook.py,sha256=XhDAJp3H5Y6usLAt3k4Ug4W3H6gLyh68nzmoWgOOLn4,6441
|
|
19
|
+
kfinance/tests/test_fetch.py,sha256=O9qOsffQYJPSAR1X_P6CNoA0Buyq2kx4vfS0FOc2Goc,13564
|
|
20
|
+
kfinance/tests/test_group_objects.py,sha256=SoMEZmkG4RYdgWOAwxLHHtzIQho92KM01YbQXPUg578,1689
|
|
21
|
+
kfinance/tests/test_objects.py,sha256=PpB3q4UPi9ctRznq48Fy6sJUsOEqdAWzMzee1BYORX4,24001
|
|
22
|
+
kfinance/tests/test_tools.py,sha256=fTN43Y8fX8l8Yyw65XJmn_xUIbrTLaPvrsuBEdKPqK0,15234
|
|
23
|
+
kfinance/tool_calling/README.md,sha256=omJq7Us6r4U45QB7hRpLjRJ5BMalCkZkh4uXBjTbJXc,2022
|
|
24
|
+
kfinance/tool_calling/__init__.py,sha256=ewTzpR4S9mzCtSRpfK7WldDX78VSRKFta-s_zBsmamA,1744
|
|
25
|
+
kfinance/tool_calling/get_business_relationship_from_identifier.py,sha256=CipXvyqEjPm6BXYP0CA9Kp1BIyiIEm7abp85x1zXRV4,1472
|
|
26
|
+
kfinance/tool_calling/get_capitalization_from_identifier.py,sha256=TdWdJDeI-jSL-1YfhnnwIA9D1SXobidvoHrjK42QmqQ,1521
|
|
27
|
+
kfinance/tool_calling/get_cusip_from_ticker.py,sha256=houhGCYXoSzaaTtCvOBf3pPsYiSbcV1Ej5nAyGuMWcU,644
|
|
28
|
+
kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py,sha256=Zp7gHP-z2ePu484wWx4xw89hl0z7NLcG3qliJT-6rUo,782
|
|
29
|
+
kfinance/tool_calling/get_financial_line_item_from_identifier.py,sha256=bMK-GUMz_wmJ-7iFS72sM8AvvUvR83JYRWzYtr9Fofw,2153
|
|
30
|
+
kfinance/tool_calling/get_financial_statement_from_identifier.py,sha256=9ouzC43skA78uI3dh3bvtM86db5RGxuWQAAJ24y-kBM,1906
|
|
31
|
+
kfinance/tool_calling/get_history_metadata_from_identifier.py,sha256=kKIInfRC1Plf-Wnx9cmaJ46RRABm6zn3F5_8qAdqrBg,728
|
|
32
|
+
kfinance/tool_calling/get_info_from_identifier.py,sha256=PRhSYpYs_iUcIVFGYCMN_7QJyhewua7c7pqngPIp-qg,766
|
|
33
|
+
kfinance/tool_calling/get_isin_from_ticker.py,sha256=2fJBcA-rNGbVOQmQ7qJEYxqejQwJ6nyWOBSFlzxG7dY,638
|
|
34
|
+
kfinance/tool_calling/get_latest.py,sha256=btGeVBmvX5QJutzrKfE6spatGGejDuHxtq53NoAaGNk,786
|
|
35
|
+
kfinance/tool_calling/get_n_quarters_ago.py,sha256=A0ilwPKUqU0YYQSz3gNsVF0Jy4YttXrSaDhYj7y8GHA,713
|
|
36
|
+
kfinance/tool_calling/get_prices_from_identifier.py,sha256=ViJkwLDvStB7grc8RuoKSDXQM399Wru4-OY3E8k1l_U,1882
|
|
37
|
+
kfinance/tool_calling/resolve_identifier.py,sha256=npslr6bBCu0qEDV1-8d24F5OC3nQ1KBMphuMbHVC1AU,626
|
|
38
|
+
kfinance/tool_calling/shared_models.py,sha256=K-NPQyE_7Ew6Cs0zxG1xO2O47gp5uDHdHtWD7wUDZX4,2132
|
|
39
|
+
kensho_kfinance-2.2.2.dist-info/METADATA,sha256=VOmB9CZIcpr2DI5tJBZuWnR039oK_iRTN4cvDFco4RQ,3436
|
|
40
|
+
kensho_kfinance-2.2.2.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
41
|
+
kensho_kfinance-2.2.2.dist-info/top_level.txt,sha256=kT_kNwVhfQoOAecY8W7uYah5xaHMoHoAdBIvXh6DaKM,9
|
|
42
|
+
kensho_kfinance-2.2.2.dist-info/RECORD,,
|
kfinance/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.2
|
|
4
|
+
- Add segments
|
|
5
|
+
|
|
6
|
+
## v2.2.1
|
|
7
|
+
- Make number of employees optional to reflect backend changes
|
|
8
|
+
|
|
9
|
+
## v2.2.0
|
|
10
|
+
- Replace get company id, get security id, get trading item id tools with resolve identifier tool
|
|
11
|
+
|
|
12
|
+
## v2.1.2
|
|
13
|
+
- Allow batch executor to handle multiple requests
|
|
14
|
+
|
|
15
|
+
## v2.1.1
|
|
16
|
+
- Use cachetools cache
|
|
17
|
+
|
|
18
|
+
## v2.1.0
|
|
19
|
+
- Filter llm tools by user permissions
|
|
20
|
+
|
|
21
|
+
## v2.0.2
|
|
22
|
+
- Add Client.tickers() method for ticker industry filters for ISRCS and GICS
|
|
23
|
+
|
|
3
24
|
## v2.0.1
|
|
4
25
|
- Fix readthedocs integration for llm tools.
|
|
5
26
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from concurrent.futures import
|
|
1
|
+
from concurrent.futures import Future
|
|
2
2
|
import functools
|
|
3
3
|
from functools import cached_property
|
|
4
4
|
import threading
|
|
@@ -55,36 +55,26 @@ def add_methods_of_singular_class_to_iterable_class(singular_cls: Type[T]) -> Ca
|
|
|
55
55
|
instances of [singular_cls].
|
|
56
56
|
"""
|
|
57
57
|
|
|
58
|
-
def process_in_thread_pool(
|
|
59
|
-
executor: ThreadPoolExecutor, method: Callable, obj: T, *args: Any, **kwargs: Any
|
|
60
|
-
) -> Any:
|
|
61
|
-
with throttle:
|
|
62
|
-
future = executor.submit(method, obj, *args, **kwargs)
|
|
63
|
-
try:
|
|
64
|
-
return future.result()
|
|
65
|
-
except HTTPError as http_err:
|
|
66
|
-
error_code = http_err.response.status_code
|
|
67
|
-
if error_code == 404:
|
|
68
|
-
return None
|
|
69
|
-
else:
|
|
70
|
-
raise http_err
|
|
71
|
-
|
|
72
58
|
def process_in_batch(
|
|
73
59
|
method: Callable, self: IterableKfinanceClass, *args: Any, **kwargs: Any
|
|
74
60
|
) -> dict:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
[
|
|
83
|
-
process_in_thread_pool(executor, method, obj, *args, **kwargs)
|
|
84
|
-
for obj in self
|
|
85
|
-
],
|
|
86
|
-
)
|
|
61
|
+
with self.kfinance_api_client.batch_request_header(batch_size=len(self)):
|
|
62
|
+
futures = []
|
|
63
|
+
for obj in self:
|
|
64
|
+
# Acquire throttle before submitting the task
|
|
65
|
+
throttle.acquire()
|
|
66
|
+
future = self.kfinance_api_client.thread_pool.submit(
|
|
67
|
+
method, obj, *args, **kwargs
|
|
87
68
|
)
|
|
69
|
+
# On success or failure, release the throttle.
|
|
70
|
+
# This releases the throttle before the
|
|
71
|
+
# `resolve_future_with_error_handling` call.
|
|
72
|
+
future.add_done_callback(lambda f: throttle.release())
|
|
73
|
+
futures.append(future)
|
|
74
|
+
|
|
75
|
+
results = {}
|
|
76
|
+
for obj, future in zip(self, futures):
|
|
77
|
+
results[obj] = resolve_future_with_error_handling(future)
|
|
88
78
|
|
|
89
79
|
return results
|
|
90
80
|
|
|
@@ -135,3 +125,18 @@ def add_methods_of_singular_class_to_iterable_class(singular_cls: Type[T]) -> Ca
|
|
|
135
125
|
return iterable_cls
|
|
136
126
|
|
|
137
127
|
return decorator
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def resolve_future_with_error_handling(future: Future) -> Any:
|
|
131
|
+
"""Return the result of a future with error handling for non-200 status codes.
|
|
132
|
+
|
|
133
|
+
If request returned a 404, return None. Otherwise, raise the error.
|
|
134
|
+
"""
|
|
135
|
+
try:
|
|
136
|
+
return future.result()
|
|
137
|
+
except HTTPError as http_err:
|
|
138
|
+
error_code = http_err.response.status_code
|
|
139
|
+
if error_code == 404:
|
|
140
|
+
return None
|
|
141
|
+
else:
|
|
142
|
+
raise http_err
|
kfinance/constants.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from datetime import date
|
|
2
|
-
from enum import Enum
|
|
3
2
|
from itertools import chain
|
|
4
|
-
from typing import TypedDict
|
|
3
|
+
from typing import NamedTuple, TypedDict
|
|
5
4
|
|
|
6
5
|
from strenum import StrEnum
|
|
7
6
|
|
|
@@ -21,13 +20,13 @@ class HistoryMetadata(TypedDict):
|
|
|
21
20
|
first_trade_date: date
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
class IdentificationTriple(
|
|
23
|
+
class IdentificationTriple(NamedTuple):
|
|
25
24
|
trading_item_id: int
|
|
26
25
|
security_id: int
|
|
27
26
|
company_id: int
|
|
28
27
|
|
|
29
28
|
|
|
30
|
-
class Capitalization(
|
|
29
|
+
class Capitalization(StrEnum):
|
|
31
30
|
"""The capitalization type"""
|
|
32
31
|
|
|
33
32
|
market_cap = "market_cap"
|
|
@@ -35,7 +34,7 @@ class Capitalization(Enum):
|
|
|
35
34
|
shares_outstanding = "shares_outstanding"
|
|
36
35
|
|
|
37
36
|
|
|
38
|
-
class PeriodType(
|
|
37
|
+
class PeriodType(StrEnum):
|
|
39
38
|
"""The period type"""
|
|
40
39
|
|
|
41
40
|
annual = "annual"
|
|
@@ -44,7 +43,7 @@ class PeriodType(Enum):
|
|
|
44
43
|
ytd = "ytd"
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
class Periodicity(
|
|
46
|
+
class Periodicity(StrEnum):
|
|
48
47
|
"""The frequency or interval at which the historical data points are sampled or aggregated. Periodicity is not the same as the date range. The date range specifies the time span over which the data is retrieved, while periodicity determines how the data within that date range is aggregated."""
|
|
49
48
|
|
|
50
49
|
day = "day"
|
|
@@ -53,7 +52,7 @@ class Periodicity(Enum):
|
|
|
53
52
|
year = "year"
|
|
54
53
|
|
|
55
54
|
|
|
56
|
-
class StatementType(
|
|
55
|
+
class StatementType(StrEnum):
|
|
57
56
|
"""The type of financial statement"""
|
|
58
57
|
|
|
59
58
|
balance_sheet = "balance_sheet"
|
|
@@ -63,6 +62,13 @@ class StatementType(Enum):
|
|
|
63
62
|
cf = "cashflow"
|
|
64
63
|
|
|
65
64
|
|
|
65
|
+
class SegmentType(StrEnum):
|
|
66
|
+
"""The type of segment"""
|
|
67
|
+
|
|
68
|
+
business = "business"
|
|
69
|
+
geographic = "geographic"
|
|
70
|
+
|
|
71
|
+
|
|
66
72
|
class BusinessRelationshipType(StrEnum):
|
|
67
73
|
"""The type of business relationship"""
|
|
68
74
|
|
|
@@ -88,6 +94,16 @@ class BusinessRelationshipType(StrEnum):
|
|
|
88
94
|
client_services = "client_services"
|
|
89
95
|
|
|
90
96
|
|
|
97
|
+
class Permission(StrEnum):
|
|
98
|
+
EarningsPermission = "EarningsPermission"
|
|
99
|
+
GICSPermission = "GICSPermission"
|
|
100
|
+
IDPermission = "IDPermission"
|
|
101
|
+
ISCRSPermission = "ISCRSPermission"
|
|
102
|
+
PricingPermission = "PricingPermission"
|
|
103
|
+
RelationshipPermission = "RelationshipPermission"
|
|
104
|
+
StatementsPermission = "StatementsPermission"
|
|
105
|
+
|
|
106
|
+
|
|
91
107
|
class YearAndQuarter(TypedDict):
|
|
92
108
|
year: int
|
|
93
109
|
quarter: int
|
kfinance/fetch.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from concurrent.futures import ThreadPoolExecutor
|
|
2
2
|
from contextlib import contextmanager
|
|
3
|
+
import logging
|
|
3
4
|
from time import time
|
|
4
5
|
from typing import Callable, Generator, Optional
|
|
5
6
|
from uuid import uuid4
|
|
@@ -13,6 +14,8 @@ from .constants import (
|
|
|
13
14
|
IndustryClassification,
|
|
14
15
|
Periodicity,
|
|
15
16
|
PeriodType,
|
|
17
|
+
Permission,
|
|
18
|
+
SegmentType,
|
|
16
19
|
)
|
|
17
20
|
|
|
18
21
|
|
|
@@ -23,6 +26,8 @@ try:
|
|
|
23
26
|
except ImportError:
|
|
24
27
|
kfinance_version = "dev"
|
|
25
28
|
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
26
31
|
|
|
27
32
|
DEFAULT_API_HOST: str = "https://kfinance.kensho.com"
|
|
28
33
|
DEFAULT_API_VERSION: int = 1
|
|
@@ -86,6 +91,7 @@ class KFinanceApiClient:
|
|
|
86
91
|
self.user_agent_source = "object_oriented"
|
|
87
92
|
self._batch_id: str | None = None
|
|
88
93
|
self._batch_size: str | None = None
|
|
94
|
+
self._user_permissions: set[Permission] | None = None
|
|
89
95
|
|
|
90
96
|
@contextmanager
|
|
91
97
|
def batch_request_header(self, batch_size: int) -> Generator:
|
|
@@ -127,6 +133,9 @@ class KFinanceApiClient:
|
|
|
127
133
|
# nosemgrep: python.jwt.security.unverified-jwt-decode.unverified-jwt-decode
|
|
128
134
|
options={"verify_signature": False},
|
|
129
135
|
).get("exp")
|
|
136
|
+
# When the access token gets refreshed, also refresh user permissions in case they
|
|
137
|
+
# have been updated.
|
|
138
|
+
self._refresh_user_permissions()
|
|
130
139
|
return self._access_token
|
|
131
140
|
|
|
132
141
|
def _get_access_token_via_refresh_token(self) -> str:
|
|
@@ -169,6 +178,33 @@ class KFinanceApiClient:
|
|
|
169
178
|
response.raise_for_status()
|
|
170
179
|
return response.json().get("access_token")
|
|
171
180
|
|
|
181
|
+
@property
|
|
182
|
+
def user_permissions(self) -> set[Permission]:
|
|
183
|
+
"""Return the permissions that the current user holds."""
|
|
184
|
+
|
|
185
|
+
if self._user_permissions is None:
|
|
186
|
+
self._refresh_user_permissions()
|
|
187
|
+
# _refresh_user_permissions updates self._user_permissions in place
|
|
188
|
+
assert self._user_permissions is not None
|
|
189
|
+
return self._user_permissions
|
|
190
|
+
|
|
191
|
+
def _refresh_user_permissions(self) -> None:
|
|
192
|
+
"""Fetches user permissions and stores them as KfinanceApiClient._user_permissions."""
|
|
193
|
+
|
|
194
|
+
user_permission_dict = self.fetch_permissions()
|
|
195
|
+
self._user_permissions = set()
|
|
196
|
+
for permission_str in user_permission_dict["permissions"]:
|
|
197
|
+
try:
|
|
198
|
+
self._user_permissions.add(Permission[permission_str])
|
|
199
|
+
except KeyError:
|
|
200
|
+
logger.warning(
|
|
201
|
+
"Could not resolve %s to a member of the Permission enum. "
|
|
202
|
+
"%s may be a new type of permission that still needs to be "
|
|
203
|
+
"added to the enum.",
|
|
204
|
+
permission_str,
|
|
205
|
+
permission_str,
|
|
206
|
+
)
|
|
207
|
+
|
|
172
208
|
def fetch(self, url: str) -> dict:
|
|
173
209
|
"""Does the request and auth"""
|
|
174
210
|
|
|
@@ -191,6 +227,11 @@ class KFinanceApiClient:
|
|
|
191
227
|
response.raise_for_status()
|
|
192
228
|
return response.json()
|
|
193
229
|
|
|
230
|
+
def fetch_permissions(self) -> dict[str, list[str]]:
|
|
231
|
+
"""Return the permissions of the user."""
|
|
232
|
+
url = f"{self.url_base}users/permissions"
|
|
233
|
+
return self.fetch(url)
|
|
234
|
+
|
|
194
235
|
def fetch_id_triple(self, identifier: str, exchange_code: Optional[str] = None) -> dict:
|
|
195
236
|
"""Get the ID triple from [identifier]."""
|
|
196
237
|
url = f"{self.url_base}id/{identifier}"
|
|
@@ -241,7 +282,7 @@ class KFinanceApiClient:
|
|
|
241
282
|
f"{self.url_base}pricing/{trading_item_id}/"
|
|
242
283
|
f"{start_date if start_date is not None else 'none'}/"
|
|
243
284
|
f"{end_date if end_date is not None else 'none'}/"
|
|
244
|
-
f"{periodicity
|
|
285
|
+
f"{periodicity if periodicity else 'none'}/"
|
|
245
286
|
f"{'adjusted' if is_adjusted else 'unadjusted'}"
|
|
246
287
|
)
|
|
247
288
|
return self.fetch(url)
|
|
@@ -265,6 +306,27 @@ class KFinanceApiClient:
|
|
|
265
306
|
)
|
|
266
307
|
return self.fetch(url)
|
|
267
308
|
|
|
309
|
+
def fetch_segments(
|
|
310
|
+
self,
|
|
311
|
+
company_id: int,
|
|
312
|
+
segment_type: SegmentType,
|
|
313
|
+
period_type: Optional[PeriodType] = None,
|
|
314
|
+
start_year: Optional[int] = None,
|
|
315
|
+
end_year: Optional[int] = None,
|
|
316
|
+
start_quarter: Optional[int] = None,
|
|
317
|
+
end_quarter: Optional[int] = None,
|
|
318
|
+
) -> dict:
|
|
319
|
+
"""Get a specified segment type for a specified duration."""
|
|
320
|
+
url = (
|
|
321
|
+
f"{self.url_base}segments/{company_id}/{segment_type}/"
|
|
322
|
+
f"{period_type if period_type else 'none'}/"
|
|
323
|
+
f"{start_year if start_year is not None else 'none'}/"
|
|
324
|
+
f"{end_year if end_year is not None else 'none'}/"
|
|
325
|
+
f"{start_quarter if start_quarter is not None else 'none'}/"
|
|
326
|
+
f"{end_quarter if end_quarter is not None else 'none'}"
|
|
327
|
+
)
|
|
328
|
+
return self.fetch(url)
|
|
329
|
+
|
|
268
330
|
def fetch_price_chart(
|
|
269
331
|
self,
|
|
270
332
|
trading_item_id: int,
|
|
@@ -278,7 +340,7 @@ class KFinanceApiClient:
|
|
|
278
340
|
f"{self.url_base}price_chart/{trading_item_id}/"
|
|
279
341
|
f"{start_date if start_date is not None else 'none'}/"
|
|
280
342
|
f"{end_date if end_date is not None else 'none'}/"
|
|
281
|
-
f"{periodicity
|
|
343
|
+
f"{periodicity if periodicity else 'none'}/"
|
|
282
344
|
f"{'adjusted' if is_adjusted else 'unadjusted'}"
|
|
283
345
|
)
|
|
284
346
|
|
|
@@ -306,7 +368,7 @@ class KFinanceApiClient:
|
|
|
306
368
|
"""Get a specified financial statement for a specified duration."""
|
|
307
369
|
url = (
|
|
308
370
|
f"{self.url_base}statements/{company_id}/{statement_type}/"
|
|
309
|
-
f"{period_type
|
|
371
|
+
f"{period_type if period_type else 'none'}/"
|
|
310
372
|
f"{start_year if start_year is not None else 'none'}/"
|
|
311
373
|
f"{end_year if end_year is not None else 'none'}/"
|
|
312
374
|
f"{start_quarter if start_quarter is not None else 'none'}/"
|
|
@@ -327,7 +389,7 @@ class KFinanceApiClient:
|
|
|
327
389
|
"""Get a specified financial line item for a specified duration."""
|
|
328
390
|
url = (
|
|
329
391
|
f"{self.url_base}line_item/{company_id}/{line_item}/"
|
|
330
|
-
f"{period_type
|
|
392
|
+
f"{period_type if period_type else 'none'}/"
|
|
331
393
|
f"{start_year if start_year is not None else 'none'}/"
|
|
332
394
|
f"{end_year if end_year is not None else 'none'}/"
|
|
333
395
|
f"{start_quarter if start_quarter is not None else 'none'}/"
|
|
@@ -358,10 +420,12 @@ class KFinanceApiClient:
|
|
|
358
420
|
self,
|
|
359
421
|
country_iso_code: str,
|
|
360
422
|
state_iso_code: Optional[str] = None,
|
|
361
|
-
) ->
|
|
423
|
+
) -> list[IdentificationTriple]:
|
|
362
424
|
"""Fetch ticker geography groups"""
|
|
363
|
-
return self.
|
|
364
|
-
|
|
425
|
+
return self._tickers_response_to_id_triple(
|
|
426
|
+
self.fetch_geography_groups(
|
|
427
|
+
country_iso_code=country_iso_code, state_iso_code=state_iso_code, fetch_ticker=True
|
|
428
|
+
)
|
|
365
429
|
)
|
|
366
430
|
|
|
367
431
|
def fetch_company_geography_groups(
|
|
@@ -381,13 +445,13 @@ class KFinanceApiClient:
|
|
|
381
445
|
url = f"{self.url_base}{'ticker_groups' if fetch_ticker else 'trading_item_groups'}/exchange/{exchange_code}"
|
|
382
446
|
return self.fetch(url)
|
|
383
447
|
|
|
384
|
-
def fetch_ticker_exchange_groups(
|
|
385
|
-
self, exchange_code: str
|
|
386
|
-
) -> dict[str, list[IdentificationTriple]]:
|
|
448
|
+
def fetch_ticker_exchange_groups(self, exchange_code: str) -> list[IdentificationTriple]:
|
|
387
449
|
"""Fetch ticker exchange groups"""
|
|
388
|
-
return self.
|
|
389
|
-
|
|
390
|
-
|
|
450
|
+
return self._tickers_response_to_id_triple(
|
|
451
|
+
self.fetch_exchange_groups(
|
|
452
|
+
exchange_code=exchange_code,
|
|
453
|
+
fetch_ticker=True,
|
|
454
|
+
)
|
|
391
455
|
)
|
|
392
456
|
|
|
393
457
|
def fetch_trading_item_exchange_groups(self, exchange_code: str) -> dict[str, list[int]]:
|
|
@@ -397,13 +461,32 @@ class KFinanceApiClient:
|
|
|
397
461
|
fetch_ticker=False,
|
|
398
462
|
)
|
|
399
463
|
|
|
464
|
+
@staticmethod
|
|
465
|
+
def _tickers_response_to_id_triple(
|
|
466
|
+
tickers_response: dict[str, list[dict]],
|
|
467
|
+
) -> list[IdentificationTriple]:
|
|
468
|
+
"""For fetch ticker cases with a dict[str, list[dict]] response, return a list[IdentificationTriple].
|
|
469
|
+
|
|
470
|
+
For example, with a given fetch tickers response:
|
|
471
|
+
{"tickers" : [{"trading_item_id": 1, "security_id": 1, "company_id": 1}, {"trading_item_id": 2,"security_id": 2,"company_id": 2}]},
|
|
472
|
+
return [[1, 1, 1], [2, 2, 2]].
|
|
473
|
+
"""
|
|
474
|
+
return [
|
|
475
|
+
IdentificationTriple(
|
|
476
|
+
trading_item_id=ticker["trading_item_id"],
|
|
477
|
+
security_id=ticker["security_id"],
|
|
478
|
+
company_id=ticker["company_id"],
|
|
479
|
+
)
|
|
480
|
+
for ticker in tickers_response["tickers"]
|
|
481
|
+
]
|
|
482
|
+
|
|
400
483
|
def fetch_ticker_combined(
|
|
401
484
|
self,
|
|
402
485
|
country_iso_code: Optional[str] = None,
|
|
403
486
|
state_iso_code: Optional[str] = None,
|
|
404
487
|
simple_industry: Optional[str] = None,
|
|
405
488
|
exchange_code: Optional[str] = None,
|
|
406
|
-
) ->
|
|
489
|
+
) -> list[IdentificationTriple]:
|
|
407
490
|
"""Fetch tickers using combined filters route"""
|
|
408
491
|
if (
|
|
409
492
|
country_iso_code is None
|
|
@@ -414,11 +497,11 @@ class KFinanceApiClient:
|
|
|
414
497
|
raise RuntimeError("Invalid parameters: No parameters provided or all set to none")
|
|
415
498
|
elif country_iso_code is None and state_iso_code is not None:
|
|
416
499
|
raise RuntimeError(
|
|
417
|
-
"Invalid parameters:
|
|
500
|
+
"Invalid parameters: country_iso_code must be provided with a state_iso_code value"
|
|
418
501
|
)
|
|
419
502
|
else:
|
|
420
503
|
url = f"{self.url_base}ticker_groups/filters/geo/{str(country_iso_code).lower()}/{str(state_iso_code).lower()}/simple/{str(simple_industry).lower()}/exchange/{str(exchange_code).lower()}"
|
|
421
|
-
return self.fetch(url)
|
|
504
|
+
return self._tickers_response_to_id_triple(self.fetch(url))
|
|
422
505
|
|
|
423
506
|
def fetch_companies_from_business_relationship(
|
|
424
507
|
self, company_id: int, relationship_type: BusinessRelationshipType
|
|
@@ -443,22 +526,11 @@ class KFinanceApiClient:
|
|
|
443
526
|
url = f"{self.url_base}relationship/{company_id}/{relationship_type}"
|
|
444
527
|
return self.fetch(url)
|
|
445
528
|
|
|
446
|
-
def fetch_from_industry_code(
|
|
447
|
-
self,
|
|
448
|
-
industry_code: str,
|
|
449
|
-
industry_classification: IndustryClassification,
|
|
450
|
-
fetch_ticker: bool = True,
|
|
451
|
-
) -> dict[str, list]:
|
|
452
|
-
"""Fetches a list of companies or identification triples that are classified in the given industry_code and industry_classification."""
|
|
453
|
-
|
|
454
|
-
url = f"{self.url_base}{'ticker_groups' if fetch_ticker else 'company_groups'}/industry/{industry_classification}/{industry_code}"
|
|
455
|
-
return self.fetch(url)
|
|
456
|
-
|
|
457
529
|
def fetch_ticker_from_industry_code(
|
|
458
530
|
self,
|
|
459
531
|
industry_code: str,
|
|
460
532
|
industry_classification: IndustryClassification,
|
|
461
|
-
) ->
|
|
533
|
+
) -> list[IdentificationTriple]:
|
|
462
534
|
"""Fetches a list of identification triples that are classified in the given industry_code and industry_classification.
|
|
463
535
|
|
|
464
536
|
Returns a dictionary of shape {"tickers": List[{“company_id”: <company_id>, “security_id”: <security_id>, “trading_item_id”: <trading_item_id>}]}.
|
|
@@ -466,14 +538,11 @@ class KFinanceApiClient:
|
|
|
466
538
|
:type industry_code: str
|
|
467
539
|
:param industry_classification: The type of industry_classification to filter on.
|
|
468
540
|
:type industry_classification: IndustryClassification
|
|
469
|
-
:return: A
|
|
470
|
-
:rtype:
|
|
541
|
+
:return: A list of identification triples [company_id, security_id, trading_item_id] that are classified in the given industry_code and industry_classification.
|
|
542
|
+
:rtype: list[IdentificationTriple]
|
|
471
543
|
"""
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
industry_classification=industry_classification,
|
|
475
|
-
fetch_ticker=True,
|
|
476
|
-
)
|
|
544
|
+
url = f"{self.url_base}ticker_groups/industry/{industry_classification}/{industry_code}"
|
|
545
|
+
return self._tickers_response_to_id_triple(self.fetch(url))
|
|
477
546
|
|
|
478
547
|
def fetch_company_from_industry_code(
|
|
479
548
|
self,
|
|
@@ -490,8 +559,5 @@ class KFinanceApiClient:
|
|
|
490
559
|
:return: A dictionary containing the list of companies that are classified in the given industry_code and industry_classification.
|
|
491
560
|
:rtype: dict[str, list[int]]
|
|
492
561
|
"""
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
industry_classification=industry_classification,
|
|
496
|
-
fetch_ticker=False,
|
|
497
|
-
)
|
|
562
|
+
url = f"{self.url_base}company_groups/industry/{industry_classification}/{industry_code}"
|
|
563
|
+
return self.fetch(url)
|