kensho-kfinance 2.0.1__tar.gz → 2.2.2__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 (73) hide show
  1. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/.gitignore +1 -0
  2. {kensho_kfinance-2.0.1/kensho_kfinance.egg-info → kensho_kfinance-2.2.2}/PKG-INFO +9 -1
  3. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/README.md +2 -0
  4. kensho_kfinance-2.2.2/images/colab_logo_32px.png +0 -0
  5. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/justfile +1 -0
  6. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2/kensho_kfinance.egg-info}/PKG-INFO +9 -1
  7. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kensho_kfinance.egg-info/SOURCES.txt +7 -3
  8. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kensho_kfinance.egg-info/requires.txt +6 -0
  9. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/CHANGELOG.md +21 -0
  10. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/batch_request_handling.py +32 -27
  11. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/constants.py +23 -7
  12. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/fetch.py +106 -40
  13. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/kfinance.py +164 -89
  14. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/meta_classes.py +118 -9
  15. kensho_kfinance-2.2.2/kfinance/tests/conftest.py +32 -0
  16. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tests/test_batch_requests.py +46 -8
  17. kensho_kfinance-2.2.2/kfinance/tests/test_client.py +54 -0
  18. kensho_kfinance-2.2.2/kfinance/tests/test_example_notebook.py +194 -0
  19. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tests/test_fetch.py +31 -2
  20. kensho_kfinance-2.2.2/kfinance/tests/test_group_objects.py +32 -0
  21. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tests/test_objects.py +40 -0
  22. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tests/test_tools.py +13 -61
  23. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/__init__.py +2 -6
  24. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_business_relationship_from_identifier.py +2 -1
  25. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_capitalization_from_identifier.py +2 -1
  26. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_cusip_from_ticker.py +2 -0
  27. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py +2 -0
  28. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_financial_line_item_from_identifier.py +2 -1
  29. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_financial_statement_from_identifier.py +2 -1
  30. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_history_metadata_from_identifier.py +2 -1
  31. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_info_from_identifier.py +3 -1
  32. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_isin_from_ticker.py +2 -0
  33. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_latest.py +2 -1
  34. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_n_quarters_ago.py +2 -1
  35. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/get_prices_from_identifier.py +2 -1
  36. kensho_kfinance-2.2.2/kfinance/tool_calling/resolve_identifier.py +18 -0
  37. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/shared_models.py +2 -0
  38. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/version.py +2 -2
  39. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/pyproject.toml +10 -1
  40. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/scripts/lint.sh +2 -0
  41. kensho_kfinance-2.2.2/usage_examples.ipynb +285 -0
  42. kensho_kfinance-2.0.1/kfinance/tool_calling/get_company_id_from_identifier.py +0 -14
  43. kensho_kfinance-2.0.1/kfinance/tool_calling/get_security_id_from_identifier.py +0 -14
  44. kensho_kfinance-2.0.1/kfinance/tool_calling/get_trading_item_id_from_identifier.py +0 -14
  45. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/.coveragerc +0 -0
  46. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/.github/workflows/ci-lint.yml +0 -0
  47. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/.github/workflows/ci-test.yml +0 -0
  48. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/.github/workflows/python-publish.yml +0 -0
  49. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/.readthedocs.yaml +0 -0
  50. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/AUTHORS.md +0 -0
  51. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/CODE_OF_CONDUCT.md +0 -0
  52. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/CONTRIBUTING.md +0 -0
  53. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/LICENSE +0 -0
  54. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/build_tool_calling_documentation.py +0 -0
  55. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/conf.py +0 -0
  56. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/index.rst +0 -0
  57. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/kfinance.rst +0 -0
  58. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/requirements.txt +0 -0
  59. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/templates/apidoc/package.rst_t +0 -0
  60. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/templates/apidoc/toc.rst_t +0 -0
  61. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/docs/tool_calling.rst +0 -0
  62. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kensho_kfinance.egg-info/dependency_links.txt +0 -0
  63. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kensho_kfinance.egg-info/top_level.txt +0 -0
  64. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/__init__.py +0 -0
  65. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/prompt.py +0 -0
  66. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/py.typed +0 -0
  67. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/server_thread.py +0 -0
  68. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tests/__init__.py +0 -0
  69. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/kfinance/tool_calling/README.md +0 -0
  70. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/scripts/copyright_line_check.sh +0 -0
  71. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/scripts/test.sh +0 -0
  72. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/setup.cfg +0 -0
  73. {kensho_kfinance-2.0.1 → kensho_kfinance-2.2.2}/setup.py +0 -0
@@ -4,6 +4,7 @@ __pycache__/
4
4
  *$py.class
5
5
 
6
6
  # Distribution / packaging
7
+ .python-version
7
8
  .Python
8
9
  build/
9
10
  develop-eggs/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 2.0.1
3
+ Version: 2.2.2
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
@@ -12,6 +12,7 @@ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.md
15
+ Requires-Dist: cachetools<6,>=5.5
15
16
  Requires-Dist: langchain-core>=0.3.15
16
17
  Requires-Dist: langchain-google-genai<3,>=2.1.0
17
18
  Requires-Dist: numpy>=1.22.4
@@ -27,12 +28,17 @@ Requires-Dist: requests<3,>=2.22.0
27
28
  Requires-Dist: urllib3>=1.21.1
28
29
  Provides-Extra: dev
29
30
  Requires-Dist: coverage<8,>=7.6.10; extra == "dev"
31
+ Requires-Dist: ipykernel<7,>=6.29; extra == "dev"
30
32
  Requires-Dist: mypy<2,>=1.15.0; extra == "dev"
33
+ Requires-Dist: nbconvert<8,>=7.16; extra == "dev"
34
+ Requires-Dist: nbformat<6,>5.10; extra == "dev"
35
+ Requires-Dist: nbqa<2,>1.9; extra == "dev"
31
36
  Requires-Dist: pytest<7,>=6.1.2; extra == "dev"
32
37
  Requires-Dist: pytest-cov<7,>=6.0.0; extra == "dev"
33
38
  Requires-Dist: requests_mock<2,>=1.12; extra == "dev"
34
39
  Requires-Dist: ruff<1,>=0.9.4; extra == "dev"
35
40
  Requires-Dist: time_machine<3,>=2.1; extra == "dev"
41
+ Requires-Dist: types-cachetools<6,>=5.5; extra == "dev"
36
42
  Dynamic: license-file
37
43
 
38
44
  # kFinance
@@ -55,6 +61,8 @@ To receive access, please email [S&P Global Market Intelligence](market.intellig
55
61
 
56
62
  Once access is obtained, get started using the [Authentication Guide](https://docs.kensho.com/llmreadyapi/kf-authentication) and [Usage Guide](https://docs.kensho.com/llmreadyapi/usage).
57
63
 
64
+ We also provide an [interactive notebook](usage_examples.ipynb) that demonstrates some usage examples.
65
+
58
66
  # Versioning
59
67
  The kFinance uses semantic versioning (major, minor, patch).
60
68
  To bump the version, add a new entry in [CHANGELOG.md](kfinance%2FCHANGELOG.md).
@@ -18,6 +18,8 @@ To receive access, please email [S&P Global Market Intelligence](market.intellig
18
18
 
19
19
  Once access is obtained, get started using the [Authentication Guide](https://docs.kensho.com/llmreadyapi/kf-authentication) and [Usage Guide](https://docs.kensho.com/llmreadyapi/usage).
20
20
 
21
+ We also provide an [interactive notebook](usage_examples.ipynb) that demonstrates some usage examples.
22
+
21
23
  # Versioning
22
24
  The kFinance uses semantic versioning (major, minor, patch).
23
25
  To bump the version, add a new entry in [CHANGELOG.md](kfinance%2FCHANGELOG.md).
@@ -24,6 +24,7 @@ lint *args:
24
24
  # See https://docs.astral.sh/ruff/formatter/#sorting-imports
25
25
  python -m ruff --config pyproject.toml check kfinance --fix {{args}}
26
26
  python -m ruff --config pyproject.toml format kfinance {{args}}
27
+ nbqa mypy usage_examples.ipynb
27
28
 
28
29
  alias t := unit-test
29
30
  # Run unit tests. Use args for optional settings
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 2.0.1
3
+ Version: 2.2.2
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
@@ -12,6 +12,7 @@ Requires-Python: >=3.10
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.md
15
+ Requires-Dist: cachetools<6,>=5.5
15
16
  Requires-Dist: langchain-core>=0.3.15
16
17
  Requires-Dist: langchain-google-genai<3,>=2.1.0
17
18
  Requires-Dist: numpy>=1.22.4
@@ -27,12 +28,17 @@ Requires-Dist: requests<3,>=2.22.0
27
28
  Requires-Dist: urllib3>=1.21.1
28
29
  Provides-Extra: dev
29
30
  Requires-Dist: coverage<8,>=7.6.10; extra == "dev"
31
+ Requires-Dist: ipykernel<7,>=6.29; extra == "dev"
30
32
  Requires-Dist: mypy<2,>=1.15.0; extra == "dev"
33
+ Requires-Dist: nbconvert<8,>=7.16; extra == "dev"
34
+ Requires-Dist: nbformat<6,>5.10; extra == "dev"
35
+ Requires-Dist: nbqa<2,>1.9; extra == "dev"
31
36
  Requires-Dist: pytest<7,>=6.1.2; extra == "dev"
32
37
  Requires-Dist: pytest-cov<7,>=6.0.0; extra == "dev"
33
38
  Requires-Dist: requests_mock<2,>=1.12; extra == "dev"
34
39
  Requires-Dist: ruff<1,>=0.9.4; extra == "dev"
35
40
  Requires-Dist: time_machine<3,>=2.1; extra == "dev"
41
+ Requires-Dist: types-cachetools<6,>=5.5; extra == "dev"
36
42
  Dynamic: license-file
37
43
 
38
44
  # kFinance
@@ -55,6 +61,8 @@ To receive access, please email [S&P Global Market Intelligence](market.intellig
55
61
 
56
62
  Once access is obtained, get started using the [Authentication Guide](https://docs.kensho.com/llmreadyapi/kf-authentication) and [Usage Guide](https://docs.kensho.com/llmreadyapi/usage).
57
63
 
64
+ We also provide an [interactive notebook](usage_examples.ipynb) that demonstrates some usage examples.
65
+
58
66
  # Versioning
59
67
  The kFinance uses semantic versioning (major, minor, patch).
60
68
  To bump the version, add a new entry in [CHANGELOG.md](kfinance%2FCHANGELOG.md).
@@ -9,6 +9,7 @@ README.md
9
9
  justfile
10
10
  pyproject.toml
11
11
  setup.py
12
+ usage_examples.ipynb
12
13
  .github/workflows/ci-lint.yml
13
14
  .github/workflows/ci-test.yml
14
15
  .github/workflows/python-publish.yml
@@ -20,6 +21,7 @@ docs/requirements.txt
20
21
  docs/tool_calling.rst
21
22
  docs/templates/apidoc/package.rst_t
22
23
  docs/templates/apidoc/toc.rst_t
24
+ images/colab_logo_32px.png
23
25
  kensho_kfinance.egg-info/PKG-INFO
24
26
  kensho_kfinance.egg-info/SOURCES.txt
25
27
  kensho_kfinance.egg-info/dependency_links.txt
@@ -37,15 +39,18 @@ kfinance/py.typed
37
39
  kfinance/server_thread.py
38
40
  kfinance/version.py
39
41
  kfinance/tests/__init__.py
42
+ kfinance/tests/conftest.py
40
43
  kfinance/tests/test_batch_requests.py
44
+ kfinance/tests/test_client.py
45
+ kfinance/tests/test_example_notebook.py
41
46
  kfinance/tests/test_fetch.py
47
+ kfinance/tests/test_group_objects.py
42
48
  kfinance/tests/test_objects.py
43
49
  kfinance/tests/test_tools.py
44
50
  kfinance/tool_calling/README.md
45
51
  kfinance/tool_calling/__init__.py
46
52
  kfinance/tool_calling/get_business_relationship_from_identifier.py
47
53
  kfinance/tool_calling/get_capitalization_from_identifier.py
48
- kfinance/tool_calling/get_company_id_from_identifier.py
49
54
  kfinance/tool_calling/get_cusip_from_ticker.py
50
55
  kfinance/tool_calling/get_earnings_call_datetimes_from_identifier.py
51
56
  kfinance/tool_calling/get_financial_line_item_from_identifier.py
@@ -56,8 +61,7 @@ kfinance/tool_calling/get_isin_from_ticker.py
56
61
  kfinance/tool_calling/get_latest.py
57
62
  kfinance/tool_calling/get_n_quarters_ago.py
58
63
  kfinance/tool_calling/get_prices_from_identifier.py
59
- kfinance/tool_calling/get_security_id_from_identifier.py
60
- kfinance/tool_calling/get_trading_item_id_from_identifier.py
64
+ kfinance/tool_calling/resolve_identifier.py
61
65
  kfinance/tool_calling/shared_models.py
62
66
  scripts/copyright_line_check.sh
63
67
  scripts/lint.sh
@@ -1,3 +1,4 @@
1
+ cachetools<6,>=5.5
1
2
  langchain-core>=0.3.15
2
3
  langchain-google-genai<3,>=2.1.0
3
4
  numpy>=1.22.4
@@ -14,9 +15,14 @@ urllib3>=1.21.1
14
15
 
15
16
  [dev]
16
17
  coverage<8,>=7.6.10
18
+ ipykernel<7,>=6.29
17
19
  mypy<2,>=1.15.0
20
+ nbconvert<8,>=7.16
21
+ nbformat<6,>5.10
22
+ nbqa<2,>1.9
18
23
  pytest<7,>=6.1.2
19
24
  pytest-cov<7,>=6.0.0
20
25
  requests_mock<2,>=1.12
21
26
  ruff<1,>=0.9.4
22
27
  time_machine<3,>=2.1
28
+ types-cachetools<6,>=5.5
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.2.2
4
+ - Add segments
5
+
6
+ ## v2.2.1
7
+ - Make number of employees optional to reflect backend changes
8
+
9
+ ## v2.2.0
10
+ - Replace get company id, get security id, get trading item id tools with resolve identifier tool
11
+
12
+ ## v2.1.2
13
+ - Allow batch executor to handle multiple requests
14
+
15
+ ## v2.1.1
16
+ - Use cachetools cache
17
+
18
+ ## v2.1.0
19
+ - Filter llm tools by user permissions
20
+
21
+ ## v2.0.2
22
+ - Add Client.tickers() method for ticker industry filters for ISRCS and GICS
23
+
3
24
  ## v2.0.1
4
25
  - Fix readthedocs integration for llm tools.
5
26
 
@@ -1,4 +1,4 @@
1
- from concurrent.futures import ThreadPoolExecutor
1
+ from concurrent.futures import Future
2
2
  import functools
3
3
  from functools import cached_property
4
4
  import threading
@@ -55,36 +55,26 @@ def add_methods_of_singular_class_to_iterable_class(singular_cls: Type[T]) -> Ca
55
55
  instances of [singular_cls].
56
56
  """
57
57
 
58
- def process_in_thread_pool(
59
- executor: ThreadPoolExecutor, method: Callable, obj: T, *args: Any, **kwargs: Any
60
- ) -> Any:
61
- with throttle:
62
- future = executor.submit(method, obj, *args, **kwargs)
63
- try:
64
- return future.result()
65
- except HTTPError as http_err:
66
- error_code = http_err.response.status_code
67
- if error_code == 404:
68
- return None
69
- else:
70
- raise http_err
71
-
72
58
  def process_in_batch(
73
59
  method: Callable, self: IterableKfinanceClass, *args: Any, **kwargs: Any
74
60
  ) -> dict:
75
- results = {}
76
- kfinance_api_client = self.kfinance_api_client
77
- with kfinance_api_client.batch_request_header(batch_size=len(self)):
78
- with kfinance_api_client.thread_pool as executor:
79
- results = dict(
80
- zip(
81
- self,
82
- [
83
- process_in_thread_pool(executor, method, obj, *args, **kwargs)
84
- for obj in self
85
- ],
86
- )
61
+ with self.kfinance_api_client.batch_request_header(batch_size=len(self)):
62
+ futures = []
63
+ for obj in self:
64
+ # Acquire throttle before submitting the task
65
+ throttle.acquire()
66
+ future = self.kfinance_api_client.thread_pool.submit(
67
+ method, obj, *args, **kwargs
87
68
  )
69
+ # On success or failure, release the throttle.
70
+ # This releases the throttle before the
71
+ # `resolve_future_with_error_handling` call.
72
+ future.add_done_callback(lambda f: throttle.release())
73
+ futures.append(future)
74
+
75
+ results = {}
76
+ for obj, future in zip(self, futures):
77
+ results[obj] = resolve_future_with_error_handling(future)
88
78
 
89
79
  return results
90
80
 
@@ -135,3 +125,18 @@ def add_methods_of_singular_class_to_iterable_class(singular_cls: Type[T]) -> Ca
135
125
  return iterable_cls
136
126
 
137
127
  return decorator
128
+
129
+
130
+ def resolve_future_with_error_handling(future: Future) -> Any:
131
+ """Return the result of a future with error handling for non-200 status codes.
132
+
133
+ If request returned a 404, return None. Otherwise, raise the error.
134
+ """
135
+ try:
136
+ return future.result()
137
+ except HTTPError as http_err:
138
+ error_code = http_err.response.status_code
139
+ if error_code == 404:
140
+ return None
141
+ else:
142
+ raise http_err
@@ -1,7 +1,6 @@
1
1
  from datetime import date
2
- from enum import Enum
3
2
  from itertools import chain
4
- from typing import TypedDict
3
+ from typing import NamedTuple, TypedDict
5
4
 
6
5
  from strenum import StrEnum
7
6
 
@@ -21,13 +20,13 @@ class HistoryMetadata(TypedDict):
21
20
  first_trade_date: date
22
21
 
23
22
 
24
- class IdentificationTriple(TypedDict):
23
+ class IdentificationTriple(NamedTuple):
25
24
  trading_item_id: int
26
25
  security_id: int
27
26
  company_id: int
28
27
 
29
28
 
30
- class Capitalization(Enum):
29
+ class Capitalization(StrEnum):
31
30
  """The capitalization type"""
32
31
 
33
32
  market_cap = "market_cap"
@@ -35,7 +34,7 @@ class Capitalization(Enum):
35
34
  shares_outstanding = "shares_outstanding"
36
35
 
37
36
 
38
- class PeriodType(Enum):
37
+ class PeriodType(StrEnum):
39
38
  """The period type"""
40
39
 
41
40
  annual = "annual"
@@ -44,7 +43,7 @@ class PeriodType(Enum):
44
43
  ytd = "ytd"
45
44
 
46
45
 
47
- class Periodicity(Enum):
46
+ class Periodicity(StrEnum):
48
47
  """The frequency or interval at which the historical data points are sampled or aggregated. Periodicity is not the same as the date range. The date range specifies the time span over which the data is retrieved, while periodicity determines how the data within that date range is aggregated."""
49
48
 
50
49
  day = "day"
@@ -53,7 +52,7 @@ class Periodicity(Enum):
53
52
  year = "year"
54
53
 
55
54
 
56
- class StatementType(Enum):
55
+ class StatementType(StrEnum):
57
56
  """The type of financial statement"""
58
57
 
59
58
  balance_sheet = "balance_sheet"
@@ -63,6 +62,13 @@ class StatementType(Enum):
63
62
  cf = "cashflow"
64
63
 
65
64
 
65
+ class SegmentType(StrEnum):
66
+ """The type of segment"""
67
+
68
+ business = "business"
69
+ geographic = "geographic"
70
+
71
+
66
72
  class BusinessRelationshipType(StrEnum):
67
73
  """The type of business relationship"""
68
74
 
@@ -88,6 +94,16 @@ class BusinessRelationshipType(StrEnum):
88
94
  client_services = "client_services"
89
95
 
90
96
 
97
+ class Permission(StrEnum):
98
+ EarningsPermission = "EarningsPermission"
99
+ GICSPermission = "GICSPermission"
100
+ IDPermission = "IDPermission"
101
+ ISCRSPermission = "ISCRSPermission"
102
+ PricingPermission = "PricingPermission"
103
+ RelationshipPermission = "RelationshipPermission"
104
+ StatementsPermission = "StatementsPermission"
105
+
106
+
91
107
  class YearAndQuarter(TypedDict):
92
108
  year: int
93
109
  quarter: int
@@ -1,5 +1,6 @@
1
1
  from concurrent.futures import ThreadPoolExecutor
2
2
  from contextlib import contextmanager
3
+ import logging
3
4
  from time import time
4
5
  from typing import Callable, Generator, Optional
5
6
  from uuid import uuid4
@@ -13,6 +14,8 @@ from .constants import (
13
14
  IndustryClassification,
14
15
  Periodicity,
15
16
  PeriodType,
17
+ Permission,
18
+ SegmentType,
16
19
  )
17
20
 
18
21
 
@@ -23,6 +26,8 @@ try:
23
26
  except ImportError:
24
27
  kfinance_version = "dev"
25
28
 
29
+ logger = logging.getLogger(__name__)
30
+
26
31
 
27
32
  DEFAULT_API_HOST: str = "https://kfinance.kensho.com"
28
33
  DEFAULT_API_VERSION: int = 1
@@ -86,6 +91,7 @@ class KFinanceApiClient:
86
91
  self.user_agent_source = "object_oriented"
87
92
  self._batch_id: str | None = None
88
93
  self._batch_size: str | None = None
94
+ self._user_permissions: set[Permission] | None = None
89
95
 
90
96
  @contextmanager
91
97
  def batch_request_header(self, batch_size: int) -> Generator:
@@ -127,6 +133,9 @@ class KFinanceApiClient:
127
133
  # nosemgrep: python.jwt.security.unverified-jwt-decode.unverified-jwt-decode
128
134
  options={"verify_signature": False},
129
135
  ).get("exp")
136
+ # When the access token gets refreshed, also refresh user permissions in case they
137
+ # have been updated.
138
+ self._refresh_user_permissions()
130
139
  return self._access_token
131
140
 
132
141
  def _get_access_token_via_refresh_token(self) -> str:
@@ -169,6 +178,33 @@ class KFinanceApiClient:
169
178
  response.raise_for_status()
170
179
  return response.json().get("access_token")
171
180
 
181
+ @property
182
+ def user_permissions(self) -> set[Permission]:
183
+ """Return the permissions that the current user holds."""
184
+
185
+ if self._user_permissions is None:
186
+ self._refresh_user_permissions()
187
+ # _refresh_user_permissions updates self._user_permissions in place
188
+ assert self._user_permissions is not None
189
+ return self._user_permissions
190
+
191
+ def _refresh_user_permissions(self) -> None:
192
+ """Fetches user permissions and stores them as KfinanceApiClient._user_permissions."""
193
+
194
+ user_permission_dict = self.fetch_permissions()
195
+ self._user_permissions = set()
196
+ for permission_str in user_permission_dict["permissions"]:
197
+ try:
198
+ self._user_permissions.add(Permission[permission_str])
199
+ except KeyError:
200
+ logger.warning(
201
+ "Could not resolve %s to a member of the Permission enum. "
202
+ "%s may be a new type of permission that still needs to be "
203
+ "added to the enum.",
204
+ permission_str,
205
+ permission_str,
206
+ )
207
+
172
208
  def fetch(self, url: str) -> dict:
173
209
  """Does the request and auth"""
174
210
 
@@ -191,6 +227,11 @@ class KFinanceApiClient:
191
227
  response.raise_for_status()
192
228
  return response.json()
193
229
 
230
+ def fetch_permissions(self) -> dict[str, list[str]]:
231
+ """Return the permissions of the user."""
232
+ url = f"{self.url_base}users/permissions"
233
+ return self.fetch(url)
234
+
194
235
  def fetch_id_triple(self, identifier: str, exchange_code: Optional[str] = None) -> dict:
195
236
  """Get the ID triple from [identifier]."""
196
237
  url = f"{self.url_base}id/{identifier}"
@@ -241,7 +282,7 @@ class KFinanceApiClient:
241
282
  f"{self.url_base}pricing/{trading_item_id}/"
242
283
  f"{start_date if start_date is not None else 'none'}/"
243
284
  f"{end_date if end_date is not None else 'none'}/"
244
- f"{periodicity.value if periodicity else 'none'}/"
285
+ f"{periodicity if periodicity else 'none'}/"
245
286
  f"{'adjusted' if is_adjusted else 'unadjusted'}"
246
287
  )
247
288
  return self.fetch(url)
@@ -265,6 +306,27 @@ class KFinanceApiClient:
265
306
  )
266
307
  return self.fetch(url)
267
308
 
309
+ def fetch_segments(
310
+ self,
311
+ company_id: int,
312
+ segment_type: SegmentType,
313
+ period_type: Optional[PeriodType] = None,
314
+ start_year: Optional[int] = None,
315
+ end_year: Optional[int] = None,
316
+ start_quarter: Optional[int] = None,
317
+ end_quarter: Optional[int] = None,
318
+ ) -> dict:
319
+ """Get a specified segment type for a specified duration."""
320
+ url = (
321
+ f"{self.url_base}segments/{company_id}/{segment_type}/"
322
+ f"{period_type if period_type else 'none'}/"
323
+ f"{start_year if start_year is not None else 'none'}/"
324
+ f"{end_year if end_year is not None else 'none'}/"
325
+ f"{start_quarter if start_quarter is not None else 'none'}/"
326
+ f"{end_quarter if end_quarter is not None else 'none'}"
327
+ )
328
+ return self.fetch(url)
329
+
268
330
  def fetch_price_chart(
269
331
  self,
270
332
  trading_item_id: int,
@@ -278,7 +340,7 @@ class KFinanceApiClient:
278
340
  f"{self.url_base}price_chart/{trading_item_id}/"
279
341
  f"{start_date if start_date is not None else 'none'}/"
280
342
  f"{end_date if end_date is not None else 'none'}/"
281
- f"{periodicity.value if periodicity else 'none'}/"
343
+ f"{periodicity if periodicity else 'none'}/"
282
344
  f"{'adjusted' if is_adjusted else 'unadjusted'}"
283
345
  )
284
346
 
@@ -306,7 +368,7 @@ class KFinanceApiClient:
306
368
  """Get a specified financial statement for a specified duration."""
307
369
  url = (
308
370
  f"{self.url_base}statements/{company_id}/{statement_type}/"
309
- f"{period_type.value if period_type else 'none'}/"
371
+ f"{period_type if period_type else 'none'}/"
310
372
  f"{start_year if start_year is not None else 'none'}/"
311
373
  f"{end_year if end_year is not None else 'none'}/"
312
374
  f"{start_quarter if start_quarter is not None else 'none'}/"
@@ -327,7 +389,7 @@ class KFinanceApiClient:
327
389
  """Get a specified financial line item for a specified duration."""
328
390
  url = (
329
391
  f"{self.url_base}line_item/{company_id}/{line_item}/"
330
- f"{period_type.value if period_type else 'none'}/"
392
+ f"{period_type if period_type else 'none'}/"
331
393
  f"{start_year if start_year is not None else 'none'}/"
332
394
  f"{end_year if end_year is not None else 'none'}/"
333
395
  f"{start_quarter if start_quarter is not None else 'none'}/"
@@ -358,10 +420,12 @@ class KFinanceApiClient:
358
420
  self,
359
421
  country_iso_code: str,
360
422
  state_iso_code: Optional[str] = None,
361
- ) -> dict[str, list[IdentificationTriple]]:
423
+ ) -> list[IdentificationTriple]:
362
424
  """Fetch ticker geography groups"""
363
- return self.fetch_geography_groups(
364
- country_iso_code=country_iso_code, state_iso_code=state_iso_code, fetch_ticker=True
425
+ return self._tickers_response_to_id_triple(
426
+ self.fetch_geography_groups(
427
+ country_iso_code=country_iso_code, state_iso_code=state_iso_code, fetch_ticker=True
428
+ )
365
429
  )
366
430
 
367
431
  def fetch_company_geography_groups(
@@ -381,13 +445,13 @@ class KFinanceApiClient:
381
445
  url = f"{self.url_base}{'ticker_groups' if fetch_ticker else 'trading_item_groups'}/exchange/{exchange_code}"
382
446
  return self.fetch(url)
383
447
 
384
- def fetch_ticker_exchange_groups(
385
- self, exchange_code: str
386
- ) -> dict[str, list[IdentificationTriple]]:
448
+ def fetch_ticker_exchange_groups(self, exchange_code: str) -> list[IdentificationTriple]:
387
449
  """Fetch ticker exchange groups"""
388
- return self.fetch_exchange_groups(
389
- exchange_code=exchange_code,
390
- fetch_ticker=True,
450
+ return self._tickers_response_to_id_triple(
451
+ self.fetch_exchange_groups(
452
+ exchange_code=exchange_code,
453
+ fetch_ticker=True,
454
+ )
391
455
  )
392
456
 
393
457
  def fetch_trading_item_exchange_groups(self, exchange_code: str) -> dict[str, list[int]]:
@@ -397,13 +461,32 @@ class KFinanceApiClient:
397
461
  fetch_ticker=False,
398
462
  )
399
463
 
464
+ @staticmethod
465
+ def _tickers_response_to_id_triple(
466
+ tickers_response: dict[str, list[dict]],
467
+ ) -> list[IdentificationTriple]:
468
+ """For fetch ticker cases with a dict[str, list[dict]] response, return a list[IdentificationTriple].
469
+
470
+ For example, with a given fetch tickers response:
471
+ {"tickers" : [{"trading_item_id": 1, "security_id": 1, "company_id": 1}, {"trading_item_id": 2,"security_id": 2,"company_id": 2}]},
472
+ return [[1, 1, 1], [2, 2, 2]].
473
+ """
474
+ return [
475
+ IdentificationTriple(
476
+ trading_item_id=ticker["trading_item_id"],
477
+ security_id=ticker["security_id"],
478
+ company_id=ticker["company_id"],
479
+ )
480
+ for ticker in tickers_response["tickers"]
481
+ ]
482
+
400
483
  def fetch_ticker_combined(
401
484
  self,
402
485
  country_iso_code: Optional[str] = None,
403
486
  state_iso_code: Optional[str] = None,
404
487
  simple_industry: Optional[str] = None,
405
488
  exchange_code: Optional[str] = None,
406
- ) -> dict[str, list[IdentificationTriple]]:
489
+ ) -> list[IdentificationTriple]:
407
490
  """Fetch tickers using combined filters route"""
408
491
  if (
409
492
  country_iso_code is None
@@ -414,11 +497,11 @@ class KFinanceApiClient:
414
497
  raise RuntimeError("Invalid parameters: No parameters provided or all set to none")
415
498
  elif country_iso_code is None and state_iso_code is not None:
416
499
  raise RuntimeError(
417
- "Invalid parameters: state_iso_code must be provided with a country_iso_code value"
500
+ "Invalid parameters: country_iso_code must be provided with a state_iso_code value"
418
501
  )
419
502
  else:
420
503
  url = f"{self.url_base}ticker_groups/filters/geo/{str(country_iso_code).lower()}/{str(state_iso_code).lower()}/simple/{str(simple_industry).lower()}/exchange/{str(exchange_code).lower()}"
421
- return self.fetch(url)
504
+ return self._tickers_response_to_id_triple(self.fetch(url))
422
505
 
423
506
  def fetch_companies_from_business_relationship(
424
507
  self, company_id: int, relationship_type: BusinessRelationshipType
@@ -443,22 +526,11 @@ class KFinanceApiClient:
443
526
  url = f"{self.url_base}relationship/{company_id}/{relationship_type}"
444
527
  return self.fetch(url)
445
528
 
446
- def fetch_from_industry_code(
447
- self,
448
- industry_code: str,
449
- industry_classification: IndustryClassification,
450
- fetch_ticker: bool = True,
451
- ) -> dict[str, list]:
452
- """Fetches a list of companies or identification triples that are classified in the given industry_code and industry_classification."""
453
-
454
- url = f"{self.url_base}{'ticker_groups' if fetch_ticker else 'company_groups'}/industry/{industry_classification}/{industry_code}"
455
- return self.fetch(url)
456
-
457
529
  def fetch_ticker_from_industry_code(
458
530
  self,
459
531
  industry_code: str,
460
532
  industry_classification: IndustryClassification,
461
- ) -> dict[str, list[IdentificationTriple]]:
533
+ ) -> list[IdentificationTriple]:
462
534
  """Fetches a list of identification triples that are classified in the given industry_code and industry_classification.
463
535
 
464
536
  Returns a dictionary of shape {"tickers": List[{“company_id”: <company_id>, “security_id”: <security_id>, “trading_item_id”: <trading_item_id>}]}.
@@ -466,14 +538,11 @@ class KFinanceApiClient:
466
538
  :type industry_code: str
467
539
  :param industry_classification: The type of industry_classification to filter on.
468
540
  :type industry_classification: IndustryClassification
469
- :return: A dictionary containing the list of identification triple [company_id, security_id, trading_item_id] that are classified in the given industry_code and industry_classification.
470
- :rtype: dict[str, list[IdentificationTriple]]
541
+ :return: A list of identification triples [company_id, security_id, trading_item_id] that are classified in the given industry_code and industry_classification.
542
+ :rtype: list[IdentificationTriple]
471
543
  """
472
- return self.fetch_from_industry_code(
473
- industry_code=industry_code,
474
- industry_classification=industry_classification,
475
- fetch_ticker=True,
476
- )
544
+ url = f"{self.url_base}ticker_groups/industry/{industry_classification}/{industry_code}"
545
+ return self._tickers_response_to_id_triple(self.fetch(url))
477
546
 
478
547
  def fetch_company_from_industry_code(
479
548
  self,
@@ -490,8 +559,5 @@ class KFinanceApiClient:
490
559
  :return: A dictionary containing the list of companies that are classified in the given industry_code and industry_classification.
491
560
  :rtype: dict[str, list[int]]
492
561
  """
493
- return self.fetch_from_industry_code(
494
- industry_code=industry_code,
495
- industry_classification=industry_classification,
496
- fetch_ticker=False,
497
- )
562
+ url = f"{self.url_base}company_groups/industry/{industry_classification}/{industry_code}"
563
+ return self.fetch(url)