kensho-kfinance 3.2.4__py3-none-any.whl → 4.0.0__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.
Files changed (57) hide show
  1. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/METADATA +3 -3
  2. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/RECORD +57 -56
  3. kfinance/CHANGELOG.md +51 -0
  4. kfinance/client/batch_request_handling.py +3 -1
  5. kfinance/client/fetch.py +127 -54
  6. kfinance/client/kfinance.py +38 -39
  7. kfinance/client/meta_classes.py +50 -20
  8. kfinance/client/models/date_and_period_models.py +32 -7
  9. kfinance/client/models/decimal_with_unit.py +14 -2
  10. kfinance/client/models/response_models.py +33 -0
  11. kfinance/client/models/tests/test_decimal_with_unit.py +9 -0
  12. kfinance/client/tests/test_batch_requests.py +5 -4
  13. kfinance/client/tests/test_fetch.py +134 -58
  14. kfinance/client/tests/test_objects.py +207 -145
  15. kfinance/conftest.py +10 -0
  16. kfinance/domains/business_relationships/business_relationship_tools.py +17 -8
  17. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +18 -16
  18. kfinance/domains/capitalizations/capitalization_models.py +7 -5
  19. kfinance/domains/capitalizations/capitalization_tools.py +38 -20
  20. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +66 -36
  21. kfinance/domains/companies/company_models.py +22 -2
  22. kfinance/domains/companies/company_tools.py +49 -16
  23. kfinance/domains/companies/tests/test_company_tools.py +27 -9
  24. kfinance/domains/competitors/competitor_tools.py +19 -5
  25. kfinance/domains/competitors/tests/test_competitor_tools.py +22 -19
  26. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +29 -8
  27. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +13 -8
  28. kfinance/domains/earnings/earning_tools.py +73 -29
  29. kfinance/domains/earnings/tests/test_earnings_tools.py +52 -43
  30. kfinance/domains/line_items/line_item_models.py +372 -16
  31. kfinance/domains/line_items/line_item_tools.py +198 -46
  32. kfinance/domains/line_items/tests/test_line_item_tools.py +305 -39
  33. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_models.py +46 -2
  34. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +55 -74
  35. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +61 -59
  36. kfinance/domains/prices/price_models.py +7 -6
  37. kfinance/domains/prices/price_tools.py +24 -16
  38. kfinance/domains/prices/tests/test_price_tools.py +47 -39
  39. kfinance/domains/segments/segment_models.py +17 -3
  40. kfinance/domains/segments/segment_tools.py +102 -42
  41. kfinance/domains/segments/tests/test_segment_tools.py +166 -37
  42. kfinance/domains/statements/statement_models.py +17 -3
  43. kfinance/domains/statements/statement_tools.py +130 -46
  44. kfinance/domains/statements/tests/test_statement_tools.py +251 -49
  45. kfinance/integrations/local_mcp/kfinance_mcp.py +1 -1
  46. kfinance/integrations/tests/test_example_notebook.py +57 -16
  47. kfinance/integrations/tool_calling/all_tools.py +5 -1
  48. kfinance/integrations/tool_calling/static_tools/get_n_quarters_ago.py +5 -0
  49. kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +13 -10
  50. kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +2 -1
  51. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +15 -4
  52. kfinance/integrations/tool_calling/tool_calling_models.py +18 -6
  53. kfinance/version.py +2 -2
  54. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/WHEEL +0 -0
  55. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/AUTHORS.md +0 -0
  56. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/licenses/LICENSE +0 -0
  57. {kensho_kfinance-3.2.4.dist-info → kensho_kfinance-4.0.0.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,46 @@
1
1
  from requests_mock import Mocker
2
2
 
3
3
  from kfinance.client.kfinance import Client
4
- from kfinance.conftest import SPGI_COMPANY_ID
5
4
  from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
6
5
  from kfinance.domains.statements.statement_models import StatementType
7
6
  from kfinance.domains.statements.statement_tools import (
8
7
  GetFinancialStatementFromIdentifiers,
9
8
  GetFinancialStatementFromIdentifiersArgs,
9
+ GetFinancialStatementFromIdentifiersResp,
10
10
  )
11
11
 
12
12
 
13
13
  class TestGetFinancialStatementFromIdentifiers:
14
14
  statement_resp = {
15
- "statements": {
16
- "2020": {"Revenues": "7442000000.000000", "Total Revenues": "7442000000.000000"},
17
- "2021": {"Revenues": "8243000000.000000", "Total Revenues": "8243000000.000000"},
18
- }
15
+ "currency": "USD",
16
+ "periods": {
17
+ "CY2020": {
18
+ "period_end_date": "2020-12-31",
19
+ "num_months": 12,
20
+ "statements": [
21
+ {
22
+ "name": "Income Statement",
23
+ "line_items": [
24
+ {"name": "Revenues", "value": "7442000000.000000", "sources": []},
25
+ {"name": "Total Revenues", "value": "7442000000.000000", "sources": []},
26
+ ],
27
+ }
28
+ ],
29
+ },
30
+ "CY2021": {
31
+ "period_end_date": "2021-12-31",
32
+ "num_months": 12,
33
+ "statements": [
34
+ {
35
+ "name": "Income Statement",
36
+ "line_items": [
37
+ {"name": "Revenues", "value": "8243000000.000000", "sources": []},
38
+ {"name": "Total Revenues", "value": "8243000000.000000", "sources": []},
39
+ ],
40
+ }
41
+ ],
42
+ },
43
+ },
19
44
  }
20
45
 
21
46
  def test_get_financial_statement_from_identifiers(
@@ -27,33 +52,88 @@ class TestGetFinancialStatementFromIdentifiers:
27
52
  THEN we get back the SPGI income statement and an error for the non-existent company.
28
53
  """
29
54
 
30
- requests_mock.get(
31
- url=f"https://kfinance.kensho.com/api/v1/statements/{SPGI_COMPANY_ID}/income_statement/none/none/none/none/none",
32
- json=self.statement_resp,
55
+ # Mock the unified_fetch_id_triples response
56
+ requests_mock.post(
57
+ url="https://kfinance.kensho.com/api/v1/ids",
58
+ json={
59
+ "identifiers_to_id_triples": {
60
+ "SPGI": {
61
+ "company_id": 21719,
62
+ "security_id": 2629107,
63
+ "trading_item_id": 2629108,
64
+ }
65
+ },
66
+ "errors": {
67
+ "NON-EXISTENT": "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
68
+ },
69
+ },
70
+ )
71
+
72
+ # Mock the fetch_statement response
73
+ requests_mock.post(
74
+ url="https://kfinance.kensho.com/api/v1/statements/",
75
+ json={"results": {"21719": self.statement_resp}, "errors": {}},
33
76
  )
34
- expected_response = {
35
- "results": {
36
- "SPGI": {
37
- "statements": {
38
- "2020": {
39
- "Revenues": "7442000000.000000",
40
- "Total Revenues": "7442000000.000000",
41
- },
42
- "2021": {
43
- "Revenues": "8243000000.000000",
44
- "Total Revenues": "8243000000.000000",
77
+ expected_response = GetFinancialStatementFromIdentifiersResp.model_validate(
78
+ {
79
+ "results": {
80
+ "SPGI": {
81
+ "currency": "USD",
82
+ "periods": {
83
+ "CY2020": {
84
+ "period_end_date": "2020-12-31",
85
+ "num_months": 12,
86
+ "statements": [
87
+ {
88
+ "name": "Income Statement",
89
+ "line_items": [
90
+ {
91
+ "name": "Revenues",
92
+ "value": "7442000000.000000",
93
+ "sources": [],
94
+ },
95
+ {
96
+ "name": "Total Revenues",
97
+ "value": "7442000000.000000",
98
+ "sources": [],
99
+ },
100
+ ],
101
+ }
102
+ ],
103
+ },
104
+ "CY2021": {
105
+ "period_end_date": "2021-12-31",
106
+ "num_months": 12,
107
+ "statements": [
108
+ {
109
+ "name": "Income Statement",
110
+ "line_items": [
111
+ {
112
+ "name": "Revenues",
113
+ "value": "8243000000.000000",
114
+ "sources": [],
115
+ },
116
+ {
117
+ "name": "Total Revenues",
118
+ "value": "8243000000.000000",
119
+ "sources": [],
120
+ },
121
+ ],
122
+ }
123
+ ],
124
+ },
45
125
  },
46
126
  }
47
- }
48
- },
49
- "errors": [
50
- "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
51
- ],
52
- }
127
+ },
128
+ "errors": [
129
+ "No identification triple found for the provided identifier: NON-EXISTENT of type: ticker"
130
+ ],
131
+ }
132
+ )
53
133
 
54
134
  tool = GetFinancialStatementFromIdentifiers(kfinance_client=mock_client)
55
135
  args = GetFinancialStatementFromIdentifiersArgs(
56
- identifiers=["SPGI", "non-existent"], statement=StatementType.income_statement
136
+ identifiers=["SPGI", "NON-EXISTENT"], statement=StatementType.income_statement
57
137
  )
58
138
  response = tool.run(args.model_dump(mode="json"))
59
139
  assert response == expected_response
@@ -66,32 +146,154 @@ class TestGetFinancialStatementFromIdentifiers:
66
146
  """
67
147
 
68
148
  company_ids = [1, 2]
69
- expected_response = {
70
- "results": {
71
- "C_1": {
72
- "statements": {
73
- "2021": {
74
- "Revenues": "8243000000.000000",
75
- "Total Revenues": "8243000000.000000",
76
- }
77
- }
78
- },
79
- "C_2": {
80
- "statements": {
81
- "2021": {
82
- "Revenues": "8243000000.000000",
83
- "Total Revenues": "8243000000.000000",
84
- }
85
- }
149
+ expected_response = GetFinancialStatementFromIdentifiersResp.model_validate(
150
+ {
151
+ "results": {
152
+ "C_1": {
153
+ "currency": "USD",
154
+ "periods": {
155
+ "CY2021": {
156
+ "period_end_date": "2021-12-31",
157
+ "num_months": 12,
158
+ "statements": [
159
+ {
160
+ "name": "Income Statement",
161
+ "line_items": [
162
+ {
163
+ "name": "Revenues",
164
+ "value": "8243000000.000000",
165
+ "sources": [],
166
+ },
167
+ {
168
+ "name": "Total Revenues",
169
+ "value": "8243000000.000000",
170
+ "sources": [],
171
+ },
172
+ ],
173
+ }
174
+ ],
175
+ }
176
+ },
177
+ },
178
+ "C_2": {
179
+ "currency": "USD",
180
+ "periods": {
181
+ "CY2021": {
182
+ "period_end_date": "2021-12-31",
183
+ "num_months": 12,
184
+ "statements": [
185
+ {
186
+ "name": "Income Statement",
187
+ "line_items": [
188
+ {
189
+ "name": "Revenues",
190
+ "value": "8243000000.000000",
191
+ "sources": [],
192
+ },
193
+ {
194
+ "name": "Total Revenues",
195
+ "value": "8243000000.000000",
196
+ "sources": [],
197
+ },
198
+ ],
199
+ }
200
+ ],
201
+ }
202
+ },
203
+ },
204
+ }
205
+ }
206
+ )
207
+
208
+ # Mock the unified_fetch_id_triples response
209
+ requests_mock.post(
210
+ url="https://kfinance.kensho.com/api/v1/ids",
211
+ json={
212
+ "identifiers_to_id_triples": {
213
+ "C_1": {"company_id": 1, "security_id": 101, "trading_item_id": 201},
214
+ "C_2": {"company_id": 2, "security_id": 102, "trading_item_id": 202},
86
215
  },
216
+ "errors": {},
217
+ },
218
+ )
219
+
220
+ # Mock the fetch_statement response
221
+ requests_mock.post(
222
+ url="https://kfinance.kensho.com/api/v1/statements/",
223
+ json={"results": {"1": self.statement_resp, "2": self.statement_resp}, "errors": {}},
224
+ )
225
+
226
+ tool = GetFinancialStatementFromIdentifiers(kfinance_client=mock_client)
227
+ args = GetFinancialStatementFromIdentifiersArgs(
228
+ identifiers=[f"{COMPANY_ID_PREFIX}{company_id}" for company_id in company_ids],
229
+ statement=StatementType.income_statement,
230
+ )
231
+ response = tool.run(args.model_dump(mode="json"))
232
+ assert response == expected_response
233
+
234
+ def test_empty_most_recent_request(self, requests_mock: Mocker, mock_client: Client) -> None:
235
+ """
236
+ GIVEN the GetFinancialStatementFromIdentifiers tool
237
+ WHEN we request most recent statement for multiple companies
238
+ THEN we only get back the most recent statement for each company
239
+ UNLESS no statements exist
240
+ """
241
+
242
+ company_ids = [1, 2]
243
+ expected_response = GetFinancialStatementFromIdentifiersResp.model_validate(
244
+ {
245
+ "results": {
246
+ "C_1": {"currency": "USD", "periods": {}},
247
+ "C_2": {
248
+ "currency": "USD",
249
+ "periods": {
250
+ "CY2021": {
251
+ "period_end_date": "2021-12-31",
252
+ "num_months": 12,
253
+ "statements": [
254
+ {
255
+ "name": "Income Statement",
256
+ "line_items": [
257
+ {
258
+ "name": "Revenues",
259
+ "value": "8243000000.000000",
260
+ "sources": [],
261
+ },
262
+ {
263
+ "name": "Total Revenues",
264
+ "value": "8243000000.000000",
265
+ "sources": [],
266
+ },
267
+ ],
268
+ }
269
+ ],
270
+ }
271
+ },
272
+ },
273
+ }
87
274
  }
88
- }
275
+ )
89
276
 
90
- for company_id in company_ids:
91
- requests_mock.get(
92
- url=f"https://kfinance.kensho.com/api/v1/statements/{company_id}/income_statement/none/none/none/none/none",
93
- json=self.statement_resp,
94
- )
277
+ # Mock the unified_fetch_id_triples response
278
+ requests_mock.post(
279
+ url="https://kfinance.kensho.com/api/v1/ids",
280
+ json={
281
+ "identifiers_to_id_triples": {
282
+ "C_1": {"company_id": 1, "security_id": 101, "trading_item_id": 201},
283
+ "C_2": {"company_id": 2, "security_id": 102, "trading_item_id": 202},
284
+ },
285
+ "errors": {},
286
+ },
287
+ )
288
+
289
+ # Mock the fetch_statement response with different data for different companies
290
+ requests_mock.post(
291
+ url="https://kfinance.kensho.com/api/v1/statements/",
292
+ json={
293
+ "results": {"1": {"currency": "USD", "periods": {}}, "2": self.statement_resp},
294
+ "errors": {},
295
+ },
296
+ )
95
297
 
96
298
  tool = GetFinancialStatementFromIdentifiers(kfinance_client=mock_client)
97
299
  args = GetFinancialStatementFromIdentifiersArgs(
@@ -19,4 +19,4 @@ class KfinanceMcp(FastMCP):
19
19
  call_tool(validate_input=False) turns off the mcp sdk validation.
20
20
  """
21
21
  super()._setup_handlers()
22
- self._mcp_server.call_tool(validate_input=False)(self._mcp_call_tool)
22
+ self._mcp_server.call_tool(validate_input=False)(self._call_tool_mcp)
@@ -55,6 +55,7 @@ def test_run_notebook(jupyter_kernel_name: str):
55
55
  # - mocks for all calls made by the client while executing the notebook
56
56
  startup_cell_code = dedent("""
57
57
  from datetime import datetime
58
+ from urllib.parse import quote
58
59
  from kfinance.client.kfinance import Client
59
60
  kfinance_client = Client(refresh_token="foo")
60
61
  api_client = kfinance_client.kfinance_api_client
@@ -85,22 +86,44 @@ def test_run_notebook(jupyter_kernel_name: str):
85
86
  )
86
87
 
87
88
  balance_sheet_resp = {
88
- "statements": {
89
- "2022Q3": {"Cash And Equivalents": "1387000000.000000"},
90
- "2022Q4": {"Cash And Equivalents": "1286000000.000000"}
89
+ "currency": "USD",
90
+ "periods": {
91
+ "CY2022Q3": {
92
+ "period_end_date": "2022-09-30",
93
+ "num_months": 3,
94
+ "statements": [
95
+ {
96
+ "name": "Balance Sheet",
97
+ "line_items": [
98
+ {"name": "Cash And Equivalents", "value": "1387000000.000000", "sources": []}
99
+ ]
100
+ }
101
+ ]
102
+ },
103
+ "CY2022Q4": {
104
+ "period_end_date": "2022-12-31",
105
+ "num_months": 3,
106
+ "statements": [
107
+ {
108
+ "name": "Balance Sheet",
109
+ "line_items": [
110
+ {"name": "Cash And Equivalents", "value": "1286000000.000000", "sources": []}
111
+ ]
112
+ }
113
+ ]
114
+ }
91
115
  }
92
116
  }
93
117
 
94
- # spgi.balance_sheet()
95
- mocker.get(
96
- url="https://kfinance.kensho.com/api/v1/statements/21719/balance_sheet/none/none/none/none/none",
97
- json=balance_sheet_resp
98
- )
99
-
100
- # spgi.balance_sheet(period_type=PeriodType.annual, start_year=2010, end_year=2019)
101
- mocker.get(
102
- url="https://kfinance.kensho.com/api/v1/statements/21719/balance_sheet/annual/2010/2019/none/none",
103
- json=balance_sheet_resp
118
+ # spgi.balance_sheet() and spgi.balance_sheet(period_type=PeriodType.annual, start_year=2010, end_year=2019)
119
+ mocker.post(
120
+ url="https://kfinance.kensho.com/api/v1/statements/",
121
+ json={
122
+ "results": {
123
+ "21719": balance_sheet_resp
124
+ },
125
+ "errors": {}
126
+ }
104
127
  )
105
128
 
106
129
  # kfinance_client.ticker("JPM").balance_sheet()
@@ -112,9 +135,27 @@ def test_run_notebook(jupyter_kernel_name: str):
112
135
  )
113
136
 
114
137
  # spgi.net_income(period_type=PeriodType.annual, start_year=2010, end_year=2019)
115
- mocker.get(
116
- url="https://kfinance.kensho.com/api/v1/line_item/21719/net_income/annual/2010/2019/none/none",
117
- json={"line_item": {"2010": "828000000.000000"}}
138
+ mocker.post(
139
+ url="https://kfinance.kensho.com/api/v1/line_item/",
140
+ json={
141
+ "results": {
142
+ "21719": {
143
+ "currency": "USD",
144
+ "periods": {
145
+ "CY2010": {
146
+ "period_end_date": "2010-12-31",
147
+ "num_months": 12,
148
+ "line_item": {
149
+ "name": "Net Income",
150
+ "value": "828000000.000000",
151
+ "sources": []
152
+ }
153
+ }
154
+ }
155
+ }
156
+ },
157
+ "errors": {}
158
+ }
118
159
  )
119
160
 
120
161
  prices_resp = {
@@ -25,7 +25,10 @@ from kfinance.domains.mergers_and_acquisitions.merger_and_acquisition_tools impo
25
25
  GetMergerInfoFromTransactionId,
26
26
  GetMergersFromIdentifiers,
27
27
  )
28
- from kfinance.domains.prices.price_tools import GetPricesFromIdentifiers
28
+ from kfinance.domains.prices.price_tools import (
29
+ GetHistoryMetadataFromIdentifiers,
30
+ GetPricesFromIdentifiers,
31
+ )
29
32
  from kfinance.domains.segments.segment_tools import GetSegmentsFromIdentifiers
30
33
  from kfinance.domains.statements.statement_tools import GetFinancialStatementFromIdentifiers
31
34
  from kfinance.integrations.tool_calling.static_tools.get_latest import GetLatest
@@ -61,6 +64,7 @@ ALL_TOOLS: list[type[KfinanceTool]] = [
61
64
  GetFinancialLineItemFromIdentifiers,
62
65
  # Prices
63
66
  GetPricesFromIdentifiers,
67
+ GetHistoryMetadataFromIdentifiers,
64
68
  # Segments
65
69
  GetSegmentsFromIdentifiers,
66
70
  # Statements
@@ -11,6 +11,11 @@ class GetNQuartersAgoArgs(BaseModel):
11
11
  n: int = Field(description="Number of quarters before the current quarter")
12
12
 
13
13
 
14
+ class GetNQuartersAgoResp(BaseModel):
15
+ year: int
16
+ quarter: int
17
+
18
+
14
19
  class GetNQuartersAgo(KfinanceTool):
15
20
  name: str = "get_n_quarters_ago"
16
21
  description: str = (
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  import time_machine
4
4
 
5
5
  from kfinance.client.kfinance import Client
6
+ from kfinance.client.models.date_and_period_models import LatestPeriods
6
7
  from kfinance.integrations.tool_calling.static_tools.get_latest import GetLatest, GetLatestArgs
7
8
 
8
9
 
@@ -15,16 +16,18 @@ class TestGetLatest:
15
16
  THEN we get back latest info
16
17
  """
17
18
 
18
- expected_resp = {
19
- "annual": {"latest_year": 2024},
20
- "now": {
21
- "current_date": "2025-01-01",
22
- "current_month": 1,
23
- "current_quarter": 1,
24
- "current_year": 2025,
25
- },
26
- "quarterly": {"latest_quarter": 4, "latest_year": 2024},
27
- }
19
+ expected_resp = LatestPeriods.model_validate(
20
+ {
21
+ "annual": {"latest_year": 2024},
22
+ "now": {
23
+ "current_date": "2025-01-01",
24
+ "current_month": 1,
25
+ "current_quarter": 1,
26
+ "current_year": 2025,
27
+ },
28
+ "quarterly": {"latest_quarter": 4, "latest_year": 2024},
29
+ }
30
+ )
28
31
  tool = GetLatest(kfinance_client=mock_client)
29
32
  resp = tool.run(GetLatestArgs().model_dump(mode="json"))
30
33
  assert resp == expected_resp
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  import time_machine
4
4
 
5
5
  from kfinance.client.kfinance import Client
6
+ from kfinance.client.models.date_and_period_models import YearAndQuarter
6
7
  from kfinance.integrations.tool_calling.static_tools.get_n_quarters_ago import (
7
8
  GetNQuartersAgo,
8
9
  GetNQuartersAgoArgs,
@@ -18,7 +19,7 @@ class TestGetNQuartersAgo:
18
19
  THEN we get back 3 quarters ago
19
20
  """
20
21
 
21
- expected_resp = {"quarter": 2, "year": 2024}
22
+ expected_resp = YearAndQuarter(year=2024, quarter=2)
22
23
  tool = GetNQuartersAgo(kfinance_client=mock_client)
23
24
  resp = tool.run(GetNQuartersAgoArgs(n=3).model_dump(mode="json"))
24
25
  assert resp == expected_resp
@@ -7,7 +7,11 @@ from requests_mock import Mocker
7
7
 
8
8
  from kfinance.client.kfinance import Client
9
9
  from kfinance.conftest import SPGI_COMPANY_ID
10
- from kfinance.domains.companies.company_tools import GetInfoFromIdentifiers
10
+ from kfinance.domains.companies.company_models import COMPANY_ID_PREFIX
11
+ from kfinance.domains.companies.company_tools import (
12
+ GetInfoFromIdentifiers,
13
+ GetInfoFromIdentifiersResp,
14
+ )
11
15
  from kfinance.integrations.tool_calling.tool_calling_models import ValidQuarter
12
16
 
13
17
 
@@ -22,13 +26,20 @@ class TestGetEndpointsFromToolCallsWithGrounding:
22
26
  """
23
27
 
24
28
  # truncated from the original
25
- resp_data = {"name": "S&P Global Inc.", "status": "Operating"}
29
+ resp_data = {
30
+ "name": "S&P Global Inc.",
31
+ "status": "Operating",
32
+ "company_id": f"{COMPANY_ID_PREFIX}{SPGI_COMPANY_ID}",
33
+ }
26
34
  resp_endpoint = [
27
35
  "https://kfinance.kensho.com/api/v1/ids",
28
36
  "https://kfinance.kensho.com/api/v1/info/21719",
29
37
  ]
30
- expected_resp = {"data": {"results": {"SPGI": resp_data}}, "endpoint_urls": resp_endpoint}
31
-
38
+ expected_resp = {
39
+ "data": GetInfoFromIdentifiersResp.model_validate({"results": {"SPGI": resp_data}}),
40
+ "endpoint_urls": resp_endpoint,
41
+ }
42
+ del resp_data["company_id"]
32
43
  requests_mock.get(
33
44
  url=f"https://kfinance.kensho.com/api/v1/info/{SPGI_COMPANY_ID}",
34
45
  json=resp_data,
@@ -1,7 +1,14 @@
1
+ import abc
1
2
  from typing import Annotated, Any, Callable, Dict, Literal, Type
2
3
 
3
4
  from langchain_core.tools import BaseTool
4
- from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, model_serializer
5
+ from pydantic import (
6
+ BaseModel,
7
+ BeforeValidator,
8
+ ConfigDict,
9
+ Field,
10
+ model_serializer,
11
+ )
5
12
 
6
13
  from kfinance.client.kfinance import Client
7
14
  from kfinance.client.permission_models import Permission
@@ -22,7 +29,7 @@ class KfinanceTool(BaseTool):
22
29
 
23
30
  model_config = ConfigDict(extra="forbid")
24
31
 
25
- def run_without_langchain(self, *args: Any, **kwargs: Any) -> Any:
32
+ def run_without_langchain(self, *args: Any, **kwargs: Any) -> dict:
26
33
  """Execute a Kfinance tool without langchain.
27
34
 
28
35
  Langchain converts json input params into the pydantic args_schema, which means that
@@ -38,7 +45,8 @@ class KfinanceTool(BaseTool):
38
45
  # This behavior matches the langchain handling. See
39
46
  # https://github.com/langchain-ai/langchain/blob/ca39680d2ab0d786bc035930778a5787e7bb5e01/libs/core/langchain_core/tools/base.py#L595-L597
40
47
  args_dict = {k: v for k, v in args_dict.items() if k in kwargs}
41
- return self._run(**args_dict)
48
+ result_model = self._run(**args_dict)
49
+ return result_model.model_dump(mode="json", exclude_none=True)
42
50
 
43
51
  def run_with_grounding(self, *args: Any, **kwargs: Any) -> Any:
44
52
  """Execute a Kfinance tool with grounding support.
@@ -47,7 +55,10 @@ class KfinanceTool(BaseTool):
47
55
  support, for returning the endpoint urls along with the data as citation info for the LRA Data Agent.
48
56
  """
49
57
  with self.kfinance_client.kfinance_api_client.endpoint_tracker() as endpoint_tracker_queue:
50
- data = self.run_without_langchain(*args, **kwargs)
58
+ args_model = self.args_schema.model_validate(kwargs)
59
+ args_dict = args_model.model_dump()
60
+ args_dict = {k: v for k, v in args_dict.items() if k in kwargs}
61
+ result_model = self._run(**args_dict)
51
62
 
52
63
  # After completion of tool data fetching and within the endpoint_tracker context manager scope, dequeue the endpoint_tracker_queue
53
64
  endpoint_urls = []
@@ -55,11 +66,12 @@ class KfinanceTool(BaseTool):
55
66
  endpoint_urls.append(endpoint_tracker_queue.get())
56
67
 
57
68
  return {
58
- "data": data,
69
+ "data": result_model,
59
70
  "endpoint_urls": endpoint_urls,
60
71
  }
61
72
 
62
- def _run(self, *args: Any, **kwargs: Any) -> Any:
73
+ @abc.abstractmethod
74
+ def _run(self, *args: Any, **kwargs: Any) -> BaseModel:
63
75
  """The code to execute the tool.
64
76
 
65
77
  Where feasible and useful, tools should use batch processing to parallelize
kfinance/version.py CHANGED
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '3.2.4'
32
- __version_tuple__ = version_tuple = (3, 2, 4)
31
+ __version__ = version = '4.0.0'
32
+ __version_tuple__ = version_tuple = (4, 0, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None