kensho-kfinance 2.2.2__tar.gz → 2.2.5__tar.gz

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 (72) hide show
  1. {kensho_kfinance-2.2.2/kensho_kfinance.egg-info → kensho_kfinance-2.2.5}/PKG-INFO +1 -1
  2. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5/kensho_kfinance.egg-info}/PKG-INFO +1 -1
  3. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kensho_kfinance.egg-info/SOURCES.txt +2 -0
  4. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/CHANGELOG.md +9 -0
  5. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/constants.py +1 -0
  6. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/fetch.py +16 -10
  7. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/meta_classes.py +36 -32
  8. kensho_kfinance-2.2.5/kfinance/pydantic_models.py +25 -0
  9. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_fetch.py +62 -1
  10. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_objects.py +2 -9
  11. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_tools.py +43 -1
  12. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/__init__.py +4 -0
  13. kensho_kfinance-2.2.5/kfinance/tool_calling/get_segments_from_identifier.py +42 -0
  14. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/version.py +2 -2
  15. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/.coveragerc +0 -0
  16. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/.github/workflows/ci-lint.yml +0 -0
  17. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/.github/workflows/ci-test.yml +0 -0
  18. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/.github/workflows/python-publish.yml +0 -0
  19. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/.gitignore +0 -0
  20. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/.readthedocs.yaml +0 -0
  21. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/AUTHORS.md +0 -0
  22. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/CODE_OF_CONDUCT.md +0 -0
  23. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/CONTRIBUTING.md +0 -0
  24. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/LICENSE +0 -0
  25. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/README.md +0 -0
  26. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/build_tool_calling_documentation.py +0 -0
  27. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/conf.py +0 -0
  28. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/index.rst +0 -0
  29. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/kfinance.rst +0 -0
  30. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/requirements.txt +0 -0
  31. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/templates/apidoc/package.rst_t +0 -0
  32. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/templates/apidoc/toc.rst_t +0 -0
  33. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/docs/tool_calling.rst +0 -0
  34. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/images/colab_logo_32px.png +0 -0
  35. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/justfile +0 -0
  36. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kensho_kfinance.egg-info/dependency_links.txt +0 -0
  37. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kensho_kfinance.egg-info/requires.txt +0 -0
  38. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kensho_kfinance.egg-info/top_level.txt +0 -0
  39. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/__init__.py +0 -0
  40. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/batch_request_handling.py +0 -0
  41. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/kfinance.py +0 -0
  42. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/prompt.py +0 -0
  43. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/py.typed +0 -0
  44. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/server_thread.py +0 -0
  45. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/__init__.py +0 -0
  46. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/conftest.py +0 -0
  47. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_batch_requests.py +0 -0
  48. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_client.py +0 -0
  49. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_example_notebook.py +0 -0
  50. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tests/test_group_objects.py +0 -0
  51. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/README.md +0 -0
  52. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_business_relationship_from_identifier.py +0 -0
  53. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_capitalization_from_identifier.py +0 -0
  54. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_cusip_from_ticker.py +0 -0
  55. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +0 -0
  56. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_financial_line_item_from_identifier.py +0 -0
  57. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_financial_statement_from_identifier.py +0 -0
  58. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_history_metadata_from_identifier.py +0 -0
  59. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_info_from_identifier.py +0 -0
  60. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_isin_from_ticker.py +0 -0
  61. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_latest.py +0 -0
  62. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_n_quarters_ago.py +0 -0
  63. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/get_prices_from_identifier.py +0 -0
  64. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/resolve_identifier.py +0 -0
  65. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/kfinance/tool_calling/shared_models.py +0 -0
  66. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/pyproject.toml +0 -0
  67. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/scripts/copyright_line_check.sh +0 -0
  68. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/scripts/lint.sh +0 -0
  69. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/scripts/test.sh +0 -0
  70. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/setup.cfg +0 -0
  71. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/setup.py +0 -0
  72. {kensho_kfinance-2.2.2 → kensho_kfinance-2.2.5}/usage_examples.ipynb +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 2.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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 2.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
@@ -36,6 +36,7 @@ kfinance/kfinance.py
36
36
  kfinance/meta_classes.py
37
37
  kfinance/prompt.py
38
38
  kfinance/py.typed
39
+ kfinance/pydantic_models.py
39
40
  kfinance/server_thread.py
40
41
  kfinance/version.py
41
42
  kfinance/tests/__init__.py
@@ -61,6 +62,7 @@ kfinance/tool_calling/get_isin_from_ticker.py
61
62
  kfinance/tool_calling/get_latest.py
62
63
  kfinance/tool_calling/get_n_quarters_ago.py
63
64
  kfinance/tool_calling/get_prices_from_identifier.py
65
+ kfinance/tool_calling/get_segments_from_identifier.py
64
66
  kfinance/tool_calling/resolve_identifier.py
65
67
  kfinance/tool_calling/shared_models.py
66
68
  scripts/copyright_line_check.sh
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.2.5
4
+ - Add parsing for relationship response with name
5
+
6
+ ## v2.2.4
7
+ - Add segments as llm tools
8
+
9
+ ## v2.2.3
10
+ - Change segments return type to nested dictionary
11
+
3
12
  ## v2.2.2
4
13
  - Add segments
5
14
 
@@ -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):
@@ -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
- ) -> dict[str, list[int]]:
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: dict[str, list[int]]
524
+ :rtype: RelationshipResponse | RelationshipResponseNoName
525
525
  """
526
526
  url = f"{self.url_base}relationship/{company_id}/{relationship_type}"
527
- return self.fetch(url)
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,
@@ -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
- companies = self.kfinance_api_client.fetch_companies_from_business_relationship(
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
- ) -> pd.DataFrame:
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 pd.DataFrame()
343
+ return {}
326
344
 
327
- results = self.kfinance_api_client.fetch_segments(
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
- ) -> pd.DataFrame:
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 DataFrame with `Year`/`Period`, `Segment Name`, `Line Item` and `Value` column.
372
- :rtype: pd.DataFrame
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
- ) -> pd.DataFrame:
391
- """Retrieves the templated geographic segments for a given company for a given period_type, start_year, start_quarter, end_year and end_quarter.
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 DataFrame with `Year`/`Period`, `Segment Name`, `Line Item` and `Value` column.
404
- :rtype: pd.DataFrame
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]
@@ -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
@@ -337,17 +337,10 @@ class TestCompany(TestCase):
337
337
 
338
338
  def test_business_segments(self) -> None:
339
339
  """test business statement"""
340
- rows = []
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
- pd.testing.assert_frame_equal(expected_segments, business_segment)
343
+ self.assertEqual(expected_segments, business_segment)
351
344
 
352
345
 
353
346
  class TestSecurity(TestCase):
@@ -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
+ )
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '2.2.2'
21
- __version_tuple__ = version_tuple = (2, 2, 2)
20
+ __version__ = version = '2.2.5'
21
+ __version_tuple__ = version_tuple = (2, 2, 5)
File without changes