kensho-kfinance 1.1.0__tar.gz → 1.2.0__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 (52) hide show
  1. kensho_kfinance-1.2.0/.readthedocs.yaml +23 -0
  2. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/PKG-INFO +2 -1
  3. kensho_kfinance-1.2.0/docs/conf.py +66 -0
  4. kensho_kfinance-1.2.0/docs/index.rst +18 -0
  5. kensho_kfinance-1.2.0/docs/kfinance.rst +7 -0
  6. kensho_kfinance-1.2.0/docs/llm_tools.rst +7 -0
  7. kensho_kfinance-1.2.0/docs/requirements.txt +4 -0
  8. kensho_kfinance-1.2.0/docs/templates/apidoc/package.rst_t +50 -0
  9. kensho_kfinance-1.2.0/docs/templates/apidoc/toc.rst_t +10 -0
  10. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/justfile +6 -0
  11. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kensho_kfinance.egg-info/PKG-INFO +2 -1
  12. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kensho_kfinance.egg-info/SOURCES.txt +8 -2
  13. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kensho_kfinance.egg-info/requires.txt +1 -0
  14. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/CHANGELOG.md +3 -0
  15. kensho_kfinance-1.2.0/kfinance/batch_request_handling.py +137 -0
  16. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/fetch.py +70 -7
  17. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/kfinance.py +21 -2
  18. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/llm_tools.py +24 -20
  19. kensho_kfinance-1.2.0/kfinance/tests/test_batch_requests.py +256 -0
  20. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/tool_schemas.py +3 -1
  21. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/version.py +2 -2
  22. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/pyproject.toml +1 -0
  23. kensho_kfinance-1.1.0/docs/index.rst +0 -9
  24. kensho_kfinance-1.1.0/docs/kfinance.rst +0 -5033
  25. kensho_kfinance-1.1.0/docs/llm_tools.rst +0 -191
  26. kensho_kfinance-1.1.0/scripts/docs/make_rst.py +0 -98
  27. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/.coveragerc +0 -0
  28. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/.github/workflows/ci-lint.yml +0 -0
  29. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/.github/workflows/ci-test.yml +0 -0
  30. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/.github/workflows/python-publish.yml +0 -0
  31. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/.gitignore +0 -0
  32. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/AUTHORS.md +0 -0
  33. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/CODE_OF_CONDUCT.md +0 -0
  34. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/CONTRIBUTING.md +0 -0
  35. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/LICENSE +0 -0
  36. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/README.md +0 -0
  37. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kensho_kfinance.egg-info/dependency_links.txt +0 -0
  38. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kensho_kfinance.egg-info/top_level.txt +0 -0
  39. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/__init__.py +0 -0
  40. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/constants.py +0 -0
  41. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/meta_classes.py +0 -0
  42. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/prompt.py +0 -0
  43. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/py.typed +0 -0
  44. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/server_thread.py +0 -0
  45. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/tests/__init__.py +0 -0
  46. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/tests/test_fetch.py +0 -0
  47. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/kfinance/tests/test_objects.py +0 -0
  48. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/scripts/copyright_line_check.sh +0 -0
  49. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/scripts/lint.sh +0 -0
  50. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/scripts/test.sh +0 -0
  51. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/setup.cfg +0 -0
  52. {kensho_kfinance-1.1.0 → kensho_kfinance-1.2.0}/setup.py +0 -0
@@ -0,0 +1,23 @@
1
+ version: 2
2
+ build:
3
+ os: ubuntu-22.04
4
+ tools:
5
+ python: "3.10"
6
+ # You can also specify other tool versions:
7
+ # nodejs: "16"
8
+ # borrowed from here:
9
+ # https://docs.readthedocs.com/platform/stable/build-customization.html#avoid-having-a-dirty-git-index
10
+ jobs:
11
+ pre_install:
12
+ - git update-index --assume-unchanged docs/conf.py
13
+
14
+ # Build documentation in the docs/ directory with Sphinx
15
+ sphinx:
16
+ configuration: docs/conf.py
17
+
18
+ # Dependencies required to build your docs
19
+ python:
20
+ install:
21
+ - method: pip
22
+ path: .
23
+ - requirements: docs/requirements.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 1.1.0
3
+ Version: 1.2.0
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
@@ -28,6 +28,7 @@ Requires-Dist: mypy<2,>=1.15.0; extra == "dev"
28
28
  Requires-Dist: pytest<7,>=6.1.2; extra == "dev"
29
29
  Requires-Dist: pytest-cov<7,>=6.0.0; extra == "dev"
30
30
  Requires-Dist: ruff<1,>=0.9.4; extra == "dev"
31
+ Requires-Dist: requests_mock<1.2,>=1.1; extra == "dev"
31
32
  Dynamic: license-file
32
33
 
33
34
  # kFinance
@@ -0,0 +1,66 @@
1
+ # Configuration file for the Sphinx documentation builder.
2
+ #
3
+ # For the full list of built-in configuration values, see the documentation:
4
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html
5
+
6
+ # -- Project information -----------------------------------------------------
7
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
8
+ from importlib.metadata import version as get_version
9
+
10
+ project = 'kensho-kfinance'
11
+ copyright = '2025, Kensho Technologies'
12
+ author = 'Kensho Technologies'
13
+
14
+ # borrowed from here:
15
+ # https://setuptools-scm.readthedocs.io/en/latest/usage/#usage-from-sphinx
16
+ release: str = get_version(project)
17
+ # for example take major/minor
18
+ version: str = ".".join(release.split('.')[:2])
19
+
20
+ # -- General configuration ---------------------------------------------------
21
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
22
+
23
+ # borrowed from internal Kensho Sphinx configuration
24
+ extensions = [
25
+ "sphinx.ext.autodoc",
26
+ "sphinx.ext.intersphinx",
27
+ "sphinx.ext.coverage",
28
+ "sphinx.ext.mathjax",
29
+ "sphinx.ext.githubpages",
30
+ "sphinx.ext.autosummary",
31
+ "sphinx.ext.viewcode",
32
+ "sphinx.ext.napoleon",
33
+ # 3rd party extensions
34
+ # m2r2 is to add support to .md files specifically to include README.md files.
35
+ # See this discussion: https://github.com/sphinx-doc/sphinx/issues/7000
36
+ "m2r2",
37
+ # A ReadTheDocs theme for Sphinx
38
+ "sphinx_rtd_theme",
39
+ ]
40
+
41
+ napoleon_google_docstring = True
42
+ napoleon_use_ivar = True
43
+
44
+ autosummary_generate = True
45
+
46
+ # Don't prepend module path prefixes to function definitions. Makes automodule docs less cluttered
47
+ add_module_names = False
48
+
49
+ # Add any paths that contain templates here, relative to this directory.
50
+ templates_path = ["templates"]
51
+
52
+ # The suffix of source filenames.
53
+ source_suffix = [".rst", ".md"]
54
+
55
+
56
+
57
+ # -- Options for HTML output -------------------------------------------------
58
+ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
59
+
60
+ html_theme = 'sphinx_rtd_theme'
61
+
62
+ # https://sphinx-rtd-theme.readthedocs.io/en/stable/configuring.html
63
+ html_theme_options = {"body_min_width": 0, "body_max_width": "none"}
64
+
65
+ # https://www.sphinx-doc.org/en/1.4.9/config.html#confval-html_sidebars
66
+ html_sidebars = {"**": ["globaltoc.html", "relations.html", "searchbox.html"]}
@@ -0,0 +1,18 @@
1
+ Index
2
+ #####################
3
+
4
+ Documentation page for kensho-kfinance library.
5
+
6
+ .. toctree::
7
+ :hidden:
8
+ :glob:
9
+ :maxdepth: 1
10
+
11
+ kfinance <kfinance>
12
+ llm_tools <llm_tools>
13
+
14
+ Indices and tables
15
+ ==================
16
+
17
+ * :ref:`genindex`
18
+ * :ref:`modindex`
@@ -0,0 +1,7 @@
1
+ kfinance
2
+ ========
3
+
4
+ .. automodule:: kfinance.kfinance
5
+ :members:
6
+ :undoc-members:
7
+ :inherited-members:
@@ -0,0 +1,7 @@
1
+ llm_tools
2
+ =========
3
+
4
+ .. automodule:: kfinance.llm_tools
5
+ :members:
6
+ :undoc-members:
7
+ :inherited-members:
@@ -0,0 +1,4 @@
1
+ sphinx==8.1.3
2
+ m2r2==0.3.3.post2
3
+ sphinx-rtd-theme==3.0.2
4
+ docutils==0.20
@@ -0,0 +1,50 @@
1
+ {% raw %}
2
+ {%- macro automodule(modname, options) -%}
3
+ .. automodule:: {{ modname }}
4
+ {%- for option in options %}
5
+ :{{ option }}:
6
+ {%- endfor %}
7
+ {%- endmacro %}
8
+
9
+ {%- macro toctree(docnames) -%}
10
+ .. toctree::
11
+ :maxdepth: {{ maxdepth }}
12
+ {% for docname in docnames %}
13
+ {{ docname }}
14
+ {%- endfor %}
15
+ {%- endmacro %}
16
+
17
+ {%- if is_namespace %}
18
+ {{- [pkgname, "namespace"] | join(" ") | e | heading }}
19
+ {% else %}
20
+ {{- [pkgname] | join(" ") | e | heading }}
21
+ {% endif %}
22
+
23
+ {%- if modulefirst and not is_namespace %}
24
+ {{ automodule(pkgname, automodule_options) }}
25
+ {% endif %}
26
+
27
+ {%- if subpackages %}
28
+ {{ toctree(subpackages) }}
29
+ {% endif %}
30
+
31
+ {%- if submodules %}
32
+ {% if separatemodules %}
33
+ {{ toctree(submodules) }}
34
+ {% else %}
35
+ {%- for submodule in submodules %}
36
+ {% if show_headings %}
37
+ {{- [submodule] | join(" ") | e | heading(2) }}
38
+ {% endif %}
39
+ {{ automodule(submodule, automodule_options) }}
40
+ {% endfor %}
41
+ {%- endif %}
42
+ {%- endif %}
43
+
44
+ {%- if not modulefirst and not is_namespace %}
45
+ Module contents
46
+ ---------------
47
+
48
+ {{ automodule(pkgname, automodule_options) }}
49
+ {% endif %}
50
+ {% endraw %}
@@ -0,0 +1,10 @@
1
+ {% raw %}
2
+ Index Tree
3
+ ==========
4
+
5
+ .. toctree::
6
+ :maxdepth: {{ maxdepth }}
7
+ {% for docname in docnames %}
8
+ {{ docname }}
9
+ {%- endfor %}
10
+ {% endraw %}
@@ -29,3 +29,9 @@ alias t := unit-test
29
29
  # Run unit tests. Use args for optional settings
30
30
  unit-test *args:
31
31
  python -m pytest {{args}}
32
+
33
+ # Build the sphinx documents locally
34
+ # First, copy the dependencies in docs/requirements.txt into pyproject.toml and install in a venv.
35
+ # Don't merge changes to pyproject and docs/output into the remote repo!
36
+ sphinx *args:
37
+ sphinx-build docs docs/output
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kensho-kfinance
3
- Version: 1.1.0
3
+ Version: 1.2.0
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
@@ -28,6 +28,7 @@ Requires-Dist: mypy<2,>=1.15.0; extra == "dev"
28
28
  Requires-Dist: pytest<7,>=6.1.2; extra == "dev"
29
29
  Requires-Dist: pytest-cov<7,>=6.0.0; extra == "dev"
30
30
  Requires-Dist: ruff<1,>=0.9.4; extra == "dev"
31
+ Requires-Dist: requests_mock<1.2,>=1.1; extra == "dev"
31
32
  Dynamic: license-file
32
33
 
33
34
  # kFinance
@@ -1,5 +1,6 @@
1
1
  .coveragerc
2
2
  .gitignore
3
+ .readthedocs.yaml
3
4
  AUTHORS.md
4
5
  CODE_OF_CONDUCT.md
5
6
  CONTRIBUTING.md
@@ -11,9 +12,13 @@ setup.py
11
12
  .github/workflows/ci-lint.yml
12
13
  .github/workflows/ci-test.yml
13
14
  .github/workflows/python-publish.yml
15
+ docs/conf.py
14
16
  docs/index.rst
15
17
  docs/kfinance.rst
16
18
  docs/llm_tools.rst
19
+ docs/requirements.txt
20
+ docs/templates/apidoc/package.rst_t
21
+ docs/templates/apidoc/toc.rst_t
17
22
  kensho_kfinance.egg-info/PKG-INFO
18
23
  kensho_kfinance.egg-info/SOURCES.txt
19
24
  kensho_kfinance.egg-info/dependency_links.txt
@@ -21,6 +26,7 @@ kensho_kfinance.egg-info/requires.txt
21
26
  kensho_kfinance.egg-info/top_level.txt
22
27
  kfinance/CHANGELOG.md
23
28
  kfinance/__init__.py
29
+ kfinance/batch_request_handling.py
24
30
  kfinance/constants.py
25
31
  kfinance/fetch.py
26
32
  kfinance/kfinance.py
@@ -32,9 +38,9 @@ kfinance/server_thread.py
32
38
  kfinance/tool_schemas.py
33
39
  kfinance/version.py
34
40
  kfinance/tests/__init__.py
41
+ kfinance/tests/test_batch_requests.py
35
42
  kfinance/tests/test_fetch.py
36
43
  kfinance/tests/test_objects.py
37
44
  scripts/copyright_line_check.sh
38
45
  scripts/lint.sh
39
- scripts/test.sh
40
- scripts/docs/make_rst.py
46
+ scripts/test.sh
@@ -15,3 +15,4 @@ mypy<2,>=1.15.0
15
15
  pytest<7,>=6.1.2
16
16
  pytest-cov<7,>=6.0.0
17
17
  ruff<1,>=0.9.4
18
+ requests_mock<1.2,>=1.1
@@ -1,4 +1,7 @@
1
1
  # Changelog
2
+ ## v1.2.0
3
+ - Add batch requests for iterable classes
4
+
2
5
  ## v1.1.0
3
6
  - Add market cap, TEV, and shares outstanding
4
7
 
@@ -0,0 +1,137 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ import functools
3
+ from functools import cached_property
4
+ import threading
5
+ from typing import Any, Callable, Iterable, Protocol, Sized, Type, TypeVar
6
+
7
+ from requests.exceptions import HTTPError
8
+
9
+ from .fetch import KFinanceApiClient
10
+
11
+
12
+ T = TypeVar("T")
13
+
14
+ MAX_WORKERS_CAP: int = 10
15
+
16
+ throttle = threading.Semaphore(MAX_WORKERS_CAP)
17
+
18
+
19
+ def add_methods_of_singular_class_to_iterable_class(singular_cls: Type[T]) -> Callable:
20
+ """Returns a decorator that sets each method, property, and cached_property of"""
21
+ "[singular_cls] as an attribute of the decorated class."
22
+
23
+ class IterableKfinanceClass(Protocol, Sized, Iterable[T]):
24
+ """A protocol to represent a iterable Kfinance classes like Tickers and Companies.
25
+
26
+ Each of these classes has a kfinance_api_client attribute.
27
+ """
28
+
29
+ kfinance_api_client: KFinanceApiClient
30
+
31
+ def decorator(iterable_cls: Type[IterableKfinanceClass]) -> Type[IterableKfinanceClass]:
32
+ """Adds functions from a singular class to an iterable class.
33
+
34
+ This decorator modifies the [iterable_cls] so that when an attribute
35
+ (method, property, or cached property) added by the decorator is accessed,
36
+ it returns a dictionary. This dictionary maps each object in [iterable_cls]
37
+ to the result of invoking the attribute on that specific object.
38
+
39
+ For example, consider a `Company` class with a `city` property and a
40
+ `Companies` class that is an iterable of `Company` instances. When the
41
+ `Companies` class is decorated, it gains a `city` property. Accessing this
42
+ property will yield a dictionary where each key is a `Company` instance
43
+ and the corresponding value is the city of that instance. The resulting
44
+ dictionary might look like:
45
+
46
+ {<kfinance.kfinance.Company object>: 'Some City'}
47
+
48
+ Error Handling:
49
+ - If the result is a 404 HTTP error, the corresponding value
50
+ for that object in the dictionary will be set to None.
51
+ - For any other HTTP error, the error is raised and bubbles up.
52
+
53
+ Note:
54
+ This decorator requires [iterable_cls] to be an iterable of
55
+ instances of [singular_cls].
56
+ """
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
+ def process_in_batch(
73
+ method: Callable, self: IterableKfinanceClass, *args: Any, **kwargs: Any
74
+ ) -> 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
+ )
87
+ )
88
+
89
+ return results
90
+
91
+ for method_name in dir(singular_cls):
92
+ method = getattr(singular_cls, method_name)
93
+ if method_name.startswith("__") or method_name.startswith("set_"):
94
+ continue
95
+ if callable(method):
96
+
97
+ def create_method_wrapper(method: Callable) -> Callable:
98
+ @functools.wraps(method)
99
+ def method_wrapper(
100
+ self: IterableKfinanceClass, *args: Any, **kwargs: Any
101
+ ) -> dict:
102
+ return process_in_batch(method, self, *args, **kwargs)
103
+
104
+ return method_wrapper
105
+
106
+ setattr(iterable_cls, method_name, create_method_wrapper(method))
107
+
108
+ elif isinstance(method, property):
109
+
110
+ def create_prop_wrapper(method: property) -> Callable:
111
+ assert method.fget is not None
112
+
113
+ @functools.wraps(method.fget)
114
+ def prop_wrapper(self: IterableKfinanceClass) -> Any:
115
+ assert method.fget is not None
116
+ return process_in_batch(method.fget, self)
117
+
118
+ return prop_wrapper
119
+
120
+ setattr(iterable_cls, method_name, property(create_prop_wrapper(method)))
121
+
122
+ elif isinstance(method, cached_property):
123
+
124
+ def create_cached_prop_wrapper(method: cached_property) -> cached_property:
125
+ @functools.wraps(method.func)
126
+ def cached_prop_wrapper(self: IterableKfinanceClass) -> Any:
127
+ return process_in_batch(method.func, self)
128
+
129
+ wrapped_cached_property = cached_property(cached_prop_wrapper)
130
+ wrapped_cached_property.__set_name__(iterable_cls, method_name)
131
+ return wrapped_cached_property
132
+
133
+ setattr(iterable_cls, method_name, create_cached_prop_wrapper(method))
134
+
135
+ return iterable_cls
136
+
137
+ return decorator
@@ -1,5 +1,8 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from contextlib import contextmanager
1
3
  from time import time
2
- from typing import Callable, Optional
4
+ from typing import Callable, Generator, Optional
5
+ from uuid import uuid4
3
6
 
4
7
  import jwt
5
8
  import requests
@@ -19,6 +22,7 @@ DEFAULT_API_HOST: str = "https://kfinance.kensho.com"
19
22
  DEFAULT_API_VERSION: int = 1
20
23
  DEFAULT_OKTA_HOST: str = "https://kensho.okta.com"
21
24
  DEFAULT_OKTA_AUTH_SERVER: str = "default"
25
+ DEFAULT_MAX_WORKERS: int = 10
22
26
 
23
27
 
24
28
  class KFinanceApiClient:
@@ -27,12 +31,33 @@ class KFinanceApiClient:
27
31
  refresh_token: Optional[str] = None,
28
32
  client_id: Optional[str] = None,
29
33
  private_key: Optional[str] = None,
34
+ thread_pool: Optional[ThreadPoolExecutor] = None,
30
35
  api_host: str = DEFAULT_API_HOST,
31
36
  api_version: int = DEFAULT_API_VERSION,
32
37
  okta_host: str = DEFAULT_OKTA_HOST,
33
38
  okta_auth_server: str = DEFAULT_OKTA_AUTH_SERVER,
34
39
  ):
35
- """Configuration of KFinance Client."""
40
+ """Configuration of KFinance Client.
41
+
42
+ :param refresh_token: users refresh token
43
+ :type refresh_token: str, Optional
44
+ :param client_id: users client id will be provided by support@kensho.com
45
+ :type client_id: str, Optional
46
+ :param private_key: users private key that corresponds to the registered public sent to support@kensho.com
47
+ :type private_key: str, Optional
48
+ :param thread_pool: the thread pool used to execute batch requests. The number of concurrent requests is
49
+ capped at 10. If no thread pool is provided, a thread pool with 10 max workers will be created when batch
50
+ requests are made.
51
+ :type thread_pool: ThreadPoolExecutor, Optional
52
+ :param api_host: the api host URL
53
+ :type api_host: str
54
+ :param api_version: the api version number
55
+ :type api_version: int
56
+ :param okta_host: the okta host URL
57
+ :type okta_host: str
58
+ :param okta_auth_server: the okta route for authentication
59
+ :type okta_auth_server: str
60
+ """
36
61
  if refresh_token is not None:
37
62
  self.refresh_token = refresh_token
38
63
  self._access_token_refresh_func: Callable[..., str] = (
@@ -48,10 +73,40 @@ class KFinanceApiClient:
48
73
  self.api_version = api_version
49
74
  self.okta_host = okta_host
50
75
  self.okta_auth_server = okta_auth_server
76
+ self._thread_pool = thread_pool
51
77
  self.url_base = f"{self.api_host}/api/v{self.api_version}/"
52
78
  self._access_token_expiry = 0
53
79
  self._access_token: str | None = None
54
80
  self.user_agent_source = "object_oriented"
81
+ self._batch_id: str | None = None
82
+ self._batch_size: str | None = None
83
+
84
+ @contextmanager
85
+ def batch_request_header(self, batch_size: int) -> Generator:
86
+ """Set batch id and batch size for batch request request headers"""
87
+ batch_id = str(uuid4())
88
+
89
+ self._batch_id = batch_id
90
+ self._batch_size = str(batch_size)
91
+
92
+ try:
93
+ yield
94
+ finally:
95
+ self._batch_id = None
96
+ self._batch_size = None
97
+
98
+ @property
99
+ def thread_pool(self) -> ThreadPoolExecutor:
100
+ """Returns the thread pool used to execute batch requests.
101
+
102
+ If the thread pool is not set, a thread pool with 10 max workers will be created
103
+ and returned.
104
+ """
105
+
106
+ if self._thread_pool is None:
107
+ self._thread_pool = ThreadPoolExecutor(max_workers=DEFAULT_MAX_WORKERS)
108
+
109
+ return self._thread_pool
55
110
 
56
111
  @property
57
112
  def access_token(self) -> str:
@@ -110,13 +165,21 @@ class KFinanceApiClient:
110
165
 
111
166
  def fetch(self, url: str) -> dict:
112
167
  """Does the request and auth"""
168
+
169
+ headers = {
170
+ "Content-Type": "application/json",
171
+ "Authorization": f"Bearer {self.access_token}",
172
+ "User-Agent": f"kfinance/{kfinance_version} {self.user_agent_source}",
173
+ }
174
+ if self._batch_id is not None:
175
+ assert self._batch_size is not None
176
+ headers.update(
177
+ {"Kfinance-Batch-Id": self._batch_id, "Kfinance-Batch-Size": self._batch_size}
178
+ )
179
+
113
180
  response = requests.get(
114
181
  url,
115
- headers={
116
- "Content-Type": "application/json",
117
- "Authorization": f"Bearer {self.access_token}",
118
- "User-Agent": f"kfinance/{kfinance_version} {self.user_agent_source}",
119
- },
182
+ headers=headers,
120
183
  timeout=60,
121
184
  )
122
185
  response.raise_for_status()
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from concurrent.futures import ThreadPoolExecutor
3
4
  from datetime import date, datetime, timezone
4
5
  from functools import cached_property
5
6
  from io import BytesIO
@@ -14,6 +15,7 @@ import numpy as np
14
15
  import pandas as pd
15
16
  from PIL.Image import Image, open as image_open
16
17
 
18
+ from .batch_request_handling import add_methods_of_singular_class_to_iterable_class
17
19
  from .constants import HistoryMetadata, IdentificationTriple, LatestPeriods, YearAndQuarter
18
20
  from .fetch import (
19
21
  DEFAULT_API_HOST,
@@ -31,7 +33,10 @@ from .llm_tools import (
31
33
  langchain_tools,
32
34
  openai_tool_descriptions,
33
35
  )
34
- from .meta_classes import CompanyFunctionsMetaClass, DelegatedCompanyFunctionsMetaClass
36
+ from .meta_classes import (
37
+ CompanyFunctionsMetaClass,
38
+ DelegatedCompanyFunctionsMetaClass,
39
+ )
35
40
  from .prompt import PROMPT
36
41
  from .server_thread import ServerThread
37
42
 
@@ -917,6 +922,7 @@ class BusinessRelationships(NamedTuple):
917
922
  return f"{type(self).__module__}.{type(self).__qualname__} of {str(dictionary)}"
918
923
 
919
924
 
925
+ @add_methods_of_singular_class_to_iterable_class(Company)
920
926
  class Companies(set):
921
927
  """Base class for representing a set of Companies"""
922
928
 
@@ -928,9 +934,11 @@ class Companies(set):
928
934
  :param company_ids: An iterable of S&P CIQ Company ids
929
935
  :type company_ids: Iterable[int]
930
936
  """
937
+ self.kfinance_api_client = kfinance_api_client
931
938
  super().__init__(Company(kfinance_api_client, company_id) for company_id in company_ids)
932
939
 
933
940
 
941
+ @add_methods_of_singular_class_to_iterable_class(Security)
934
942
  class Securities(set):
935
943
  """Base class for representing a set of Securities"""
936
944
 
@@ -945,6 +953,7 @@ class Securities(set):
945
953
  super().__init__(Security(kfinance_api_client, security_id) for security_id in security_ids)
946
954
 
947
955
 
956
+ @add_methods_of_singular_class_to_iterable_class(TradingItem)
948
957
  class TradingItems(set):
949
958
  """Base class for representing a set of Trading Items"""
950
959
 
@@ -958,14 +967,16 @@ class TradingItems(set):
958
967
  :param company_ids: An iterable of S&P CIQ Company ids
959
968
  :type company_ids: Iterable[int]
960
969
  """
970
+ self.kfinance_api_client = kfinance_api_client
961
971
  super().__init__(
962
972
  TradingItem(kfinance_api_client, trading_item_id)
963
973
  for trading_item_id in trading_item_ids
964
974
  )
965
975
 
966
976
 
977
+ @add_methods_of_singular_class_to_iterable_class(Ticker)
967
978
  class Tickers(set):
968
- """Base TickerSet class for representing a set of Tickers"""
979
+ """Base class for representing a set of Tickers"""
969
980
 
970
981
  def __init__(
971
982
  self,
@@ -1041,6 +1052,7 @@ class Client:
1041
1052
  refresh_token: Optional[str] = None,
1042
1053
  client_id: Optional[str] = None,
1043
1054
  private_key: Optional[str] = None,
1055
+ thread_pool: Optional[ThreadPoolExecutor] = None,
1044
1056
  api_host: str = DEFAULT_API_HOST,
1045
1057
  api_version: int = DEFAULT_API_VERSION,
1046
1058
  okta_host: str = DEFAULT_OKTA_HOST,
@@ -1054,6 +1066,10 @@ class Client:
1054
1066
  :type client_id: str, Optional
1055
1067
  :param private_key: users private key that corresponds to the registered public sent to support@kensho.com
1056
1068
  :type private_key: str, Optional
1069
+ :param thread_pool: the thread pool used to execute batch requests. The number of concurrent requests is
1070
+ capped at 10. If no thread pool is provided, a thread pool with 10 max workers will be created when batch
1071
+ requests are made.
1072
+ :type thread_pool: ThreadPoolExecutor, Optional
1057
1073
  :param api_host: the api host URL
1058
1074
  :type api_host: str
1059
1075
  :param api_version: the api version number
@@ -1071,6 +1087,7 @@ class Client:
1071
1087
  api_host=api_host,
1072
1088
  api_version=api_version,
1073
1089
  okta_host=okta_host,
1090
+ thread_pool=thread_pool,
1074
1091
  )
1075
1092
  # method 2 keypair
1076
1093
  elif client_id is not None and private_key is not None:
@@ -1081,6 +1098,7 @@ class Client:
1081
1098
  api_version=api_version,
1082
1099
  okta_host=okta_host,
1083
1100
  okta_auth_server=okta_auth_server,
1101
+ thread_pool=thread_pool,
1084
1102
  )
1085
1103
  # method 3 automatic login getting a refresh token
1086
1104
  else:
@@ -1099,6 +1117,7 @@ class Client:
1099
1117
  api_host=api_host,
1100
1118
  api_version=api_version,
1101
1119
  okta_host=okta_host,
1120
+ thread_pool=thread_pool,
1102
1121
  )
1103
1122
  stdout.write("Login credentials received.\n")
1104
1123