kensho-kfinance 2.9.0__py3-none-any.whl → 3.0.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.

Potentially problematic release.


This version of kensho-kfinance might be problematic. Click here for more details.

Files changed (135) hide show
  1. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.1.dist-info}/METADATA +1 -1
  2. kensho_kfinance-3.0.1.dist-info/RECORD +111 -0
  3. kfinance/CHANGELOG.md +6 -0
  4. kfinance/__init__.py +1 -0
  5. kfinance/client/README.md +9 -0
  6. kfinance/{batch_request_handling.py → client/batch_request_handling.py} +63 -27
  7. kfinance/{fetch.py → client/fetch.py} +23 -29
  8. kfinance/{kfinance.py → client/kfinance.py} +37 -33
  9. kfinance/{meta_classes.py → client/meta_classes.py} +26 -35
  10. kfinance/{decimal_with_unit.py → client/models/decimal_with_unit.py} +1 -1
  11. kfinance/{tests → client/models/tests}/test_decimal_with_unit.py +1 -1
  12. kfinance/client/permission_models.py +16 -0
  13. kfinance/client/tests/__init__.py +0 -0
  14. kfinance/{tests → client/tests}/test_batch_requests.py +8 -6
  15. kfinance/{tests → client/tests}/test_client.py +25 -19
  16. kfinance/{tests → client/tests}/test_fetch.py +11 -29
  17. kfinance/{tests → client/tests}/test_group_objects.py +1 -1
  18. kfinance/{tests → client/tests}/test_objects.py +33 -29
  19. kfinance/{tests/conftest.py → conftest.py} +14 -2
  20. kfinance/domains/README.md +14 -0
  21. kfinance/domains/__init__.py +0 -0
  22. kfinance/domains/business_relationships/__init__.py +0 -0
  23. kfinance/{models → domains/business_relationships}/business_relationship_models.py +10 -0
  24. kfinance/domains/business_relationships/business_relationship_tools.py +74 -0
  25. kfinance/domains/business_relationships/tests/__init__.py +0 -0
  26. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +55 -0
  27. kfinance/domains/capitalizations/__init__.py +0 -0
  28. kfinance/{models → domains/capitalizations}/capitalization_models.py +24 -17
  29. kfinance/domains/capitalizations/capitalization_tools.py +89 -0
  30. kfinance/domains/capitalizations/tests/__init__.py +0 -0
  31. kfinance/{tests/test_models → domains/capitalizations/tests}/test_capitalization_models.py +8 -10
  32. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +85 -0
  33. kfinance/domains/companies/__init__.py +0 -0
  34. kfinance/domains/companies/company_identifiers.py +175 -0
  35. kfinance/domains/companies/company_models.py +27 -0
  36. kfinance/domains/companies/company_tools.py +66 -0
  37. kfinance/domains/companies/tests/__init__.py +0 -0
  38. kfinance/domains/companies/tests/test_company_tools.py +26 -0
  39. kfinance/domains/competitors/__init__.py +0 -0
  40. kfinance/{models → domains/competitors}/competitor_models.py +7 -0
  41. kfinance/domains/competitors/competitor_tools.py +62 -0
  42. kfinance/domains/competitors/tests/__init__.py +0 -0
  43. kfinance/domains/competitors/tests/test_competitor_tools.py +45 -0
  44. kfinance/domains/cusip_and_isin/__init__.py +0 -0
  45. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +80 -0
  46. kfinance/domains/cusip_and_isin/tests/__init__.py +0 -0
  47. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +57 -0
  48. kfinance/domains/earnings/__init__.py +0 -0
  49. kfinance/domains/earnings/earning_models.py +41 -0
  50. kfinance/domains/earnings/earning_tools.py +174 -0
  51. kfinance/domains/earnings/tests/__init__.py +0 -0
  52. kfinance/domains/earnings/tests/test_earnings_tools.py +195 -0
  53. kfinance/domains/line_items/__init__.py +0 -0
  54. kfinance/domains/line_items/line_item_tools.py +117 -0
  55. kfinance/domains/line_items/tests/__init__.py +0 -0
  56. kfinance/domains/line_items/tests/test_line_item_tools.py +86 -0
  57. kfinance/domains/mergers_and_acquisitions/__init__.py +0 -0
  58. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +176 -0
  59. kfinance/domains/mergers_and_acquisitions/tests/__init__.py +0 -0
  60. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +124 -0
  61. kfinance/domains/prices/__init__.py +0 -0
  62. kfinance/{models → domains/prices}/price_models.py +1 -1
  63. kfinance/domains/prices/price_tools.py +165 -0
  64. kfinance/domains/prices/tests/__init__.py +0 -0
  65. kfinance/{tests/test_models → domains/prices/tests}/test_price_models.py +2 -2
  66. kfinance/domains/prices/tests/test_price_tools.py +141 -0
  67. kfinance/domains/segments/__init__.py +0 -0
  68. kfinance/domains/segments/segment_tools.py +91 -0
  69. kfinance/domains/segments/tests/__init__.py +0 -0
  70. kfinance/domains/segments/tests/test_segment_tools.py +80 -0
  71. kfinance/domains/statements/__init__.py +0 -0
  72. kfinance/domains/statements/statement_tools.py +116 -0
  73. kfinance/domains/statements/tests/__init__.py +0 -0
  74. kfinance/domains/statements/tests/test_statement_tools.py +73 -0
  75. kfinance/integrations/README.md +8 -0
  76. kfinance/integrations/__init__.py +0 -0
  77. kfinance/integrations/mcp/__init__.py +0 -0
  78. kfinance/{mcp.py → integrations/mcp/mcp.py} +2 -2
  79. kfinance/integrations/tests/__init__.py +0 -0
  80. kfinance/{tests → integrations/tests}/test_example_notebook.py +4 -4
  81. kfinance/{tool_calling → integrations/tool_calling}/README.md +2 -2
  82. kfinance/integrations/tool_calling/__init__.py +0 -0
  83. kfinance/integrations/tool_calling/all_tools.py +55 -0
  84. kfinance/{tool_calling → integrations/tool_calling}/prompts.py +3 -2
  85. kfinance/integrations/tool_calling/static_tools/README.md +4 -0
  86. kfinance/integrations/tool_calling/static_tools/__init__.py +0 -0
  87. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_latest.py +3 -3
  88. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_n_quarters_ago.py +3 -3
  89. kfinance/integrations/tool_calling/static_tools/tests/__init__.py +0 -0
  90. kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +30 -0
  91. kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +24 -0
  92. kfinance/integrations/tool_calling/tests/__init__.py +0 -0
  93. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +69 -0
  94. kfinance/{tool_calling/shared_models.py → integrations/tool_calling/tool_calling_models.py} +37 -7
  95. kfinance/models/permission_models.py +1 -0
  96. kfinance/version.py +2 -2
  97. kensho_kfinance-2.9.0.dist-info/RECORD +0 -70
  98. kfinance/models/id_models.py +0 -7
  99. kfinance/prompt.py +0 -526
  100. kfinance/pydantic_models.py +0 -33
  101. kfinance/tests/test_tools.py +0 -804
  102. kfinance/tool_calling/__init__.py +0 -53
  103. kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +0 -42
  104. kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -30
  105. kfinance/tool_calling/get_capitalization_from_identifier.py +0 -35
  106. kfinance/tool_calling/get_competitors_from_identifier.py +0 -25
  107. kfinance/tool_calling/get_cusip_from_ticker.py +0 -20
  108. kfinance/tool_calling/get_earnings.py +0 -33
  109. kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -48
  110. kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -44
  111. kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -17
  112. kfinance/tool_calling/get_info_from_identifier.py +0 -16
  113. kfinance/tool_calling/get_isin_from_ticker.py +0 -20
  114. kfinance/tool_calling/get_latest_earnings.py +0 -30
  115. kfinance/tool_calling/get_merger_info_from_transaction_id.py +0 -69
  116. kfinance/tool_calling/get_mergers_from_identifier.py +0 -44
  117. kfinance/tool_calling/get_next_earnings.py +0 -30
  118. kfinance/tool_calling/get_prices_from_identifier.py +0 -46
  119. kfinance/tool_calling/get_segments_from_identifier.py +0 -44
  120. kfinance/tool_calling/get_transcript.py +0 -23
  121. kfinance/tool_calling/resolve_identifier.py +0 -18
  122. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.1.dist-info}/WHEEL +0 -0
  123. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.1.dist-info}/licenses/AUTHORS.md +0 -0
  124. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.1.dist-info}/licenses/LICENSE +0 -0
  125. {kensho_kfinance-2.9.0.dist-info → kensho_kfinance-3.0.1.dist-info}/top_level.txt +0 -0
  126. /kfinance/{models → client}/__init__.py +0 -0
  127. /kfinance/{models → client}/industry_models.py +0 -0
  128. /kfinance/{tests → client/models}/__init__.py +0 -0
  129. /kfinance/{models → client/models}/currency_models.py +0 -0
  130. /kfinance/{models → client/models}/date_and_period_models.py +0 -0
  131. /kfinance/{tests/test_models → client/models/tests}/__init__.py +0 -0
  132. /kfinance/{server_thread.py → client/server_thread.py} +0 -0
  133. /kfinance/{models → domains/line_items}/line_item_models.py +0 -0
  134. /kfinance/{models → domains/segments}/segment_models.py +0 -0
  135. /kfinance/{models → domains/statements}/statement_models.py +0 -0
@@ -1,804 +0,0 @@
1
- import contextlib
2
- from contextlib import nullcontext as does_not_raise
3
- from datetime import date, datetime
4
-
5
- from langchain_core.utils.function_calling import convert_to_openai_tool
6
- from pydantic import BaseModel, ValidationError
7
- import pytest
8
- from pytest import raises
9
- from requests_mock import Mocker
10
- import time_machine
11
-
12
- from kfinance.kfinance import Client, NoEarningsDataError
13
- from kfinance.models.business_relationship_models import BusinessRelationshipType
14
- from kfinance.models.capitalization_models import Capitalization
15
- from kfinance.models.competitor_models import CompetitorSource
16
- from kfinance.models.segment_models import SegmentType
17
- from kfinance.models.statement_models import StatementType
18
- from kfinance.tests.conftest import SPGI_COMPANY_ID, SPGI_SECURITY_ID, SPGI_TRADING_ITEM_ID
19
- from kfinance.tests.test_objects import MOCK_COMPANY_DB, MOCK_MERGERS_DB, ordered
20
- from kfinance.tool_calling import (
21
- GetCompetitorsFromIdentifier,
22
- GetEarnings,
23
- GetFinancialLineItemFromIdentifier,
24
- GetFinancialStatementFromIdentifier,
25
- GetHistoryMetadataFromIdentifier,
26
- GetInfoFromIdentifier,
27
- GetIsinFromTicker,
28
- GetLatest,
29
- GetLatestEarnings,
30
- GetNextEarnings,
31
- GetNQuartersAgo,
32
- GetPricesFromIdentifier,
33
- GetTranscript,
34
- ResolveIdentifier,
35
- )
36
- from kfinance.tool_calling.get_advisors_for_company_in_transaction_from_identifier import (
37
- GetAdvisorsForCompanyInTransactionFromIdentifier,
38
- GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
39
- )
40
- from kfinance.tool_calling.get_business_relationship_from_identifier import (
41
- GetBusinessRelationshipFromIdentifier,
42
- GetBusinessRelationshipFromIdentifierArgs,
43
- )
44
- from kfinance.tool_calling.get_capitalization_from_identifier import (
45
- GetCapitalizationFromIdentifier,
46
- GetCapitalizationFromIdentifierArgs,
47
- )
48
- from kfinance.tool_calling.get_competitors_from_identifier import (
49
- GetCompetitorsFromIdentifierArgs,
50
- )
51
- from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker, GetCusipFromTickerArgs
52
- from kfinance.tool_calling.get_financial_line_item_from_identifier import (
53
- GetFinancialLineItemFromIdentifierArgs,
54
- )
55
- from kfinance.tool_calling.get_financial_statement_from_identifier import (
56
- GetFinancialStatementFromIdentifierArgs,
57
- )
58
- from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTickerArgs
59
- from kfinance.tool_calling.get_latest import GetLatestArgs
60
- from kfinance.tool_calling.get_merger_info_from_transaction_id import (
61
- GetMergerInfoFromTransactionId,
62
- GetMergerInfoFromTransactionIdArgs,
63
- )
64
- from kfinance.tool_calling.get_mergers_from_identifier import GetMergersFromIdentifier
65
- from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgoArgs
66
- from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifierArgs
67
- from kfinance.tool_calling.get_segments_from_identifier import (
68
- GetSegmentsFromIdentifier,
69
- GetSegmentsFromIdentifierArgs,
70
- )
71
- from kfinance.tool_calling.get_transcript import GetTranscriptArgs
72
- from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier, ValidQuarter
73
-
74
-
75
- class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
76
- def test_get_companies_advising_company_in_transaction_from_identifier(
77
- self, requests_mock: Mocker, mock_client: Client
78
- ):
79
- expected_response = {
80
- "advisors": [
81
- {
82
- "advisor_company_id": 251994106,
83
- "advisor_company_name": "Kensho Technologies, Inc.",
84
- "advisor_type_name": "Professional Mongo Enjoyer",
85
- }
86
- ]
87
- }
88
- transaction_id = 517414
89
- requests_mock.get(
90
- url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}/advisors/21835",
91
- json=expected_response,
92
- )
93
- tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
94
- args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
95
- identifier="MSFT", transaction_id=transaction_id
96
- )
97
- response = tool.run(args.model_dump(mode="json"))
98
- assert response == expected_response["advisors"]
99
-
100
-
101
- class TestGetMergerInfoFromTransactionId:
102
- def test_get_merger_info_from_transaction_id(self, requests_mock: Mocker, mock_client: Client):
103
- expected_response = MOCK_MERGERS_DB["517414"]
104
- transaction_id = 517414
105
- requests_mock.get(
106
- url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}",
107
- json=expected_response,
108
- )
109
- tool = GetMergerInfoFromTransactionId(kfinance_client=mock_client)
110
- args = GetMergerInfoFromTransactionIdArgs(transaction_id=transaction_id)
111
- response = tool.run(args.model_dump(mode="json"))
112
- assert ordered(response) == ordered(expected_response)
113
-
114
-
115
- class TestGetMergersFromIdentifier:
116
- def test_get_mergers_from_identifier(self, requests_mock: Mocker, mock_client: Client):
117
- expected_response = MOCK_COMPANY_DB["21835"]["mergers"]
118
- company_id = 21835
119
- requests_mock.get(
120
- url=f"https://kfinance.kensho.com/api/v1/mergers/{company_id}", json=expected_response
121
- )
122
- tool = GetMergersFromIdentifier(kfinance_client=mock_client)
123
- args = ToolArgsWithIdentifier(identifier="MSFT")
124
- response = tool.run(args.model_dump(mode="json"))
125
- assert ordered(response) == ordered(expected_response)
126
-
127
-
128
- class TestGetBusinessRelationshipFromIdentifier:
129
- def test_get_business_relationship_from_identifier(
130
- self, requests_mock: Mocker, mock_client: Client
131
- ):
132
- """
133
- GIVEN the GetBusinessRelationshipFromIdentifier tool
134
- WHEN we request SPGI suppliers
135
- THEN we get back the SPGI suppliers
136
- """
137
- supplier_resp = {"current": [883103], "previous": [472898, 8182358]}
138
-
139
- requests_mock.get(
140
- url=f"https://kfinance.kensho.com/api/v1/relationship/{SPGI_COMPANY_ID}/supplier",
141
- json=supplier_resp,
142
- )
143
-
144
- tool = GetBusinessRelationshipFromIdentifier(kfinance_client=mock_client)
145
- args = GetBusinessRelationshipFromIdentifierArgs(
146
- identifier="SPGI", business_relationship=BusinessRelationshipType.supplier
147
- )
148
- resp = tool.run(args.model_dump(mode="json"))
149
- # Companies is a set, so we have to sort the result
150
- resp["previous"].sort()
151
- assert resp == supplier_resp
152
-
153
-
154
- class TestGetCapitalizationFromIdentifier:
155
- def test_get_capitalization_from_identifier(self, requests_mock: Mocker, mock_client: Client):
156
- """
157
- GIVEN the GetCapitalizationFromIdentifier tool
158
- WHEN we request the SPGI market cap
159
- THEN we get back the SPGI market cap
160
- """
161
-
162
- requests_mock.get(
163
- url=f"https://kfinance.kensho.com/api/v1/market_cap/{SPGI_COMPANY_ID}/none/none",
164
- json={
165
- "currency": "USD",
166
- "market_caps": [
167
- {
168
- "date": "2024-04-10",
169
- "market_cap": "132766738270.000000",
170
- "tev": "147455738270.000000",
171
- "shares_outstanding": 313099562,
172
- },
173
- {
174
- "date": "2024-04-11",
175
- "market_cap": "132416066761.000000",
176
- "tev": "147105066761.000000",
177
- "shares_outstanding": 313099562,
178
- },
179
- ],
180
- },
181
- )
182
-
183
- expected_response = {
184
- "market_cap": [
185
- {"2024-04-10": {"unit": "USD", "value": "132766738270.00"}},
186
- {"2024-04-11": {"unit": "USD", "value": "132416066761.00"}},
187
- ]
188
- }
189
-
190
- tool = GetCapitalizationFromIdentifier(kfinance_client=mock_client)
191
- args = GetCapitalizationFromIdentifierArgs(
192
- identifier="SPGI", capitalization=Capitalization.market_cap
193
- )
194
- response = tool.run(args.model_dump(mode="json"))
195
- assert response == expected_response
196
-
197
-
198
- class TestGetCusipFromTicker:
199
- def test_get_cusip_from_ticker(self, requests_mock: Mocker, mock_client: Client):
200
- """
201
- GIVEN the GetCusipFromTicker tool
202
- WHEN we pass args with the SPGI ticker
203
- THEN we get back the SPGI cusip
204
- """
205
-
206
- spgi_cusip = "78409V104"
207
- requests_mock.get(
208
- url=f"https://kfinance.kensho.com/api/v1/cusip/{SPGI_SECURITY_ID}",
209
- json={"cusip": spgi_cusip},
210
- )
211
- tool = GetCusipFromTicker(kfinance_client=mock_client)
212
- resp = tool.run(GetCusipFromTickerArgs(ticker_str="SPGI").model_dump(mode="json"))
213
- assert resp == spgi_cusip
214
-
215
-
216
- class TestGetFinancialLineItemFromIdentifier:
217
- def test_get_financial_line_item_from_identifier(
218
- self, mock_client: Client, requests_mock: Mocker
219
- ):
220
- """
221
- GIVEN the GetFinancialLineItemFromIdentifier tool
222
- WHEN we request SPGI revenue
223
- THEN we get back the SPGI revenue
224
- """
225
-
226
- requests_mock.get(
227
- url=f"https://kfinance.kensho.com/api/v1/line_item/{SPGI_COMPANY_ID}/revenue/none/none/none/none/none",
228
- json={
229
- "line_item": {
230
- "2020": "7442000000.000000",
231
- "2021": "8297000000.000000",
232
- "2022": "11181000000.000000",
233
- "2023": "12497000000.000000",
234
- "2024": "14208000000.000000",
235
- }
236
- },
237
- )
238
- expected_response = "| | 2020 | 2021 | 2022 | 2023 | 2024 |\n|:--------|----------:|----------:|-----------:|-----------:|-----------:|\n| revenue | 7.442e+09 | 8.297e+09 | 1.1181e+10 | 1.2497e+10 | 1.4208e+10 |"
239
-
240
- tool = GetFinancialLineItemFromIdentifier(kfinance_client=mock_client)
241
- args = GetFinancialLineItemFromIdentifierArgs(identifier="SPGI", line_item="revenue")
242
- response = tool.run(args.model_dump(mode="json"))
243
- assert response == expected_response
244
-
245
- def test_line_items_and_aliases_included_in_schema(self, mock_client: Client):
246
- """
247
- GIVEN a GetFinancialLineItemFromIdentifier tool
248
- WHEN we generate an openai schema from the tool
249
- THEN all line items and aliases are included in the line item enum
250
- """
251
- tool = GetFinancialLineItemFromIdentifier(kfinance_client=mock_client)
252
- oai_schema = convert_to_openai_tool(tool)
253
- line_items = oai_schema["function"]["parameters"]["properties"]["line_item"]["enum"]
254
- # revenue is a line item
255
- assert "revenue" in line_items
256
- # normal_revenue is an alias for revenue
257
- assert "normal_revenue" in line_items
258
-
259
-
260
- class TestGetFinancialStatementFromIdentifier:
261
- def test_get_financial_statement_from_identifier(
262
- self, mock_client: Client, requests_mock: Mocker
263
- ):
264
- """
265
- GIVEN the GetFinancialLineItemFromIdentifier tool
266
- WHEN we request the SPGI income statement
267
- THEN we get back the SPGI income statement
268
- """
269
-
270
- requests_mock.get(
271
- url=f"https://kfinance.kensho.com/api/v1/statements/{SPGI_COMPANY_ID}/income_statement/none/none/none/none/none",
272
- # truncated from the original API response
273
- json={
274
- "statements": {
275
- "2020": {"Revenues": "7442000000.000000", "Total Revenues": "7442000000.000000"}
276
- }
277
- },
278
- )
279
- expected_response = "| | 2020 |\n|:---------------|----------:|\n| Revenues | 7.442e+09 |\n| Total Revenues | 7.442e+09 |"
280
-
281
- tool = GetFinancialStatementFromIdentifier(kfinance_client=mock_client)
282
- args = GetFinancialStatementFromIdentifierArgs(
283
- identifier="SPGI", statement=StatementType.income_statement
284
- )
285
- response = tool.run(args.model_dump(mode="json"))
286
- assert response == expected_response
287
-
288
-
289
- class TestGetSegmentsFromIdentifier:
290
- def test_get_segments_from_identifier(self, mock_client: Client, requests_mock: Mocker):
291
- """
292
- GIVEN the GetSegmentsFromIdentifier tool
293
- WHEN we request the SPGI business segment
294
- THEN we get back the SPGI business segment
295
- """
296
-
297
- segments_response = {
298
- "segments": {
299
- "2020": {
300
- "Commodity Insights": {
301
- "CAPEX": -7000000.0,
302
- "D&A": 17000000.0,
303
- },
304
- "Unallocated Assets Held for Sale": None,
305
- },
306
- "2021": {
307
- "Commodity Insights": {
308
- "CAPEX": -2000000.0,
309
- "D&A": 12000000.0,
310
- },
311
- "Unallocated Assets Held for Sale": {"Total Assets": 321000000.0},
312
- },
313
- },
314
- }
315
- requests_mock.get(
316
- url=f"https://kfinance.kensho.com/api/v1/segments/{SPGI_COMPANY_ID}/business/none/none/none/none/none",
317
- # truncated from the original API response
318
- json=segments_response,
319
- )
320
-
321
- tool = GetSegmentsFromIdentifier(kfinance_client=mock_client)
322
- args = GetSegmentsFromIdentifierArgs(identifier="SPGI", segment_type=SegmentType.business)
323
- response = tool.run(args.model_dump(mode="json"))
324
- assert response == segments_response["segments"]
325
-
326
-
327
- class TestGetHistoryMetadataFromIdentifier:
328
- def test_get_history_metadata_from_identifier(self, mock_client: Client, requests_mock: Mocker):
329
- """
330
- GIVEN the GetHistoryMetadataFromIdentifier tool
331
- WHEN request history metadata for SPGI
332
- THEN we get back the SPGI history metadata
333
- """
334
-
335
- metadata_resp = {
336
- "currency": "USD",
337
- "exchange_name": "NYSE",
338
- "first_trade_date": "1968-01-02",
339
- "instrument_type": "Equity",
340
- "symbol": "SPGI",
341
- }
342
- expected_resp = {
343
- "currency": "USD",
344
- "exchange_name": "NYSE",
345
- "first_trade_date": date(1968, 1, 2),
346
- "instrument_type": "Equity",
347
- "symbol": "SPGI",
348
- }
349
- requests_mock.get(
350
- url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/metadata",
351
- json=metadata_resp,
352
- )
353
-
354
- tool = GetHistoryMetadataFromIdentifier(kfinance_client=mock_client)
355
- resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
356
- assert resp == expected_resp
357
-
358
-
359
- class TestGetInfoFromIdentifier:
360
- def test_get_info_from_identifier(self, mock_client: Client, requests_mock: Mocker):
361
- """
362
- GIVEN the GetInfoFromIdentifier tool
363
- WHEN request info for SPGI
364
- THEN we get back info for SPGI
365
- """
366
-
367
- # truncated from the original
368
- info_resp = {"name": "S&P Global Inc.", "status": "Operating"}
369
- requests_mock.get(
370
- url=f"https://kfinance.kensho.com/api/v1/info/{SPGI_COMPANY_ID}",
371
- json=info_resp,
372
- )
373
-
374
- tool = GetInfoFromIdentifier(kfinance_client=mock_client)
375
- resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
376
- assert resp == str(info_resp)
377
-
378
-
379
- class TestGetIsinFromTicker:
380
- def test_get_isin_from_ticker(self, requests_mock: Mocker, mock_client: Client):
381
- """
382
- GIVEN the GetIsinFromTicker tool
383
- WHEN we pass args with the SPGI ticker
384
- THEN we get back the SPGI isin
385
- """
386
-
387
- spgi_isin = "US78409V1044"
388
- requests_mock.get(
389
- url=f"https://kfinance.kensho.com/api/v1/isin/{SPGI_SECURITY_ID}",
390
- json={"isin": spgi_isin},
391
- )
392
-
393
- tool = GetIsinFromTicker(kfinance_client=mock_client)
394
- resp = tool.run(GetIsinFromTickerArgs(ticker_str="SPGI").model_dump(mode="json"))
395
- assert resp == spgi_isin
396
-
397
-
398
- class TestGetLatest:
399
- @time_machine.travel(datetime(2025, 1, 1, 12, tzinfo=datetime.now().astimezone().tzinfo))
400
- def test_get_latest(self, mock_client: Client):
401
- """
402
- GIVEN the GetLatest tool
403
- WHEN request latest info
404
- THEN we get back latest info
405
- """
406
-
407
- expected_resp = {
408
- "annual": {"latest_year": 2024},
409
- "now": {
410
- "current_date": "2025-01-01",
411
- "current_month": 1,
412
- "current_quarter": 1,
413
- "current_year": 2025,
414
- },
415
- "quarterly": {"latest_quarter": 4, "latest_year": 2024},
416
- }
417
- tool = GetLatest(kfinance_client=mock_client)
418
- resp = tool.run(GetLatestArgs().model_dump(mode="json"))
419
- assert resp == expected_resp
420
-
421
-
422
- class TestGetNQuartersAgo:
423
- @time_machine.travel(datetime(2025, 1, 1, 12, tzinfo=datetime.now().astimezone().tzinfo))
424
- def test_get_n_quarters_ago(self, mock_client: Client):
425
- """
426
- GIVEN the GetNQuartersAgo tool
427
- WHEN we request 3 quarters ago
428
- THEN we get back 3 quarters ago
429
- """
430
-
431
- expected_resp = {"quarter": 2, "year": 2024}
432
- tool = GetNQuartersAgo(kfinance_client=mock_client)
433
- resp = tool.run(GetNQuartersAgoArgs(n=3).model_dump(mode="json"))
434
- assert resp == expected_resp
435
-
436
-
437
- class TestPricesFromIdentifier:
438
- def test_get_prices_from_identifier(self, mock_client: Client, requests_mock: Mocker):
439
- """
440
- GIVEN the GetPricesFromIdentifier tool
441
- WHEN we request prices for SPGI
442
- THEN we get back prices for SPGI
443
- """
444
-
445
- requests_mock.get(
446
- url=f"https://kfinance.kensho.com/api/v1/pricing/{SPGI_TRADING_ITEM_ID}/none/none/day/adjusted",
447
- # truncated response
448
- json={
449
- "currency": "USD",
450
- "prices": [
451
- {
452
- "date": "2024-04-11",
453
- "open": "424.260000",
454
- "high": "425.990000",
455
- "low": "422.040000",
456
- "close": "422.920000",
457
- "volume": "1129158",
458
- },
459
- {
460
- "date": "2024-04-12",
461
- "open": "419.230000",
462
- "high": "421.940000",
463
- "low": "416.450000",
464
- "close": "417.810000",
465
- "volume": "1182229",
466
- },
467
- ],
468
- },
469
- )
470
- expected_response = {
471
- "prices": [
472
- {
473
- "date": "2024-04-11",
474
- "open": {"value": "424.26", "unit": "USD"},
475
- "high": {"value": "425.99", "unit": "USD"},
476
- "low": {"value": "422.04", "unit": "USD"},
477
- "close": {"value": "422.92", "unit": "USD"},
478
- "volume": {"value": "1129158", "unit": "Shares"},
479
- },
480
- {
481
- "date": "2024-04-12",
482
- "open": {"value": "419.23", "unit": "USD"},
483
- "high": {"value": "421.94", "unit": "USD"},
484
- "low": {"value": "416.45", "unit": "USD"},
485
- "close": {"value": "417.81", "unit": "USD"},
486
- "volume": {"value": "1182229", "unit": "Shares"},
487
- },
488
- ]
489
- }
490
-
491
- tool = GetPricesFromIdentifier(kfinance_client=mock_client)
492
- response = tool.run(GetPricesFromIdentifierArgs(identifier="SPGI").model_dump(mode="json"))
493
- assert response == expected_response
494
-
495
-
496
- class TestResolveIdentifier:
497
- def test_resolve_identifier(self, mock_client: Client):
498
- """
499
- GIVEN the ResolveIdentifier tool
500
- WHEN request to resolve SPGI
501
- THEN we get back a dict with the SPGI company id, security id, and trading item id
502
- """
503
- tool = ResolveIdentifier(kfinance_client=mock_client)
504
- resp = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
505
- assert resp == {
506
- "company_id": SPGI_COMPANY_ID,
507
- "security_id": SPGI_SECURITY_ID,
508
- "trading_item_id": SPGI_TRADING_ITEM_ID,
509
- }
510
-
511
-
512
- class TestGetLatestEarnings:
513
- def test_get_latest_earnings(self, requests_mock: Mocker, mock_client: Client):
514
- """
515
- GIVEN the GetLatestEarnings tool
516
- WHEN we request the latest earnings for SPGI
517
- THEN we get back the latest SPGI earnings
518
- """
519
- earnings_data = {
520
- "earnings": [
521
- {
522
- "name": "SPGI Q4 2024 Earnings Call",
523
- "datetime": "2025-02-11T13:30:00Z",
524
- "keydevid": 12345,
525
- },
526
- {
527
- "name": "SPGI Q3 2024 Earnings Call",
528
- "datetime": "2024-10-30T12:30:00Z",
529
- "keydevid": 12344,
530
- },
531
- ]
532
- }
533
-
534
- requests_mock.get(
535
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
536
- json=earnings_data,
537
- )
538
-
539
- expected_response = {
540
- "name": "SPGI Q4 2024 Earnings Call",
541
- "key_dev_id": 12345,
542
- "datetime": "2025-02-11T13:30:00+00:00",
543
- }
544
-
545
- tool = GetLatestEarnings(kfinance_client=mock_client)
546
- response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
547
- assert response == expected_response
548
-
549
- def test_get_latest_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
550
- """
551
- GIVEN the GetLatestEarnings tool
552
- WHEN we request the latest earnings for a company with no data
553
- THEN we get a NoEarningsDataError exception
554
- """
555
- earnings_data = {"earnings": []}
556
-
557
- requests_mock.get(
558
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
559
- json=earnings_data,
560
- )
561
-
562
- tool = GetLatestEarnings(kfinance_client=mock_client)
563
- with raises(NoEarningsDataError, match="Latest earnings for SPGI not found"):
564
- tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
565
-
566
-
567
- class TestGetNextEarnings:
568
- def test_get_next_earnings_(self, requests_mock: Mocker, mock_client: Client):
569
- """
570
- GIVEN the GetNextEarnings tool
571
- WHEN we request the next earnings for SPGI
572
- THEN we get back the next SPGI earnings
573
- """
574
- earnings_data = {
575
- "earnings": [
576
- {
577
- "name": "SPGI Q1 2025 Earnings Call",
578
- "datetime": "2025-04-29T12:30:00Z",
579
- "keydevid": 12346,
580
- },
581
- {
582
- "name": "SPGI Q4 2024 Earnings Call",
583
- "datetime": "2025-02-11T13:30:00Z",
584
- "keydevid": 12345,
585
- },
586
- ]
587
- }
588
-
589
- requests_mock.get(
590
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
591
- json=earnings_data,
592
- )
593
-
594
- expected_response = {
595
- "name": "SPGI Q1 2025 Earnings Call",
596
- "key_dev_id": 12346,
597
- "datetime": "2025-04-29T12:30:00+00:00",
598
- }
599
-
600
- with time_machine.travel("2025-03-01T00:00:00+00:00"):
601
- tool = GetNextEarnings(kfinance_client=mock_client)
602
- response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
603
- assert response == expected_response
604
-
605
- def test_get_next_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
606
- """
607
- GIVEN the GetNextEarnings tool
608
- WHEN we request the next earnings for a company with no data
609
- THEN we get a NoEarningsDataError exception
610
- """
611
- earnings_data = {"earnings": []}
612
-
613
- requests_mock.get(
614
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
615
- json=earnings_data,
616
- )
617
-
618
- with time_machine.travel("2025-03-01T00:00:00+00:00"):
619
- tool = GetNextEarnings(kfinance_client=mock_client)
620
- with raises(NoEarningsDataError, match="Next earnings for SPGI not found"):
621
- tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
622
-
623
-
624
- class TestGetEarnings:
625
- def test_get_earnings(self, requests_mock: Mocker, mock_client: Client):
626
- """
627
- GIVEN the GetEarnings tool
628
- WHEN we request all earnings for SPGI
629
- THEN we get back all SPGI earnings
630
- """
631
- earnings_data = {
632
- "earnings": [
633
- {
634
- "name": "SPGI Q1 2025 Earnings Call",
635
- "datetime": "2025-04-29T12:30:00Z",
636
- "keydevid": 12346,
637
- },
638
- {
639
- "name": "SPGI Q4 2024 Earnings Call",
640
- "datetime": "2025-02-11T13:30:00Z",
641
- "keydevid": 12345,
642
- },
643
- ]
644
- }
645
-
646
- requests_mock.get(
647
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
648
- json=earnings_data,
649
- )
650
-
651
- expected_response = [
652
- {
653
- "name": "SPGI Q1 2025 Earnings Call",
654
- "key_dev_id": 12346,
655
- "datetime": "2025-04-29T12:30:00+00:00",
656
- },
657
- {
658
- "name": "SPGI Q4 2024 Earnings Call",
659
- "key_dev_id": 12345,
660
- "datetime": "2025-02-11T13:30:00+00:00",
661
- },
662
- ]
663
-
664
- tool = GetEarnings(kfinance_client=mock_client)
665
- response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
666
- assert response == expected_response
667
-
668
- def test_get_earnings_no_data(self, requests_mock: Mocker, mock_client: Client):
669
- """
670
- GIVEN the GetEarnings tool
671
- WHEN we request all earnings for a company with no data
672
- THEN we get a NoEarningslDataError exception
673
- """
674
- earnings_data = {"earnings": []}
675
-
676
- requests_mock.get(
677
- url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}",
678
- json=earnings_data,
679
- )
680
-
681
- tool = GetEarnings(kfinance_client=mock_client)
682
- with raises(NoEarningsDataError, match="Earnings for SPGI not found"):
683
- tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
684
-
685
-
686
- class TestGetTranscript:
687
- def test_get_transcript(self, requests_mock: Mocker, mock_client: Client):
688
- """
689
- GIVEN the GetTranscript tool
690
- WHEN we request a transcript by key_dev_id
691
- THEN we get back the transcript text
692
- """
693
- transcript_data = {
694
- "transcript": [
695
- {
696
- "person_name": "Operator",
697
- "text": "Good morning, everyone.",
698
- "component_type": "speech",
699
- },
700
- {
701
- "person_name": "CEO",
702
- "text": "Thank you for joining us today.",
703
- "component_type": "speech",
704
- },
705
- ]
706
- }
707
-
708
- requests_mock.get(
709
- url="https://kfinance.kensho.com/api/v1/transcript/12345",
710
- json=transcript_data,
711
- )
712
-
713
- expected_response = (
714
- "Operator: Good morning, everyone.\n\nCEO: Thank you for joining us today."
715
- )
716
-
717
- tool = GetTranscript(kfinance_client=mock_client)
718
- response = tool.run(GetTranscriptArgs(key_dev_id=12345).model_dump(mode="json"))
719
- assert response == expected_response
720
-
721
-
722
- class TestGetCompetitorsFromIdentifier:
723
- def test_get_competitors_from_identifier(self, mock_client: Client, requests_mock: Mocker):
724
- """
725
- GIVEN the GetCompetitorsFromIdentifier tool
726
- WHEN we request the SPGI competitors that are named by competitors
727
- THEN we get back the SPGI competitors that are named by competitors
728
- """
729
- expected_competitors_response = {
730
- "companies": [
731
- {"company_id": 35352, "company_name": "The Descartes Systems Group Inc."},
732
- {"company_id": 4003514, "company_name": "London Stock Exchange Group plc"},
733
- ]
734
- }
735
- requests_mock.get(
736
- url=f"https://kfinance.kensho.com/api/v1/competitors/{SPGI_COMPANY_ID}/named_by_competitor",
737
- # truncated from the original API response
738
- json=expected_competitors_response,
739
- )
740
-
741
- tool = GetCompetitorsFromIdentifier(kfinance_client=mock_client)
742
- args = GetCompetitorsFromIdentifierArgs(
743
- identifier="SPGI", competitor_source=CompetitorSource.named_by_competitor
744
- )
745
- response = tool.run(args.model_dump(mode="json"))
746
- assert response == expected_competitors_response
747
-
748
-
749
- class TestGetEndpointsFromToolCallsWithGrounding:
750
- def test_get_info_from_identifier_with_grounding(
751
- self, mock_client: Client, requests_mock: Mocker
752
- ):
753
- """
754
- GIVEN a KfinanceTool tool
755
- WHEN we run the tool with `run_with_grounding`
756
- THEN we get back endpoint urls in addition to the usual tool response.
757
- """
758
-
759
- # truncated from the original
760
- resp_data = "{'name': 'S&P Global Inc.', 'status': 'Operating'}"
761
- resp_endpoint = [
762
- "https://kfinance.kensho.com/api/v1/id/SPGI",
763
- "https://kfinance.kensho.com/api/v1/info/21719",
764
- ]
765
- expected_resp = {"data": resp_data, "endpoint_urls": resp_endpoint}
766
-
767
- requests_mock.get(
768
- url=f"https://kfinance.kensho.com/api/v1/info/{SPGI_COMPANY_ID}",
769
- json=resp_data,
770
- )
771
-
772
- tool = GetInfoFromIdentifier(kfinance_client=mock_client)
773
- resp = tool.run_with_grounding(identifier="SPGI")
774
- assert resp == expected_resp
775
-
776
-
777
- class TestValidQuarter:
778
- class QuarterModel(BaseModel):
779
- quarter: ValidQuarter | None
780
-
781
- @pytest.mark.parametrize(
782
- "input_quarter, expectation, expected_quarter",
783
- [
784
- pytest.param(1, does_not_raise(), 1, id="int input works"),
785
- pytest.param("1", does_not_raise(), 1, id="str input works"),
786
- pytest.param(None, does_not_raise(), None, id="None input works"),
787
- pytest.param(5, pytest.raises(ValidationError), None, id="invalid int raises"),
788
- pytest.param("5", pytest.raises(ValidationError), None, id="invalid str raises"),
789
- ],
790
- )
791
- def test_valid_quarter(
792
- self,
793
- input_quarter: int | str | None,
794
- expectation: contextlib.AbstractContextManager,
795
- expected_quarter: int | None,
796
- ) -> None:
797
- """
798
- GIVEN a model that uses `ValidQuarter`
799
- WHEN we deserialize with int, str, or None
800
- THEN valid str get coerced to int. Invalid values raise.
801
- """
802
- with expectation:
803
- res = self.QuarterModel.model_validate(dict(quarter=input_quarter))
804
- assert res.quarter == expected_quarter