kensho-kfinance 2.8.0__py3-none-any.whl → 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (134) hide show
  1. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/METADATA +2 -2
  2. kensho_kfinance-3.0.0.dist-info/RECORD +110 -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} +105 -111
  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/tests/__init__.py +0 -0
  13. kfinance/{tests → client/tests}/test_batch_requests.py +8 -6
  14. kfinance/{tests → client/tests}/test_client.py +25 -19
  15. kfinance/{tests → client/tests}/test_fetch.py +11 -29
  16. kfinance/{tests → client/tests}/test_group_objects.py +1 -1
  17. kfinance/{tests → client/tests}/test_objects.py +111 -63
  18. kfinance/{tests/conftest.py → conftest.py} +14 -2
  19. kfinance/domains/README.md +14 -0
  20. kfinance/domains/__init__.py +0 -0
  21. kfinance/domains/business_relationships/__init__.py +0 -0
  22. kfinance/{models → domains/business_relationships}/business_relationship_models.py +10 -0
  23. kfinance/domains/business_relationships/business_relationship_tools.py +74 -0
  24. kfinance/domains/business_relationships/tests/__init__.py +0 -0
  25. kfinance/domains/business_relationships/tests/test_business_relationship_tools.py +55 -0
  26. kfinance/domains/capitalizations/__init__.py +0 -0
  27. kfinance/{models → domains/capitalizations}/capitalization_models.py +24 -17
  28. kfinance/domains/capitalizations/capitalization_tools.py +89 -0
  29. kfinance/domains/capitalizations/tests/__init__.py +0 -0
  30. kfinance/{tests/test_models → domains/capitalizations/tests}/test_capitalization_models.py +8 -10
  31. kfinance/domains/capitalizations/tests/test_capitalization_tools.py +85 -0
  32. kfinance/domains/companies/__init__.py +0 -0
  33. kfinance/domains/companies/company_identifiers.py +175 -0
  34. kfinance/domains/companies/company_models.py +27 -0
  35. kfinance/domains/companies/company_tools.py +66 -0
  36. kfinance/domains/companies/tests/__init__.py +0 -0
  37. kfinance/domains/companies/tests/test_company_tools.py +26 -0
  38. kfinance/domains/competitors/__init__.py +0 -0
  39. kfinance/{models → domains/competitors}/competitor_models.py +7 -0
  40. kfinance/domains/competitors/competitor_tools.py +62 -0
  41. kfinance/domains/competitors/tests/__init__.py +0 -0
  42. kfinance/domains/competitors/tests/test_competitor_tools.py +45 -0
  43. kfinance/domains/cusip_and_isin/__init__.py +0 -0
  44. kfinance/domains/cusip_and_isin/cusip_and_isin_tools.py +80 -0
  45. kfinance/domains/cusip_and_isin/tests/__init__.py +0 -0
  46. kfinance/domains/cusip_and_isin/tests/test_cusip_and_isin_tools.py +57 -0
  47. kfinance/domains/earnings/__init__.py +0 -0
  48. kfinance/domains/earnings/earning_models.py +41 -0
  49. kfinance/domains/earnings/earning_tools.py +174 -0
  50. kfinance/domains/earnings/tests/__init__.py +0 -0
  51. kfinance/domains/earnings/tests/test_earnings_tools.py +195 -0
  52. kfinance/domains/line_items/__init__.py +0 -0
  53. kfinance/domains/line_items/line_item_tools.py +114 -0
  54. kfinance/domains/line_items/tests/__init__.py +0 -0
  55. kfinance/domains/line_items/tests/test_line_item_tools.py +86 -0
  56. kfinance/domains/mergers_and_acquisitions/__init__.py +0 -0
  57. kfinance/domains/mergers_and_acquisitions/merger_and_acquisition_tools.py +176 -0
  58. kfinance/domains/mergers_and_acquisitions/tests/__init__.py +0 -0
  59. kfinance/domains/mergers_and_acquisitions/tests/test_merger_and_acquisition_tools.py +124 -0
  60. kfinance/domains/prices/__init__.py +0 -0
  61. kfinance/{models → domains/prices}/price_models.py +1 -1
  62. kfinance/domains/prices/price_tools.py +165 -0
  63. kfinance/domains/prices/tests/__init__.py +0 -0
  64. kfinance/{tests/test_models → domains/prices/tests}/test_price_models.py +2 -2
  65. kfinance/domains/prices/tests/test_price_tools.py +141 -0
  66. kfinance/domains/segments/__init__.py +0 -0
  67. kfinance/domains/segments/segment_tools.py +91 -0
  68. kfinance/domains/segments/tests/__init__.py +0 -0
  69. kfinance/domains/segments/tests/test_segment_tools.py +80 -0
  70. kfinance/domains/statements/__init__.py +0 -0
  71. kfinance/domains/statements/statement_tools.py +113 -0
  72. kfinance/domains/statements/tests/__init__.py +0 -0
  73. kfinance/domains/statements/tests/test_statement_tools.py +73 -0
  74. kfinance/integrations/README.md +8 -0
  75. kfinance/integrations/__init__.py +0 -0
  76. kfinance/integrations/mcp/__init__.py +0 -0
  77. kfinance/{mcp.py → integrations/mcp/mcp.py} +2 -2
  78. kfinance/integrations/tests/__init__.py +0 -0
  79. kfinance/{tests → integrations/tests}/test_example_notebook.py +4 -4
  80. kfinance/{tool_calling → integrations/tool_calling}/README.md +2 -2
  81. kfinance/integrations/tool_calling/__init__.py +0 -0
  82. kfinance/integrations/tool_calling/all_tools.py +55 -0
  83. kfinance/{tool_calling → integrations/tool_calling}/prompts.py +3 -2
  84. kfinance/integrations/tool_calling/static_tools/README.md +4 -0
  85. kfinance/integrations/tool_calling/static_tools/__init__.py +0 -0
  86. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_latest.py +3 -3
  87. kfinance/{tool_calling → integrations/tool_calling/static_tools}/get_n_quarters_ago.py +3 -3
  88. kfinance/integrations/tool_calling/static_tools/tests/__init__.py +0 -0
  89. kfinance/integrations/tool_calling/static_tools/tests/test_get_lastest.py +30 -0
  90. kfinance/integrations/tool_calling/static_tools/tests/test_get_n_quarters_ago.py +24 -0
  91. kfinance/integrations/tool_calling/tests/__init__.py +0 -0
  92. kfinance/integrations/tool_calling/tests/test_tool_calling_models.py +69 -0
  93. kfinance/{tool_calling/shared_models.py → integrations/tool_calling/tool_calling_models.py} +37 -7
  94. kfinance/version.py +2 -2
  95. kensho_kfinance-2.8.0.dist-info/RECORD +0 -70
  96. kfinance/models/id_models.py +0 -7
  97. kfinance/prompt.py +0 -526
  98. kfinance/pydantic_models.py +0 -33
  99. kfinance/tests/test_tools.py +0 -804
  100. kfinance/tool_calling/__init__.py +0 -53
  101. kfinance/tool_calling/get_advisors_for_company_in_transaction_from_identifier.py +0 -39
  102. kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -30
  103. kfinance/tool_calling/get_capitalization_from_identifier.py +0 -35
  104. kfinance/tool_calling/get_competitors_from_identifier.py +0 -25
  105. kfinance/tool_calling/get_cusip_from_ticker.py +0 -20
  106. kfinance/tool_calling/get_earnings.py +0 -33
  107. kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -48
  108. kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -44
  109. kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -17
  110. kfinance/tool_calling/get_info_from_identifier.py +0 -16
  111. kfinance/tool_calling/get_isin_from_ticker.py +0 -20
  112. kfinance/tool_calling/get_latest_earnings.py +0 -30
  113. kfinance/tool_calling/get_merger_info_from_transaction_id.py +0 -68
  114. kfinance/tool_calling/get_mergers_from_identifier.py +0 -41
  115. kfinance/tool_calling/get_next_earnings.py +0 -30
  116. kfinance/tool_calling/get_prices_from_identifier.py +0 -46
  117. kfinance/tool_calling/get_segments_from_identifier.py +0 -44
  118. kfinance/tool_calling/get_transcript.py +0 -23
  119. kfinance/tool_calling/resolve_identifier.py +0 -18
  120. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/WHEEL +0 -0
  121. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/AUTHORS.md +0 -0
  122. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/licenses/LICENSE +0 -0
  123. {kensho_kfinance-2.8.0.dist-info → kensho_kfinance-3.0.0.dist-info}/top_level.txt +0 -0
  124. /kfinance/{models → client}/__init__.py +0 -0
  125. /kfinance/{models → client}/industry_models.py +0 -0
  126. /kfinance/{tests → client/models}/__init__.py +0 -0
  127. /kfinance/{models → client/models}/currency_models.py +0 -0
  128. /kfinance/{models → client/models}/date_and_period_models.py +0 -0
  129. /kfinance/{tests/test_models → client/models/tests}/__init__.py +0 -0
  130. /kfinance/{models → client}/permission_models.py +0 -0
  131. /kfinance/{server_thread.py → client/server_thread.py} +0 -0
  132. /kfinance/{models → domains/line_items}/line_item_models.py +0 -0
  133. /kfinance/{models → domains/segments}/segment_models.py +0 -0
  134. /kfinance/{models → domains/statements}/statement_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  from unittest.mock import Mock
2
2
 
3
- from kfinance.kfinance import Client, IdentificationTriple, Tickers
3
+ from kfinance.client.kfinance import Client, IdentificationTriple, Tickers
4
4
 
5
5
 
6
6
  class TestTickers:
@@ -10,20 +10,24 @@ import pandas as pd
10
10
  from PIL.Image import open as image_open
11
11
  import time_machine
12
12
 
13
- from kfinance.kfinance import (
14
- AdvisedCompany,
13
+ from kfinance.client.kfinance import (
15
14
  BusinessRelationships,
16
15
  Company,
17
16
  Earnings,
18
17
  MergerOrAcquisition,
18
+ ParticipantInMerger,
19
19
  Security,
20
20
  Ticker,
21
21
  TradingItem,
22
22
  Transcript,
23
23
  )
24
- from kfinance.models.business_relationship_models import BusinessRelationshipType
25
- from kfinance.models.capitalization_models import Capitalizations
26
- from kfinance.pydantic_models import CompanyIdAndName, RelationshipResponse
24
+ from kfinance.domains.business_relationships.business_relationship_models import (
25
+ BusinessRelationshipType,
26
+ RelationshipResponse,
27
+ )
28
+ from kfinance.domains.capitalizations.capitalization_models import Capitalizations
29
+ from kfinance.domains.companies.company_models import CompanyIdAndName
30
+ from kfinance.domains.earnings.earning_models import EarningsCallResp
27
31
 
28
32
 
29
33
  msft_company_id = "21835"
@@ -70,25 +74,27 @@ MOCK_COMPANY_DB = {
70
74
  "iso_country": "USA",
71
75
  },
72
76
  "earnings_call_dates": {"earnings": ["2004-07-22T21:30:00"]},
73
- "earnings": {
74
- "earnings": [
75
- {
76
- "name": "Microsoft Corporation, Q4 2024 Earnings Call, Jul 25, 2024",
77
- "key_dev_id": 1916266380,
78
- "datetime": "2024-07-25T21:30:00",
79
- },
80
- {
81
- "name": "Microsoft Corporation, Q1 2025 Earnings Call, Oct 24, 2024",
82
- "keydevid": 1916266381,
83
- "datetime": "2024-10-24T21:30:00",
84
- },
85
- {
86
- "name": "Microsoft Corporation, Q2 2025 Earnings Call, Jan 25, 2025",
87
- "keydevid": 1916266382,
88
- "datetime": "2025-01-25T21:30:00",
89
- },
90
- ]
91
- },
77
+ "earnings": EarningsCallResp.model_validate(
78
+ {
79
+ "earnings": [
80
+ {
81
+ "name": "Microsoft Corporation, Q4 2024 Earnings Call, Jul 25, 2024",
82
+ "key_dev_id": 1916266380,
83
+ "datetime": "2024-07-25T21:30:00Z",
84
+ },
85
+ {
86
+ "name": "Microsoft Corporation, Q1 2025 Earnings Call, Oct 24, 2024",
87
+ "key_dev_id": 1916266381,
88
+ "datetime": "2024-10-24T21:30:00Z",
89
+ },
90
+ {
91
+ "name": "Microsoft Corporation, Q2 2025 Earnings Call, Jan 25, 2025",
92
+ "key_dev_id": 1916266382,
93
+ "datetime": "2025-01-25T21:30:00Z",
94
+ },
95
+ ]
96
+ }
97
+ ),
92
98
  "statements": {
93
99
  "income_statement": {
94
100
  "statements": {
@@ -125,16 +131,40 @@ MOCK_COMPANY_DB = {
125
131
  },
126
132
  "mergers": {
127
133
  "target": [
128
- {"transaction_id": 10998717, "merger_title": "Closed M/A of Microsoft Corporation"},
129
- {"transaction_id": 28237969, "merger_title": "Closed M/A of Microsoft Corporation"},
134
+ {
135
+ "transaction_id": 10998717,
136
+ "merger_title": "Closed M/A of Microsoft Corporation",
137
+ "closed_date": "2021-01-01",
138
+ },
139
+ {
140
+ "transaction_id": 28237969,
141
+ "merger_title": "Closed M/A of Microsoft Corporation",
142
+ "closed_date": "2022-01-01",
143
+ },
130
144
  ],
131
145
  "buyer": [
132
- {"transaction_id": 517414, "merger_title": "Closed M/A of MongoMusic, Inc."},
133
- {"transaction_id": 596722, "merger_title": "Closed M/A of Digital Anvil, Inc."},
146
+ {
147
+ "transaction_id": 517414,
148
+ "merger_title": "Closed M/A of MongoMusic, Inc.",
149
+ "closed_date": "2023-01-01",
150
+ },
151
+ {
152
+ "transaction_id": 596722,
153
+ "merger_title": "Closed M/A of Digital Anvil, Inc.",
154
+ "closed_date": "2023-01-01",
155
+ },
134
156
  ],
135
157
  "seller": [
136
- {"transaction_id": 455551, "merger_title": "Closed M/A of VacationSpot.com, Inc."},
137
- {"transaction_id": 456045, "merger_title": "Closed M/A of TransPoint, LLC"},
158
+ {
159
+ "transaction_id": 455551,
160
+ "merger_title": "Closed M/A of VacationSpot.com, Inc.",
161
+ "closed_date": "2024-01-01",
162
+ },
163
+ {
164
+ "transaction_id": 456045,
165
+ "merger_title": "Closed M/A of TransPoint, LLC",
166
+ "closed_date": "2025-01-01",
167
+ },
138
168
  ],
139
169
  },
140
170
  "advisors": {
@@ -380,8 +410,8 @@ class MockKFinanceApiClient:
380
410
  def fetch_mergers_for_company(self, company_id):
381
411
  return copy.deepcopy(MOCK_COMPANY_DB[company_id]["mergers"])
382
412
 
383
- def fetch_merger_info(self, transaction_id):
384
- return copy.deepcopy(MOCK_MERGERS_DB[transaction_id])
413
+ def fetch_merger_info(self, transaction_id: int):
414
+ return copy.deepcopy(MOCK_MERGERS_DB[str(transaction_id)])
385
415
 
386
416
  def fetch_advisors_for_company_in_merger(self, transaction_id, advised_company_id):
387
417
  return copy.deepcopy(MOCK_COMPANY_DB[advised_company_id]["advisors"][transaction_id])
@@ -444,26 +474,31 @@ class TestCompany(TestCase):
444
474
  def setUp(self):
445
475
  """setup tests"""
446
476
  self.kfinance_api_client = MockKFinanceApiClient()
447
- self.msft_company = AdvisedCompany(
448
- self.kfinance_api_client, company_id=msft_company_id, transaction_id=msft_buys_mongo
477
+ self.msft_company = ParticipantInMerger(
478
+ kfinance_api_client=self.kfinance_api_client,
479
+ transaction_id=msft_buys_mongo,
480
+ company=Company(
481
+ kfinance_api_client=self.kfinance_api_client,
482
+ company_id=msft_company_id,
483
+ ),
449
484
  )
450
485
 
451
486
  def test_company_id(self) -> None:
452
487
  """test company id"""
453
488
  expected_company_id = msft_company_id
454
- company_id = self.msft_company.company_id
489
+ company_id = self.msft_company.company.company_id
455
490
  self.assertEqual(expected_company_id, company_id)
456
491
 
457
492
  def test_info(self) -> None:
458
493
  """test info"""
459
494
  expected_info = MOCK_COMPANY_DB[msft_company_id]["info"]
460
- info = self.msft_company.info
495
+ info = self.msft_company.company.info
461
496
  self.assertEqual(expected_info, info)
462
497
 
463
498
  def test_name(self) -> None:
464
499
  """test name"""
465
500
  expected_name = MOCK_COMPANY_DB[msft_company_id]["info"]["name"]
466
- name = self.msft_company.name
501
+ name = self.msft_company.company.name
467
502
  self.assertEqual(expected_name, name)
468
503
 
469
504
  def test_founding_date(self) -> None:
@@ -471,7 +506,7 @@ class TestCompany(TestCase):
471
506
  expected_founding_date = datetime.strptime(
472
507
  MOCK_COMPANY_DB[msft_company_id]["info"]["founding_date"], "%Y-%m-%d"
473
508
  ).date()
474
- founding_date = self.msft_company.founding_date
509
+ founding_date = self.msft_company.company.founding_date
475
510
  self.assertEqual(expected_founding_date, founding_date)
476
511
 
477
512
  def test_earnings_call_datetimes(self) -> None:
@@ -481,7 +516,7 @@ class TestCompany(TestCase):
481
516
  MOCK_COMPANY_DB[msft_company_id]["earnings_call_dates"]["earnings"][0]
482
517
  ).replace(tzinfo=timezone.utc)
483
518
  ]
484
- earnings_call_datetimes = self.msft_company.earnings_call_datetimes
519
+ earnings_call_datetimes = self.msft_company.company.earnings_call_datetimes
485
520
  self.assertEqual(expected_earnings_call_datetimes, earnings_call_datetimes)
486
521
 
487
522
  def test_income_statement(self) -> None:
@@ -493,7 +528,7 @@ class TestCompany(TestCase):
493
528
  .apply(pd.to_numeric)
494
529
  .replace(np.nan, None)
495
530
  )
496
- income_statement = self.msft_company.income_statement()
531
+ income_statement = self.msft_company.company.income_statement()
497
532
  pd.testing.assert_frame_equal(expected_income_statement, income_statement)
498
533
 
499
534
  def test_revenue(self) -> None:
@@ -505,14 +540,14 @@ class TestCompany(TestCase):
505
540
  .replace(np.nan, None)
506
541
  .set_index(pd.Index(["revenue"]))
507
542
  )
508
- revenue = self.msft_company.revenue()
543
+ revenue = self.msft_company.company.revenue()
509
544
  pd.testing.assert_frame_equal(expected_revenue, revenue)
510
545
 
511
546
  def test_business_segments(self) -> None:
512
547
  """test business statement"""
513
548
  expected_segments = MOCK_COMPANY_DB[msft_company_id]["segments"]
514
549
 
515
- business_segment = self.msft_company.business_segments()
550
+ business_segment = self.msft_company.company.business_segments()
516
551
  self.assertEqual(expected_segments, business_segment)
517
552
 
518
553
  def test_relationships(self) -> None:
@@ -523,7 +558,9 @@ class TestCompany(TestCase):
523
558
 
524
559
  expected_suppliers = MOCK_COMPANY_DB[msft_company_id][BusinessRelationshipType.supplier]
525
560
 
526
- suppliers_via_method = self.msft_company.relationships(BusinessRelationshipType.supplier)
561
+ suppliers_via_method = self.msft_company.company.relationships(
562
+ BusinessRelationshipType.supplier
563
+ )
527
564
  self.assertIsInstance(suppliers_via_method, BusinessRelationships)
528
565
  # Company ids should match
529
566
  self.assertEqual(
@@ -536,23 +573,35 @@ class TestCompany(TestCase):
536
573
  )
537
574
 
538
575
  # Fetching via property should return the same result
539
- suppliers_via_property = self.msft_company.supplier
576
+ suppliers_via_property = self.msft_company.company.supplier
540
577
  self.assertEqual(suppliers_via_property, suppliers_via_method)
541
578
 
542
579
  def test_mergers(self) -> None:
543
580
  expected_mergers = MOCK_COMPANY_DB[msft_company_id]["mergers"]
544
- mergers = self.msft_company.mergers_and_acquisitions
581
+ mergers = self.msft_company.company.mergers_and_acquisitions
545
582
  mergers_json = {
546
583
  "target": [
547
- {"transaction_id": merger.transaction_id, "merger_title": merger.merger_title}
584
+ {
585
+ "transaction_id": merger.transaction_id,
586
+ "merger_title": merger.merger_title,
587
+ "closed_date": merger.closed_date,
588
+ }
548
589
  for merger in mergers["target"]
549
590
  ],
550
591
  "buyer": [
551
- {"transaction_id": merger.transaction_id, "merger_title": merger.merger_title}
592
+ {
593
+ "transaction_id": merger.transaction_id,
594
+ "merger_title": merger.merger_title,
595
+ "closed_date": merger.closed_date,
596
+ }
552
597
  for merger in mergers["buyer"]
553
598
  ],
554
599
  "seller": [
555
- {"transaction_id": merger.transaction_id, "merger_title": merger.merger_title}
600
+ {
601
+ "transaction_id": merger.transaction_id,
602
+ "merger_title": merger.merger_title,
603
+ "closed_date": merger.closed_date,
604
+ }
556
605
  for merger in mergers["seller"]
557
606
  ],
558
607
  }
@@ -571,7 +620,7 @@ class TestCompany(TestCase):
571
620
  company_ids: list[int] = []
572
621
  advisor_type_names: list[str] = []
573
622
  for advisor in advisors:
574
- company_ids.append(advisor.company_id)
623
+ company_ids.append(advisor.company.company_id)
575
624
  advisor_type_names.append(advisor.advisor_type_name)
576
625
  self.assertListEqual(expected_company_ids, company_ids)
577
626
  self.assertListEqual(expected_advisor_type_names, advisor_type_names)
@@ -847,12 +896,10 @@ class TestTicker(TestCase):
847
896
  THEN the Ticker object can correctly extract market caps from the dict.
848
897
  """
849
898
 
850
- expected_response = {
851
- "market_cap": [
852
- {"2025-01-01": {"unit": "USD", "value": "3133802247084.00"}},
853
- {"2025-01-02": {"unit": "USD", "value": "3112092395218.00"}},
854
- ]
855
- }
899
+ expected_response = [
900
+ {"date": "2025-01-01", "market_cap": {"unit": "USD", "value": "3133802247084.00"}},
901
+ {"date": "2025-01-02", "market_cap": {"unit": "USD", "value": "3112092395218.00"}},
902
+ ]
856
903
  market_caps = self.msft_ticker_from_ticker.market_cap()
857
904
  assert market_caps == expected_response
858
905
 
@@ -970,9 +1017,10 @@ class TestMerger(TestCase):
970
1017
  def setUp(self):
971
1018
  self.kfinance_api_client = MockKFinanceApiClient()
972
1019
  self.merger = MergerOrAcquisition(
973
- self.kfinance_api_client,
974
- transaction_id=msft_buys_mongo,
1020
+ kfinance_api_client=self.kfinance_api_client,
1021
+ transaction_id=int(msft_buys_mongo),
975
1022
  merger_title="Closed M/A of MongoMusic, Inc.",
1023
+ closed_date=date(2021, 1, 1),
976
1024
  )
977
1025
 
978
1026
  def test_merger_info(self) -> None:
@@ -984,16 +1032,16 @@ class TestMerger(TestCase):
984
1032
  ],
985
1033
  "participants": {
986
1034
  "target": {
987
- "company_id": self.merger.get_participants["target"].company_id,
988
- "company_name": self.merger.get_participants["target"].name,
1035
+ "company_id": self.merger.get_participants["target"].company.company_id,
1036
+ "company_name": self.merger.get_participants["target"].company.name,
989
1037
  },
990
1038
  "buyers": [
991
- {"company_id": company.company_id, "company_name": company.name}
992
- for company in self.merger.get_participants["buyers"]
1039
+ {"company_id": buyer.company.company_id, "company_name": buyer.company.name}
1040
+ for buyer in self.merger.get_participants["buyers"]
993
1041
  ],
994
1042
  "sellers": [
995
- {"company_id": company.company_id, "company_name": company.name}
996
- for company in self.merger.get_participants["sellers"]
1043
+ {"company_id": seller.company.company_id, "company_name": seller.company.name}
1044
+ for seller in self.merger.get_participants["sellers"]
997
1045
  ],
998
1046
  },
999
1047
  "consideration": {
@@ -3,7 +3,7 @@ from datetime import datetime
3
3
  import pytest
4
4
  from requests_mock import Mocker
5
5
 
6
- from kfinance.kfinance import Client
6
+ from kfinance.client.kfinance import Client
7
7
 
8
8
 
9
9
  SPGI_COMPANY_ID = 21719
@@ -18,7 +18,7 @@ def mock_client(requests_mock: Mocker) -> Client:
18
18
  client = Client(refresh_token="foo")
19
19
  # Set access token so that the client doesn't try to fetch it.
20
20
  client.kfinance_api_client._access_token = "foo" # noqa: SLF001
21
- client.kfinance_api_client._access_token_expiry = datetime(2100, 1, 1).timestamp() # noqa: SLF001
21
+ client.kfinance_api_client._access_token_expiry = int(datetime(2100, 1, 1).timestamp()) # noqa: SLF001
22
22
 
23
23
  # Create a mock for the SPGI id triple.
24
24
  requests_mock.get(
@@ -33,4 +33,16 @@ def mock_client(requests_mock: Mocker) -> Client:
33
33
  url="https://kfinance.kensho.com/api/v1/id/MSFT",
34
34
  json={"trading_item_id": 2630413, "security_id": 2630412, "company_id": 21835},
35
35
  )
36
+
37
+ # Create mock security id and trading item id for company ids 1 and 2:
38
+ for company_id in [1, 2]:
39
+ requests_mock.get(
40
+ url=f"https://kfinance.kensho.com/api/v1/securities/{company_id}/primary",
41
+ json={"primary_security": company_id},
42
+ )
43
+ requests_mock.get(
44
+ url=f"https://kfinance.kensho.com/api/v1/trading_items/{company_id}/primary",
45
+ json={"primary_trading_item": company_id},
46
+ )
47
+
36
48
  return client
@@ -0,0 +1,14 @@
1
+ # Domains
2
+
3
+ Domains loosely correspond to datasets and permission boundaries.
4
+ They are used to group together related tools and models,
5
+ for example all earnings related tools and models.
6
+
7
+ There are no hard boundaries around what constitutes a domain. If you
8
+ think it makes sense to group certain tools or models together, it probably does.
9
+
10
+ Each domain will usually have a tools file with `KfinanceTools` and a
11
+ models file with enums, pydantic models, data classes, or typed dicts
12
+ related to the domain. We may at some point also move ORM models related
13
+ to the domain into these sub folders. Each domain should have a `tests`
14
+ directory for tool and model tests.
File without changes
File without changes
@@ -1,5 +1,8 @@
1
+ from pydantic import BaseModel
1
2
  from strenum import StrEnum
2
3
 
4
+ from kfinance.domains.companies.company_models import CompanyIdAndName
5
+
3
6
 
4
7
  class BusinessRelationshipType(StrEnum):
5
8
  """The type of business relationship"""
@@ -24,3 +27,10 @@ class BusinessRelationshipType(StrEnum):
24
27
  transfer_agent_client = "transfer_agent_client"
25
28
  vendor = "vendor"
26
29
  client_services = "client_services"
30
+
31
+
32
+ class RelationshipResponse(BaseModel):
33
+ """A response from the relationship endpoint that includes both company_id and name."""
34
+
35
+ current: list[CompanyIdAndName]
36
+ previous: list[CompanyIdAndName]
@@ -0,0 +1,74 @@
1
+ from textwrap import dedent
2
+ from typing import Type
3
+
4
+ from pydantic import BaseModel
5
+
6
+ from kfinance.client.batch_request_handling import Task, process_tasks_in_thread_pool_executor
7
+ from kfinance.client.permission_models import Permission
8
+ from kfinance.domains.business_relationships.business_relationship_models import (
9
+ BusinessRelationshipType,
10
+ )
11
+ from kfinance.domains.companies.company_identifiers import (
12
+ fetch_company_ids_from_identifiers,
13
+ parse_identifiers,
14
+ )
15
+ from kfinance.integrations.tool_calling.tool_calling_models import (
16
+ KfinanceTool,
17
+ ToolArgsWithIdentifiers,
18
+ )
19
+
20
+
21
+ class GetBusinessRelationshipFromIdentifiersArgs(ToolArgsWithIdentifiers):
22
+ # no description because the description for enum fields comes from the enum docstring.
23
+ business_relationship: BusinessRelationshipType
24
+
25
+
26
+ class GetBusinessRelationshipFromIdentifiers(KfinanceTool):
27
+ name: str = "get_business_relationship_from_identifiers"
28
+ description: str = dedent("""
29
+ Get the current and previous company IDs that are relationship_type for a list of identifiers.
30
+
31
+ Example:
32
+ Query: "What are the previous borrowers of SPGI and JPM?"
33
+ Function: get_business_relationship_from_identifiers(identifiers=["SPGI", "JPM"], business_relationship=BusinessRelationshipType.borrower)
34
+ """).strip()
35
+ args_schema: Type[BaseModel] = GetBusinessRelationshipFromIdentifiersArgs
36
+ accepted_permissions: set[Permission] | None = {Permission.RelationshipPermission}
37
+
38
+ def _run(self, identifiers: list[str], business_relationship: BusinessRelationshipType) -> dict:
39
+ """Sample response:
40
+
41
+ {
42
+ "SPGI": {
43
+ "current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
44
+ "previous": [
45
+ {"company_id": "C_472898", "company_name": "Morgan Stanley"},
46
+ {"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
47
+ ],
48
+ }
49
+ }
50
+ """
51
+
52
+ api_client = self.kfinance_client.kfinance_api_client
53
+ parsed_identifiers = parse_identifiers(identifiers=identifiers, api_client=api_client)
54
+ identifiers_to_company_ids = fetch_company_ids_from_identifiers(
55
+ identifiers=parsed_identifiers, api_client=api_client
56
+ )
57
+
58
+ tasks = [
59
+ Task(
60
+ func=api_client.fetch_companies_from_business_relationship,
61
+ kwargs=dict(
62
+ company_id=company_id,
63
+ relationship_type=business_relationship,
64
+ ),
65
+ result_key=identifier,
66
+ )
67
+ for identifier, company_id in identifiers_to_company_ids.items()
68
+ ]
69
+
70
+ relationship_responses = process_tasks_in_thread_pool_executor(
71
+ api_client=api_client, tasks=tasks
72
+ )
73
+
74
+ return {str(k): v.model_dump(mode="json") for k, v in relationship_responses.items()}
@@ -0,0 +1,55 @@
1
+ from requests_mock import Mocker
2
+
3
+ from kfinance.client.kfinance import Client
4
+ from kfinance.conftest import SPGI_COMPANY_ID
5
+ from kfinance.domains.business_relationships.business_relationship_models import (
6
+ BusinessRelationshipType,
7
+ )
8
+ from kfinance.domains.business_relationships.business_relationship_tools import (
9
+ GetBusinessRelationshipFromIdentifiers,
10
+ GetBusinessRelationshipFromIdentifiersArgs,
11
+ )
12
+
13
+
14
+ class TestGetBusinessRelationshipFromIdentifiers:
15
+ def test_get_business_relationship_from_identifiers(
16
+ self, requests_mock: Mocker, mock_client: Client
17
+ ):
18
+ """
19
+ GIVEN the GetBusinessRelationshipFromIdentifiers tool
20
+ WHEN we request SPGI suppliers
21
+ THEN we get back the SPGI suppliers
22
+ """
23
+ supplier_resp = {
24
+ "current": [{"company_id": 883103, "company_name": "CRISIL Limited"}],
25
+ "previous": [
26
+ {"company_id": 472898, "company_name": "Morgan Stanley"},
27
+ {"company_id": 8182358, "company_name": "Eloqua, Inc."},
28
+ ],
29
+ }
30
+ expected_result = {
31
+ "SPGI": {
32
+ "current": [{"company_id": "C_883103", "company_name": "CRISIL Limited"}],
33
+ "previous": [
34
+ {"company_id": "C_472898", "company_name": "Morgan Stanley"},
35
+ {"company_id": "C_8182358", "company_name": "Eloqua, Inc."},
36
+ ],
37
+ }
38
+ }
39
+
40
+ requests_mock.get(
41
+ url=f"https://kfinance.kensho.com/api/v1/relationship/{SPGI_COMPANY_ID}/supplier",
42
+ json=supplier_resp,
43
+ )
44
+
45
+ tool = GetBusinessRelationshipFromIdentifiers(kfinance_client=mock_client)
46
+ args = GetBusinessRelationshipFromIdentifiersArgs(
47
+ identifiers=["SPGI"], business_relationship=BusinessRelationshipType.supplier
48
+ )
49
+ resp = tool.run(args.model_dump(mode="json"))
50
+
51
+ assert list(resp.keys()) == ["SPGI"]
52
+ # Sort companies by ID to make the result deterministic
53
+ resp["SPGI"]["current"].sort(key=lambda x: x["company_id"])
54
+ resp["SPGI"]["previous"].sort(key=lambda x: x["company_id"])
55
+ assert resp == expected_result
File without changes
@@ -5,7 +5,7 @@ from typing import Any
5
5
  from pydantic import BaseModel, Field, model_validator
6
6
  from strenum import StrEnum
7
7
 
8
- from kfinance.decimal_with_unit import Money, Shares
8
+ from kfinance.client.models.decimal_with_unit import Money, Shares
9
9
 
10
10
 
11
11
  class Capitalization(StrEnum):
@@ -68,23 +68,30 @@ class Capitalizations(BaseModel):
68
68
  capitalization[key] = dict(unit=currency, value=capitalization[key])
69
69
  return data
70
70
 
71
- def jsonify_single_attribute(self, capitalization_to_extract: Capitalization) -> dict:
72
- """Return a json representation of a single attribute like "market_cap".
71
+ def model_dump_json_single_metric(
72
+ self, capitalization_metric: Capitalization, only_include_most_recent_value: bool = False
73
+ ) -> dict:
74
+ """Dump only a single metric (market cap, tev, or shares outstanding) to json
73
75
 
74
- Example response:
75
- {
76
- "market_cap": [
77
- {'2024-06-24': {'unit': 'USD', 'value': '139231113000.00'}},
78
- {'2024-06-25': {'unit': 'USD', 'value': '140423262000.00'}}
79
- ]
80
- }
76
+ If only_include_most_recent_value is set to True, only the most recent value gets included.
81
77
 
78
+ Sample response:
79
+ [
80
+ {
81
+ 'date': datetime.date(2024, 4, 10),
82
+ 'market_cap': {'value': Decimal('132766738270.00'), 'unit': 'USD'}
83
+ }
84
+ ]
82
85
  """
83
86
 
84
- capitalizations = []
85
- for capitalization in self.capitalizations:
86
- attribute_val = getattr(capitalization, capitalization_to_extract.value)
87
- capitalizations.append(
88
- {capitalization.date.isoformat(): attribute_val.model_dump(mode="json")}
89
- )
90
- return {capitalization_to_extract: capitalizations}
87
+ return self.model_dump(
88
+ mode="json",
89
+ include={ # type: ignore[arg-type]
90
+ "capitalizations": {
91
+ 0 if only_include_most_recent_value else "__all__": {
92
+ "date",
93
+ capitalization_metric.value,
94
+ }
95
+ }
96
+ },
97
+ )["capitalizations"]