diff options
author | Henrique Joaquim <henriquecjoaquim@gmail.com> | 2024-06-18 15:19:39 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-18 14:19:39 +0000 |
commit | fdfacc8363af4190c93c0c8ed63207a2a8b8b36a (patch) | |
tree | 144be7245a7c0a44ec9d3804fceeac6313a50953 | |
parent | 5c79635da2146d2e45c9d5d38017941c77cdf0aa (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.py | 43 | ||||
-rw-r--r-- | cli/openbb_cli/argparse_translator/reference_processor.py | 5 | ||||
-rw-r--r-- | cli/openbb_cli/argparse_translator/utils.py | 2 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/base_controller.py | 97 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/base_platform_controller.py | 78 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/choices.py | 16 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/cli_controller.py | 12 | ||||
-rw-r--r-- | cli/openbb_cli/controllers/utils.py | 9 | ||||
-rw-r--r-- | cli/poetry.lock | 247 | ||||
-rw-r--r-- | cli/pyproject.toml | 3 |
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", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, |