kensho-kfinance 2.2.2__py3-none-any.whl → 2.2.5__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.2.2.dist-info → kensho_kfinance-2.2.5.dist-info}/METADATA +1 -1
- {kensho_kfinance-2.2.2.dist-info → kensho_kfinance-2.2.5.dist-info}/RECORD +17 -15
- {kensho_kfinance-2.2.2.dist-info → kensho_kfinance-2.2.5.dist-info}/WHEEL +1 -1
- kfinance/CHANGELOG.md +9 -0
- kfinance/constants.py +1 -0
- kfinance/fetch.py +16 -10
- kfinance/meta_classes.py +36 -32
- kfinance/pydantic_models.py +25 -0
- kfinance/tests/test_fetch.py +62 -1
- kfinance/tests/test_objects.py +2 -9
- kfinance/tests/test_tools.py +43 -1
- kfinance/tool_calling/__init__.py +4 -0
- kfinance/tool_calling/get_segments_from_identifier.py +42 -0
- kfinance/version.py +2 -2
- {kensho_kfinance-2.2.2.dist-info → kensho_kfinance-2.2.5.dist-info}/licenses/AUTHORS.md +0 -0
- {kensho_kfinance-2.2.2.dist-info → kensho_kfinance-2.2.5.dist-info}/licenses/LICENSE +0 -0
- {kensho_kfinance-2.2.2.dist-info → kensho_kfinance-2.2.5.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.2.
|
|
3
|
+
Version: 2.2.5
|
|
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
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
kensho_kfinance-2.2.
|
|
2
|
-
kensho_kfinance-2.2.
|
|
3
|
-
kfinance/CHANGELOG.md,sha256=
|
|
1
|
+
kensho_kfinance-2.2.5.dist-info/licenses/AUTHORS.md,sha256=0h9ClbI0pu1oKj1M28ROUsaxrbZg-6ukQGl6X4y9noI,68
|
|
2
|
+
kensho_kfinance-2.2.5.dist-info/licenses/LICENSE,sha256=bsY4blvSgq6o0FMQ3RXa2NCgco--nHCCchLXzxr6kms,83
|
|
3
|
+
kfinance/CHANGELOG.md,sha256=Nlkx-7DFWd9kyeKUdm7pqHW7bFi8ZdruQxCmOo88TpY,1302
|
|
4
4
|
kfinance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
kfinance/batch_request_handling.py,sha256=p6p_G4_BL06GgeKlh7P1k9CUqOMahWCLEw1NoBwbLvU,5698
|
|
6
|
-
kfinance/constants.py,sha256=
|
|
7
|
-
kfinance/fetch.py,sha256=
|
|
6
|
+
kfinance/constants.py,sha256=UuFzqL253-2tRQfma785K9tfaZGv-o821tO2tVLwc5Q,48813
|
|
7
|
+
kfinance/fetch.py,sha256=y4B-xPKrGNDVkicSf904q-M1hXHtNN8AuJ0Y55x_r2s,23464
|
|
8
8
|
kfinance/kfinance.py,sha256=_U69k0Dcwkw1B_lzaUZy8N2-c-v93ZKoUAVVZb6wBUM,52758
|
|
9
|
-
kfinance/meta_classes.py,sha256=
|
|
9
|
+
kfinance/meta_classes.py,sha256=3V0nSXDDoake5o7kXnrqXuqNIiwI75KR4IYxFqSPhTE,20736
|
|
10
10
|
kfinance/prompt.py,sha256=PtVB8c_FcSlVdyGgByAnIFGzuUuBaEjciCqnBJl1hSQ,25133
|
|
11
11
|
kfinance/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
kfinance/pydantic_models.py,sha256=WWLjcxBvVOpW2Wzpq1zKKju4uMlIeH4nKvM2GqLsjEE,597
|
|
12
13
|
kfinance/server_thread.py,sha256=jUnt1YGoYDkqqz1MbCwd44zJs1T_Z2BCgvj75bdtLgA,2574
|
|
13
|
-
kfinance/version.py,sha256=
|
|
14
|
+
kfinance/version.py,sha256=VXEVDzLsFHRu1B1J9TV6pbjt3sWduv6BDj43YpV5xIM,511
|
|
14
15
|
kfinance/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
16
|
kfinance/tests/conftest.py,sha256=voB-w8P_6L3Nel3rdgylXKe5WWaS1q7nCFt1O04uqoY,948
|
|
16
17
|
kfinance/tests/test_batch_requests.py,sha256=uXJF2IcRdyBm5SthwIUHMKtkGZ21MY84pg_k1JeSNOY,11430
|
|
17
18
|
kfinance/tests/test_client.py,sha256=O7icZCSDhlQ9WGhzoXlpiSvbuA-mQNJHBYVsilyP_dE,2209
|
|
18
19
|
kfinance/tests/test_example_notebook.py,sha256=XhDAJp3H5Y6usLAt3k4Ug4W3H6gLyh68nzmoWgOOLn4,6441
|
|
19
|
-
kfinance/tests/test_fetch.py,sha256=
|
|
20
|
+
kfinance/tests/test_fetch.py,sha256=LbE8JLS4OsByfQ_GsGxYwSX5Z4Dgfr-T6bFaZo1k7Oo,16079
|
|
20
21
|
kfinance/tests/test_group_objects.py,sha256=SoMEZmkG4RYdgWOAwxLHHtzIQho92KM01YbQXPUg578,1689
|
|
21
|
-
kfinance/tests/test_objects.py,sha256=
|
|
22
|
-
kfinance/tests/test_tools.py,sha256=
|
|
22
|
+
kfinance/tests/test_objects.py,sha256=CSk3iN-uDt-E6gaOX4jExuYPT8Up-YNC3hafpenadcA,23614
|
|
23
|
+
kfinance/tests/test_tools.py,sha256=ne4XhebhoxDHFl4kRLl10j4K-SXLrxS6jv6Ypt0Gv_E,16846
|
|
23
24
|
kfinance/tool_calling/README.md,sha256=omJq7Us6r4U45QB7hRpLjRJ5BMalCkZkh4uXBjTbJXc,2022
|
|
24
|
-
kfinance/tool_calling/__init__.py,sha256=
|
|
25
|
+
kfinance/tool_calling/__init__.py,sha256=Fb30Snu09B63NscSUJGGnFAdlXSDrTAOJlzjYvMyZXk,1873
|
|
25
26
|
kfinance/tool_calling/get_business_relationship_from_identifier.py,sha256=CipXvyqEjPm6BXYP0CA9Kp1BIyiIEm7abp85x1zXRV4,1472
|
|
26
27
|
kfinance/tool_calling/get_capitalization_from_identifier.py,sha256=TdWdJDeI-jSL-1YfhnnwIA9D1SXobidvoHrjK42QmqQ,1521
|
|
27
28
|
kfinance/tool_calling/get_cusip_from_ticker.py,sha256=houhGCYXoSzaaTtCvOBf3pPsYiSbcV1Ej5nAyGuMWcU,644
|
|
@@ -34,9 +35,10 @@ kfinance/tool_calling/get_isin_from_ticker.py,sha256=2fJBcA-rNGbVOQmQ7qJEYxqejQw
|
|
|
34
35
|
kfinance/tool_calling/get_latest.py,sha256=btGeVBmvX5QJutzrKfE6spatGGejDuHxtq53NoAaGNk,786
|
|
35
36
|
kfinance/tool_calling/get_n_quarters_ago.py,sha256=A0ilwPKUqU0YYQSz3gNsVF0Jy4YttXrSaDhYj7y8GHA,713
|
|
36
37
|
kfinance/tool_calling/get_prices_from_identifier.py,sha256=ViJkwLDvStB7grc8RuoKSDXQM399Wru4-OY3E8k1l_U,1882
|
|
38
|
+
kfinance/tool_calling/get_segments_from_identifier.py,sha256=WIqJ1wWE6Z87VBREGu42nRc6_eJqUbGKcE9elzqBQJE,1867
|
|
37
39
|
kfinance/tool_calling/resolve_identifier.py,sha256=npslr6bBCu0qEDV1-8d24F5OC3nQ1KBMphuMbHVC1AU,626
|
|
38
40
|
kfinance/tool_calling/shared_models.py,sha256=K-NPQyE_7Ew6Cs0zxG1xO2O47gp5uDHdHtWD7wUDZX4,2132
|
|
39
|
-
kensho_kfinance-2.2.
|
|
40
|
-
kensho_kfinance-2.2.
|
|
41
|
-
kensho_kfinance-2.2.
|
|
42
|
-
kensho_kfinance-2.2.
|
|
41
|
+
kensho_kfinance-2.2.5.dist-info/METADATA,sha256=ilytuQ2DVw1h-i2Qo3EV-VKP6_p0OgiuF-Fzdvo5R9M,3436
|
|
42
|
+
kensho_kfinance-2.2.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
43
|
+
kensho_kfinance-2.2.5.dist-info/top_level.txt,sha256=kT_kNwVhfQoOAecY8W7uYah5xaHMoHoAdBIvXh6DaKM,9
|
|
44
|
+
kensho_kfinance-2.2.5.dist-info/RECORD,,
|
kfinance/CHANGELOG.md
CHANGED
kfinance/constants.py
CHANGED
|
@@ -102,6 +102,7 @@ class Permission(StrEnum):
|
|
|
102
102
|
PricingPermission = "PricingPermission"
|
|
103
103
|
RelationshipPermission = "RelationshipPermission"
|
|
104
104
|
StatementsPermission = "StatementsPermission"
|
|
105
|
+
SegmentsPermission = "SegmentsPermission"
|
|
105
106
|
|
|
106
107
|
|
|
107
108
|
class YearAndQuarter(TypedDict):
|
kfinance/fetch.py
CHANGED
|
@@ -6,6 +6,7 @@ from typing import Callable, Generator, Optional
|
|
|
6
6
|
from uuid import uuid4
|
|
7
7
|
|
|
8
8
|
import jwt
|
|
9
|
+
from pydantic import ValidationError
|
|
9
10
|
import requests
|
|
10
11
|
|
|
11
12
|
from .constants import (
|
|
@@ -17,6 +18,7 @@ from .constants import (
|
|
|
17
18
|
Permission,
|
|
18
19
|
SegmentType,
|
|
19
20
|
)
|
|
21
|
+
from .pydantic_models import RelationshipResponse, RelationshipResponseNoName
|
|
20
22
|
|
|
21
23
|
|
|
22
24
|
# version.py gets autogenerated by setuptools-scm and is not available
|
|
@@ -505,26 +507,30 @@ class KFinanceApiClient:
|
|
|
505
507
|
|
|
506
508
|
def fetch_companies_from_business_relationship(
|
|
507
509
|
self, company_id: int, relationship_type: BusinessRelationshipType
|
|
508
|
-
) ->
|
|
509
|
-
"""Fetches a dictionary of current and previous company IDs associated with a given company ID based on the specified relationship type.
|
|
510
|
-
|
|
511
|
-
The returned dictionary has the following structure:
|
|
512
|
-
{
|
|
513
|
-
"current": List[int],
|
|
514
|
-
"previous": List[int]
|
|
515
|
-
}
|
|
510
|
+
) -> RelationshipResponse | RelationshipResponseNoName:
|
|
511
|
+
"""Fetches a dictionary of current and previous company IDs and names associated with a given company ID based on the specified relationship type.
|
|
516
512
|
|
|
517
513
|
Example: fetch_companies_from_business_relationship(company_id=1234, relationship_type="distributor") returns a dictionary of company 1234's current and previous distributors.
|
|
518
514
|
|
|
515
|
+
As of 2024-05-28, we are changing the response on the backend from
|
|
516
|
+
RelationshipResponseNoName to RelationshipResponse. This function can handle both response
|
|
517
|
+
types.
|
|
518
|
+
|
|
519
519
|
:param company_id: The ID of the company for which associated companies are being fetched.
|
|
520
520
|
:type company_id: int
|
|
521
521
|
:param relationship_type: The type of relationship to filter by. Valid relationship types are defined in the BusinessRelationshipType class.
|
|
522
522
|
:type relationship_type: BusinessRelationshipType
|
|
523
523
|
:return: A dictionary containing lists of current and previous company IDs that have the specified relationship with the given company_id.
|
|
524
|
-
:rtype:
|
|
524
|
+
:rtype: RelationshipResponse | RelationshipResponseNoName
|
|
525
525
|
"""
|
|
526
526
|
url = f"{self.url_base}relationship/{company_id}/{relationship_type}"
|
|
527
|
-
|
|
527
|
+
result = self.fetch(url)
|
|
528
|
+
# Try to parse as the newer RelationshipResponse and fall back to
|
|
529
|
+
# RelationshipResponseNoName if that fails.
|
|
530
|
+
try:
|
|
531
|
+
return RelationshipResponse.model_validate(result)
|
|
532
|
+
except ValidationError:
|
|
533
|
+
return RelationshipResponseNoName.model_validate(result)
|
|
528
534
|
|
|
529
535
|
def fetch_ticker_from_industry_code(
|
|
530
536
|
self,
|
kfinance/meta_classes.py
CHANGED
|
@@ -9,12 +9,12 @@ import pandas as pd
|
|
|
9
9
|
|
|
10
10
|
from .constants import LINE_ITEMS, BusinessRelationshipType, PeriodType, SegmentType
|
|
11
11
|
from .fetch import KFinanceApiClient
|
|
12
|
+
from .pydantic_models import RelationshipResponse
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
from .kfinance import BusinessRelationships
|
|
16
17
|
|
|
17
|
-
|
|
18
18
|
logger = logging.getLogger(__name__)
|
|
19
19
|
|
|
20
20
|
|
|
@@ -223,14 +223,32 @@ class CompanyFunctionsMetaClass:
|
|
|
223
223
|
"""
|
|
224
224
|
from .kfinance import BusinessRelationships, Companies
|
|
225
225
|
|
|
226
|
-
|
|
227
|
-
self.company_id,
|
|
228
|
-
relationship_type,
|
|
229
|
-
)
|
|
230
|
-
return BusinessRelationships(
|
|
231
|
-
Companies(self.kfinance_api_client, companies["current"]),
|
|
232
|
-
Companies(self.kfinance_api_client, companies["previous"]),
|
|
226
|
+
relationship_resp = self.kfinance_api_client.fetch_companies_from_business_relationship(
|
|
227
|
+
company_id=self.company_id,
|
|
228
|
+
relationship_type=relationship_type,
|
|
233
229
|
)
|
|
230
|
+
if isinstance(relationship_resp, RelationshipResponse):
|
|
231
|
+
return BusinessRelationships(
|
|
232
|
+
current=Companies(
|
|
233
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
234
|
+
company_ids=[c.company_id for c in relationship_resp.current],
|
|
235
|
+
),
|
|
236
|
+
previous=Companies(
|
|
237
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
238
|
+
company_ids=[c.company_id for c in relationship_resp.previous],
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
else:
|
|
242
|
+
return BusinessRelationships(
|
|
243
|
+
current=Companies(
|
|
244
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
245
|
+
company_ids=relationship_resp.current,
|
|
246
|
+
),
|
|
247
|
+
previous=Companies(
|
|
248
|
+
kfinance_api_client=self.kfinance_api_client,
|
|
249
|
+
company_ids=relationship_resp.previous,
|
|
250
|
+
),
|
|
251
|
+
)
|
|
234
252
|
|
|
235
253
|
def market_cap(
|
|
236
254
|
self,
|
|
@@ -312,7 +330,7 @@ class CompanyFunctionsMetaClass:
|
|
|
312
330
|
end_year: Optional[int] = None,
|
|
313
331
|
start_quarter: Optional[int] = None,
|
|
314
332
|
end_quarter: Optional[int] = None,
|
|
315
|
-
) ->
|
|
333
|
+
) -> dict:
|
|
316
334
|
"""Get the company's segments"""
|
|
317
335
|
try:
|
|
318
336
|
self.validate_inputs(
|
|
@@ -322,9 +340,9 @@ class CompanyFunctionsMetaClass:
|
|
|
322
340
|
end_quarter=end_quarter,
|
|
323
341
|
)
|
|
324
342
|
except ValueError:
|
|
325
|
-
return
|
|
343
|
+
return {}
|
|
326
344
|
|
|
327
|
-
|
|
345
|
+
return self.kfinance_api_client.fetch_segments(
|
|
328
346
|
company_id=self.company_id,
|
|
329
347
|
segment_type=segment_type,
|
|
330
348
|
period_type=period_type,
|
|
@@ -334,20 +352,6 @@ class CompanyFunctionsMetaClass:
|
|
|
334
352
|
end_quarter=end_quarter,
|
|
335
353
|
)["segments"]
|
|
336
354
|
|
|
337
|
-
period_name = (
|
|
338
|
-
"Year" if (period_type == PeriodType.annual or period_type is None) else "Period"
|
|
339
|
-
)
|
|
340
|
-
|
|
341
|
-
# flatten the nested dictionary and return as a DataFrame
|
|
342
|
-
rows = []
|
|
343
|
-
for period, segments in results.items():
|
|
344
|
-
for segment_name, line_items in segments.items():
|
|
345
|
-
for line_item, value in line_items.items():
|
|
346
|
-
rows.append([period, segment_name, line_item, value])
|
|
347
|
-
return pd.DataFrame(
|
|
348
|
-
rows, columns=[period_name, "Segment Name", "Line Item", "Value"]
|
|
349
|
-
).replace(np.nan, None)
|
|
350
|
-
|
|
351
355
|
def business_segments(
|
|
352
356
|
self,
|
|
353
357
|
period_type: Optional[PeriodType] = None,
|
|
@@ -355,7 +359,7 @@ class CompanyFunctionsMetaClass:
|
|
|
355
359
|
end_year: Optional[int] = None,
|
|
356
360
|
start_quarter: Optional[int] = None,
|
|
357
361
|
end_quarter: Optional[int] = None,
|
|
358
|
-
) ->
|
|
362
|
+
) -> dict:
|
|
359
363
|
"""Retrieves the templated line of business segments for a given period_type, start_year, start_quarter, end_year and end_quarter.
|
|
360
364
|
|
|
361
365
|
:param period_type: The period_type requested for. Can be “annual”, “quarterly”, "ytd". Defaults to “annual” when start_quarter and end_quarter are None.
|
|
@@ -368,8 +372,8 @@ class CompanyFunctionsMetaClass:
|
|
|
368
372
|
:type start_quarter: int, optional
|
|
369
373
|
:param end_quarter: The ending calendar quarter, defaults to None
|
|
370
374
|
:type end_quarter: int, optional
|
|
371
|
-
:return: A
|
|
372
|
-
:rtype:
|
|
375
|
+
:return: A dictionary containing the templated line of business segments for each time period, segment name, line item, and value.
|
|
376
|
+
:rtype: dict
|
|
373
377
|
"""
|
|
374
378
|
return self._segments(
|
|
375
379
|
segment_type=SegmentType.business,
|
|
@@ -387,8 +391,8 @@ class CompanyFunctionsMetaClass:
|
|
|
387
391
|
end_year: Optional[int] = None,
|
|
388
392
|
start_quarter: Optional[int] = None,
|
|
389
393
|
end_quarter: Optional[int] = None,
|
|
390
|
-
) ->
|
|
391
|
-
"""Retrieves the templated geographic segments for a given
|
|
394
|
+
) -> dict:
|
|
395
|
+
"""Retrieves the templated geographic segments for a given period_type, start_year, start_quarter, end_year and end_quarter.
|
|
392
396
|
|
|
393
397
|
:param period_type: The period_type requested for. Can be “annual”, “quarterly”, "ytd". Defaults to “annual” when start_quarter and end_quarter are None.
|
|
394
398
|
:type start_year: PeriodType, optional
|
|
@@ -400,8 +404,8 @@ class CompanyFunctionsMetaClass:
|
|
|
400
404
|
:type start_quarter: int, optional
|
|
401
405
|
:param end_quarter: The ending calendar quarter, defaults to None
|
|
402
406
|
:type end_quarter: int, optional
|
|
403
|
-
:return: A
|
|
404
|
-
:rtype:
|
|
407
|
+
:return: A dictionary containing the templated geographic segments for each time period, segment name, line item, and value.
|
|
408
|
+
:rtype: dict
|
|
405
409
|
"""
|
|
406
410
|
return self._segments(
|
|
407
411
|
segment_type=SegmentType.geographic,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RelationshipResponseNoName(BaseModel):
|
|
5
|
+
"""A response from the relationship endpoint before adding the company name.
|
|
6
|
+
|
|
7
|
+
Each element in `current` and `previous` is a company_id.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
current: list[int]
|
|
11
|
+
previous: list[int]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CompanyIdAndName(BaseModel):
|
|
15
|
+
"""A company_id and name"""
|
|
16
|
+
|
|
17
|
+
company_id: int
|
|
18
|
+
company_name: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class RelationshipResponse(BaseModel):
|
|
22
|
+
"""A response from the relationship endpoint that includes both company_id and name."""
|
|
23
|
+
|
|
24
|
+
current: list[CompanyIdAndName]
|
|
25
|
+
previous: list[CompanyIdAndName]
|
kfinance/tests/test_fetch.py
CHANGED
|
@@ -2,9 +2,17 @@ from unittest import TestCase
|
|
|
2
2
|
from unittest.mock import MagicMock
|
|
3
3
|
|
|
4
4
|
import pytest
|
|
5
|
+
from requests_mock import Mocker
|
|
5
6
|
|
|
6
|
-
from kfinance.constants import Periodicity, PeriodType
|
|
7
|
+
from kfinance.constants import BusinessRelationshipType, Periodicity, PeriodType
|
|
7
8
|
from kfinance.fetch import KFinanceApiClient
|
|
9
|
+
from kfinance.kfinance import Client
|
|
10
|
+
from kfinance.pydantic_models import (
|
|
11
|
+
CompanyIdAndName,
|
|
12
|
+
RelationshipResponse,
|
|
13
|
+
RelationshipResponseNoName,
|
|
14
|
+
)
|
|
15
|
+
from kfinance.tests.conftest import SPGI_COMPANY_ID
|
|
8
16
|
|
|
9
17
|
|
|
10
18
|
def build_mock_api_client() -> KFinanceApiClient:
|
|
@@ -279,3 +287,56 @@ class TestMarketCap:
|
|
|
279
287
|
expected_fetch_url = f"{client.url_base}users/permissions"
|
|
280
288
|
client.fetch_permissions()
|
|
281
289
|
client.fetch.assert_called_with(expected_fetch_url)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class TestFetchCompaniesFromBusinessRelationship:
|
|
293
|
+
def test_old_response_format(self, requests_mock: Mocker, mock_client: Client) -> None:
|
|
294
|
+
"""
|
|
295
|
+
GIVEN a business relationship request
|
|
296
|
+
WHEN the api returns a response in the old (no name) format
|
|
297
|
+
THEN the response can successfully be parsed.
|
|
298
|
+
"""
|
|
299
|
+
http_resp = {"current": [883103], "previous": [472898, 8182358]}
|
|
300
|
+
expected_result = RelationshipResponseNoName(current=[883103], previous=[472898, 8182358])
|
|
301
|
+
requests_mock.get(
|
|
302
|
+
url=f"{mock_client.kfinance_api_client.url_base}relationship/{SPGI_COMPANY_ID}/{BusinessRelationshipType.supplier}",
|
|
303
|
+
json=http_resp,
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
resp = mock_client.kfinance_api_client.fetch_companies_from_business_relationship(
|
|
307
|
+
company_id=SPGI_COMPANY_ID, relationship_type=BusinessRelationshipType.supplier
|
|
308
|
+
)
|
|
309
|
+
assert resp == expected_result
|
|
310
|
+
|
|
311
|
+
def test_new_response_format(self, requests_mock: Mocker, mock_client: Client) -> None:
|
|
312
|
+
"""
|
|
313
|
+
GIVEN a business relationship request
|
|
314
|
+
WHEN the api returns a response in the new (with name) format
|
|
315
|
+
THEN the response can successfully be parsed.
|
|
316
|
+
"""
|
|
317
|
+
|
|
318
|
+
http_resp = {
|
|
319
|
+
"current": [{"company_name": "foo", "company_id": 883103}],
|
|
320
|
+
"previous": [
|
|
321
|
+
{"company_name": "bar", "company_id": 472898},
|
|
322
|
+
{"company_name": "baz", "company_id": 8182358},
|
|
323
|
+
],
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
expected_result = RelationshipResponse(
|
|
327
|
+
current=[CompanyIdAndName(company_name="foo", company_id=883103)],
|
|
328
|
+
previous=[
|
|
329
|
+
CompanyIdAndName(company_name="bar", company_id=472898),
|
|
330
|
+
CompanyIdAndName(company_name="baz", company_id=8182358),
|
|
331
|
+
],
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
requests_mock.get(
|
|
335
|
+
url=f"{mock_client.kfinance_api_client.url_base}relationship/{SPGI_COMPANY_ID}/{BusinessRelationshipType.supplier}",
|
|
336
|
+
json=http_resp,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
resp = mock_client.kfinance_api_client.fetch_companies_from_business_relationship(
|
|
340
|
+
company_id=SPGI_COMPANY_ID, relationship_type=BusinessRelationshipType.supplier
|
|
341
|
+
)
|
|
342
|
+
assert resp == expected_result
|
kfinance/tests/test_objects.py
CHANGED
|
@@ -337,17 +337,10 @@ class TestCompany(TestCase):
|
|
|
337
337
|
|
|
338
338
|
def test_business_segments(self) -> None:
|
|
339
339
|
"""test business statement"""
|
|
340
|
-
|
|
341
|
-
for period, segments in MOCK_COMPANY_DB[msft_company_id]["segments"].items():
|
|
342
|
-
for segment_name, line_items in segments.items():
|
|
343
|
-
for line_item, value in line_items.items():
|
|
344
|
-
rows.append([period, segment_name, line_item, value])
|
|
345
|
-
expected_segments = pd.DataFrame(
|
|
346
|
-
rows, columns=["Year", "Segment Name", "Line Item", "Value"]
|
|
347
|
-
).replace(np.nan, None)
|
|
340
|
+
expected_segments = MOCK_COMPANY_DB[msft_company_id]["segments"]
|
|
348
341
|
|
|
349
342
|
business_segment = self.msft_company.business_segments()
|
|
350
|
-
|
|
343
|
+
self.assertEqual(expected_segments, business_segment)
|
|
351
344
|
|
|
352
345
|
|
|
353
346
|
class TestSecurity(TestCase):
|
kfinance/tests/test_tools.py
CHANGED
|
@@ -4,7 +4,7 @@ from langchain_core.utils.function_calling import convert_to_openai_tool
|
|
|
4
4
|
from requests_mock import Mocker
|
|
5
5
|
import time_machine
|
|
6
6
|
|
|
7
|
-
from kfinance.constants import BusinessRelationshipType, Capitalization, StatementType
|
|
7
|
+
from kfinance.constants import BusinessRelationshipType, Capitalization, SegmentType, StatementType
|
|
8
8
|
from kfinance.kfinance import Client
|
|
9
9
|
from kfinance.tests.conftest import SPGI_COMPANY_ID, SPGI_SECURITY_ID, SPGI_TRADING_ITEM_ID
|
|
10
10
|
from kfinance.tool_calling import (
|
|
@@ -38,6 +38,10 @@ from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTickerArgs
|
|
|
38
38
|
from kfinance.tool_calling.get_latest import GetLatestArgs
|
|
39
39
|
from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgoArgs
|
|
40
40
|
from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifierArgs
|
|
41
|
+
from kfinance.tool_calling.get_segments_from_identifier import (
|
|
42
|
+
GetSegmentsFromIdentifier,
|
|
43
|
+
GetSegmentsFromIdentifierArgs,
|
|
44
|
+
)
|
|
41
45
|
from kfinance.tool_calling.shared_models import ToolArgsWithIdentifier
|
|
42
46
|
|
|
43
47
|
|
|
@@ -217,6 +221,44 @@ class TestGetFinancialStatementFromIdentifier:
|
|
|
217
221
|
assert response == expected_response
|
|
218
222
|
|
|
219
223
|
|
|
224
|
+
class TestGetSegmentsFromIdentifier:
|
|
225
|
+
def test_get_segments_from_identifier(self, mock_client: Client, requests_mock: Mocker):
|
|
226
|
+
"""
|
|
227
|
+
GIVEN the GetSegmentsFromIdentifier tool
|
|
228
|
+
WHEN we request the SPGI business segment
|
|
229
|
+
THEN we get back the SPGI business segment
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
segments_response = {
|
|
233
|
+
"segments": {
|
|
234
|
+
"2020": {
|
|
235
|
+
"Commodity Insights": {
|
|
236
|
+
"CAPEX": -7000000.0,
|
|
237
|
+
"D&A": 17000000.0,
|
|
238
|
+
},
|
|
239
|
+
"Unallocated Assets Held for Sale": None,
|
|
240
|
+
},
|
|
241
|
+
"2021": {
|
|
242
|
+
"Commodity Insights": {
|
|
243
|
+
"CAPEX": -2000000.0,
|
|
244
|
+
"D&A": 12000000.0,
|
|
245
|
+
},
|
|
246
|
+
"Unallocated Assets Held for Sale": {"Total Assets": 321000000.0},
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
requests_mock.get(
|
|
251
|
+
url=f"https://kfinance.kensho.com/api/v1/segments/{SPGI_COMPANY_ID}/business/none/none/none/none/none",
|
|
252
|
+
# truncated from the original API response
|
|
253
|
+
json=segments_response,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
tool = GetSegmentsFromIdentifier(kfinance_client=mock_client)
|
|
257
|
+
args = GetSegmentsFromIdentifierArgs(identifier="SPGI", segment_type=SegmentType.business)
|
|
258
|
+
response = tool.run(args.model_dump(mode="json"))
|
|
259
|
+
assert response == segments_response["segments"]
|
|
260
|
+
|
|
261
|
+
|
|
220
262
|
class TestGetHistoryMetadataFromIdentifier:
|
|
221
263
|
def test_get_history_metadata_from_identifier(self, mock_client: Client, requests_mock: Mocker):
|
|
222
264
|
"""
|
|
@@ -22,6 +22,9 @@ from kfinance.tool_calling.get_isin_from_ticker import GetIsinFromTicker
|
|
|
22
22
|
from kfinance.tool_calling.get_latest import GetLatest
|
|
23
23
|
from kfinance.tool_calling.get_n_quarters_ago import GetNQuartersAgo
|
|
24
24
|
from kfinance.tool_calling.get_prices_from_identifier import GetPricesFromIdentifier
|
|
25
|
+
from kfinance.tool_calling.get_segments_from_identifier import (
|
|
26
|
+
GetSegmentsFromIdentifier,
|
|
27
|
+
)
|
|
25
28
|
from kfinance.tool_calling.resolve_identifier import ResolveIdentifier
|
|
26
29
|
from kfinance.tool_calling.shared_models import KfinanceTool
|
|
27
30
|
|
|
@@ -40,4 +43,5 @@ ALL_TOOLS: list[Type[KfinanceTool]] = [
|
|
|
40
43
|
GetFinancialLineItemFromIdentifier,
|
|
41
44
|
GetBusinessRelationshipFromIdentifier,
|
|
42
45
|
ResolveIdentifier,
|
|
46
|
+
GetSegmentsFromIdentifier,
|
|
43
47
|
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from typing import Literal, Type
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from kfinance.constants import PeriodType, Permission, SegmentType
|
|
6
|
+
from kfinance.tool_calling.shared_models import KfinanceTool, ToolArgsWithIdentifier
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GetSegmentsFromIdentifierArgs(ToolArgsWithIdentifier):
|
|
10
|
+
# no description because the description for enum fields comes from the enum docstring.
|
|
11
|
+
segment_type: SegmentType
|
|
12
|
+
period_type: PeriodType | None = Field(default=None, description="The period type")
|
|
13
|
+
start_year: int | None = Field(default=None, description="The starting year for the data range")
|
|
14
|
+
end_year: int | None = Field(default=None, description="The ending year for the data range")
|
|
15
|
+
start_quarter: Literal[1, 2, 3, 4] | None = Field(default=None, description="Starting quarter")
|
|
16
|
+
end_quarter: Literal[1, 2, 3, 4] | None = Field(default=None, description="Ending quarter")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class GetSegmentsFromIdentifier(KfinanceTool):
|
|
20
|
+
name: str = "get_segments_from_identifier"
|
|
21
|
+
description: str = "Get the templated segments associated with an identifier."
|
|
22
|
+
args_schema: Type[BaseModel] = GetSegmentsFromIdentifierArgs
|
|
23
|
+
required_permission: Permission | None = Permission.StatementsPermission
|
|
24
|
+
|
|
25
|
+
def _run(
|
|
26
|
+
self,
|
|
27
|
+
identifier: str,
|
|
28
|
+
segment_type: SegmentType,
|
|
29
|
+
period_type: PeriodType | None = None,
|
|
30
|
+
start_year: int | None = None,
|
|
31
|
+
end_year: int | None = None,
|
|
32
|
+
start_quarter: Literal[1, 2, 3, 4] | None = None,
|
|
33
|
+
end_quarter: Literal[1, 2, 3, 4] | None = None,
|
|
34
|
+
) -> str:
|
|
35
|
+
ticker = self.kfinance_client.ticker(identifier)
|
|
36
|
+
return getattr(ticker, segment_type.value + "_segments")(
|
|
37
|
+
period_type=period_type,
|
|
38
|
+
start_year=start_year,
|
|
39
|
+
end_year=end_year,
|
|
40
|
+
start_quarter=start_quarter,
|
|
41
|
+
end_quarter=end_quarter,
|
|
42
|
+
)
|
kfinance/version.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|