summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHenrique Joaquim <henriquecjoaquim@gmail.com>2024-06-18 15:19:39 +0100
committerGitHub <noreply@github.com>2024-06-18 14:19:39 +0000
commitfdfacc8363af4190c93c0c8ed63207a2a8b8b36a (patch)
tree144be7245a7c0a44ec9d3804fceeac6313a50953
parent5c79635da2146d2e45c9d5d38017941c77cdf0aa (diff)
[Feature] Improved cached results (#6506)
* flag to store cached results, or not * using keys to reference obbjects in the registry and data processing commands * out of bonds check * controlling results, by index or key and displaying charts * autocompletion * deps * forward ref --------- Co-authored-by: Igor Radovanovic <74266147+IgorWounds@users.noreply.github.com>
-rw-r--r--cli/openbb_cli/argparse_translator/obbject_registry.py43
-rw-r--r--cli/openbb_cli/argparse_translator/reference_processor.py5
-rw-r--r--cli/openbb_cli/argparse_translator/utils.py2
-rw-r--r--cli/openbb_cli/controllers/base_controller.py97
-rw-r--r--cli/openbb_cli/controllers/base_platform_controller.py78
-rw-r--r--cli/openbb_cli/controllers/choices.py16
-rw-r--r--cli/openbb_cli/controllers/cli_controller.py12
-rw-r--r--cli/openbb_cli/controllers/utils.py9
-rw-r--r--cli/poetry.lock247
-rw-r--r--cli/pyproject.toml3
10 files changed, 321 insertions, 191 deletions
diff --git a/cli/openbb_cli/argparse_translator/obbject_registry.py b/cli/openbb_cli/argparse_translator/obbject_registry.py
index aa0f876942b..2a802e94b9e 100644
--- a/cli/openbb_cli/argparse_translator/obbject_registry.py
+++ b/cli/openbb_cli/argparse_translator/obbject_registry.py
@@ -1,7 +1,7 @@
"""Registry for OBBjects."""
import json
-from typing import Dict, List
+from typing import Dict, List, Optional, Union
from openbb_core.app.model.obbject import OBBject
@@ -29,11 +29,32 @@ class Registry:
return True
return False
- def get(self, idx: int) -> OBBject:
+ def get(self, arg: Union[int, str]) -> Optional[OBBject]:
+ """Return the obbject with index or key."""
+ if isinstance(arg, int):
+ return self._get_by_index(arg)
+ if isinstance(arg, str):
+ return self._get_by_key(arg)
+
+ raise ValueError("Couldn't get the `OBBject` with the provided argument.")
+
+ def _get_by_key(self, key: str) -> Optional[OBBject]:
+ """Return the obbject with key."""
+ for obbject in self._obbjects:
+ if obbject.extra.get("register_key", "") == key:
+ return obbject
+ return None
+
+ def _get_by_index(self, idx: int) -> Optional[OBBject]:
"""Return the obbject at index idx."""
# the list should work as a stack
# i.e., the last element needs to be accessed by idx=0 and so on
reversed_list = list(reversed(self._obbjects))
+
+ # check if the index is out of bounds
+ if idx >= len(reversed_list):
+ return None
+
return reversed_list[idx]
def remove(self, idx: int = -1):
@@ -46,10 +67,10 @@ class Registry:
@property
def all(self) -> Dict[int, Dict]:
- """Return all obbjects in the registry"""
+ """Return all obbjects in the registry."""
def _handle_standard_params(obbject: OBBject) -> str:
- """Handle standard params for obbjects"""
+ """Handle standard params for obbjects."""
standard_params_json = ""
std_params = getattr(
obbject, "_standard_params", {}
@@ -63,7 +84,7 @@ class Registry:
return standard_params_json
def _handle_data_repr(obbject: OBBject) -> str:
- """Handle data representation for obbjects"""
+ """Handle data representation for obbjects."""
data_repr = ""
if hasattr(obbject, "results") and obbject.results:
data_schema = (
@@ -86,11 +107,21 @@ class Registry:
"standard params": _handle_standard_params(obbject),
"data": _handle_data_repr(obbject),
"command": obbject.extra.get("command", ""),
+ "key": obbject.extra.get("register_key", ""),
}
return obbjects
@property
def obbjects(self) -> List[OBBject]:
- """Return all obbjects in the registry"""
+ """Return all obbjects in the registry."""
return self._obbjects
+
+ @property
+ def obbject_keys(self) -> List[str]:
+ """Return all obbject keys in the registry."""
+ return [
+ obbject.extra["register_key"]
+ for obbject in self._obbjects
+ if "register_key" in obbject.extra
+ ]
diff --git a/cli/openbb_cli/argparse_translator/reference_processor.py b/cli/openbb_cli/argparse_translator/reference_processor.py
index 53cba266cf5..0ba6fef1dd3 100644
--- a/cli/openbb_cli/argparse_translator/reference_processor.py
+++ b/cli/openbb_cli/argparse_translator/reference_processor.py
@@ -1,8 +1,13 @@
"""Module for the ReferenceToArgumentsProcessor class."""
+# `ForwardRef`needs to be imported because the usage of `eval()`,
+# which creates a ForwardRef
+# which would raise a not defined error if it's not imported here.
+# pylint: disable=unused-import
from typing import (
Any,
Dict,
+ ForwardRef, # noqa: F401
List,
Literal,
Optional,
diff --git a/cli/openbb_cli/argparse_translator/utils.py b/cli/openbb_cli/argparse_translator/utils.py
index 3aa274a5736..aebba54d23e 100644
--- a/cli/openbb_cli/argparse_translator/utils.py
+++ b/cli/openbb_cli/argparse_translator/utils.py
@@ -65,7 +65,7 @@ def get_argument_optional_choices(parser: ArgumentParser, argument_name: str) ->
and hasattr(action, "optional_choices")
):
return (
- action.optional_choices # pylint: disable=no-member # this is a custom attribute
+ action.optional_choices # type: ignore[attr-defined] # this is a custom attribute
)
return False
diff --git a/cli/openbb_cli/controllers/base_controller.py b/cli/openbb_cli/controllers/base_controller.py
index 00ed8da88b4..91a353f8b60 100644
--- a/cli/openbb_cli/controllers/base_controller.py
+++ b/cli/openbb_cli/controllers/base_controller.py
@@ -24,6 +24,7 @@ from openbb_cli.controllers.utils import (
print_rich_table,
remove_file,
system_clear,
+ validate_register_key,
)
from openbb_cli.session import Session
from prompt_toolkit.formatted_text import HTML
@@ -628,19 +629,77 @@ class BaseController(metaclass=ABCMeta):
"'OBBjects' where all execution results are stored. "
"It is organized as a stack, with the most recent result at index 0.",
)
+ parser.add_argument("--index", dest="index", help="Index of the result.")
+ parser.add_argument("--key", dest="key", help="Key of the result.")
+ parser.add_argument(
+ "--chart", action="store_true", dest="chart", help="Display chart."
+ )
+ parser.add_argument(
+ "--export", dest="export", help="Export data.", nargs="+", default=None
+ )
+
ns_parser = self.parse_simple_args(parser, other_args)
if ns_parser:
- results = session.obbject_registry.all
- if results:
- df = pd.DataFrame.from_dict(results, orient="index")
- print_rich_table(
- df,
- show_index=True,
- index_name="stack index",
- title="OBBject Results",
- )
- else:
- session.console.print("[info]No results found.[/info]")
+ if not ns_parser.index and not ns_parser.key:
+ results = session.obbject_registry.all
+ if results:
+ df = pd.DataFrame.from_dict(results, orient="index")
+ print_rich_table(
+ df,
+ show_index=True,
+ index_name="stack index",
+ title="OBBject Results",
+ )
+ else:
+ session.console.print("[info]No results found.[/info]")
+ elif ns_parser.index:
+ try:
+ index = int(ns_parser.index)
+ obbject = session.obbject_registry.get(index)
+ if obbject:
+ if ns_parser.chart and obbject.chart:
+ obbject.show()
+ else:
+ title = obbject.extra.get("command", "")
+ df = obbject.to_dataframe()
+ print_rich_table(
+ df=df,
+ show_index=True,
+ title=title,
+ export=ns_parser.export,
+ )
+ if ns_parser.chart and not obbject.chart:
+ session.console.print(
+ "[info]No chart available.[/info]"
+ )
+ else:
+ session.console.print(
+ f"[info]No result found at index {index}.[/info]"
+ )
+ except ValueError:
+ session.console.print(
+ f"[red]Index must be an integer, not '{ns_parser.index}'.[/red]"
+ )
+ elif ns_parser.key:
+ obbject = session.obbject_registry.get(ns_parser.key)
+ if obbject:
+ if ns_parser.chart and obbject.chart:
+ obbject.show()
+ else:
+ title = obbject.extra.get("command", "")
+ df = obbject.to_dataframe()
+ print_rich_table(
+ df=df,
+ show_index=True,
+ title=title,
+ export=ns_parser.export,
+ )
+ if ns_parser.chart and not obbject.chart:
+ session.console.print("[info]No chart available.[/info]")
+ else:
+ session.console.print(
+ f"[info]No result found with key '{ns_parser.key}'.[/info]"
+ )
@staticmethod
def parse_simple_args(parser: argparse.ArgumentParser, other_args: List[str]):
@@ -774,6 +833,22 @@ class BaseController(metaclass=ABCMeta):
help="Number of entries to show in data.",
type=check_positive,
)
+
+ parser.add_argument(
+ "--register_obbject",
+ dest="register_obbject",
+ action="store_false",
+ default=True,
+ help="Flag to store data in the OBBject registry, True by default.",
+ )
+ parser.add_argument(
+ "--register_key",
+ dest="register_key",
+ default="",
+ help="Key to reference data in the OBBject registry.",
+ type=validate_register_key,
+ )
+
if session.settings.USE_CLEAR_AFTER_CMD:
system_clear()
diff --git a/cli/openbb_cli/controllers/base_platform_controller.py b/cli/openbb_cli/controllers/base_platform_controller.py
index 658e73b8a9f..a3cd0d1527c 100644
--- a/cli/openbb_cli/controllers/base_platform_controller.py
+++ b/cli/openbb_cli/controllers/base_platform_controller.py
@@ -75,20 +75,31 @@ class PlatformController(BaseController):
for _, trl in self.translators.items():
for action in trl._parser._actions: # pylint: disable=protected-access
if action.dest == "data":
+ # Generate choices by combining indexed and key-based choices
action.choices = [
"OBB" + str(i)
for i in range(len(session.obbject_registry.obbjects))
+ ] + [
+ obbject.extra["register_key"]
+ for obbject in session.obbject_registry.obbjects
+ if "register_key" in obbject.extra
]
+
action.type = str
action.nargs = None
def _intersect_data_processing_commands(self, ns_parser):
"""Intersect data processing commands and change the obbject id into an actual obbject."""
if hasattr(ns_parser, "data"):
- ns_parser.data = int(ns_parser.data.replace("OBB", ""))
- if ns_parser.data in range(len(session.obbject_registry.obbjects)):
+ if "OBB" in ns_parser.data:
+ ns_parser.data = int(ns_parser.data.replace("OBB", ""))
+
+ if (ns_parser.data in range(len(session.obbject_registry.obbjects))) or (
+ ns_parser.data in session.obbject_registry.obbject_keys
+ ):
obbject = session.obbject_registry.get(ns_parser.data)
- setattr(ns_parser, "data", obbject.results)
+ if obbject and isinstance(obbject, OBBject):
+ setattr(ns_parser, "data", obbject.results)
return ns_parser
@@ -152,6 +163,11 @@ class PlatformController(BaseController):
try:
ns_parser = self._intersect_data_processing_commands(ns_parser)
+ store_obbject = (
+ hasattr(ns_parser, "register_obbject")
+ and ns_parser.register_obbject
+ )
+
obbject = translator.execute_func(parsed_args=ns_parser)
df: pd.DataFrame = pd.DataFrame()
fig: Optional[OpenBBFigure] = None
@@ -159,7 +175,11 @@ class PlatformController(BaseController):
if obbject:
if isinstance(obbject, OBBject):
- if session.max_obbjects_exceeded() and obbject.results:
+ if (
+ session.max_obbjects_exceeded()
+ and obbject.results
+ and store_obbject
+ ):
session.obbject_registry.remove()
session.console.print(
"[yellow]Maximum number of OBBjects reached. The oldest entry was removed.[yellow]"
@@ -167,25 +187,45 @@ class PlatformController(BaseController):
# use the obbject to store the command so we can display it later on results
obbject.extra["command"] = f"{title} {' '.join(other_args)}"
-
- register_result = session.obbject_registry.register(obbject)
-
- # we need to force to re-link so that the new obbject
- # is immediately available for data processing commands
- self._link_obbject_to_data_processing_commands()
- # also update the completer
- self.update_completer(self.choices_default)
-
+ # if there is a registry key in the parser, store to the obbject
if (
- session.settings.SHOW_MSG_OBBJECT_REGISTRY
- and register_result
+ hasattr(ns_parser, "register_key")
+ and ns_parser.register_key
):
- session.console.print(
- "Added `OBBject` to cached results."
+ if (
+ ns_parser.register_key
+ not in session.obbject_registry.obbject_keys
+ ):
+ obbject.extra["register_key"] = str(
+ ns_parser.register_key
+ )
+ else:
+ session.console.print(
+ f"[yellow]Key `{ns_parser.register_key}` already exists in the registry."
+ "The `OBBject` was kept without the key.[/yellow]"
+ )
+
+ if store_obbject:
+ # store the obbject in the registry
+ register_result = session.obbject_registry.register(
+ obbject
)
- # making the dataframe available
- # either for printing or exporting
+ # we need to force to re-link so that the new obbject
+ # is immediately available for data processing commands
+ self._link_obbject_to_data_processing_commands()
+ # also update the completer
+ self.update_completer(self.choices_default)
+
+ if (
+ session.settings.SHOW_MSG_OBBJECT_REGISTRY
+ and register_result
+ ):
+ session.console.print(
+ "Added `OBBject` to cached results."
+ )
+
+ # making the dataframe available either for printing or exporting
df = obbject.to_dataframe()
export = hasattr(ns_parser, "export") and ns_parser.export
diff --git a/cli/openbb_cli/controllers/choices.py b/cli/openbb_cli/controllers/choices.py
index 338a3fed185..3afc5234c11 100644
--- a/cli/openbb_cli/controllers/choices.py
+++ b/cli/openbb_cli/controllers/choices.py
@@ -10,6 +10,7 @@ from unittest.mock import patch
from openbb_cli.controllers.utils import (
check_file_type_saved,
check_positive,
+ validate_register_key,
)
from openbb_cli.session import Session
@@ -93,6 +94,21 @@ def __mock_parse_known_args_and_warn(
type=check_positive,
)
+ parser.add_argument(
+ "--register_obbject",
+ dest="register_obbject",
+ action="store_false",
+ default=True,
+ help="Flag to store data in the OBBject registry, True by default.",
+ )
+ parser.add_argument(
+ "--register_key",
+ dest="register_key",
+ default="",
+ help="Key to reference data in the OBBject registry.",
+ type=validate_register_key,
+ )
+
def __mock_parse_simple_args(parser: ArgumentParser, other_args: List[str]) -> None:
"""Add arguments.
diff --git a/cli/openbb_cli/controllers/cli_controller.py b/cli/openbb_cli/controllers/cli_controller.py
index 807af177ce5..004239bf436 100644
--- a/cli/openbb_cli/controllers/cli_controller.py
+++ b/cli/openbb_cli/controllers/cli_controller.py
@@ -17,6 +17,7 @@ from typing import Any, Dict, List, Optional
import pandas as pd
import requests
+from openbb import obb
from openbb_cli.config import constants
from openbb_cli.config.constants import (
ASSETS_DIRECTORY,
@@ -46,8 +47,6 @@ from prompt_toolkit.formatted_text import HTML
from prompt_toolkit.styles import Style
from pydantic import BaseModel
-from openbb import obb
-
PLATFORM_ROUTERS = {
d: "menu" if not isinstance(getattr(obb, d), BaseModel) else "command"
for d in dir(obb)
@@ -203,7 +202,14 @@ class CLIController(BaseController):
"-h": "--help",
}
choices["stop"] = {"--help": None, "-h": "--help"}
- choices["results"] = {"--help": None, "-h": "--help"}
+ choices["results"] = {
+ "--help": None,
+ "-h": "--help",
+ "--export": None,
+ "--index": None,
+ "--key": None,
+ "--chart": None,
+ }
self.update_completer(choices)
diff --git a/cli/openbb_cli/controllers/utils.py b/cli/openbb_cli/controllers/utils.py
index c85bf236c5d..c921ad5ab3a 100644
--- a/cli/openbb_cli/controllers/utils.py
+++ b/cli/openbb_cli/controllers/utils.py
@@ -539,6 +539,15 @@ def check_positive(value) -> int:
return new_value
+def validate_register_key(value: str) -> str:
+ """Validate the register key to ensure it does not contain the reserved word 'OBB'."""
+ if "OBB" in value:
+ raise argparse.ArgumentTypeError(
+ "The register key cannot contain the reserved word 'OBB'."
+ )
+ return str(value)
+
+
def get_user_agent() -> str:
"""Get a not very random user agent."""
user_agent_strings = [
diff --git a/cli/poetry.lock b/cli/poetry.lock
index ab0eb3f5966..15065576f63 100644
--- a/cli/poetry.lock
+++ b/cli/poetry.lock
@@ -591,13 +591,13 @@ wmi = ["wmi (>=1.5.1)"]
[[package]]
name = "email-validator"
-version = "2.1.1"
+version = "2.1.2"
description = "A robust email address syntax and deliverability validation library."
optional = false
python-versions = ">=3.8"
files = [
- {file = "email_validator-2.1.1-py3-none-any.whl", hash = "sha256:97d882d174e2a65732fb43bfce81a3a834cbc1bde8bf419e30ef5ea976370a05"},
- {file = "email_validator-2.1.1.tar.gz", hash = "sha256:200a70680ba08904be6d1eef729205cc0d687634399a5924d842533efb824b84"},
+ {file = "email_validator-2.1.2-py3-none-any.whl", hash = "sha256:d89f6324e13b1e39889eab7f9ca2f91dc9aebb6fa50a6d8bd4329ab50f251115"},
+ {file = "email_validator-2.1.2.tar.gz", hash = "sha256:14c0f3d343c4beda37400421b39fa411bbe33a75df20825df73ad53e06a9f04c"},
]
[package.dependencies]
@@ -698,13 +698,13 @@ standard = ["fastapi", "uvicorn[standard] (>=0.15.0)"]
[[package]]
name = "fastjsonschema"
-version = "2.19.1"
+version = "2.20.0"
description = "Fastest Python implementation of JSON schema"
optional = false
python-versions = "*"
files = [
- {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"},
- {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"},
+ {file = "fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a"},
+ {file = "fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23"},
]
[package.extras]
@@ -804,8 +804,6 @@ files = [
{file = "frozendict-2.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d13b4310db337f4d2103867c5a05090b22bc4d50ca842093779ef541ea9c9eea"},
{file = "frozendict-2.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:b3b967d5065872e27b06f785a80c0ed0a45d1f7c9b85223da05358e734d858ca"},
{file = "frozendict-2.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:4ae8d05c8d0b6134bfb6bfb369d5fa0c4df21eabb5ca7f645af95fdc6689678e"},
- {file = "frozendict-2.4.4-py311-none-any.whl", hash = "sha256:705efca8d74d3facbb6ace80ab3afdd28eb8a237bfb4063ed89996b024bc443d"},
- {file = "frozendict-2.4.4-py312-none-any.whl", hash = "sha256:d9647563e76adb05b7cde2172403123380871360a114f546b4ae1704510801e5"},
{file = "frozendict-2.4.4.tar.gz", hash = "sha256:3f7c031b26e4ee6a3f786ceb5e3abf1181c4ade92dce1f847da26ea2c96008c7"},
]
@@ -1345,13 +1343,9 @@ files = [
{file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"},
{file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"},
- {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"},
- {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"},
{file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"},
- {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"},
- {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"},
{file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"},
{file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"},
{file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"},
@@ -1539,13 +1533,13 @@ files = [
[[package]]
name = "more-itertools"
-version = "10.2.0"
+version = "10.3.0"
description = "More routines for operating on iterables, beyond itertools"
optional = false
python-versions = ">=3.8"
files = [
- {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"},
- {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"},
+ {file = "more-itertools-10.3.0.tar.gz", hash = "sha256:e5d93ef411224fbcef366a6e8ddc4c5781bc6359d43412a65dd5964e46111463"},
+ {file = "more_itertools-10.3.0-py3-none-any.whl", hash = "sha256:ea6a02e24a9161e51faad17a8782b92a0df82c12c1c8886fec7f0c3fa1a1b320"},
]
[[package]]
@@ -1748,51 +1742,6 @@ files = [
]
[[package]]
-name = "numpy"
-version = "1.26.4"
-description = "Fundamental package for array computing in Python"
-optional = false
-python-versions = ">=3.9"
-files = [
- {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
- {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
- {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
- {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
- {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
- {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
- {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
- {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
- {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
- {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
- {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
- {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
- {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
- {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
- {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
- {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
- {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
- {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
- {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
- {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", ha