kensho-kfinance 2.4.2__py3-none-any.whl → 2.6.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.
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/METADATA +1 -1
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/RECORD +29 -26
- kfinance/CHANGELOG.md +15 -0
- kfinance/batch_request_handling.py +2 -16
- kfinance/constants.py +14 -2
- kfinance/fetch.py +56 -0
- kfinance/kfinance.py +403 -90
- kfinance/mcp.py +23 -35
- kfinance/meta_classes.py +37 -12
- kfinance/tests/conftest.py +4 -0
- kfinance/tests/scratch.py +0 -0
- kfinance/tests/test_batch_requests.py +0 -32
- kfinance/tests/test_fetch.py +21 -0
- kfinance/tests/test_objects.py +239 -3
- kfinance/tests/test_tools.py +136 -24
- kfinance/tool_calling/__init__.py +2 -4
- kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +39 -0
- kfinance/tool_calling/get_competitors_from_identifier.py +24 -0
- kfinance/tool_calling/get_financial_line_item_from_identifier.py +3 -3
- kfinance/tool_calling/get_financial_statement_from_identifier.py +3 -3
- kfinance/tool_calling/get_merger_info_from_transaction_id.py +62 -0
- kfinance/tool_calling/get_mergers_from_identifier.py +41 -0
- kfinance/tool_calling/get_segments_from_identifier.py +3 -3
- kfinance/tool_calling/shared_models.py +17 -2
- kfinance/version.py +2 -2
- kfinance/tests/test_mcp.py +0 -16
- kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +0 -19
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/WHEEL +0 -0
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.4.2.dist-info → kensho_kfinance-2.6.1.dist-info}/top_level.txt +0 -0
kfinance/tests/test_tools.py
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
from contextlib import nullcontext as does_not_raise
|
|
1
3
|
from datetime import date, datetime
|
|
2
4
|
|
|
3
5
|
from langchain_core.utils.function_calling import convert_to_openai_tool
|
|
6
|
+
from pydantic import BaseModel, ValidationError
|
|
7
|
+
import pytest
|
|
4
8
|
from pytest import raises
|
|
5
9
|
from requests_mock import Mocker
|
|
6
10
|
import time_machine
|
|
7
11
|
|
|
8
|
-
from kfinance.constants import
|
|
12
|
+
from kfinance.constants import (
|
|
13
|
+
BusinessRelationshipType,
|
|
14
|
+
Capitalization,
|
|
15
|
+
CompetitorSource,
|
|
16
|
+
SegmentType,
|
|
17
|
+
StatementType,
|
|
18
|
+
)
|
|
9
19
|
from kfinance.kfinance import Client, NoEarningsDataError
|
|
10
20
|
from kfinance.tests.conftest import SPGI_COMPANY_ID, SPGI_SECURITY_ID, SPGI_TRADING_ITEM_ID
|
|
21
|
+
from kfinance.tests.test_objects import MOCK_COMPANY_DB, MOCK_MERGERS_DB, ordered
|
|
11
22
|
from kfinance.tool_calling import (
|
|
23
|
+
GetCompetitorsFromIdentifier,
|
|
12
24
|
GetEarnings,
|
|
13
|
-
GetEarningsCallDatetimesFromIdentifier,
|
|
14
25
|
GetFinancialLineItemFromIdentifier,
|
|
15
26
|
GetFinancialStatementFromIdentifier,
|
|
16
27
|
GetHistoryMetadataFromIdentifier,
|
|
@@ -24,6 +35,10 @@ from kfinance.tool_calling import (
|
|
|
24
35
|
GetTranscript,
|
|
25
36
|
ResolveIdentifier,
|
|
26
37
|
)
|
|
38
|
+
from kfinance.tool_calling.get_advisors_for_company_in_transaction_from_identifier import (
|
|
39
|
+
GetAdvisorsForCompanyInTransactionFromIdentifier,
|
|
40
|
+
GetAdvisorsForCompanyInTransactionFromIdentifierArgs,
|
|
41
|
+
)
|
|
27
42
|
from kfinance.tool_calling.get_business_relationship_from_identifier import (
|
|
28
43
|
GetBusinessRelationshipFromIdentifier,
|
|
29
44
|
GetBusinessRelationshipFromIdentifierArgs,
|
|
@@ -32,6 +47,9 @@ from kfinance.tool_calling.get_capitalization_from_identifier import (
|
|
|
32
47
|
GetCapitalizationFromIdentifier,
|
|
33
48
|
GetCapitalizationFromIdentifierArgs,
|
|
34
49
|
)
|
|
50
|
+
from kfinance.tool_calling.get_competitors_from_identifier import (
|
|
51
|
+
GetCompetitorsFromIdentifierArgs,
|
|
52
|
+
)
|
|
35
53
|
from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker, GetCusipFromTickerArgs
|
|
36
54
|
from kfinance.tool_calling.get_financial_line_item_from_identifier import (
|
|
37
55
|
GetFinancialLineItemFromIdentifierArgs,
|
|
@@ -41,6 +59,11 @@ from kfinance.tool_calling.get_financial_statement_from_identifier import (
|
|
|
41
59
|
)
|
|
42
60
|
from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTickerArgs
|
|
43
61
|
from kfinance.tool_calling.get_latest import GetLatestArgs
|
|
62
|
+
from kfinance.tool_calling.get_merger_info_from_transaction_id import (
|
|
63
|
+
GetMergerInfoFromTransactionId,
|
|
64
|
+
GetMergerInfoFromTransactionIdArgs,
|
|
65
|
+
)
|
|
66
|
+
from kfinance.tool_calling.get_mergers_from_identifier import GetMergersFromIdentifier
|
|
44
67
|
from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgoArgs
|
|
45
68
|
from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifierArgs
|
|
46
69
|
from kfinance.tool_calling.get_segments_from_identifier import (
|
|
@@ -48,7 +71,60 @@ from kfinance.tool_calling.get_segments_from_identifier import (
|
|
|
48
71
|
GetSegmentsFromIdentifierArgs,
|
|
49
72
|
)
|
|
50
73
|
from kfinance.tool_calling.get_transcript import GetTranscriptArgs
|
|
51
|
-
from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier
|
|
74
|
+
from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier, ValidQuarter
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestGetCompaniesAdvisingCompanyInTransactionFromIdentifier:
|
|
78
|
+
def test_get_companies_advising_company_in_transaction_from_identifier(
|
|
79
|
+
self, requests_mock: Mocker, mock_client: Client
|
|
80
|
+
):
|
|
81
|
+
expected_response = {
|
|
82
|
+
"advisors": [
|
|
83
|
+
{
|
|
84
|
+
"advisor_company_id": 251994106,
|
|
85
|
+
"advisor_company_name": "Kensho Technologies, Inc.",
|
|
86
|
+
"advisor_type_name": "Professional Mongo Enjoyer",
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
transaction_id = 517414
|
|
91
|
+
requests_mock.get(
|
|
92
|
+
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}/advisors/21835",
|
|
93
|
+
json=expected_response,
|
|
94
|
+
)
|
|
95
|
+
tool = GetAdvisorsForCompanyInTransactionFromIdentifier(kfinance_client=mock_client)
|
|
96
|
+
args = GetAdvisorsForCompanyInTransactionFromIdentifierArgs(
|
|
97
|
+
identifier="MSFT", transaction_id=transaction_id
|
|
98
|
+
)
|
|
99
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
100
|
+
assert response == expected_response["advisors"]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class TestGetMergerInfoFromTransactionId:
|
|
104
|
+
def test_get_merger_info_from_transaction_id(self, requests_mock: Mocker, mock_client: Client):
|
|
105
|
+
expected_response = MOCK_MERGERS_DB["517414"]
|
|
106
|
+
transaction_id = 517414
|
|
107
|
+
requests_mock.get(
|
|
108
|
+
url=f"https://kfinance.kensho.com/api/v1/merger/info/{transaction_id}",
|
|
109
|
+
json=expected_response,
|
|
110
|
+
)
|
|
111
|
+
tool = GetMergerInfoFromTransactionId(kfinance_client=mock_client)
|
|
112
|
+
args = GetMergerInfoFromTransactionIdArgs(transaction_id=transaction_id)
|
|
113
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
114
|
+
assert ordered(response) == ordered(expected_response)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TestGetMergersFromIdentifier:
|
|
118
|
+
def test_get_mergers_from_identifier(self, requests_mock: Mocker, mock_client: Client):
|
|
119
|
+
expected_response = MOCK_COMPANY_DB["21835"]["mergers"]
|
|
120
|
+
company_id = 21835
|
|
121
|
+
requests_mock.get(
|
|
122
|
+
url=f"https://kfinance.kensho.com/api/v1/mergers/{company_id}", json=expected_response
|
|
123
|
+
)
|
|
124
|
+
tool = GetMergersFromIdentifier(kfinance_client=mock_client)
|
|
125
|
+
args = ToolArgsWithIdentifier(identifier="MSFT")
|
|
126
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
127
|
+
assert ordered(response) == ordered(expected_response)
|
|
52
128
|
|
|
53
129
|
|
|
54
130
|
class TestGetBusinessRelationshipFromIdentifier:
|
|
@@ -133,27 +209,6 @@ class TestGetCusipFromTicker:
|
|
|
133
209
|
assert resp == spgi_cusip
|
|
134
210
|
|
|
135
211
|
|
|
136
|
-
class TestGetEarningsCallDatetimesFromTicker:
|
|
137
|
-
def test_get_earnings_call_datetimes_from_ticker(
|
|
138
|
-
self, requests_mock: Mocker, mock_client: Client
|
|
139
|
-
):
|
|
140
|
-
"""
|
|
141
|
-
GIVEN the GetEarningsCallDatetimesFromIdentifier tool
|
|
142
|
-
WHEN we request earnings call datetimes for SPGI
|
|
143
|
-
THEN we get back the expected SPGI earnings call datetimes
|
|
144
|
-
"""
|
|
145
|
-
|
|
146
|
-
requests_mock.get(
|
|
147
|
-
url=f"https://kfinance.kensho.com/api/v1/earnings/{SPGI_COMPANY_ID}/dates",
|
|
148
|
-
json={"earnings": ["2025-04-29T12:30:00", "2025-02-11T13:30:00"]},
|
|
149
|
-
)
|
|
150
|
-
expected_response = '["2025-04-29T12:30:00+00:00", "2025-02-11T13:30:00+00:00"]'
|
|
151
|
-
|
|
152
|
-
tool = GetEarningsCallDatetimesFromIdentifier(kfinance_client=mock_client)
|
|
153
|
-
response = tool.run(ToolArgsWithIdentifier(identifier="SPGI").model_dump(mode="json"))
|
|
154
|
-
assert response == expected_response
|
|
155
|
-
|
|
156
|
-
|
|
157
212
|
class TestGetFinancialLineItemFromIdentifier:
|
|
158
213
|
def test_get_financial_line_item_from_identifier(
|
|
159
214
|
self, mock_client: Client, requests_mock: Mocker
|
|
@@ -638,3 +693,60 @@ class TestGetTranscript:
|
|
|
638
693
|
tool = GetTranscript(kfinance_client=mock_client)
|
|
639
694
|
response = tool.run(GetTranscriptArgs(key_dev_id=12345).model_dump(mode="json"))
|
|
640
695
|
assert response == expected_response
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
class TestGetCompetitorsFromIdentifier:
|
|
699
|
+
def test_get_competitors_from_identifier(self, mock_client: Client, requests_mock: Mocker):
|
|
700
|
+
"""
|
|
701
|
+
GIVEN the GetCompetitorsFromIdentifier tool
|
|
702
|
+
WHEN we request the SPGI competitors that are named by competitors
|
|
703
|
+
THEN we get back the SPGI competitors that are named by competitors
|
|
704
|
+
"""
|
|
705
|
+
expected_competitors_response = {
|
|
706
|
+
"companies": [
|
|
707
|
+
{"company_id": 35352, "company_name": "The Descartes Systems Group Inc."},
|
|
708
|
+
{"company_id": 4003514, "company_name": "London Stock Exchange Group plc"},
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
requests_mock.get(
|
|
712
|
+
url=f"https://kfinance.kensho.com/api/v1/competitors/{SPGI_COMPANY_ID}/named_by_competitor",
|
|
713
|
+
# truncated from the original API response
|
|
714
|
+
json=expected_competitors_response,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
tool = GetCompetitorsFromIdentifier(kfinance_client=mock_client)
|
|
718
|
+
args = GetCompetitorsFromIdentifierArgs(
|
|
719
|
+
identifier="SPGI", competitor_source=CompetitorSource.named_by_competitor
|
|
720
|
+
)
|
|
721
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
722
|
+
assert response == expected_competitors_response
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
class TestValidQuarter:
|
|
726
|
+
class QuarterModel(BaseModel):
|
|
727
|
+
quarter: ValidQuarter | None
|
|
728
|
+
|
|
729
|
+
@pytest.mark.parametrize(
|
|
730
|
+
"input_quarter, expectation, expected_quarter",
|
|
731
|
+
[
|
|
732
|
+
pytest.param(1, does_not_raise(), 1, id="int input works"),
|
|
733
|
+
pytest.param("1", does_not_raise(), 1, id="str input works"),
|
|
734
|
+
pytest.param(None, does_not_raise(), None, id="None input works"),
|
|
735
|
+
pytest.param(5, pytest.raises(ValidationError), None, id="invalid int raises"),
|
|
736
|
+
pytest.param("5", pytest.raises(ValidationError), None, id="invalid str raises"),
|
|
737
|
+
],
|
|
738
|
+
)
|
|
739
|
+
def test_valid_quarter(
|
|
740
|
+
self,
|
|
741
|
+
input_quarter: int | str | None,
|
|
742
|
+
expectation: contextlib.AbstractContextManager,
|
|
743
|
+
expected_quarter: int | None,
|
|
744
|
+
) -> None:
|
|
745
|
+
"""
|
|
746
|
+
GIVEN a model that uses `ValidQuarter`
|
|
747
|
+
WHEN we deserialize with int, str, or None
|
|
748
|
+
THEN valid str get coerced to int. Invalid values raise.
|
|
749
|
+
"""
|
|
750
|
+
with expectation:
|
|
751
|
+
res = self.QuarterModel.model_validate(dict(quarter=input_quarter))
|
|
752
|
+
assert res.quarter == expected_quarter
|
|
@@ -4,11 +4,9 @@ from kfinance.tool_calling.get_business_relationship_from_identifier import (
|
|
|
4
4
|
GetBusinessRelationshipFromIdentifier,
|
|
5
5
|
)
|
|
6
6
|
from kfinance.tool_calling.get_capitalization_from_identifier import GetCapitalizationFromIdentifier
|
|
7
|
+
from kfinance.tool_calling.get_competitors_from_identifier import GetCompetitorsFromIdentifier
|
|
7
8
|
from kfinance.tool_calling.get_cusip_from_ticker import GetCusipFromTicker
|
|
8
9
|
from kfinance.tool_calling.get_earnings import GetEarnings
|
|
9
|
-
from kfinance.tool_calling.get_earnings_call_datetimes_from_identifier import (
|
|
10
|
-
GetEarningsCallDatetimesFromIdentifier,
|
|
11
|
-
)
|
|
12
10
|
from kfinance.tool_calling.get_financial_line_item_from_identifier import (
|
|
13
11
|
GetFinancialLineItemFromIdentifier,
|
|
14
12
|
)
|
|
@@ -39,7 +37,6 @@ ALL_TOOLS: list[Type[KfinanceTool]] = [
|
|
|
39
37
|
GetIsinFromTicker,
|
|
40
38
|
GetCusipFromTicker,
|
|
41
39
|
GetInfoFromIdentifier,
|
|
42
|
-
GetEarningsCallDatetimesFromIdentifier,
|
|
43
40
|
GetEarnings,
|
|
44
41
|
GetLatestEarnings,
|
|
45
42
|
GetNextEarnings,
|
|
@@ -52,4 +49,5 @@ ALL_TOOLS: list[Type[KfinanceTool]] = [
|
|
|
52
49
|
GetBusinessRelationshipFromIdentifier,
|
|
53
50
|
ResolveIdentifier,
|
|
54
51
|
GetSegmentsFromIdentifier,
|
|
52
|
+
GetCompetitorsFromIdentifier,
|
|
55
53
|
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from kfinance.constants import Permission
|
|
6
|
+
from kfinance.kfinance import AdvisedCompany
|
|
7
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GetAdvisorsForCompanyInTransactionFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
11
|
+
transaction_id: int | None = Field(description="The ID of the merger.", default=None)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GetAdvisorsForCompanyInTransactionFromIdentifier(KfinanceTool):
|
|
15
|
+
name: str = "get_advisors_for_company_in_transaction_from_identifier"
|
|
16
|
+
description: str = 'Get the companies advising a company in a given transaction. For example, "Who advised S&P Global during their purchase of Kensho?"'
|
|
17
|
+
args_schema: Type[BaseModel] = GetAdvisorsForCompanyInTransactionFromIdentifierArgs
|
|
18
|
+
required_permission: Permission | None = Permission.MergersPermission
|
|
19
|
+
|
|
20
|
+
def _run(self, identifier: str, transaction_id: int) -> list:
|
|
21
|
+
ticker = self.kfinance_client.ticker(identifier)
|
|
22
|
+
advised_company = AdvisedCompany(
|
|
23
|
+
kfinance_api_client=ticker.kfinance_api_client,
|
|
24
|
+
company_id=ticker.company.company_id,
|
|
25
|
+
transaction_id=transaction_id,
|
|
26
|
+
)
|
|
27
|
+
advisors = advised_company.advisors
|
|
28
|
+
|
|
29
|
+
if advisors:
|
|
30
|
+
return [
|
|
31
|
+
{
|
|
32
|
+
"advisor_company_id": advisor.company_id,
|
|
33
|
+
"advisor_company_name": advisor.name,
|
|
34
|
+
"advisor_type_name": advisor.advisor_type_name,
|
|
35
|
+
}
|
|
36
|
+
for advisor in advisors
|
|
37
|
+
]
|
|
38
|
+
else:
|
|
39
|
+
return []
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from kfinance.constants import CompetitorSource, Permission
|
|
2
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GetCompetitorsFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
6
|
+
# no description because the description for enum fields comes from the enum docstring.
|
|
7
|
+
competitor_source: CompetitorSource
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GetCompetitorsFromIdentifier(KfinanceTool):
|
|
11
|
+
name: str = "get_competitors_from_identifier"
|
|
12
|
+
description: str = "Retrieves a list of competitors for a given company, optionally filtered by the source of the competitor information."
|
|
13
|
+
args_schema = GetCompetitorsFromIdentifierArgs
|
|
14
|
+
required_permission: Permission | None = Permission.CompetitorsPermission
|
|
15
|
+
|
|
16
|
+
def _run(
|
|
17
|
+
self,
|
|
18
|
+
identifier: str,
|
|
19
|
+
competitor_source: CompetitorSource,
|
|
20
|
+
) -> dict:
|
|
21
|
+
ticker = self.kfinance_client.ticker(identifier)
|
|
22
|
+
return self.kfinance_client.kfinance_api_client.fetch_competitors(
|
|
23
|
+
company_id=ticker.company_id, competitor_source=competitor_source
|
|
24
|
+
)
|
|
@@ -3,7 +3,7 @@ from typing import Literal, Type
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
5
|
from kfinance.constants import LINE_ITEM_NAMES_AND_ALIASES, PeriodType, Permission
|
|
6
|
-
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
6
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier, ValidQuarter
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class GetFinancialLineItemFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
@@ -16,8 +16,8 @@ class GetFinancialLineItemFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
|
16
16
|
period_type: PeriodType | None = Field(default=None, description="The period type")
|
|
17
17
|
start_year: int | None = Field(default=None, description="The starting year for the data range")
|
|
18
18
|
end_year: int | None = Field(default=None, description="The ending year for the data range")
|
|
19
|
-
start_quarter:
|
|
20
|
-
end_quarter:
|
|
19
|
+
start_quarter: ValidQuarter | None = Field(default=None, description="Starting quarter")
|
|
20
|
+
end_quarter: ValidQuarter | None = Field(default=None, description="Ending quarter")
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class GetFinancialLineItemFromIdentifier(KfinanceTool):
|
|
@@ -3,7 +3,7 @@ from typing import Literal, Type
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
5
|
from kfinance.constants import PeriodType, Permission, StatementType
|
|
6
|
-
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
6
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier, ValidQuarter
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class GetFinancialStatementFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
@@ -12,8 +12,8 @@ class GetFinancialStatementFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
|
12
12
|
period_type: PeriodType | None = Field(default=None, description="The period type")
|
|
13
13
|
start_year: int | None = Field(default=None, description="The starting year for the data range")
|
|
14
14
|
end_year: int | None = Field(default=None, description="The ending year for the data range")
|
|
15
|
-
start_quarter:
|
|
16
|
-
end_quarter:
|
|
15
|
+
start_quarter: ValidQuarter | None = Field(default=None, description="Starting quarter")
|
|
16
|
+
end_quarter: ValidQuarter | None = Field(default=None, description="Ending quarter")
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class GetFinancialStatementFromIdentifier(KfinanceTool):
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from kfinance.constants import Permission
|
|
6
|
+
from kfinance.kfinance import MergerOrAcquisition
|
|
7
|
+
from kfinance.tool_calling.shared_models import KfinanceTool
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GetMergerInfoFromTransactionIdArgs(BaseModel):
|
|
11
|
+
transaction_id: int | None = Field(description="The ID of the merger.", default=None)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class GetMergerInfoFromTransactionId(KfinanceTool):
|
|
15
|
+
name: str = "get_merger_info_from_transaction_id"
|
|
16
|
+
description: str = 'Get the timeline, the participants, and the consideration of the merger or acquisition from the given transaction ID. For example, "How much was Ben & Jerrys purchased for?" or "What was the price per share for LinkedIn?" or "When did S&P purchase Kensho?"'
|
|
17
|
+
args_schema: Type[BaseModel] = GetMergerInfoFromTransactionIdArgs
|
|
18
|
+
required_permission: Permission | None = Permission.MergersPermission
|
|
19
|
+
|
|
20
|
+
def _run(self, transaction_id: int) -> dict:
|
|
21
|
+
merger_or_acquisition = MergerOrAcquisition(
|
|
22
|
+
kfinance_api_client=self.kfinance_client.kfinance_api_client,
|
|
23
|
+
transaction_id=transaction_id,
|
|
24
|
+
merger_title=None,
|
|
25
|
+
)
|
|
26
|
+
merger_timeline = merger_or_acquisition.get_timeline
|
|
27
|
+
merger_participants = merger_or_acquisition.get_participants
|
|
28
|
+
merger_consideration = merger_or_acquisition.get_consideration
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
"timeline": [
|
|
32
|
+
{"status": timeline["status"], "date": timeline["date"].strftime("%Y-%m-%d")}
|
|
33
|
+
for timeline in merger_timeline.to_dict(orient="records")
|
|
34
|
+
],
|
|
35
|
+
"participants": {
|
|
36
|
+
"target": {
|
|
37
|
+
"company_id": merger_participants["target"].company_id,
|
|
38
|
+
"company_name": merger_participants["target"].name,
|
|
39
|
+
},
|
|
40
|
+
"buyers": [
|
|
41
|
+
{"company_id": buyer.company_id, "company_name": buyer.name}
|
|
42
|
+
for buyer in merger_participants["buyers"]
|
|
43
|
+
],
|
|
44
|
+
"sellers": [
|
|
45
|
+
{"company_id": seller.company_id, "company_name": seller.name}
|
|
46
|
+
for seller in merger_participants["sellers"]
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
"consideration": {
|
|
50
|
+
"currency_name": merger_consideration["currency_name"],
|
|
51
|
+
"current_calculated_gross_total_transaction_value": merger_consideration[
|
|
52
|
+
"current_calculated_gross_total_transaction_value"
|
|
53
|
+
],
|
|
54
|
+
"current_calculated_implied_equity_value": merger_consideration[
|
|
55
|
+
"current_calculated_implied_equity_value"
|
|
56
|
+
],
|
|
57
|
+
"current_calculated_implied_enterprise_value": merger_consideration[
|
|
58
|
+
"current_calculated_implied_enterprise_value"
|
|
59
|
+
],
|
|
60
|
+
"details": merger_consideration["details"].to_dict(orient="records"),
|
|
61
|
+
},
|
|
62
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from kfinance.constants import Permission
|
|
6
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GetMergersFromIdentifier(KfinanceTool):
|
|
10
|
+
name: str = "get_mergers_from_identifier"
|
|
11
|
+
description: str = 'Get the transaction IDs that involve the given identifier. For example, "Which companies did Microsoft purchase?" or "Which company bought Ben & Jerrys?"'
|
|
12
|
+
args_schema: Type[BaseModel] = ToolArgsWithIdentifier
|
|
13
|
+
required_permission: Permission | None = Permission.MergersPermission
|
|
14
|
+
|
|
15
|
+
def _run(self, identifier: str) -> dict:
|
|
16
|
+
ticker = self.kfinance_client.ticker(identifier)
|
|
17
|
+
mergers_and_acquisitions = ticker.company.mergers_and_acquisitions
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
"target": [
|
|
21
|
+
{
|
|
22
|
+
"transaction_id": merger_or_acquisition.transaction_id,
|
|
23
|
+
"merger_title": merger_or_acquisition.merger_title,
|
|
24
|
+
}
|
|
25
|
+
for merger_or_acquisition in mergers_and_acquisitions["target"]
|
|
26
|
+
],
|
|
27
|
+
"buyer": [
|
|
28
|
+
{
|
|
29
|
+
"transaction_id": merger_or_acquisition.transaction_id,
|
|
30
|
+
"merger_title": merger_or_acquisition.merger_title,
|
|
31
|
+
}
|
|
32
|
+
for merger_or_acquisition in mergers_and_acquisitions["buyer"]
|
|
33
|
+
],
|
|
34
|
+
"seller": [
|
|
35
|
+
{
|
|
36
|
+
"transaction_id": merger_or_acquisition.transaction_id,
|
|
37
|
+
"merger_title": merger_or_acquisition.merger_title,
|
|
38
|
+
}
|
|
39
|
+
for merger_or_acquisition in mergers_and_acquisitions["seller"]
|
|
40
|
+
],
|
|
41
|
+
}
|
|
@@ -3,7 +3,7 @@ from typing import Literal, Type
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
5
|
from kfinance.constants import PeriodType, Permission, SegmentType
|
|
6
|
-
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
6
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier, ValidQuarter
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class GetSegmentsFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
@@ -12,8 +12,8 @@ class GetSegmentsFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
|
12
12
|
period_type: PeriodType | None = Field(default=None, description="The period type")
|
|
13
13
|
start_year: int | None = Field(default=None, description="The starting year for the data range")
|
|
14
14
|
end_year: int | None = Field(default=None, description="The ending year for the data range")
|
|
15
|
-
start_quarter:
|
|
16
|
-
end_quarter:
|
|
15
|
+
start_quarter: ValidQuarter | None = Field(default=None, description="Starting quarter")
|
|
16
|
+
end_quarter: ValidQuarter | None = Field(default=None, description="Ending quarter")
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class GetSegmentsFromIdentifier(KfinanceTool):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from typing import Any, Type
|
|
1
|
+
from typing import Annotated, Any, Literal, Type
|
|
2
2
|
|
|
3
3
|
from langchain_core.tools import BaseTool
|
|
4
|
-
from pydantic import BaseModel, ConfigDict, Field
|
|
4
|
+
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
|
|
5
5
|
|
|
6
6
|
from kfinance.constants import Permission
|
|
7
7
|
from kfinance.kfinance import Client
|
|
@@ -53,3 +53,18 @@ class ToolArgsWithIdentifier(BaseModel):
|
|
|
53
53
|
identifier: str = Field(
|
|
54
54
|
description="The identifier, which can be a ticker symbol, ISIN, or CUSIP"
|
|
55
55
|
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def convert_str_to_int(v: Any) -> Any:
|
|
59
|
+
"""Convert strings to integers if possible."""
|
|
60
|
+
if isinstance(v, str) and v.isdigit():
|
|
61
|
+
return int(v)
|
|
62
|
+
return v
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Valid Quarter is a literal type, which converts strings to int before
|
|
66
|
+
# validating them.
|
|
67
|
+
# Claude seems to often pass strings to int literals, which raise a
|
|
68
|
+
# ValidationError during deserialization unless they have been converted
|
|
69
|
+
# to int.
|
|
70
|
+
ValidQuarter = Annotated[Literal[1, 2, 3, 4], BeforeValidator(convert_str_to_int)]
|
kfinance/version.py
CHANGED
kfinance/tests/test_mcp.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
from typing import Type
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from kfinance.kfinance import Client
|
|
6
|
-
from kfinance.mcp import build_doc_string
|
|
7
|
-
from kfinance.tool_calling import ALL_TOOLS
|
|
8
|
-
from kfinance.tool_calling.shared_models import KfinanceTool
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TestDocStringBuilding:
|
|
12
|
-
@pytest.mark.parametrize("tool_class", ALL_TOOLS)
|
|
13
|
-
def test_build_doc_string(self, mock_client: Client, tool_class: Type[KfinanceTool]):
|
|
14
|
-
"""This test build the docstring for each tool. A success is considered if no exception is raised"""
|
|
15
|
-
tool = tool_class(kfinance_client=mock_client)
|
|
16
|
-
build_doc_string(tool)
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from typing import Type
|
|
3
|
-
|
|
4
|
-
from pydantic import BaseModel
|
|
5
|
-
|
|
6
|
-
from kfinance.constants import Permission
|
|
7
|
-
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class GetEarningsCallDatetimesFromIdentifier(KfinanceTool):
|
|
11
|
-
name: str = "get_earnings_call_datetimes_from_identifier"
|
|
12
|
-
description: str = "Get earnings call datetimes associated with an identifier."
|
|
13
|
-
args_schema: Type[BaseModel] = ToolArgsWithIdentifier
|
|
14
|
-
required_permission: Permission | None = Permission.EarningsPermission
|
|
15
|
-
|
|
16
|
-
def _run(self, identifier: str) -> str:
|
|
17
|
-
ticker = self.kfinance_client.ticker(identifier)
|
|
18
|
-
earnings_call_datetimes = ticker.earnings_call_datetimes
|
|
19
|
-
return json.dumps([dt.isoformat() for dt in earnings_call_datetimes])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|