diff --git a/.gitattributes b/.gitattributes index 8525ffe3eb56d670178659c565019b2a232a4b74..67233d2f8c6f00b6bbf3145719cc97331a598af6 100644 --- a/.gitattributes +++ b/.gitattributes @@ -35,3 +35,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text build/conda/installer/assets/dmg_volume.icns filter=lfs diff=lfs merge=lfs -text build/conda/installer/assets/Installer_vertical2.bmp filter=lfs diff=lfs merge=lfs -text +cli/openbb_cli/assets/styles/default/Consolas.ttf filter=lfs diff=lfs merge=lfs -text diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000000000000000000000000000000000000..6e01548f76c02564a4a042c946e17d2b11308712 --- /dev/null +++ b/cli/README.md @@ -0,0 +1,67 @@ +# OpenBB Platform CLI + +[![Downloads](https://static.pepy.tech/badge/openbb)](https://pepy.tech/project/openbb) +[![LatestRelease](https://badge.fury.io/py/openbb.svg)](https://github.com/OpenBB-finance/OpenBB) + +| OpenBB is committed to build the future of investment research by focusing on an open source infrastructure accessible to everyone, everywhere. | +| :---------------------------------------------------------------------------------------------------------------------------------------------: | +| ![OpenBBLogo](https://user-images.githubusercontent.com/25267873/218899768-1f0964b8-326c-4f35-af6f-ea0946ac970b.png) | +| Check our website at [openbb.co](www.openbb.co) | + +## Overview + +The OpenBB Platform CLI is a command line interface that wraps [OpenBB Platform](https://docs.openbb.co/platform). + +It offers a convenient way to interact with the OpenBB Platform and its extensions, as well as automated data collection via OpenBB Routine Scripts. + +Find the most complete documentation, examples, and usage guides for the OpenBB Platform CLI [here](https://docs.openbb.co/cli). + +## Installation + +The command below provides access to all the available OpenBB extensions behind the OpenBB Platform, find the complete list [here](https://my.openbb.co/app/platform/extensions). + +```bash +pip install openbb-cli +``` + +> Note: Find the most complete installation hints and tips [here](https://docs.openbb.co/cli/installation). + +After the installation is complete, you can deploy the OpenBB Platform CLI by running the following command: + +```bash +openbb +``` + +Which should result in the following output: + +![image](https://github.com/OpenBB-finance/OpenBB/assets/48914296/f606bb6e-fa00-4fc8-bad2-8269bb4fc38e) + +## API keys + +To fully leverage the OpenBB Platform you need to get some API keys to connect with data providers. Here are the 3 options on where to set them: + +1. OpenBB Hub +2. Local file + +### 1. OpenBB Hub + +Set your keys at [OpenBB Hub](https://my.openbb.co/app/platform/credentials) and get your personal access token from to connect with your account. + +> Once you log in, on the Platform CLI (through the `/account` menu, all your credentials will be in sync with the OpenBB Hub.) + +### 2. Local file + +You can specify the keys directly in the `~/.openbb_platform/user_settings.json` file. + +Populate this file with the following template and replace the values with your keys: + +```json +{ + "credentials": { + "fmp_api_key": "REPLACE_ME", + "polygon_api_key": "REPLACE_ME", + "benzinga_api_key": "REPLACE_ME", + "fred_api_key": "REPLACE_ME" + } +} +``` diff --git a/cli/integration/test_commands.py b/cli/integration/test_commands.py new file mode 100644 index 0000000000000000000000000000000000000000..517acd28eaa7320e0b2373a6fb4e7e4b9ac58a9e --- /dev/null +++ b/cli/integration/test_commands.py @@ -0,0 +1,29 @@ +import io + +import pytest +from openbb_cli.cli import main + + +@pytest.mark.parametrize( + "input_values", + [ + "/equity/price/historical --symbol aapl --provider fmp", + "/equity/price/historical --symbol msft --provider yfinance", + "/equity/price/historical --symbol goog --provider polygon", + "/crypto/price/historical --symbol btc --provider fmp", + "/currency/price/historical --symbol eur --provider fmp", + "/derivatives/futures/historical --symbol cl --provider fmp", + "/etf/price/historical --symbol spy --provider fmp", + "/economy", + ], +) +@pytest.mark.integration +def test_launch_with_cli_input(monkeypatch, input_values): + """Test launching the CLI and providing input via stdin with multiple parameters.""" + stdin = io.StringIO(input_values) + monkeypatch.setattr("sys.stdin", stdin) + + try: + main() + except Exception as e: + pytest.fail(f"Main function raised an exception: {e}") diff --git a/cli/integration/test_integration_base_controller.py b/cli/integration/test_integration_base_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..efab88d1b303e6a2ce5325ef63718f4697dcbebe --- /dev/null +++ b/cli/integration/test_integration_base_controller.py @@ -0,0 +1,89 @@ +"""Integration tests for the base_controller module.""" + +from unittest.mock import Mock, patch + +import pytest +from openbb_cli.controllers.base_controller import BaseController +from openbb_cli.session import Session + +# pylint: disable=unused-variable, redefined-outer-name + + +class TestController(BaseController): + """Test controller for the BaseController.""" + + PATH = "/test/" + + def print_help(self): + """Print help message.""" + + +@pytest.fixture +def base_controller(): + """Set up the environment for each test function.""" + session = Session() # noqa: F841 + controller = TestController() + return controller + + +@pytest.mark.integration +def test_check_path_valid(base_controller): + """Test that check_path does not raise an error for a valid path.""" + base_controller.PATH = "/equity/" + try: + base_controller.check_path() + except ValueError: + pytest.fail("check_path raised ValueError unexpectedly!") + + +@pytest.mark.integration +def test_check_path_invalid(base_controller): + """Test that check_path raises an error for an invalid path.""" + with pytest.raises(ValueError): + base_controller.PATH = "invalid_path" # Missing leading '/' + base_controller.check_path() + + with pytest.raises(ValueError): + base_controller.PATH = "/invalid_path" # Missing trailing '/' + base_controller.check_path() + + +@pytest.mark.integration +def test_parse_input(base_controller): + """Test the parse_input method.""" + input_str = "/equity/price/help" + expected_output = ["", "equity", "price", "help"] + assert ( + base_controller.parse_input(input_str) == expected_output + ), "Input parsing failed" + + +@pytest.mark.integration +def test_switch_command_execution(base_controller): + """Test the switch method.""" + base_controller.queue = [] + base_controller.switch("/home/../reset/") + assert base_controller.queue == [ + "home", + "..", + "reset", + ], "Switch did not update the queue correctly" + + +@patch("openbb_cli.controllers.base_controller.BaseController.call_help") +@pytest.mark.integration +def test_command_routing(mock_call_help, base_controller): + """Test the command routing.""" + base_controller.switch("help") + mock_call_help.assert_called_once() + + +@pytest.mark.integration +def test_custom_reset(base_controller): + """Test the custom reset method.""" + base_controller.custom_reset = Mock(return_value=["custom", "reset"]) + base_controller.call_reset(None) + expected_queue = ["quit", "reset", "custom", "reset"] + assert ( + base_controller.queue == expected_queue + ), f"Expected queue to be {expected_queue}, but was {base_controller.queue}" diff --git a/cli/integration/test_integration_base_platform_controller.py b/cli/integration/test_integration_base_platform_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..2d645f14129d7cbc4f2e8cb86dbb66aafef33d77 --- /dev/null +++ b/cli/integration/test_integration_base_platform_controller.py @@ -0,0 +1,81 @@ +"""Test the base platform controller.""" + +from unittest.mock import MagicMock, Mock, patch + +import pytest +from openbb_cli.controllers.base_platform_controller import ( + PlatformController, + Session, +) + +# pylint: disable=protected-access, unused-variable, redefined-outer-name + + +@pytest.fixture +def platform_controller(): + """Return a platform controller.""" + session = Session() # noqa: F841 + translators = {"test_command": MagicMock(), "test_menu": MagicMock()} # noqa: F841 + translators["test_command"]._parser = Mock( + _actions=[Mock(dest="data", choices=[], type=str, nargs=None)] + ) + translators["test_command"].execute_func = Mock(return_value=Mock()) + translators["test_menu"]._parser = Mock( + _actions=[Mock(dest="data", choices=[], type=str, nargs=None)] + ) + translators["test_menu"].execute_func = Mock(return_value=Mock()) + + controller = PlatformController( + name="test", parent_path=["platform"], translators=translators + ) + return controller + + +@pytest.mark.integration +def test_platform_controller_initialization(platform_controller): + """Test the initialization of the platform controller.""" + expected_path = "/platform/test/" + assert ( + expected_path == platform_controller.PATH + ), "Controller path was not set correctly" + + +@pytest.mark.integration +def test_command_generation(platform_controller): + """Test the generation of commands.""" + command_name = "test_command" + mock_execute_func = Mock(return_value=(Mock(), None)) + platform_controller.translators[command_name].execute_func = mock_execute_func + + platform_controller._generate_command_call( + name=command_name, translator=platform_controller.translators[command_name] + ) + command_method_name = f"call_{command_name}" + assert hasattr( + platform_controller, command_method_name + ), "Command method was not created" + + +@patch( + "openbb_cli.controllers.base_platform_controller.PlatformController._link_obbject_to_data_processing_commands" +) +@patch( + "openbb_cli.controllers.base_platform_controller.PlatformController._generate_commands" +) +@patch( + "openbb_cli.controllers.base_platform_controller.PlatformController._generate_sub_controllers" +) +@pytest.mark.integration +def test_platform_controller_calls( + mock_sub_controllers, mock_commands, mock_link_commands +): + """Test the calls of the platform controller.""" + translators = {"test_command": Mock()} + translators["test_command"].parser = Mock() + translators["test_command"].execute_func = Mock() + _ = PlatformController( + name="test", parent_path=["platform"], translators=translators + ) + mock_sub_controllers.assert_called_once() + mock_commands.assert_called_once() + mock_link_commands.assert_called_once() diff --git a/cli/integration/test_integration_cli_controller.py b/cli/integration/test_integration_cli_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..8b76a8499f30557ec8739c77f0782bc5eb9bdc68 --- /dev/null +++ b/cli/integration/test_integration_cli_controller.py @@ -0,0 +1,26 @@ +"""Test the CLI controller integration.""" + +from openbb_cli.controllers.cli_controller import ( + CLIController, +) + + +def test_parse_input_valid_commands(): + """Test parse_input method.""" + controller = CLIController() + input_string = "exe --file test.openbb" + expected_output = [ + "exe --file test.openbb" + ] # Adjust based on actual expected behavior + assert controller.parse_input(input_string) == expected_output + + +def test_parse_input_invalid_commands(): + """Test parse_input method.""" + controller = CLIController() + input_string = "nonexistentcommand args" + expected_output = ["nonexistentcommand args"] + actual_output = controller.parse_input(input_string) + assert ( + actual_output == expected_output + ), f"Expected {expected_output}, got {actual_output}" diff --git a/cli/integration/test_integration_hub_service.py b/cli/integration/test_integration_hub_service.py new file mode 100644 index 0000000000000000000000000000000000000000..9413372b7867636f996042e0f327549c4bb19bc8 --- /dev/null +++ b/cli/integration/test_integration_hub_service.py @@ -0,0 +1,62 @@ +"""Integration tests for the hub_service module.""" + +from unittest.mock import create_autospec, patch + +import pytest +import requests +from openbb_cli.controllers.hub_service import upload_routine +from openbb_core.app.model.hub.hub_session import HubSession + +# pylint: disable=unused-argument, redefined-outer-name, unused-variable + + +@pytest.fixture +def auth_header(): + """Return a fake auth header.""" + return "Bearer fake_token" + + +@pytest.fixture +def hub_session_mock(): + """Return a mock HubSession.""" + mock = create_autospec(HubSession, instance=True) + mock.username = "TestUser" + return mock + + +# Fixture for routine data +@pytest.fixture +def routine_data(): + """Return a dictionary with routine data.""" + return { + "name": "Test Routine", + "description": "A test routine", + "routine": "print('Hello World')", + "override": False, + "tags": "test", + "public": True, + } + + +@pytest.mark.integration +def test_upload_routine_timeout(auth_header, routine_data): + """Test upload_routine with a timeout exception.""" + with patch( + "requests.post", side_effect=requests.exceptions.Timeout + ) as mocked_post: # noqa: F841 + + response = upload_routine(auth_header, **routine_data) + + assert response is None + + +@pytest.mark.integration +def test_upload_routine_connection_error(auth_header, routine_data): + """Test upload_routine with a connection error.""" + with patch( + "requests.post", side_effect=requests.exceptions.ConnectionError + ) as mocked_post: # noqa: F841 + + response = upload_routine(auth_header, **routine_data) + + assert response is None diff --git a/cli/integration/test_integration_obbject_registry.py b/cli/integration/test_integration_obbject_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..388bcb8f0311b392ef070bbbbfbca6ddef56d073 --- /dev/null +++ b/cli/integration/test_integration_obbject_registry.py @@ -0,0 +1,54 @@ +"""Test the obbject registry.""" + +from openbb_cli.argparse_translator.obbject_registry import Registry +from openbb_core.app.model.obbject import OBBject + +# pylint: disable=unused-variable +# ruff: noqa: disable=F841 + + +def test_registry_operations(): + """Test the registry operations.""" + registry = Registry() + obbject1 = OBBject( + id="1", results=True, extra={"register_key": "key1", "command": "cmd1"} + ) + obbject2 = OBBject( + id="2", results=True, extra={"register_key": "key2", "command": "cmd2"} + ) + obbject3 = OBBject( # noqa: F841 + id="3", results=True, extra={"register_key": "key3", "command": "cmd3"} + ) + + # Add obbjects to the registry + assert registry.register(obbject1) is True + assert registry.register(obbject2) is True + # Attempt to add the same object again + assert registry.register(obbject1) is False + # Ensure the registry size is correct + assert len(registry.obbjects) == 2 + + # Get by index + assert registry.get(0) == obbject2 + assert registry.get(1) == obbject1 + # Get by key + assert registry.get("key1") == obbject1 + assert registry.get("key2") == obbject2 + # Invalid index/key + assert registry.get(2) is None + assert registry.get("invalid_key") is None + + # Remove an object + registry.remove(0) + assert len(registry.obbjects) == 1 + assert registry.get("key2") is None + + # Validate the 'all' property + all_obbjects = registry.all + assert "command" in all_obbjects[0] + assert all_obbjects[0]["command"] == "cmd1" + + # Clean up by removing all objects + registry.remove() + assert len(registry.obbjects) == 0 + assert registry.get("key1") is None diff --git a/cli/openbb_cli/__init__.py b/cli/openbb_cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..542734dabf3842c32b796e16cdf52328132d304d --- /dev/null +++ b/cli/openbb_cli/__init__.py @@ -0,0 +1 @@ +"""Package init""" diff --git a/cli/openbb_cli/argparse_translator/__init__.py b/cli/openbb_cli/argparse_translator/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cli/openbb_cli/argparse_translator/argparse_argument.py b/cli/openbb_cli/argparse_translator/argparse_argument.py new file mode 100644 index 0000000000000000000000000000000000000000..2a40d9f1e4c60856fb405c68169b73e87121e9ce --- /dev/null +++ b/cli/openbb_cli/argparse_translator/argparse_argument.py @@ -0,0 +1,63 @@ +"""Pydantic models for argparse arguments and argument groups.""" + +from typing import ( + Any, + List, + Literal, + Optional, + Tuple, +) + +from pydantic import BaseModel, model_validator + +SEP = "__" + + +class ArgparseArgumentModel(BaseModel): + """Pydantic model for an argparse argument.""" + + name: str + type: Any + dest: str + default: Any + required: bool + action: Literal["store_true", "store"] + help: Optional[str] + nargs: Optional[Literal["+"]] + choices: Optional[Tuple] + + @model_validator(mode="after") # type: ignore + @classmethod + def validate_action(cls, values: "ArgparseArgumentModel"): + """Validate the action based on the type.""" + if values.type is bool and values.action != "store_true": + raise ValueError('If type is bool, action must be "store_true"') + return values + + @model_validator(mode="after") # type: ignore + @classmethod + def remove_props_on_store_true(cls, values: "ArgparseArgumentModel"): + """Remove type, nargs, and choices if action is store_true.""" + if values.action == "store_true": + values.type = None + values.nargs = None + values.choices = None + return values + + # override + def model_dump(self, **kwargs): + """Override the model_dump method to remove empty choices.""" + res = super().model_dump(**kwargs) + + # Check if choices is present and if it's an empty tuple remove it + if "choices" in res and not res["choices"]: + del res["choices"] + + return res + + +class ArgparseArgumentGroupModel(BaseModel): + """Pydantic model for a custom argument group.""" + + name: str + arguments: List[ArgparseArgumentModel] diff --git a/cli/openbb_cli/argparse_translator/argparse_class_processor.py b/cli/openbb_cli/argparse_translator/argparse_class_processor.py new file mode 100644 index 0000000000000000000000000000000000000000..af7b9e56ab4dd05599fe1d1046b0b62e1b0d0541 --- /dev/null +++ b/cli/openbb_cli/argparse_translator/argparse_class_processor.py @@ -0,0 +1,147 @@ +"""Module for the ArgparseClassProcessor class.""" + +import inspect +from typing import Any, Dict, Optional, Type + +# TODO: this needs to be done differently +from openbb_core.app.static.container import Container + +from openbb_cli.argparse_translator.argparse_translator import ArgparseTranslator +from openbb_cli.argparse_translator.reference_processor import ( + ReferenceToArgumentsProcessor, +) + + +class ArgparseClassProcessor: + """Process a target class to create ArgparseTranslators for its methods.""" + + # reference variable used to create custom groups for the ArgpaseTranslators + _reference: Dict[str, Any] = {} + + def __init__( + self, + target_class: Type, + add_help: bool = False, + reference: Optional[Dict[str, Any]] = None, + ): + """ + Initialize the ArgparseClassProcessor. + + Parameters + ---------- + target_class : Type + The target class whose methods will be processed. + add_help : Optional[bool] + Whether to add help to the ArgparseTranslators. + """ + self._target_class: Type = target_class + self._add_help: bool = add_help + self._translators: Dict[str, ArgparseTranslator] = {} + self._paths: Dict[str, str] = {} + + ArgparseClassProcessor._reference = reference or {} + + self._translators = self._process_class( + target=self._target_class, add_help=self._add_help + ) + self._paths[self._get_class_name(self._target_class)] = "path" + self._build_paths(target=self._target_class) + + @property + def translators(self) -> Dict[str, ArgparseTranslator]: + """ + Get the ArgparseTranslators associated with the target class. + + Returns + ------- + Dict[str, ArgparseTranslator] + The ArgparseTranslators associated with the target class. + """ + return self._translators + + @property + def paths(self) -> Dict[str, str]: + """ + Get the paths associated with the target class. + + Returns + ------- + Dict[str, str] + The paths associated with the target class. + """ + return self._paths + + @classmethod + def _custom_groups_from_reference(cls, class_name: str, function_name: str) -> Dict: + route = f"/{class_name.replace('_', '/')}/{function_name}" + reference = {route: cls._reference[route]} if route in cls._reference else {} + if not reference: + return {} + rp = ReferenceToArgumentsProcessor(reference) + return rp.custom_groups.get(route, {}) # type: ignore + + @classmethod + def _process_class( + cls, + target: type, + add_help: bool = False, + ) -> Dict[str, ArgparseTranslator]: + methods = {} + + for name, member in inspect.getmembers(target): + if name.startswith("__") or name.startswith("_"): + continue + if inspect.ismethod(member): + class_name = cls._get_class_name(target) + methods[f"{class_name}_{name}"] = ArgparseTranslator( + func=member, + add_help=add_help, + custom_argument_groups=cls._custom_groups_from_reference( # type: ignore + class_name=class_name, function_name=name + ), + ) + elif isinstance(member, Container): + methods = { + **methods, + **cls._process_class( + target=getattr(target, name), add_help=add_help + ), + } + + return methods + + @staticmethod + def _get_class_name(target: type) -> str: + return ( + str(type(target)) + .rsplit(".", maxsplit=1)[-1] + .replace("'>", "") + .replace("ROUTER_", "") + .lower() + ) + + def get_translator(self, command: str) -> ArgparseTranslator: + """ + Retrieve the ArgparseTranslator object associated with a specific menu and command. + + Parameters + ---------- + command : str + The command associated with the ArgparseTranslator. + + Returns + ------- + ArgparseTranslator + The ArgparseTranslator associated with the specified menu and command. + """ + return self._translators[command] + + def _build_paths(self, target: type, depth: int = 1): + for name, member in inspect.getmembers(target): + if name.startswith("__") or name.startswith("_"): + continue + if inspect.ismethod(member): + pass + elif isinstance(member, Container): + self._build_paths(target=getattr(target, name), depth=depth + 1) + self._paths[f"{name}"] = "sub" * depth + "path" diff --git a/cli/openbb_cli/argparse_translator/argparse_translator.py b/cli/openbb_cli/argparse_translator/argparse_translator.py new file mode 100644 index 0000000000000000000000000000000000000000..cfa33cae3962059a44f6f6f3b7d50be40579e08c --- /dev/null +++ b/cli/openbb_cli/argparse_translator/argparse_translator.py @@ -0,0 +1,490 @@ +"""Module for translating a function into an argparse program.""" + +import argparse +import inspect +import re +from copy import deepcopy +from typing import ( + Any, + Callable, + Dict, + List, + Literal, + Optional, + Tuple, + Type, + Union, + get_args, + get_origin, + get_type_hints, +) + +from openbb_core.app.model.field import OpenBBField +from pydantic import BaseModel +from typing_extensions import Annotated + +from openbb_cli.argparse_translator.argparse_argument import ( + ArgparseArgumentGroupModel, + ArgparseArgumentModel, +) +from openbb_cli.argparse_translator.utils import ( + get_argument_choices, + get_argument_optional_choices, + in_group, + remove_argument, + set_optional_choices, +) + +# pylint: disable=protected-access + +SEP = "__" + + +class ArgparseTranslator: + """Class to translate a function into an argparse program.""" + + def __init__( + self, + func: Callable, + custom_argument_groups: Optional[List[ArgparseArgumentGroupModel]] = None, + add_help: Optional[bool] = True, + ): + """ + Initialize the ArgparseTranslator. + + Args: + func (Callable): The function to translate into an argparse program. + add_help (Optional[bool], optional): Whether to add the help argument. Defaults to False. + """ + self.func = func + self.signature = inspect.signature(func) + self.type_hints = get_type_hints(func) + self.provider_parameters: Dict[str, List[str]] = {} + + self._parser = argparse.ArgumentParser( + prog=func.__name__, + description=self._build_description(func.__doc__), # type: ignore + formatter_class=argparse.RawTextHelpFormatter, + add_help=add_help if add_help else False, + ) + self._required = self._parser.add_argument_group("required arguments") + + if any(param in self.type_hints for param in self.signature.parameters): + self._generate_argparse_arguments(self.signature.parameters) + + if custom_argument_groups: + for group in custom_argument_groups: + self.provider_parameters[group.name] = [] + argparse_group = self._parser.add_argument_group(group.name) + for argument in group.arguments: + self._handle_argument_in_groups(argument, argparse_group) + + def _handle_argument_in_groups(self, argument, group): + """Handle the argument and add it to the parser.""" + + def _update_providers( + input_string: str, new_provider: List[Optional[str]] + ) -> str: + pattern = r"\(provider:\s*(.*?)\)" + providers = re.findall(pattern, input_string) + providers.extend(new_provider) + # remove pattern from help and add with new providers + input_string = re.sub(pattern, "", input_string).strip() + return f"{input_string} (provider: {', '.join(providers)})" + + # check if the argument is already in use, if not, add it + if f"--{argument.name}" not in self._parser._option_string_actions: + kwargs = argument.model_dump(exclude={"name"}, exclude_none=True) + group.add_argument(f"--{argument.name}", **kwargs) + if group.title in self.provider_parameters: + self.provider_parameters[group.title].append(argument.name) + + else: + kwargs = argument.model_dump(exclude={"name"}, exclude_none=True) + model_choices = kwargs.get("choices", ()) or () + # extend choices + existing_choices = get_argument_choices(self._parser, argument.name) + choices = tuple(set(existing_choices + model_choices)) + optional_choices = bool(existing_choices and not model_choices) + + # check if the argument is in the required arguments + if in_group(self._parser, argument.name, group_title="required arguments"): + for action in self._required._group_actions: + if action.dest == argument.name and choices: + # update choices + action.choices = choices + set_optional_choices(action, optional_choices) + return + + # check if the argument is in the optional arguments + if in_group(self._parser, argument.name, group_title="optional arguments"): + for action in self._parser._actions: + if action.dest == argument.name: + # update choices + if choices: + action.choices = choices + set_optional_choices(action, optional_choices) + if argument.name not in self.signature.parameters: + # update help + action.help = _update_providers( + action.help or "", [group.title] + ) + return + + # we need to check if the optional choices were set in other group + # before we remove the argument from the group, otherwise we will lose info + if not optional_choices: + optional_choices = get_argument_optional_choices( + self._parser, argument.name + ) + + # if the argument is in use, remove it from all groups + # and return the groups that had the argument + groups_w_arg = remove_argument(self._parser, argument.name) + groups_w_arg.append(group.title) # add current group + + # add it to the optional arguments group instead + if choices: + kwargs["choices"] = choices # update choices + # add provider info to the help + kwargs["help"] = _update_providers(argument.help or "", groups_w_arg) + action = self._parser.add_argument(f"--{argument.name}", **kwargs) + set_optional_choices(action, optional_choices) + + @property + def parser(self) -> argparse.ArgumentParser: + """Get the argparse parser.""" + return deepcopy(self._parser) + + @staticmethod + def _build_description(func_doc: str) -> str: + """Build the description of the argparse program from the function docstring.""" + patterns = ["openbb\n ======", "Parameters\n ----------"] + + if func_doc: + for pattern in patterns: + if pattern in func_doc: + func_doc = func_doc[: func_doc.index(pattern)].strip() + break + + return func_doc + + @staticmethod + def _param_is_default(param: inspect.Parameter) -> bool: + """Return True if the parameter has a default value.""" + return param.default != inspect.Parameter.empty + + def _get_action_type(self, param: inspect.Parameter) -> str: + """Return the argparse action type for the given parameter.""" + param_type = self.type_hints[param.name] + type_origin = get_origin(param_type) + if param_type is bool or ( + type_origin is Union and bool in get_args(param_type) + ): + return "store_true" + return "store" + + def _get_type_and_choices( # noqa: PLR0912 # pylint: disable=too-many-return-statements, too-many-branches, too-many-statements + self, param: inspect.Parameter + ) -> Tuple[Type[Any], Tuple[Any, ...]]: + """Return the type and choices for the given parameter.""" + param_type = self.type_hints[param.name] + type_origin = get_origin(param_type) + + choices: tuple[Any, ...] = () + + if type_origin is Literal: + choices = get_args(param_type) + # Special handling for boolean literals + if all(isinstance(choice, bool) for choice in choices): + return bool, () + param_type = type(choices[0]) # type: ignore + + if type_origin is list: + param_type = get_args(param_type)[0] + + if get_origin(param_type) is Literal: + choices = get_args(param_type) + # Special handling for boolean literals in lists + if all(isinstance(choice, bool) for choice in choices): + return bool, () + param_type = type(choices[0]) # type: ignore + + if type_origin is Union: + union_args = get_args(param_type) + # Check if Union contains bool type + if bool in union_args and (str in union_args or len(union_args) == 2): + return bool, () + + # Check if Union contains Literal types and extract all choices + literal_choices: list = [] + for arg in union_args: + if get_origin(arg) is Literal: + literal_choices.extend(get_args(arg)) + + if literal_choices: + # Check if all choices are boolean + if all(isinstance(choice, bool) for choice in literal_choices): + return bool, () + # We have Literal types in the Union, use their choices + choices = tuple(literal_choices) + param_type = type(choices[0]) # type: ignore + elif str in union_args: + param_type = str + + # check if it's an Optional, which would be a Union with NoneType + if type(None) in get_args(param_type): + # remove NoneType from the args + args = [arg for arg in get_args(param_type) if arg is not None] + # if there is only one arg left, use it + if len(args) == 1: + param_type = args[0] + + if get_origin(param_type) is Literal: + choices = get_args(param_type) + # Special handling for boolean literals + if all(isinstance(choice, bool) for choice in choices): + return bool, () + param_type = type(choices[0]) # type: ignore + elif len(args) > 1: + # Handle Union with multiple types (not just Optional) + # Try to extract Literal types again from the filtered args + literal_choices = [] + for arg in args: + if get_origin(arg) is Literal: + literal_choices.extend(get_args(arg)) + + if literal_choices: + # Check if all choices are boolean + if all(isinstance(choice, bool) for choice in literal_choices): + return bool, () + choices = tuple(set(literal_choices)) + param_type = type(choices[0]) # type: ignore + + # if there are custom choices, override + custom_choices = self._get_argument_custom_choices(param) + if custom_choices and param_type is not bool: + choices = tuple(custom_choices) + + return param_type, choices + + @staticmethod + def _split_annotation( + base_annotation: Type[Any], custom_annotation_type: Type + ) -> Tuple[Type[Any], List[Any]]: + """Find the base annotation and the custom annotations, namely the OpenBBField.""" + if get_origin(base_annotation) is not Annotated: + return base_annotation, [] + base_annotation, *maybe_custom_annotations = get_args(base_annotation) + return base_annotation, [ + annotation + for annotation in maybe_custom_annotations + if isinstance(annotation, custom_annotation_type) + ] + + @classmethod + def _get_argument_custom_help(cls, param: inspect.Parameter) -> Optional[str]: + """Return the help annotation for the given parameter.""" + base_annotation = param.annotation + _, custom_annotations = cls._split_annotation(base_annotation, OpenBBField) + help_annotation = ( + custom_annotations[0].description if custom_annotations else None + ) + return help_annotation + + @classmethod + def _get_argument_custom_choices(cls, param: inspect.Parameter) -> Optional[str]: + """Return the help annotation for the given parameter.""" + base_annotation = param.annotation + _, custom_annotations = cls._split_annotation(base_annotation, OpenBBField) + choices_annotation = ( + custom_annotations[0].choices if custom_annotations else None + ) + return choices_annotation + + def _get_nargs(self, param: inspect.Parameter) -> Optional[str]: + """Return the nargs annotation for the given parameter.""" + param_type = self.type_hints[param.name] + origin = get_origin(param_type) + + if origin is list: + return "+" + + if origin is Union and any( + get_origin(arg) is list for arg in get_args(param_type) + ): + return "+" + + return None + + def _generate_argparse_arguments(self, parameters) -> None: + """Generate the argparse arguments from the function parameters.""" + for param in parameters.values(): + if param.name == "kwargs": + continue + + param_type, choices = self._get_type_and_choices(param) + + # if the param is a custom type, we need to flatten it + if inspect.isclass(param_type) and issubclass(param_type, BaseModel): + # update type hints with the custom type fields + type_hints = get_type_hints(param_type) + # prefix the type hints keys with the param name + type_hints = { + f"{param.name}{SEP}{key}": value + for key, value in type_hints.items() + } + self.type_hints.update(type_hints) + # create a signature from the custom type + sig = inspect.signature(param_type) + + # add help to the annotation + annotated_parameters: List[inspect.Parameter] = [] + for child_param in sig.parameters.values(): + new_child_param = child_param.replace( + name=f"{param.name}{SEP}{child_param.name}", + annotation=Annotated[ + child_param.annotation, + OpenBBField( + description=param_type.model_json_schema()[ + "properties" + ][child_param.name].get("description", None) + ), + ], + kind=inspect.Parameter.KEYWORD_ONLY, + ) + annotated_parameters.append(new_child_param) + + # replacing with the annotated parameters + new_signature = inspect.Signature( + parameters=annotated_parameters, + return_annotation=sig.return_annotation, + ) + self._generate_argparse_arguments(new_signature.parameters) + + # the custom type itself should not be added as an argument + continue + + required = not self._param_is_default(param) + + # Get the appropriate action based on the parameter type + action = self._get_action_type(param) + + # For boolean parameters with action="store_true", we should not use any choices + if param_type is bool: + choices = () + action = "store_true" + + argument = ArgparseArgumentModel( + name=param.name, + type=param_type, + dest=param.name, + default=param.default, + required=required, + action=action, + help=self._get_argument_custom_help(param), + nargs=self._get_nargs(param), + choices=choices, + ) + kwargs = argument.model_dump(exclude={"name"}, exclude_none=True) + + if required: + self._required.add_argument( + f"--{argument.name}", + **kwargs, + ) + else: + self._parser.add_argument( + f"--{argument.name}", + **kwargs, + ) + + @staticmethod + def _unflatten_args(args: dict) -> Dict[str, Any]: + """Unflatten the args that were flattened by the custom types.""" + result: Dict[str, Any] = {} + for key, value in args.items(): + if SEP in key: + parts = key.split(SEP) + nested_dict = result + for part in parts[:-1]: + if part not in nested_dict: + nested_dict[part] = {} + nested_dict = nested_dict[part] + nested_dict[parts[-1]] = value + else: + result[key] = value + return result + + def _update_with_custom_types(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: + """Update the kwargs with the custom types.""" + # for each argument in the signature that is a custom type, we need to + # update the kwargs with the custom type kwargs + for param in self.signature.parameters.values(): + if param.name == "kwargs": + continue + param_type, _ = self._get_type_and_choices(param) + if inspect.isclass(param_type) and issubclass(param_type, BaseModel): + custom_type_kwargs = kwargs[param.name] + kwargs[param.name] = param_type(**custom_type_kwargs) + + return kwargs + + def execute_func( + self, + parsed_args: Optional[argparse.Namespace] = None, + ) -> Any: + """ + Execute the original function with the parsed arguments. + + Args: + parsed_args (Optional[argparse.Namespace], optional): The parsed arguments. Defaults to None. + + Returns: + Any: The return value of the original function. + + """ + kwargs = self._unflatten_args(vars(parsed_args)) + kwargs = self._update_with_custom_types(kwargs) + provider = kwargs.get("provider") + provider_args: List = [] + if provider and provider in self.provider_parameters: + provider_args = self.provider_parameters[provider] + else: + for args in self.provider_parameters.values(): + provider_args.extend(args) + + # remove kwargs not matching the signature, provider parameters, or are empty. + kwargs = { + key: value + for key, value in kwargs.items() + if ( + (key in self.signature.parameters or key in provider_args) + and (value or value is False) + ) + } + return self.func(**kwargs) + + def parse_args_and_execute(self) -> Any: + """ + Parse the arguments and executes the original function. + + Returns: + Any: The return value of the original function. + """ + parsed_args = self._parser.parse_args() + + return self.execute_func(parsed_args) + + def translate(self) -> Callable: + """ + Wrap the original function with an argparse program. + + Returns: + Callable: The original function wrapped with an argparse program. + """ + + def wrapper_func(): + return self.parse_args_and_execute() + + return wrapper_func diff --git a/cli/openbb_cli/argparse_translator/obbject_registry.py b/cli/openbb_cli/argparse_translator/obbject_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..a6e9bb4969d25806c552e57a29042a1ee5ffb7fa --- /dev/null +++ b/cli/openbb_cli/argparse_translator/obbject_registry.py @@ -0,0 +1,129 @@ +"""Registry for OBBjects.""" + +import json +from typing import Dict, List, Optional, Union + +from openbb_core.app.model.obbject import OBBject + + +class Registry: + """Registry for OBBjects.""" + + def __init__(self): + """Initialize the registry.""" + self._obbjects: List[OBBject] = [] + + @staticmethod + def _contains_obbject(uuid: str, obbjects: List[OBBject]) -> bool: + """Check if obbject with uuid is in the registry.""" + return any(obbject.id == uuid for obbject in obbjects) + + def register(self, obbject: OBBject) -> bool: + """Designed to add an OBBject instance to the registry.""" + if ( + isinstance(obbject, OBBject) + and not self._contains_obbject(obbject.id, self._obbjects) + and obbject.results + ): + self._obbjects.append(obbject) + return True + return False + + 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): + """Remove the obbject at index idx, default is the last element.""" + # 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)) + del reversed_list[idx] + self._obbjects = list(reversed(reversed_list)) + + @property + def all(self) -> Dict[int, Dict]: + """Return all obbjects in the registry.""" + + def _handle_standard_params(obbject: OBBject) -> str: + """Handle standard params for obbjects.""" + standard_params_json = "" + std_params = getattr( + obbject, "_standard_params", {} + ) # pylint: disable=protected-access + if std_params: + standard_params = { + k: str(v)[:30] for k, v in std_params.items() if v and k != "data" + } + standard_params_json = json.dumps(standard_params) + + return standard_params_json + + def _handle_data_repr(obbject: OBBject) -> str: + """Handle data representation for obbjects.""" + data_repr = "" + if hasattr(obbject, "results") and obbject.results: + data_schema = ( + obbject.results[0].model_json_schema() + if obbject.results + and isinstance(obbject.results, list) + and hasattr(obbject.results[0], "model_json_schema") + else "" + ) + if data_schema and "title" in data_schema: + data_repr = f"{data_schema['title']}" # type: ignore + if data_schema and "description" in data_schema: + data_repr += f" - {data_schema['description'].split('.')[0]}" # type: ignore + + return data_repr + + obbjects = {} + for i, obbject in enumerate(list(reversed(self._obbjects))): + obbjects[i] = { + "route": obbject._route, # pylint: disable=protected-access + "provider": obbject.provider, + "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 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 new file mode 100644 index 0000000000000000000000000000000000000000..0ba6fef1dd3aec1fce46e198308fb4ad7cdb18be --- /dev/null +++ b/cli/openbb_cli/argparse_translator/reference_processor.py @@ -0,0 +1,142 @@ +"""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, + Tuple, + Union, + get_args, + get_origin, +) + +from openbb_cli.argparse_translator.argparse_argument import ( + ArgparseArgumentGroupModel, + ArgparseArgumentModel, +) + + +class ReferenceToArgumentsProcessor: + """Class to process the reference and build custom argument groups.""" + + def __init__(self, reference: Dict[str, Dict]): + """Initialize the ReferenceToArgumentsProcessor.""" + self._reference = reference + self._custom_groups: Dict[str, List[ArgparseArgumentGroupModel]] = {} + + self._build_custom_groups() + + @property + def custom_groups(self) -> Dict[str, List[ArgparseArgumentGroupModel]]: + """Get the custom groups.""" + return self._custom_groups + + @staticmethod + def _make_type_parsable(type_: str) -> type: + """Make the type parsable by removing the annotations.""" + if "Union" in type_ and "str" in type_: + return str + if "Union" in type_ and "int" in type_: + return int + if type_ in ["date", "datetime.time", "time"]: + return str + + if any(x in type_ for x in ["gt=", "ge=", "lt=", "le="]): + if "Annotated" in type_: + type_ = type_.replace("Annotated[", "").replace("]", "") + type_ = type_.split(",")[0] + + return eval(type_) # noqa: S307, E501 pylint: disable=eval-used + + def _parse_type(self, type_: str) -> type: + """Parse the type from the string representation.""" + type_ = self._make_type_parsable(type_) # type: ignore + + if get_origin(type_) is Literal: + type_ = type(get_args(type_)[0]) # type: ignore + + return type_ # type: ignore + + def _get_nargs(self, type_: type) -> Optional[Union[int, str]]: + """Get the nargs for the given type.""" + if get_origin(type_) is list: + return "+" + return None + + def _get_choices(self, type_: str, custom_choices: Any) -> Tuple: + """Get the choices for the given type.""" + type_ = self._make_type_parsable(type_) # type: ignore + type_origin = get_origin(type_) + + choices: tuple[Any, ...] = () + + if type_origin is Literal: + choices = get_args(type_) + + if type_origin is list: + type_ = get_args(type_)[0] + + if get_origin(type_) is Literal: + choices = get_args(type_) + + if type_origin is Union and type(None) in get_args(type_): + # remove NoneType from the args + args = [arg for arg in get_args(type_) if arg != type(None)] + # if there is only one arg left, use it + if len(args) > 1: + raise ValueError("Union with NoneType should have only one type left") + type_ = args[0] + + if get_origin(type_) is Literal: + choices = get_args(type_) + + if custom_choices: + return tuple(custom_choices) + + return choices + + def _build_custom_groups(self): + """Build the custom groups from the reference.""" + for route, v in self._reference.items(): + for provider, args in v["parameters"].items(): + if provider == "standard": + continue + + custom_arguments = [] + for arg in args: + if arg.get("standard"): + continue + + type_ = self._parse_type(arg["type"]) + + custom_arguments.append( + ArgparseArgumentModel( + name=arg["name"], + type=type_, + dest=arg["name"], + default=arg["default"], + required=not (arg["optional"]), + action="store" if type_ != bool else "store_true", + help=arg["description"], + nargs=self._get_nargs(type_), # type: ignore + choices=self._get_choices( + arg["type"], custom_choices=arg["choices"] + ), + ) + ) + + group = ArgparseArgumentGroupModel( + name=provider, arguments=custom_arguments + ) + + if route not in self._custom_groups: + self._custom_groups[route] = [] + + self._custom_groups[route].append(group) diff --git a/cli/openbb_cli/argparse_translator/utils.py b/cli/openbb_cli/argparse_translator/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..aebba54d23e6f694a8020c0d51239567844b52d3 --- /dev/null +++ b/cli/openbb_cli/argparse_translator/utils.py @@ -0,0 +1,76 @@ +"""Utilities for argparse_translator module.""" + +from argparse import Action, ArgumentParser +from typing import List, Optional, Tuple + + +def in_group(parser: ArgumentParser, argument_name: str, group_title: str) -> bool: + """Check if an argument is in a group of an ArgumentParser.""" + for action_group in parser._action_groups: # pylint: disable=protected-access + if action_group.title == group_title: + for ( + action + ) in action_group._group_actions: # pylint: disable=protected-access + opts = action.option_strings + if (opts and opts[0] == argument_name) or action.dest == argument_name: + return True + return False + + +def remove_argument(parser: ArgumentParser, argument_name: str) -> List[Optional[str]]: + """Remove an argument from an ArgumentParser.""" + groups_w_arg = [] + + # remove the argument from the parser + for action in parser._actions: # pylint: disable=protected-access + opts = action.option_strings + if (opts and opts[0] == argument_name) or action.dest == argument_name: + parser._remove_action(action) # pylint: disable=protected-access + break + + # remove from all groups + for action_group in parser._action_groups: # pylint: disable=protected-access + for action in action_group._group_actions: # pylint: disable=protected-access + opts = action.option_strings + if (opts and opts[0] == argument_name) or action.dest == argument_name: + action_group._group_actions.remove( # pylint: disable=protected-access + action + ) + groups_w_arg.append(action_group.title) + + # remove from _action_groups dict + parser._option_string_actions.pop( # pylint: disable=protected-access + f"--{argument_name}", None + ) + + return groups_w_arg + + +def get_argument_choices(parser: ArgumentParser, argument_name: str) -> Tuple: + """Get the choices of an argument from an ArgumentParser.""" + for action in parser._actions: # pylint: disable=protected-access + opts = action.option_strings + if (opts and opts[0] == argument_name) or action.dest == argument_name: + return tuple(action.choices or ()) + return () + + +def get_argument_optional_choices(parser: ArgumentParser, argument_name: str) -> bool: + """Get the optional_choices attribute of an argument from an ArgumentParser.""" + for action in parser._actions: # pylint: disable=protected-access + opts = action.option_strings + if ( + (opts and opts[0] == argument_name) + or action.dest == argument_name + and hasattr(action, "optional_choices") + ): + return ( + action.optional_choices # type: ignore[attr-defined] # this is a custom attribute + ) + return False + + +def set_optional_choices(action: Action, optional_choices: bool): + """Set the optional_choices attribute of an action.""" + if not hasattr(action, "optional_choices") and optional_choices: + setattr(action, "optional_choices", optional_choices) diff --git a/cli/openbb_cli/assets/routines/routine_example.openbb b/cli/openbb_cli/assets/routines/routine_example.openbb new file mode 100644 index 0000000000000000000000000000000000000000..72c7fa574aab3f23016ef1389476d9ef84ff7ce6 --- /dev/null +++ b/cli/openbb_cli/assets/routines/routine_example.openbb @@ -0,0 +1,21 @@ +# Go into the equity context +equity + +# Get the company's profile +profile --symbol aapl + +# Get company's statements +fundamental +balance --symbol aapl +cash --symbol aapl +transcript --symbol aapl --year 2023 + +# Load company's historical data +../price +historical --symbol aapl + +# Get its performance +performance --symbol aapl + +# Export the candle as an image and the historical data into a csv +historical --symbol aapl --chart --export csv,jpg \ No newline at end of file diff --git a/cli/openbb_cli/assets/styles/default/Consolas.ttf b/cli/openbb_cli/assets/styles/default/Consolas.ttf new file mode 100644 index 0000000000000000000000000000000000000000..0444db8552bb24d5e13ab7580bb18f9551ab8cc3 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/Consolas.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:423504695a3de1f80c618e3e6ead215a6b891be06c179bf048bb5a80d5d0eda3 +size 358460 diff --git a/cli/openbb_cli/assets/styles/default/dark.mpfstyle.json b/cli/openbb_cli/assets/styles/default/dark.mpfstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..069d22e4369a1dc1d2c5c3ebd776db2252b212ff --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/dark.mpfstyle.json @@ -0,0 +1,47 @@ +{ + "style_name": "dark", + "base_mpf_style": null, + "marketcolors": { + "candle": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "edge": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "wick": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "ohlc": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "volume": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "vcedge": { + "up": "#219E4F", + "down": "#9E1711" + }, + "vcdopcod": true, + "alpha": 1 + }, + "mavcolors": [ + "#EB3DBC", + "#31EBEA", + "#EB8C54", + "#EB5549" + ], + "y_on_right": true, + "gridcolor": "#A3A0A2", + "gridstyle": ":", + "facecolor": "black", + "edgecolor": null, + "figcolor": null, + "gridaxis": null, + "rc": null, + "legacy_rc": null +} diff --git a/cli/openbb_cli/assets/styles/default/dark.mplrc.json b/cli/openbb_cli/assets/styles/default/dark.mplrc.json new file mode 100644 index 0000000000000000000000000000000000000000..0a73435331178243cdd34ceda818bb0b8b57731c --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/dark.mplrc.json @@ -0,0 +1,7 @@ +{ + "xticks_rotation": 10, + "tight_layout_padding": 2, + "pie_wedgeprops": {"linewidth": 0.5, "edgecolor": "#FFFFFF"}, + "pie_startangle": 90, + "volume_bar_width": 0.5 +} diff --git a/cli/openbb_cli/assets/styles/default/dark.mplstyle b/cli/openbb_cli/assets/styles/default/dark.mplstyle new file mode 100644 index 0000000000000000000000000000000000000000..9c991ad0081dd8236f5aeb88e9374c22b2270f94 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/dark.mplstyle @@ -0,0 +1,96 @@ +# The cool hackerman style + +# LINES +# http://matplotlib.org/api/artist_api.html#module-matplotlib.lines +lines.linewidth: 1.5 +lines.color: F5EFF3 +lines.linestyle: - +lines.marker: None +lines.markeredgewidth: 0.5 +lines.markersize: 4 +lines.dash_joinstyle: miter +lines.dash_capstyle: butt +lines.solid_joinstyle: miter +lines.solid_capstyle: projecting +lines.antialiased: True + +patch.edgecolor: F5EFF3 +patch.facecolor: 00aaff +patch.linewidth: 1 + +# TEXT +# http://matplotlib.org/api/font_manager_api.html +font.family: sans-serif +font.sans-serif: Consolas, Deja Vu Sans, Arial, Helvetica +font.style: normal +font.variant: normal +font.weight: medium +font.stretch: normal +font.size: 16 + +text.color: F5EFF3 + +axes.spines.left: true +axes.spines.bottom: true +axes.spines.top: true +axes.spines.right: true +axes.linewidth: 0.4 + +axes.labelsize: small +axes.titlelocation: left + +axes.facecolor: black +axes.edgecolor: F5EFF3 +axes.labelcolor: F5EFF3 +axes.grid: true +axes.grid.which: major + +axes.prop_cycle: cycler('color', ['ffed00', 'ef7d00', 'e4003a', 'c13246', '822661', '48277c', '005ca9', '00aaff', '9b30d9', 'af005f', '5f00af', 'af87ff']) + +xtick.color: F5EFF3 +ytick.color: F5EFF3 + +xtick.major.size: 2 +xtick.minor.size: 1 +xtick.labelsize: small +xtick.alignment: center +xtick.major.width: 0.2 + +ytick.major.size: 2 +ytick.minor.size: 1 +ytick.labelsize: small + +ytick.left: False +ytick.labelleft: False +ytick.right: True +ytick.labelright: True +ytick.major.width: 0.2 + +grid.color: F5EFF3 +grid.linewidth: 0.4 +grid.linestyle: : + +legend.framealpha: 0.6 +legend.frameon: true +legend.facecolor: black +legend.edgecolor: black +legend.scatterpoints: 3 +legend.fontsize: small +legend.loc: upper left + +figure.facecolor: black +figure.edgecolor: black +figure.subplot.hspace: 0.2 + +savefig.facecolor: black +savefig.edgecolor: black + +### Boxplots +boxplot.boxprops.color: F5EFF3 +boxplot.capprops.color: F5EFF3 +boxplot.flierprops.color: F5EFF3 +boxplot.flierprops.markeredgecolor: F5EFF3 +boxplot.whiskerprops.color: F5EFF3 +boxplot.medianprops.color: F5EFF3 +boxplot.meanprops.markeredgecolor: F5EFF3 +boxplot.meanprops.color: F5EFF3 diff --git a/cli/openbb_cli/assets/styles/default/dark.pltstyle.json b/cli/openbb_cli/assets/styles/default/dark.pltstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..f1f75a99fa25dcd8411d405dc1a38b4f80756c9d --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/dark.pltstyle.json @@ -0,0 +1,132 @@ +{ + "line": { + "up_color": "#00ACFF", + "down_color": "#e4003a", + "color": "#ffed00", + "width": 1.5 + }, + "data": { + "candlestick": [ + { + "decreasing": { + "fillcolor": "#e4003a", + "line": { + "color": "#e4003a" + } + }, + "increasing": { + "fillcolor": "#00ACFF", + "line": { + "color": "#00ACFF" + } + }, + "type": "candlestick" + } + ] + }, + "layout": { + "annotationdefaults": { + "showarrow": false + }, + "autotypenumbers": "strict", + "colorway": [ + "#ffed00", + "#ef7d00", + "#e4003a", + "#c13246", + "#822661", + "#48277c", + "#005ca9", + "#00aaff", + "#9b30d9", + "#af005f", + "#5f00af", + "#af87ff" + ], + "dragmode": "pan", + "font": { + "family": "Fira Code", + "size": 18 + }, + "hoverlabel": { + "align": "left" + }, + "mapbox": { + "style": "dark" + }, + "hovermode": "x", + "legend": { + "bgcolor": "rgba(0, 0, 0, 0.5)", + "x": 0.01, + "xanchor": "left", + "y": 0.99, + "yanchor": "top", + "font": { + "size": 15 + } + }, + "legend2": { + "bgcolor": "rgba(0, 0, 0, 0.5)", + "font": { + "size": 15 + } + }, + "legend3": { + "bgcolor": "rgba(0, 0, 0, 0.5)", + "font": { + "size": 15 + } + }, + "legend4": { + "bgcolor": "rgba(0, 0, 0, 0.5)", + "font": { + "size": 15 + } + }, + "legend5": { + "bgcolor": "rgba(0, 0, 0, 0.5)", + "font": { + "size": 15 + } + }, + "paper_bgcolor": "#000000", + "plot_bgcolor": "#000000", + "xaxis": { + "automargin": true, + "autorange": true, + "rangeslider": { + "visible": false + }, + "showgrid": true, + "showline": true, + "tickfont": { + "size": 14 + }, + "zeroline": false, + "tick0": 1, + "title": { + "standoff": 20 + }, + "linecolor": "#F5EFF3", + "mirror": true, + "ticks": "outside" + }, + "yaxis": { + "anchor": "x", + "automargin": true, + "fixedrange": false, + "zeroline": false, + "showgrid": true, + "showline": true, + "side": "right", + "tick0": 0.5, + "title": { + "standoff": 20 + }, + "gridcolor": "#283442", + "linecolor": "#F5EFF3", + "mirror": true, + "ticks": "outside" + } + } +} diff --git a/cli/openbb_cli/assets/styles/default/dark.richstyle.json b/cli/openbb_cli/assets/styles/default/dark.richstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..9590d25b3c94b0720dccdde61b4ddd1497164985 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/dark.richstyle.json @@ -0,0 +1,9 @@ +{ + "info": "rgb(224,131,48)", + "cmds": "rgb(102,203,228)", + "param": "rgb(247,206,70)", + "menu": "rgb(50,115,185)", + "src": "rgb(216,90,64)", + "unvl": "grey30", + "help": "#FAC900" +} diff --git a/cli/openbb_cli/assets/styles/default/light.mpfstyle.json b/cli/openbb_cli/assets/styles/default/light.mpfstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..9f75a73a30f024e9e274ec10ee09b5d2e3f45890 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/light.mpfstyle.json @@ -0,0 +1,47 @@ +{ + "style_name": "light", + "base_mpf_style": null, + "marketcolors": { + "candle": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "edge": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "wick": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "ohlc": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "volume": { + "up": "#00ACFF", + "down": "#e4003a" + }, + "vcedge": { + "up": "#219E4F", + "down": "#9E1711" + }, + "vcdopcod": true, + "alpha": 1 + }, + "mavcolors": [ + "#EB3DBC", + "#31EBEA", + "#EB8C54", + "#EB5549" + ], + "y_on_right": true, + "gridcolor": "grey", + "gridstyle": ":", + "facecolor": "white", + "edgecolor": null, + "figcolor": null, + "gridaxis": null, + "rc": null, + "legacy_rc": null +} diff --git a/cli/openbb_cli/assets/styles/default/light.mplrc.json b/cli/openbb_cli/assets/styles/default/light.mplrc.json new file mode 100644 index 0000000000000000000000000000000000000000..a7b3d08bcbff742acff018574c26c76020210ba1 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/light.mplrc.json @@ -0,0 +1,7 @@ +{ + "xticks_rotation": 10, + "tight_layout_padding": 2, + "pie_wedgeprops": {"linewidth": 0.5, "edgecolor": "#000000"}, + "pie_startangle": 90, + "volume_bar_width": 0.5 +} diff --git a/cli/openbb_cli/assets/styles/default/light.mplstyle b/cli/openbb_cli/assets/styles/default/light.mplstyle new file mode 100644 index 0000000000000000000000000000000000000000..807f6caa752bf6adf3ef64c4edc951875d4a10c2 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/light.mplstyle @@ -0,0 +1,95 @@ +# The cool hackerman style + +# LINES +# http://matplotlib.org/api/artist_api.html#module-matplotlib.lines +lines.linewidth: 1.1 +lines.color: 0F0F0F +lines.linestyle: - +lines.marker: None +lines.markeredgewidth: 0.5 +lines.markersize: 4 +lines.dash_joinstyle: miter +lines.dash_capstyle: butt +lines.solid_joinstyle: miter +lines.solid_capstyle: projecting +lines.antialiased: True + +patch.edgecolor: 000000 +patch.facecolor: 822661 +patch.linewidth: 1 + +# TEXT +# http://matplotlib.org/api/font_manager_api.html +font.family: sans-serif +font.sans-serif: Consolas, Deja Vu Sans, Arial, Helvetica +font.style: normal +font.variant: normal +font.weight: medium +font.stretch: normal +font.size: 13 + +text.color: 0F0F0F + +axes.spines.left: true +axes.spines.bottom: true +axes.spines.top: true +axes.spines.right: true +axes.linewidth: 0.2 + +axes.labelsize: small +axes.titlelocation: left + +axes.facecolor: white +axes.edgecolor: 0F0F0F +axes.labelcolor: 0F0F0F +axes.grid: true +axes.grid.which: major + +axes.prop_cycle: cycler('color', ['254495', 'c13246', '48277c', 'e4003a', 'ef7d00', '822661', 'ffed00', '00aaff', '9b30d9', 'af005f', '5f00af', 'af87ff']) + +xtick.color: 0F0F0F +ytick.color: 0F0F0F + +xtick.major.size: 2 +xtick.minor.size: 1 +xtick.labelsize: small + +ytick.major.size: 2 +ytick.minor.size: 1 +ytick.labelsize: small +xtick.alignment: center + +ytick.left: False +ytick.labelleft: False +ytick.right: True +ytick.labelright: True + + +grid.color: 0F0F0F +grid.linewidth: 0.4 +grid.linestyle: : + +legend.framealpha: 0.6 +legend.frameon: true +legend.facecolor: white +legend.edgecolor: white +legend.scatterpoints: 3 +legend.fontsize: small +legend.loc: best + +figure.facecolor: white +figure.edgecolor: white +figure.subplot.hspace: 0.2 + +savefig.facecolor: white +savefig.edgecolor: white + +### Boxplots +boxplot.boxprops.color: 0F0F0F +boxplot.capprops.color: 0F0F0F +boxplot.flierprops.color: 0F0F0F +boxplot.flierprops.markeredgecolor: 0F0F0F +boxplot.whiskerprops.color: 0F0F0F +boxplot.medianprops.color: 0F0F0F +boxplot.meanprops.markeredgecolor: 0F0F0F +boxplot.meanprops.color: 0F0F0F diff --git a/cli/openbb_cli/assets/styles/default/light.pltstyle.json b/cli/openbb_cli/assets/styles/default/light.pltstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..6572f11da1a0af912f78ab3c7a85020f89492093 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/light.pltstyle.json @@ -0,0 +1,871 @@ +{ + "line": { + "up_color": "#009600", + "down_color": "#c80000", + "color": "#0d0887", + "width": 1.5, + "down_color_transparent": "rgba(200, 0, 0, 0.4)", + "up_color_transparent": "rgba(0, 150, 0, 0.4)" + }, + "data": { + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "#C8D4E3", + "linecolor": "#C8D4E3", + "minorgridcolor": "#C8D4E3", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ], + "candlestick": [ + { + "decreasing": { + "fillcolor": "#c80000", + "line": { + "color": "#990000" + } + }, + "increasing": { + "fillcolor": "#009600", + "line": { + "color": "#007500" + } + }, + "type": "candlestick" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1, + "showarrow": false + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0.0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1.0, + "#f0f921" + ] + ] + }, + "colorway": [ + "#254495", + "#c13246", + "#48277c", + "#e4003a", + "#ef7d00", + "#822661", + "#ffed00", + "#00aaff", + "#9b30d9", + "#af005f", + "#5f00af", + "#af87ff" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "#C8D4E3" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "x", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "#EBF0F8", + "linecolor": "#EBF0F8", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "#DFE8F3", + "gridwidth": 2, + "linecolor": "#EBF0F8", + "showbackground": true, + "ticks": "", + "zerolinecolor": "#EBF0F8" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "baxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "#DFE8F3", + "linecolor": "#A2B1C6", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "ticks": "", + "zerolinewidth": 2, + "rangeslider": { + "visible": false + }, + "showgrid": true, + "showline": true, + "tickfont": { + "size": 15 + }, + "mirror": true, + "zeroline": false + }, + "yaxis": { + "automargin": true, + "ticks": "", + "tickfont": { + "size": 15 + }, + "zerolinewidth": 2, + "fixedrange": false, + "showgrid": true, + "showline": true, + "side": "right", + "mirror": true, + "zeroline": false + }, + "dragmode": "pan", + "legend": { + "bgcolor": "rgba(0, 0, 0, 0)", + "x": 1.1, + "xanchor": "left", + "y": 0.99, + "yanchor": "top" + }, + "legend2": { + "bgcolor": "rgba(0, 0, 0, 0)" + }, + "legend3": { + "bgcolor": "rgba(0, 0, 0, 0)" + }, + "legend4": { + "bgcolor": "rgba(0, 0, 0, 0)" + }, + "legend5": { + "bgcolor": "rgba(0, 0, 0, 0)" + } + } +} diff --git a/cli/openbb_cli/assets/styles/default/light.richstyle.json b/cli/openbb_cli/assets/styles/default/light.richstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..e09be31a8426d7ef2fcda05f458bdcf1f706ed21 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/light.richstyle.json @@ -0,0 +1,9 @@ +{ + "info": "rgb(224,131,48)", + "cmds": "rgb(70,156,222)", + "param": "rgb(247,206,70)", + "menu": "rgb(50,115,185)", + "src": "rgb(216,90,64)", + "unvl": "grey30", + "help": "#FAC900" +} diff --git a/cli/openbb_cli/assets/styles/default/tables.pltstyle.json b/cli/openbb_cli/assets/styles/default/tables.pltstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..69f80ef4d88cdf04d85492afd1b1cf4ce66e2b99 --- /dev/null +++ b/cli/openbb_cli/assets/styles/default/tables.pltstyle.json @@ -0,0 +1,102 @@ +{ + "data": { + "candlestick": [ + { + "decreasing": { + "fillcolor": "#e4003a", + "line": { + "color": "#e4003a" + } + }, + "increasing": { + "fillcolor": "#00ACFF", + "line": { + "color": "#00ACFF" + } + }, + "type": "candlestick" + } + ] + }, + "layout": { + "annotationdefaults": { + "showarrow": false + }, + "autotypenumbers": "strict", + "colorway": [ + "#ffed00", + "#ef7d00", + "#e4003a", + "#c13246", + "#822661", + "#48277c", + "#005ca9", + "#00aaff", + "#9b30d9", + "#af005f", + "#5f00af", + "#af87ff" + ], + "dragmode": "pan", + "font": { + "family": "Fira Code", + "size": 18 + }, + "hoverlabel": { + "align": "left" + }, + "mapbox": { + "style": "light" + }, + "hovermode": "x", + "legend": { + "bgcolor": "rgba(0, 0, 0, 0)", + "x": 0.01, + "xanchor": "left", + "y": 0.99, + "yanchor": "top", + "font": { + "size": 15 + } + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "xaxis": { + "automargin": true, + "autorange": true, + "rangeslider": { + "visible": false + }, + "showgrid": true, + "showline": true, + "tickfont": { + "size": 14 + }, + "zeroline": false, + "tick0": 1, + "title": { + "standoff": 20 + }, + "linecolor": "#F5EFF3", + "mirror": true, + "ticks": "outside" + }, + "yaxis": { + "anchor": "x", + "automargin": true, + "fixedrange": false, + "zeroline": false, + "showgrid": true, + "showline": true, + "side": "right", + "tick0": 0.5, + "title": { + "standoff": 20 + }, + "gridcolor": "#283442", + "linecolor": "#F5EFF3", + "mirror": true, + "ticks": "outside" + } + } +} diff --git a/cli/openbb_cli/assets/styles/user/openbb.richstyle.json b/cli/openbb_cli/assets/styles/user/openbb.richstyle.json new file mode 100644 index 0000000000000000000000000000000000000000..5d8b5d87e21025d346fd24a4390ef72547f04079 --- /dev/null +++ b/cli/openbb_cli/assets/styles/user/openbb.richstyle.json @@ -0,0 +1,9 @@ +{ + "info": "rgb(224,131,48)", + "cmds": "#2A7C6E", + "param": "rgb(247,206,70)", + "menu": "#427A2E", + "src": "rgb(216,90,64)", + "unvl": "grey30", + "help": "#FAC900" +} diff --git a/cli/openbb_cli/cli.py b/cli/openbb_cli/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..f39b7fdd2aa18a79825dbdbd8b27445eee63cdf7 --- /dev/null +++ b/cli/openbb_cli/cli.py @@ -0,0 +1,31 @@ +"""OpenBB Platform CLI entry point.""" + +import sys + +from openbb_cli.utils.utils import change_logging_sub_app, reset_logging_sub_app + + +def main(): + """Use the main entry point for the OpenBB Platform CLI.""" + print("Loading...\n") # noqa: T201 + + # pylint: disable=import-outside-toplevel + from openbb_cli.config.setup import bootstrap + from openbb_cli.controllers.cli_controller import launch + + bootstrap() + + dev = "--dev" in sys.argv[1:] + debug = "--debug" in sys.argv[1:] + + launch(dev, debug) + + +if __name__ == "__main__": + initial_logging_sub_app = change_logging_sub_app() + try: + main() + except Exception: + pass + finally: + reset_logging_sub_app(initial_logging_sub_app) diff --git a/cli/openbb_cli/config/__init__.py b/cli/openbb_cli/config/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..96aa4d3b1a83fa2151603e4175a70688ac84dc96 --- /dev/null +++ b/cli/openbb_cli/config/__init__.py @@ -0,0 +1 @@ +"""Core config init.""" diff --git a/cli/openbb_cli/config/completer.py b/cli/openbb_cli/config/completer.py new file mode 100644 index 0000000000000000000000000000000000000000..9cc50a9b7eb48e4570aec3f1dbde35377d40a3bc --- /dev/null +++ b/cli/openbb_cli/config/completer.py @@ -0,0 +1,427 @@ +"""Nested completer for completion of OpenBB hierarchical data structures.""" + +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Mapping, + Optional, + Pattern, + Set, + Union, +) + +from prompt_toolkit.completion import CompleteEvent, Completer, Completion +from prompt_toolkit.document import Document +from prompt_toolkit.formatted_text import AnyFormattedText +from prompt_toolkit.history import FileHistory + +NestedDict = Mapping[str, Union[Any, Set[str], None, Completer]] + +# pylint: disable=too-many-arguments,global-statement,too-many-branches,global-variable-not-assigned + + +class WordCompleter(Completer): + """Simple autocompletion on a list of words. + + :param words: List of words or callable that returns a list of words. + :param ignore_case: If True, case-insensitive completion. + :param meta_dict: Optional dict mapping words to their meta-text. (This + should map strings to strings or formatted text.) + :param WORD: When True, use WORD characters. + :param sentence: When True, don't complete by comparing the word before the + cursor, but by comparing all the text before the cursor. In this case, + the list of words is just a list of strings, where each string can + contain spaces. (Can not be used together with the WORD option.) + :param match_middle: When True, match not only the start, but also in the + middle of the word. + :param pattern: Optional compiled regex for finding the word before + the cursor to complete. When given, use this regex pattern instead of + default one (see document._FIND_WORD_RE) + """ + + def __init__( + self, + words: Union[List[str], Callable[[], List[str]]], + ignore_case: bool = False, + display_dict: Optional[Mapping[str, AnyFormattedText]] = None, + meta_dict: Optional[Mapping[str, AnyFormattedText]] = None, + WORD: bool = True, + sentence: bool = False, + match_middle: bool = False, + pattern: Optional[Pattern[str]] = None, + ) -> None: + """Initialize the WordCompleter.""" + assert not (WORD and sentence) # noqa: S101 + + self.words = words + self.ignore_case = ignore_case + self.display_dict = display_dict or {} + self.meta_dict = meta_dict or {} + self.WORD = WORD + self.sentence = sentence + self.match_middle = match_middle + self.pattern = pattern + + def get_completions( + self, + document: Document, + _complete_event: CompleteEvent, + ) -> Iterable[Completion]: + """Get completions.""" + # Get list of words. + words = self.words + if callable(words): + words = words() + + # Get word/text before cursor. + if self.sentence: + word_before_cursor = document.text_before_cursor + else: + word_before_cursor = document.get_word_before_cursor( + WORD=self.WORD, pattern=self.pattern + ) + if ( + "--" in document.text_before_cursor + and document.text_before_cursor.rfind(" --") + >= document.text_before_cursor.rfind(" -") + ): + word_before_cursor = f'--{document.text_before_cursor.split("--")[-1]}' + elif f"--{word_before_cursor}" == document.text_before_cursor: + word_before_cursor = document.text_before_cursor + + if self.ignore_case: + word_before_cursor = word_before_cursor.lower() + + def word_matches(word: str) -> bool: + """Set True when the word before the cursor matches.""" + if self.ignore_case: + word = word.lower() + + if self.match_middle: + return word_before_cursor in word + return word.startswith(word_before_cursor) + + for a in words: + if word_matches(a): + display = self.display_dict.get(a, a) + display_meta = self.meta_dict.get(a, "") + yield Completion( + text=a, + start_position=-len(word_before_cursor), + display=display, + display_meta=display_meta, + ) + + +class NestedCompleter(Completer): + """Completer which wraps around several other completers, and calls any the + one that corresponds with the first word of the input. + + By combining multiple `NestedCompleter` instances, we can achieve multiple + hierarchical levels of autocompletion. This is useful when `WordCompleter` + is not sufficient. + + If you need multiple levels, check out the `from_nested_dict` classmethod. + """ + + complementary: List = list() + + def __init__( + self, options: Dict[str, Optional[Completer]], ignore_case: bool = True + ) -> None: + """Initialize the NestedCompleter.""" + self.flags_processed: List = list() + self.original_options = options + self.options = options + self.ignore_case = ignore_case + self.complementary = list() + + def __repr__(self) -> str: + """Return string representation of NestedCompleter.""" + return f"NestedCompleter({self.options!r}, ignore_case={self.ignore_case!r})" + + @classmethod + def from_nested_dict(cls, data: dict) -> "NestedCompleter": + """Create a `NestedCompleter`. + + It starts from a nested dictionary data structure, like this: + + .. code:: + + data = { + 'show': { + 'version': None, + 'interfaces': None, + 'clock': None, + 'ip': {'interface': {'brief'}} + }, + 'exit': None + 'enable': None + } + + The value should be `None` if there is no further completion at some + point. If all values in the dictionary are None, it is also possible to + use a set instead. + + Values in this data structure can be a completers as well. + """ + options: Dict[str, Any] = {} + for key, value in data.items(): + if isinstance(value, Completer): + options[key] = value + elif isinstance(value, dict): + options[key] = cls.from_nested_dict(value) + elif isinstance(value, set): + options[key] = cls.from_nested_dict({item: None for item in value}) + elif isinstance(key, str) and isinstance(value, str): + options[key] = options[value] + else: + assert value is None # noqa: S101 + options[key] = None + + for items in cls.complementary: + if items[0] in options: + options[items[1]] = options[items[0]] + elif items[1] in options: + options[items[0]] = options[items[1]] + + return cls(options) + + def get_completions( # noqa: PLR0912 + self, document: Document, complete_event: CompleteEvent + ) -> Iterable[Completion]: + """Get completions.""" + # Split document. + cmd = "" + text = document.text_before_cursor.lstrip() + if " " in text: + cmd = text.split(" ")[0] + if "-" in text: + if text.rfind("--") == -1 or text.rfind("-") - 1 > text.rfind("--"): + unprocessed_text = "-" + text.split("-")[-1] + else: + unprocessed_text = "--" + text.split("--")[-1] + else: + unprocessed_text = text + stripped_len = len(document.text_before_cursor) - len(text) + + # Check if there are multiple flags for the same command + if self.complementary: + for same_flags in self.complementary: + if ( + same_flags[0] in self.flags_processed + and same_flags[1] not in self.flags_processed + ) or ( + same_flags[1] in self.flags_processed + and same_flags[0] not in self.flags_processed + ): + if same_flags[0] in self.flags_processed: + self.flags_processed.append(same_flags[1]) + elif same_flags[1] in self.flags_processed: + self.flags_processed.append(same_flags[0]) + + if cmd: + self.options = { + k: self.original_options.get(cmd).options[k] # type: ignore + for k in self.original_options.get(cmd).options # type: ignore + if k not in self.flags_processed + } + else: + self.options = { + k: self.original_options[k] + for k in self.original_options + if k not in self.flags_processed + } + + # If there is a space, check for the first term, and use a subcompleter. + if " " in unprocessed_text: + first_term = unprocessed_text.split()[0] + + # user is updating one of the values + if unprocessed_text[-1] != " ": + self.flags_processed = [ + flag for flag in self.flags_processed if flag != first_term + ] + + if self.complementary: + for same_flags in self.complementary: + if ( + same_flags[0] in self.flags_processed + and same_flags[1] not in self.flags_processed + ) or ( + same_flags[1] in self.flags_processed + and same_flags[0] not in self.flags_processed + ): + if same_flags[0] in self.flags_processed: + self.flags_processed.remove(same_flags[0]) + elif same_flags[1] in self.flags_processed: + self.flags_processed.remove(same_flags[1]) + + if cmd and self.original_options.get(cmd): + self.options = self.original_options + else: + self.options = { + k: self.original_options[k] + for k in self.original_options + if k not in self.flags_processed + } + + if "-" not in text: + completer = self.options.get(first_term) + elif cmd in self.options and self.options.get(cmd): + completer = self.options.get(cmd).options.get(first_term) # type: ignore + else: + completer = self.options.get(first_term) + + # If we have a sub completer, use this for the completions. + if completer is not None: + remaining_text = unprocessed_text[len(first_term) :].lstrip() + move_cursor = len(text) - len(remaining_text) + stripped_len + + new_document = Document( + remaining_text, + cursor_position=document.cursor_position - move_cursor, + ) + + # Provides auto-completion but if user doesn't take it still keep going + if " " in new_document.text: + if ( + new_document.text in [f"{opt} " for opt in self.options] + or unprocessed_text[-1] == " " + ): + self.flags_processed.append(first_term) + if cmd: + self.options = { + k: self.original_options.get(cmd).options[k] # type: ignore + for k in self.original_options.get(cmd).options # type: ignore + if k not in self.flags_processed + } + else: + self.options = { + k: self.original_options[k] + for k in self.original_options + if k not in self.flags_processed + } + + # In case the users inputs a single boolean flag + elif not completer.options: # type: ignore + self.flags_processed.append(first_term) + + if self.complementary: + for same_flags in self.complementary: + if ( + same_flags[0] in self.flags_processed + and same_flags[1] not in self.flags_processed + ) or ( + same_flags[1] in self.flags_processed + and same_flags[0] not in self.flags_processed + ): + if same_flags[0] in self.flags_processed: + self.flags_processed.append(same_flags[1]) + elif same_flags[1] in self.flags_processed: + self.flags_processed.append(same_flags[0]) + + if cmd: + self.options = { + k: self.original_options.get(cmd).options[k] # type: ignore + for k in self.original_options.get(cmd).options # type: ignore + if k not in self.flags_processed + } + else: + self.options = { + k: self.original_options[k] + for k in self.original_options + if k not in self.flags_processed + } + + else: + # This is a NestedCompleter + yield from completer.get_completions(new_document, complete_event) + + # No space in the input: behave exactly like `WordCompleter`. + else: + # check if the prompt has been updated in the meantime + if " " in text or "-" in text: + actual_flags_processed = [ + flag for flag in self.flags_processed if flag in text + ] + + if self.complementary: + for same_flags in self.complementary: + if ( + same_flags[0] in actual_flags_processed + and same_flags[1] not in actual_flags_processed + ) or ( + same_flags[1] in actual_flags_processed + and same_flags[0] not in actual_flags_processed + ): + if same_flags[0] in actual_flags_processed: + actual_flags_processed.append(same_flags[1]) + elif same_flags[1] in actual_flags_processed: + actual_flags_processed.append(same_flags[0]) + + if len(actual_flags_processed) < len(self.flags_processed): + self.flags_processed = actual_flags_processed + if cmd: + self.options = { + k: self.original_options.get(cmd).options[k] # type: ignore + for k in self.original_options.get(cmd).options # type: ignore + if k not in self.flags_processed + } + else: + self.options = { + k: self.original_options[k] + for k in self.original_options + if k not in self.flags_processed + } + + command = self.options.get(cmd) + options = command.options if command else {} # type: ignore + command_options = [f"{cmd} {opt}" for opt in options] + text_list = [text in val for val in command_options] + if cmd and cmd in self.options and text_list: + completer = WordCompleter( + list(self.options.get(cmd).options.keys()), # type: ignore + ignore_case=self.ignore_case, + ) + elif bool([val for val in self.options if text in val]): + completer = WordCompleter( + list(self.options.keys()), ignore_case=self.ignore_case + ) + else: + # The user has delete part of the first command and we need to reset options + if bool([val for val in self.original_options if text in val]): + self.options = self.original_options + self.flags_processed = list() + completer = WordCompleter( + list(self.options.keys()), ignore_case=self.ignore_case + ) + + # This is a WordCompleter + yield from completer.get_completions(document, complete_event) + + +class CustomFileHistory(FileHistory): + """Filtered file history.""" + + def sanitize_input(self, string: str) -> str: + """Sanitize sensitive information from the input string by parsing arguments.""" + keywords = ["--password", "--email", "--pat"] + string_list = string.split(" ") + + for kw in keywords: + if kw in string_list: + index = string_list.index(kw) + if len(string_list) > index + 1: + string_list[index + 1] = "********" + + result = " ".join(string_list) + return result + + def store_string(self, string: str) -> None: + """Store string in history.""" + string = self.sanitize_input(string) + super().store_string(string) diff --git a/cli/openbb_cli/config/console.py b/cli/openbb_cli/config/console.py new file mode 100644 index 0000000000000000000000000000000000000000..cbb0f8f2696e10512467d954f6113ba183a02270 --- /dev/null +++ b/cli/openbb_cli/config/console.py @@ -0,0 +1,93 @@ +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple + +from rich import panel +from rich.console import ( + Console as RichConsole, + Theme, +) +from rich.text import Text + +from openbb_cli.config.menu_text import RICH_TAGS + +if TYPE_CHECKING: + from openbb_cli.models.settings import Settings + + +class Console: + """Create a rich console to wrap the console print with a Panel.""" + + def __init__( + self, + settings: "Settings", + style: Optional[Dict[str, Any]] = None, + ): + """Initialize the ConsoleAndPanel class.""" + self._console = RichConsole( + theme=Theme(style), + highlight=False, + soft_wrap=True, + ) + self._settings = settings + self.menu_text = "" + self.menu_path = "" + + @staticmethod + def _filter_rich_tags(text): + """Filter out rich tags from text.""" + for val in RICH_TAGS: + text = text.replace(val, "") + + return text + + @staticmethod + def _blend_text( + message: str, color1: Tuple[int, int, int], color2: Tuple[int, int, int] + ) -> Text: + """Blend text from one color to another.""" + text = Text(message) + r1, g1, b1 = color1 + r2, g2, b2 = color2 + dr = r2 - r1 + dg = g2 - g1 + db = b2 - b1 + size = len(text) + 5 + for index in range(size): + blend = index / size + color = f"#{int(r1 + dr * blend):02X}{int(g1 + dg * blend):02X}{int(b1 + db * blend):02X}" + text.stylize(color, index, index + 1) + return text + + def print(self, *args, **kwargs): + """Print the text to the console.""" + if kwargs and "text" in list(kwargs) and "menu" in list(kwargs): + if not self._settings.TEST_MODE: + if self._settings.ENABLE_RICH_PANEL: + if self._settings.SHOW_VERSION: + version = self._settings.VERSION + version = f"[param]OpenBB Platform CLI v{version}[/param] (https://openbb.co)" + else: + version = ( + "[param]OpenBB Platform CLI[/param] (https://openbb.co)" + ) + self._console.print( + panel.Panel( + "\n" + kwargs["text"], + title=kwargs["menu"], + subtitle_align="right", + subtitle=version, + ) + ) + + else: + self._console.print(kwargs["text"]) + else: + print(self._filter_rich_tags(kwargs["text"])) # noqa: T201 + elif not self._settings.TEST_MODE: + self._console.print(*args, **kwargs) + else: + print(*args, **kwargs) # noqa: T201 + + def input(self, *args, **kwargs): + """Get input from the user.""" + self.print(*args, **kwargs, end="") + return input() diff --git a/cli/openbb_cli/config/constants.py b/cli/openbb_cli/config/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..adac9bfd6f2304212c9f6b21cbf073b95ea97b05 --- /dev/null +++ b/cli/openbb_cli/config/constants.py @@ -0,0 +1,80 @@ +"""Constants module.""" + +from pathlib import Path + +# Paths +HOME_DIRECTORY = Path.home() +REPOSITORY_DIRECTORY = Path(__file__).parent.parent.parent.parent +SRC_DIRECTORY = Path(__file__).parent.parent +SETTINGS_DIRECTORY = HOME_DIRECTORY / ".openbb_platform" +ASSETS_DIRECTORY = SRC_DIRECTORY / "assets" +STYLES_DIRECTORY = ASSETS_DIRECTORY / "styles" +ENV_FILE_SETTINGS = SETTINGS_DIRECTORY / ".cli.env" +HIST_FILE_PROMPT = SETTINGS_DIRECTORY / ".cli.his" + + +DEFAULT_ROUTINES_URL = "https://openbb-cms.directus.app/items/Routines" +TIMEOUT = 30 +CONNECTION_ERROR_MSG = "[red]Connection error.[/red]" +CONNECTION_TIMEOUT_MSG = "[red]Connection timeout.[/red]" +SCRIPT_TAGS = [ + "stocks", + "crypto", + "etf", + "economy", + "forex", + "fixed income", + "alternative", + "funds", + "bonds", + "macro", + "mutual funds", + "equities", + "options", + "dark pool", + "shorts", + "insider", + "behavioral analysis", + "fundamental analysis", + "technical analysis", + "quantitative analysis", + "forecasting", + "government", + "comparison", + "nft", + "on chain", + "off chain", + "screener", + "report", + "overview", + "rates", + "econometrics", + "portfolio", + "real estate", +] +AVAILABLE_FLAIRS = { + ":openbb": "(🦋)", + ":bug": "(🐛)", + ":rocket": "(🚀)", + ":diamond": "(💎)", + ":stars": "(✨)", + ":baseball": "(⚾)", + ":boat": "(⛵)", + ":phone": "(☎)", + ":mercury": "(☿)", + ":hidden": "", + ":sun": "(☼)", + ":moon": "(🌕)", + ":nuke": "(☢)", + ":hazard": "(☣)", + ":tunder": "(☈)", + ":king": "(♔)", + ":queen": "(♕)", + ":knight": "(♘)", + ":recycle": "(♻)", + ":scales": "(⚖)", + ":ball": "(⚽)", + ":golf": "(⛳)", + ":peace": "(☮)", + ":yy": "(☯)", +} diff --git a/cli/openbb_cli/config/menu_text.py b/cli/openbb_cli/config/menu_text.py new file mode 100644 index 0000000000000000000000000000000000000000..e7be5babae6862abb56dcd86895f9b80f2cf21a0 --- /dev/null +++ b/cli/openbb_cli/config/menu_text.py @@ -0,0 +1,165 @@ +"""Rich Module.""" + +__docformat__ = "numpy" + +from typing import Dict, List + +from openbb import obb + +# https://rich.readthedocs.io/en/stable/appendix/colors.html#appendix-colors +# https://rich.readthedocs.io/en/latest/highlighting.html#custom-highlighters + + +RICH_TAGS = [ + "[menu]", + "[/menu]", + "[cmds]", + "[/cmds]", + "[info]", + "[/info]", + "[param]", + "[/param]", + "[src]", + "[/src]", + "[help]", + "[/help]", +] + + +class MenuText: + """Create menu text with rich colors to be displayed by CLI.""" + + CMD_NAME_LENGTH = 23 + CMD_DESCRIPTION_LENGTH = 65 + CMD_PROVIDERS_LENGTH = 23 + SECTION_SPACING = 4 + + def __init__(self, path: str = ""): + """Initialize menu help.""" + self.menu_text = "" + self.menu_path = path + self.warnings: List[Dict[str, str]] = [] + + @staticmethod + def _get_providers(command_path: str) -> List: + """Return the preferred provider for the given command. + + Parameters + ---------- + command_path: str + The command to find the provider for. E.g. "/equity/price/historical + + Returns + ------- + List + The list of providers for the given command. + """ + command_reference = obb.reference.get("paths", {}).get(command_path, {}) # type: ignore + if command_reference: + providers = list(command_reference["parameters"].keys()) + return [provider for provider in providers if provider != "standard"] + return [] + + def _format_cmd_name(self, name: str) -> str: + """Truncate command name length if it is too long.""" + if len(name) > self.CMD_NAME_LENGTH: + new_name = name[: self.CMD_NAME_LENGTH] + + if "_" in name: + name_split = name.split("_") + + new_name = ( + "_".join(name_split[:2]) if len(name_split) > 2 else name_split[0] + ) + + if len(new_name) > self.CMD_NAME_LENGTH: + new_name = new_name[: self.CMD_NAME_LENGTH] + + if new_name != name: + self.warnings.append( + { + "warning": "Command name too long", + "actual command": f"`{name}`", + "displayed command": f"`{new_name}`", + } + ) + name = new_name + + return name + + def _format_cmd_description( + self, name: str, description: str, trim: bool = True + ) -> str: + """Truncate command description length if it is too long.""" + if not description or description == f"{self.menu_path}{name}": + description = "" + return ( + description[: self.CMD_DESCRIPTION_LENGTH - 3] + "..." + if len(description) > self.CMD_DESCRIPTION_LENGTH and trim + else description + ) + + def add_raw(self, text: str, left_spacing: bool = False): + """Append raw text (without translation).""" + if left_spacing: + self.menu_text += f"{self.SECTION_SPACING * ' '}{text}\n" + else: + self.menu_text += text + + def add_info(self, text: str): + """Append information text (after translation).""" + self.menu_text += f"[info]{text}:[/info]\n" + + def add_cmd(self, name: str, description: str = "", disable: bool = False): + """Append command text (after translation).""" + formatted_name = self._format_cmd_name(name) + name_padding = (self.CMD_NAME_LENGTH - len(formatted_name)) * " " + providers = self._get_providers(f"{self.menu_path}{name}") + formatted_description = self._format_cmd_description( + formatted_name, + description, + bool(providers), + ) + description_padding = ( + self.CMD_DESCRIPTION_LENGTH - len(formatted_description) + ) * " " + spacing = self.SECTION_SPACING * " " + description_padding = ( + self.CMD_DESCRIPTION_LENGTH - len(formatted_description) + ) * " " + cmd = f"{spacing}{formatted_name + name_padding}{spacing}{formatted_description+description_padding}" + cmd = f"[unvl]{cmd}[/unvl]" if disable else f"[cmds]{cmd}[/cmds]" + + if providers: + cmd += rf"{spacing}[src]\[{', '.join(providers)}][/src]" + + self.menu_text += cmd + "\n" + + def add_menu( + self, + name: str, + description: str = "", + disable: bool = False, + ): + """Append menu text (after translation).""" + spacing = (self.CMD_NAME_LENGTH - len(name) + self.SECTION_SPACING) * " " + + if not description or description == f"{self.menu_path}{name}": + description = "" + + if len(description) > self.CMD_DESCRIPTION_LENGTH: + description = description[: self.CMD_DESCRIPTION_LENGTH - 3] + "..." + + menu = f"{name}{spacing}{description}" + tag = "unvl" if disable else "menu" + self.menu_text += f"[{tag}]> {menu}[/{tag}]\n" + + def add_setting(self, name: str, status: bool = True, description: str = ""): + """Append menu text (after translation).""" + spacing = (self.CMD_NAME_LENGTH - len(name) + self.SECTION_SPACING) * " " + indentation = self.SECTION_SPACING * " " + color = "green" if status else "red" + + self.menu_text += ( + f"[{color}]{indentation}{name}{spacing}{description}[/{color}]\n" + ) diff --git a/cli/openbb_cli/config/setup.py b/cli/openbb_cli/config/setup.py new file mode 100644 index 0000000000000000000000000000000000000000..4e682a535e80b9c4c22a5ee72d4f752d62bbec84 --- /dev/null +++ b/cli/openbb_cli/config/setup.py @@ -0,0 +1,11 @@ +"""Configuration for the CLI.""" + +from pathlib import Path + +from openbb_cli.config.constants import ENV_FILE_SETTINGS, SETTINGS_DIRECTORY + + +def bootstrap(): + """Setup pre-launch configurations for the CLI.""" + SETTINGS_DIRECTORY.mkdir(parents=True, exist_ok=True) + Path(ENV_FILE_SETTINGS).touch(exist_ok=True) diff --git a/cli/openbb_cli/config/style.py b/cli/openbb_cli/config/style.py new file mode 100644 index 0000000000000000000000000000000000000000..439d2a410d4d620d7d450e25bce73b9d5587410f --- /dev/null +++ b/cli/openbb_cli/config/style.py @@ -0,0 +1,108 @@ +"""Chart and style helpers for Plotly.""" + +# pylint: disable=C0302,R0902,W3301 +import json +from pathlib import Path +from typing import Any, Dict, List, Optional + +from rich.console import Console + +from openbb_cli.config.constants import STYLES_DIRECTORY + +console = Console() + + +class Style: + """The class that helps with handling of style configurations. + + It serves styles for 2 libraries. For `Plotly` this class serves absolute paths + to the .pltstyle files. For `Plotly` and `Rich` this class serves custom + styles as python dictionaries. + """ + + STYLES_REPO = STYLES_DIRECTORY + + console_styles_available: Dict[str, Path] = {} + console_style: Dict[str, Any] = {} + + line_color: str = "" + up_color: str = "" + down_color: str = "" + up_colorway: List[str] = [] + down_colorway: List[str] = [] + up_color_transparent: str = "" + down_color_transparent: str = "" + + line_width: float = 1.5 + + def __init__( + self, + style: Optional[str] = "", + directory: Optional[Path] = None, + ): + """Initialize the class.""" + self._load(directory) + self.apply(style, directory) + + def apply( + self, style: Optional[str] = None, directory: Optional[Path] = None + ) -> None: + """Apply the style to the console.""" + if style: + if style in self.console_styles_available: + json_path: Optional[Path] = self.console_styles_available[style] + else: + self._load(directory) + if style in self.console_styles_available: + json_path = self.console_styles_available[style] + else: + console.print(f"\nInvalid console style '{style}', using default.") + json_path = self.console_styles_available.get("dark", None) + + if json_path: + self.console_style = self._from_json(json_path) + else: + console.print("Error loading default.") + + def _from_directory(self, folder: Optional[Path]) -> None: + """Load custom styles from folder. + + Parses the styles/default and styles/user folders and loads style files. + To be recognized files need to follow a naming convention: + *.pltstyle - plotly stylesheets + *.richstyle.json - rich stylesheets + + Parameters + ---------- + folder : str + Path to the folder containing the stylesheets + """ + if not folder or not folder.exists(): + return + + for attr, ext in zip( + ["console_styles_available"], + [".richstyle.json"], + ): + for file in folder.rglob(f"*{ext}"): + getattr(self, attr)[file.name.replace(ext, "")] = file + + def _load(self, directory: Optional[Path] = None) -> None: + """Load custom styles from default and user folders.""" + self._from_directory(self.STYLES_REPO) + self._from_directory(directory) + + def _from_json(self, file: Path) -> Dict[str, Any]: + """Load style from json file.""" + with open(file) as f: + json_style: dict = json.load(f) + for key, value in json_style.items(): + json_style[key] = value.replace( + " ", "" + ) # remove whitespaces so Rich can parse it + return json_style + + @property + def available_styles(self) -> List[str]: + """Return available styles.""" + return list(self.console_styles_available.keys()) diff --git a/cli/openbb_cli/controllers/base_controller.py b/cli/openbb_cli/controllers/base_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..da54491c11e42ff8fd8242a03ad5cd7def2a27a2 --- /dev/null +++ b/cli/openbb_cli/controllers/base_controller.py @@ -0,0 +1,1032 @@ +"""Base controller for the CLI.""" + +import argparse +import difflib +import os +import re +import shlex +from abc import ABCMeta, abstractmethod +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, List, Literal, Optional, Tuple, Union + +import pandas as pd +from openbb_cli.config.completer import NestedCompleter +from openbb_cli.config.constants import SCRIPT_TAGS +from openbb_cli.controllers.choices import build_controller_choice_map +from openbb_cli.controllers.hub_service import upload_routine +from openbb_cli.controllers.utils import ( + check_file_type_saved, + check_positive, + get_flair_and_username, + handle_obbject_display, + parse_and_split_input, + parse_unknown_args_to_dict, + print_guest_block_msg, + print_rich_table, + remove_file, + system_clear, + validate_register_key, +) +from openbb_cli.session import Session +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.styles import Style + +# pylint: disable=C0301,C0302,R0902,global-statement,too-many-boolean-expressions +# pylint: disable=R0912 + +controllers: Dict[str, Any] = {} +session = Session() + + +# TODO: We should try to avoid these global variables +RECORD_SESSION = False +RECORD_SESSION_LOCAL_ONLY = False +SESSION_RECORDED = list() +SESSION_RECORDED_NAME = "" +SESSION_RECORDED_DESCRIPTION = "" +SESSION_RECORDED_TAGS = "" +SESSION_RECORDED_PUBLIC = False + + +class BaseController(metaclass=ABCMeta): + """Base class for a cli controller.""" + + CHOICES_COMMON = [ + "cls", + "home", + "h", + "?", + "help", + "q", + "quit", + "..", + "e", + "exit", + "r", + "reset", + "stop", + "whoami", + "results", + ] + + CHOICES_COMMANDS: List[str] = [] + CHOICES_MENUS: List[str] = [] + NEWS_CHOICES: dict = {} + COMMAND_SEPARATOR = "/" + KEYS_MENU = "keys" + COMMAND_SEPARATOR + PATH: str = "" + FILE_PATH: str = "" + CHOICES_GENERATION = False + + @property + def choices_default(self): + """Return the default choices.""" + choices = ( + build_controller_choice_map(controller=self) + if self.CHOICES_GENERATION + else {} + ) + + return choices + + def __init__(self, queue: Optional[List[str]] = None) -> None: + """Create the base class for any controller in the codebase. + + Used to simplify the creation of menus. + + queue: List[str] + The current queue of jobs to process separated by "/" + E.g. /stocks/load gme/dps/sidtc/../exit + """ + self.check_path() + self.path = [x for x in self.PATH.split("/") if x != ""] + self.queue = ( + self.parse_input(an_input="/".join(queue)) + if (queue and self.PATH != "/") + else list() + ) + + controller_choices = self.CHOICES_COMMANDS + self.CHOICES_MENUS + if controller_choices: + self.controller_choices = controller_choices + self.CHOICES_COMMON + else: + self.controller_choices = self.CHOICES_COMMON + + self.completer: Union[None, NestedCompleter] = None + + self.parser = argparse.ArgumentParser( + add_help=False, + prog=self.path[-1] if self.PATH != "/" else "cli", + ) + self.parser.exit_on_error = False # type: ignore + self.parser.add_argument("cmd", choices=self.controller_choices) + + def update_completer(self, choices) -> None: + """Update the completer with new choices.""" + if session.prompt_session and session.settings.USE_PROMPT_TOOLKIT: + self.completer = NestedCompleter.from_nested_dict(choices) + + def check_path(self) -> None: + """Check if command path is valid.""" + path = self.PATH + if path[0] != "/": + raise ValueError("Path must begin with a '/' character.") + if path[-1] != "/": + raise ValueError("Path must end with a '/' character.") + if not re.match("^[a-z/]*$", path): + raise ValueError( + "Path must only contain lowercase letters and '/' characters." + ) + + def load_class(self, class_ins, *args, **kwargs): + """Check for an existing instance of the controller before creating a new one.""" + self.save_class() + arguments = len(args) + len(kwargs) + + if class_ins.PATH in controllers and arguments == 1: + old_class = controllers[class_ins.PATH] + old_class.queue = self.queue + return old_class.menu() + return class_ins(*args, **kwargs).menu() + + def save_class(self) -> None: + """Save the current instance of the class to be loaded later.""" + controllers[self.PATH] = self + + def custom_reset(self) -> List[str]: + """Implement custom reset. + + This will be replaced by any children with custom_reset functions. + """ + return [] + + @abstractmethod + def print_help(self) -> None: + """Print help placeholder.""" + raise NotImplementedError("Must override print_help.") + + def parse_input(self, an_input: str) -> list: + """Parse controller input. + + Splits the command chain from user input into a list of individual commands + while respecting the forward slash in the command arguments. + + In the default scenario only unix-like paths are handles by the parser. + Override this function in the controller classes that inherit from this one to + resolve edge cases specific to command arguments on those controllers. + + When handling edge cases add additional regular expressions to the list. + + Parameters + ---------- + an_input : str + User input string + + Returns + ---------- + list + Command queue as list + """ + custom_filters: list = [] + commands = parse_and_split_input( + an_input=an_input, custom_filters=custom_filters + ) + return commands + + def switch(self, an_input: str) -> List[str]: + """Process and dispatch input. + + Returns + ---------- + List[str] + list of commands in the queue to execute + """ + actions = self.parse_input(an_input) + + if an_input and an_input != "reset": + session.console.print() + + # Empty command + if len(actions) == 0: + pass + + # Navigation slash is being used first split commands + elif len(actions) > 1: + # Absolute path is specified + if not actions[0]: + actions[0] = "home" + + # Add all instructions to the queue + for cmd in actions[::-1]: + if cmd: + self.queue.insert(0, cmd) + + # Single command fed, process + else: + try: + (known_args, other_args) = self.parser.parse_known_args( + shlex.split(an_input) + ) + except Exception as exc: + raise SystemExit from exc + + if RECORD_SESSION: + SESSION_RECORDED.append(an_input) + + # Redirect commands to their correct functions + if known_args.cmd: + if known_args.cmd in ("..", "q"): + known_args.cmd = "quit" + elif known_args.cmd in ("e"): + known_args.cmd = "exit" + elif known_args.cmd in ("?", "h"): + known_args.cmd = "help" + elif known_args.cmd == "r": + known_args.cmd = "reset" + + getattr( + self, + "call_" + known_args.cmd, + lambda _: "Command not recognized!", + )(other_args) + + if ( + an_input + and an_input != "reset" + and ( + not self.queue or (self.queue and self.queue[0] not in ("quit", "help")) + ) + ): + session.console.print() + + return self.queue + + def call_cls(self, _) -> None: + """Process cls command.""" + system_clear() + + def call_home(self, _) -> None: + """Process home command.""" + self.save_class() + if self.PATH.count("/") == 1 and session.settings.ENABLE_EXIT_AUTO_HELP: + self.print_help() + for _ in range(self.PATH.count("/") - 1): + self.queue.insert(0, "quit") + + def call_help(self, _) -> None: + """Process help command.""" + self.print_help() + + def call_quit(self, _) -> None: + """Process quit menu command.""" + self.save_class() + self.queue.insert(0, "quit") + + def call_exit(self, _) -> None: + # Not sure how to handle controller loading here + """Process exit cli command.""" + self.save_class() + for _ in range(self.PATH.count("/")): + self.queue.insert(0, "quit") + + if not session.is_local(): + remove_file( + Path(session.user.preferences.export_directory, "routines", "hub") + ) + + def call_reset(self, _) -> None: + """Process reset command. + + If you would like to have customization in the reset process define a method + `custom_reset` in the child class. + """ + self.save_class() + if self.PATH != "/": + if self.custom_reset(): + self.queue = self.custom_reset() + self.queue + else: + for val in self.path[::-1]: + self.queue.insert(0, val) + self.queue.insert(0, "reset") + for _ in range(len(self.path)): + self.queue.insert(0, "quit") + + def call_record(self, other_args) -> None: + """Process record command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="record", + description="Start recording session into .openbb routine file", + ) + parser.add_argument( + "-n", + "--name", + action="store", + dest="name", + type=str, + default="", + help="Routine title name to be saved - only use characters, digits and whitespaces.", + nargs="+", + ) + parser.add_argument( + "-d", + "--description", + type=str, + dest="description", + help="The description of the routine", + default=f"Routine recorded at {datetime.now().strftime('%H:%M')} from the OpenBB Platform CLI", + nargs="+", + ) + parser.add_argument( + "--tag1", + type=str, + dest="tag1", + help=f"The tag associated with the routine. Select from: {', '.join(SCRIPT_TAGS)}", + default="", + nargs="+", + ) + parser.add_argument( + "--tag2", + type=str, + dest="tag2", + help=f"The tag associated with the routine. Select from: {', '.join(SCRIPT_TAGS)}", + default="", + nargs="+", + ) + parser.add_argument( + "--tag3", + type=str, + dest="tag3", + help=f"The tag associated with the routine. Select from: {', '.join(SCRIPT_TAGS)}", + default="", + nargs="+", + ) + parser.add_argument( + "-p", + "--public", + dest="public", + action="store_true", + help="Whether the routine should be public or not", + default=False, + ) + + if other_args and "-" not in other_args[0][0]: + other_args.insert(0, "-n") + + ns_parser, _ = self.parse_simple_args(parser, other_args) + + if ns_parser: + if not ns_parser.name: + session.console.print( + "[red]Set a routine title by using the '-n' flag. E.g. 'record -n Morning routine'[/red]" + ) + return + + tag1 = ( + " ".join(ns_parser.tag1) + if isinstance(ns_parser.tag1, list) + else ns_parser.tag1 + ) + if tag1 and tag1 not in SCRIPT_TAGS: + session.console.print( + f"[red]The parameter 'tag1' needs to be one of the following {', '.join(SCRIPT_TAGS)}[/red]" + ) + return + + tag2 = ( + " ".join(ns_parser.tag2) + if isinstance(ns_parser.tag2, list) + else ns_parser.tag2 + ) + if tag2 and tag2 not in SCRIPT_TAGS: + session.console.print( + f"[red]The parameter 'tag2' needs to be one of the following {', '.join(SCRIPT_TAGS)}[/red]" + ) + return + + tag3 = ( + " ".join(ns_parser.tag3) + if isinstance(ns_parser.tag3, list) + else ns_parser.tag3 + ) + if tag3 and tag3 not in SCRIPT_TAGS: + session.console.print( + f"[red]The parameter 'tag3' needs to be one of the following {', '.join(SCRIPT_TAGS)}[/red]" + ) + return + + if session.is_local(): + session.console.print( + "[red]Recording session to the OpenBB Hub is not supported in guest mode.[/red]" + ) + session.console.print( + "\n[yellow]Visit the OpenBB Hub to register: http://my.openbb.co[/yellow]" + ) + session.console.print( + "\n[yellow]Your routine will be saved locally.[/yellow]\n" + ) + + # Check if title has a valid format + title = " ".join(ns_parser.name) if ns_parser.name else "" + pattern = re.compile(r"^[a-zA-Z0-9\s]+$") + if not pattern.match(title): + session.console.print( + f"[red]Title '{title}' has invalid format. Please use only digits, characters and whitespaces.[/]" + ) + return + + global RECORD_SESSION # noqa: PLW0603 + global RECORD_SESSION_LOCAL_ONLY # noqa: PLW0603 + global SESSION_RECORDED_NAME # noqa: PLW0603 + global SESSION_RECORDED_DESCRIPTION # noqa: PLW0603 + global SESSION_RECORDED_TAGS # noqa: PLW0603 + global SESSION_RECORDED_PUBLIC # noqa: PLW0603 + + RECORD_SESSION_LOCAL_ONLY = session.is_local() + RECORD_SESSION = True + SESSION_RECORDED_NAME = title + SESSION_RECORDED_DESCRIPTION = ( + " ".join(ns_parser.description) + if isinstance(ns_parser.description, list) + else ns_parser.description + ) + SESSION_RECORDED_TAGS = tag1 if tag1 else "" + SESSION_RECORDED_TAGS += "," + tag2 if tag2 else "" + SESSION_RECORDED_TAGS += "," + tag3 if tag3 else "" + + SESSION_RECORDED_PUBLIC = ns_parser.public + + session.console.print( + f"[green]The routine '{title}' is successfully being recorded.[/green]" + ) + session.console.print( + "\n[yellow]Remember to run 'stop' command when you are done!\n[/yellow]" + ) + + def call_stop(self, other_args) -> None: + """Process stop command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="stop", + description="Stop recording session into .openbb routine file", + ) + # This is only for auto-completion purposes + _, _ = self.parse_simple_args(parser, other_args) + + if "-h" not in other_args and "--help" not in other_args: + global RECORD_SESSION # noqa: PLW0603 + global SESSION_RECORDED # noqa: PLW0603 + + if not RECORD_SESSION: + session.console.print( + "[red]There is no session being recorded. Start one using the command 'record'[/red]\n" + ) + elif len(SESSION_RECORDED) < 5: + session.console.print( + "[red]Run at least 4 commands before stopping recording a session.[/red]\n" + ) + else: + current_user = session.user + + # Check if the user just wants to store routine locally + # This works regardless of whether they are logged in or not + if RECORD_SESSION_LOCAL_ONLY: + # Whitespaces are replaced by underscores and an .openbb extension is added + title_for_local_storage = ( + SESSION_RECORDED_NAME.replace(" ", "_") + ".openbb" + ) + + routine_file = os.path.join( + f"{current_user.preferences.export_directory}/routines", + title_for_local_storage, + ) + + # If file already exists, add a timestamp to the name + if os.path.isfile(routine_file): + i = session.console.input( + "A local routine with the same name already exists, " + "do you want to override it? (y/n): " + ) + session.console.print("") + while i.lower() not in ["y", "yes", "n", "no"]: + i = session.console.input("Select 'y' or 'n' to proceed: ") + session.console.print("") + + if i.lower() in ["n", "no"]: + new_name = ( + datetime.now().strftime("%Y%m%d_%H%M%S_") + + title_for_local_storage + ) + routine_file = os.path.join( + current_user.preferences.export_directory, + "routines", + new_name, + ) + session.console.print( + f"[yellow]The routine name has been updated to '{new_name}'[/yellow]\n" + ) + + # Writing to file + Path(os.path.dirname(routine_file)).mkdir( + parents=True, exist_ok=True + ) + + with open(routine_file, "w") as file1: + lines = ["# OpenBB Platform CLI - Routine", "\n"] + + username = getattr( + session.user.profile.hub_session, "username", "local" + ) + + lines += ( + [f"# Author: {username}", "\n\n"] if username else ["\n"] + ) + lines += [ + f"# Title: {SESSION_RECORDED_NAME}", + "\n", + f"# Tags: {SESSION_RECORDED_TAGS}", + "\n\n", + f"# Description: {SESSION_RECORDED_DESCRIPTION}", + "\n\n", + ] + lines += [c + "\n" for c in SESSION_RECORDED[:-1]] + # Writing data to a file + file1.writelines(lines) + + session.console.print( + f"[green]Your routine has been recorded and saved here: {routine_file}[/green]\n" + ) + + # If user doesn't specify they want to store routine locally + # Confirm that the user is logged in + elif not session.is_local(): + routine = "\n".join(SESSION_RECORDED[:-1]) + hub_session = current_user.profile.hub_session + + if routine is not None: + auth_header = ( + f"{hub_session.token_type} {hub_session.access_token.get_secret_value()}" + if hub_session + else None + ) + kwargs = { + "auth_header": auth_header, + "name": SESSION_RECORDED_NAME, + "description": SESSION_RECORDED_DESCRIPTION, + "routine": routine, + "tags": SESSION_RECORDED_TAGS, + "public": SESSION_RECORDED_PUBLIC, + } + response = upload_routine(**kwargs) # type: ignore + if response is not None and response.status_code == 409: + i = session.console.input( + "A routine with the same name already exists, " + "do you want to replace it? (y/n): " + ) + session.console.print("") + if i.lower() in ["y", "yes"]: + kwargs["override"] = True # type: ignore + response = upload_routine(**kwargs) # type: ignore + else: + session.console.print("[info]Aborted.[/info]") + + # Clear session to be recorded again + RECORD_SESSION = False + SESSION_RECORDED = list() + + def call_whoami(self, other_args: List[str]) -> None: + """Process whoami command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="whoami", + description="Show current user", + ) + ns_parser, _ = self.parse_simple_args(parser, other_args) + + if ns_parser: + current_user = session.user + local_user = session.is_local() + if not local_user: + hub_session = current_user.profile.hub_session + session.console.print( + f"[info]email:[/info] {hub_session.email if hub_session else 'N/A'}" + ) + session.console.print( + f"[info]uuid:[/info] {hub_session.user_uuid if hub_session else 'N/A'}" + ) + else: + print_guest_block_msg() + + def call_results(self, other_args: List[str]): + """Process results command.""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="results", + description="Process results command. This command displays a registry of " + "'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", + default="", + type=check_file_type_saved(["csv", "json", "xlsx", "png", "jpg"]), + dest="export", + help="Export raw data into csv, json, xlsx and figure into png or jpg.", + nargs="+", + ) + parser.add_argument( + "--sheet-name", + dest="sheet_name", + default=None, + nargs="+", + help="Name of excel sheet to save data to. Only valid for .xlsx files.", + ) + + ns_parser, unknown_args = self.parse_simple_args( + parser, other_args, unknown_args=True + ) + + if ns_parser: + kwargs = parse_unknown_args_to_dict(unknown_args) + 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: + handle_obbject_display( + obbject=obbject, + chart=ns_parser.chart, + export=ns_parser.export, + sheet_name=ns_parser.sheet_name, + **kwargs, + ) + 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: + handle_obbject_display( + obbject=obbject, + chart=ns_parser.chart, + export=ns_parser.export, + sheet_name=ns_parser.sheet_name, + **kwargs, + ) + 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], + unknown_args: bool = False, + ) -> Tuple[Optional[argparse.Namespace], Optional[List[str]]]: + """Parse list of arguments into the supplied parser. + + Parameters + ---------- + parser: argparse.ArgumentParser + Parser with predefined arguments + other_args: List[str] + List of arguments to parse + unknown_args: bool + Flag to indicate if unknown arguments should be returned + + Returns + ------- + ns_parser: argparse.Namespace + Namespace with parsed arguments + l_unknown_args: List[str] + List of unknown arguments + """ + parser.add_argument( + "-h", "--help", action="store_true", help="show this help message" + ) + + if session.settings.USE_CLEAR_AFTER_CMD: + system_clear() + + try: + (ns_parser, l_unknown_args) = parser.parse_known_args(other_args) + except SystemExit: + # In case the command has required argument that isn't specified + session.console.print("\n") + return None, None + + if ns_parser.help: + txt_help = parser.format_help() + session.console.print(f"[help]{txt_help}[/help]") + return None, None + + if l_unknown_args and not unknown_args: + session.console.print( + f"The following args couldn't be interpreted: {l_unknown_args}\n" + ) + return ns_parser, l_unknown_args + + @classmethod + def parse_known_args_and_warn( + cls, + parser: argparse.ArgumentParser, + other_args: List[str], + export_allowed: Literal[ + "no_export", "raw_data_only", "figures_only", "raw_data_and_figures" + ] = "no_export", + raw: bool = False, + limit: int = 0, + ): + """Parse list of arguments into the supplied parser. + + Parameters + ---------- + parser: argparse.ArgumentParser + Parser with predefined arguments + other_args: List[str] + list of arguments to parse + export_allowed: Literal["no_export", "raw_data_only", "figures_only", "raw_data_and_figures"] + Export options + raw: bool + Add the --raw flag + limit: int + Add a --limit flag with this number default + + Returns + ---------- + ns_parser: + Namespace with parsed arguments + """ + parser.add_argument( + "-h", "--help", action="store_true", help="show this help message" + ) + + if export_allowed != "no_export": + choices_export = [] + help_export = "Does not export!" + + if export_allowed == "raw_data_only": + choices_export = ["csv", "json", "xlsx"] + help_export = "Export raw data into csv, json or xlsx." + elif export_allowed == "figures_only": + choices_export = ["png", "jpg"] + help_export = "Export figure into png or jpg." + else: + choices_export = ["csv", "json", "xlsx", "png", "jpg"] + help_export = ( + "Export raw data into csv, json, xlsx and figure into png or jpg." + ) + + parser.add_argument( + "--export", + default="", + type=check_file_type_saved(choices_export), + dest="export", + help=help_export, + nargs="+", + ) + + # If excel is an option, add the sheet name + if export_allowed in [ + "raw_data_only", + "raw_data_and_figures", + ]: + parser.add_argument( + "--sheet-name", + dest="sheet_name", + default=None, + nargs="+", + help="Name of excel sheet to save data to. Only valid for .xlsx files.", + ) + + if raw: + parser.add_argument( + "--raw", + dest="raw", + action="store_true", + default=False, + help="Flag to display raw data", + ) + if limit > 0: + parser.add_argument( + "-l", + "--limit", + dest="limit", + default=limit, + 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() + + if "--help" in other_args or "-h" in other_args: + txt_help = parser.format_help() + "\n" + session.console.print(f"[help]{txt_help}[/help]") + return None + + try: + # Determine the index of the routine arguments + routine_args_index = next( + ( + i + 1 + for i, arg in enumerate(other_args) + if arg in ("-i", "--input") + and "routine_args" + in [ + action.dest + for action in parser._actions # pylint: disable=protected-access + ] + ), + -1, + ) + # Split comma-separated arguments, except for the argument at routine_args_index + other_args = [ + part + for index, arg in enumerate(other_args) + for part in (arg.split(",") if index != routine_args_index else [arg]) + ] + + # Check if the action has optional choices, if yes, remove them + for action in parser._actions: # pylint: disable=protected-access + if hasattr(action, "optional_choices") and action.optional_choices: + action.choices = None + + (ns_parser, l_unknown_args) = parser.parse_known_args(other_args) + + if export_allowed in [ + "raw_data_only", + "raw_data_and_figures", + ]: + ns_parser.is_image = any( + ext in ns_parser.export for ext in ["png", "jpg"] + ) + + except SystemExit: + # In case the command has required argument that isn't specified + + return None + + if l_unknown_args: + session.console.print( + f"The following args couldn't be interpreted: {l_unknown_args}" + ) + return ns_parser + + def menu(self, custom_path_menu_above: str = ""): + """Enter controller menu.""" + settings = session.settings + an_input = "HELP_ME" + + while True: + # There is a command in the queue + if self.queue and len(self.queue) > 0: + if self.queue[0] in ("q", "..", "quit"): + self.save_class() + # Go back to the root in order to go to the right directory because + # there was a jump between indirect menus + if custom_path_menu_above: + self.queue.insert(1, custom_path_menu_above) + + if len(self.queue) > 1: + return self.queue[1:] + + if settings.ENABLE_EXIT_AUTO_HELP: + return ["help"] + return [] + + # Consume 1 element from the queue + an_input = self.queue[0] + self.queue = self.queue[1:] + + # Print location because this was an instruction and we want user to know the action + if ( + an_input + and an_input != "home" + and an_input != "help" + and an_input.split(" ")[0] in self.controller_choices + ): + session.console.print( + f"{get_flair_and_username()} {self.PATH} $ {an_input}" + ) + + # Get input command from user + else: + # Display help menu when entering on this menu from a level above + if an_input == "HELP_ME": + self.print_help() + + try: + prompt_session = session.prompt_session + if prompt_session and settings.USE_PROMPT_TOOLKIT: + # Check if toolbar hint was enabled + if settings.TOOLBAR_HINT: + an_input = prompt_session.prompt( + f"{get_flair_and_username()} {self.PATH} $ ", + completer=self.completer, + search_ignore_case=True, + bottom_toolbar=HTML( + ' help menu ' + ' return to previous menu ' + ' exit the program ' + ' ' + "see usage and available options " + f"{self.path[-1].capitalize()} (cmd/menu) Documentation" + ), + style=Style.from_dict( + {"bottom-toolbar": "#ffffff bg:#333333"} + ), + ) + else: + an_input = prompt_session.prompt( + f"{get_flair_and_username()} {self.PATH} $ ", + completer=self.completer, + search_ignore_case=True, + ) + # Get input from user without auto-completion + else: + an_input = input(f"{get_flair_and_username()} {self.PATH} $ ") + + except (KeyboardInterrupt, EOFError): + # Exit in case of keyboard interrupt + an_input = "exit" + + try: + # Allow user to go back to root + an_input = "home" if an_input == "/" else an_input + + # Process the input command + self.queue = self.switch(an_input) + + except SystemExit: + session.console.print( + f"[red]The command '{an_input}' doesn't exist on the {self.PATH} menu.[/red]\n", + ) + similar_cmd = difflib.get_close_matches( + an_input.split(" ")[0] if " " in an_input else an_input, + self.controller_choices, + n=1, + cutoff=0.7, + ) + if similar_cmd: + if " " in an_input: + candidate_input = ( + f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}" + ) + if candidate_input == an_input: + an_input = "" + self.queue = [] + session.console.print("\n") + continue + + an_input = candidate_input + else: + an_input = similar_cmd[0] + + session.console.print( + f"[green]Replacing by '{an_input}'.[/green]\n" + ) + self.queue.insert(0, an_input) diff --git a/cli/openbb_cli/controllers/base_platform_controller.py b/cli/openbb_cli/controllers/base_platform_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..a727163808b8e71df6e140242fe90fb595cf34c8 --- /dev/null +++ b/cli/openbb_cli/controllers/base_platform_controller.py @@ -0,0 +1,392 @@ +"""Platform Equity Controller.""" + +import os +from functools import partial, update_wrapper +from types import MethodType +from typing import Dict, List, Optional + +import pandas as pd +from openbb import obb +from openbb_charting.core.openbb_figure import OpenBBFigure +from openbb_cli.argparse_translator.argparse_class_processor import ( + ArgparseClassProcessor, +) +from openbb_cli.config.menu_text import MenuText +from openbb_cli.controllers.base_controller import BaseController +from openbb_cli.controllers.utils import export_data, print_rich_table +from openbb_cli.session import Session +from openbb_core.app.model.obbject import OBBject + +session = Session() + + +class DummyTranslation: + """Dummy Translation for testing.""" + + def __init__(self): + """Construct a Dummy Translation Class.""" + self.paths = {} + self.translators = {} + + +class PlatformController(BaseController): + """Platform Controller Base class.""" + + CHOICES_GENERATION = True + + def __init__( # pylint: disable=too-many-positional-arguments + self, + name: str, + parent_path: List[str], + platform_target: Optional[type] = None, + queue: Optional[List[str]] = None, + translators: Optional[Dict] = None, + ): + """Construct a Platform based Controller.""" + self.PATH = f"/{'/'.join(parent_path)}/{name}/" if parent_path else f"/{name}/" + super().__init__(queue) + self._name = name + + if not (platform_target or translators): + raise ValueError("Either platform_target or translators must be provided.") + + self._translated_target = ( + ArgparseClassProcessor( + target_class=platform_target, reference=obb.reference["paths"] # type: ignore + ) + if platform_target + else DummyTranslation() + ) + self.translators = ( + translators + if translators is not None + else getattr(self._translated_target, "translators", {}) + ) + self.paths = getattr(self._translated_target, "paths", {}) + + if self.translators: + self._link_obbject_to_data_processing_commands() + self._generate_commands() + self._generate_sub_controllers() + self.update_completer(self.choices_default) + + def _link_obbject_to_data_processing_commands(self): + """Link data processing commands to OBBject registry.""" + 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"): + 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) + if obbject and isinstance(obbject, OBBject): + setattr(ns_parser, "data", obbject.results) + + return ns_parser + + def _generate_sub_controllers(self): + """Handle paths.""" + for path, value in self.paths.items(): + if value == "path": + continue + + sub_menu_translators = {} + choices_commands = [] + + for translator_name, translator in self.translators.items(): + if f"{self._name}_{path}" in translator_name: + new_name = translator_name.replace(f"{self._name}_{path}_", "") + sub_menu_translators[new_name] = translator + choices_commands.append(new_name) + + if translator_name in self.CHOICES_COMMANDS: + self.CHOICES_COMMANDS.remove(translator_name) + + # Create the sub controller as a new class + class_name = f"{self._name.capitalize()}{path.capitalize()}Controller" + SubController = type( + class_name, + (PlatformController,), + { + "CHOICES_GENERATION": True, + # "CHOICES_MENUS": [], + "CHOICES_COMMANDS": choices_commands, + }, + ) + + self._generate_controller_call( + controller=SubController, + name=path, + parent_path=self.path, + translators=sub_menu_translators, + ) + + def _generate_commands(self): + """Generate commands.""" + for name, translator in self.translators.items(): + # Prepare the translator name to create a command call in the controller + new_name = name.replace(f"{self._name}_", "") + + self._generate_command_call(name=new_name, translator=translator) + + def _generate_command_call(self, name, translator): + """Generate command call.""" + + def method(self, other_args: List[str], translator=translator): + """Call the translator.""" + parser = translator.parser + + if ns_parser := self.parse_known_args_and_warn( + parser=parser, + other_args=other_args, + export_allowed="raw_data_and_figures", + ): + try: + ns_parser = self._intersect_data_processing_commands(ns_parser) + export = hasattr(ns_parser, "export") and ns_parser.export + 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 + title = f"{self.PATH}{translator.func.__name__}" + + if obbject: + if isinstance(obbject, list): + obbject = OBBject(results=obbject) + + if isinstance(obbject, OBBject): + 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]" + ) + + # use the obbject to store the command so we can display it later on results + obbject.extra["command"] = f"{title} {' '.join(other_args)}" + # if there is a registry key in the parser, store to the obbject + if ( + hasattr(ns_parser, "register_key") + and ns_parser.register_key + ): + 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 + ) + + # 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() + + if hasattr(ns_parser, "chart") and ns_parser.chart: + fig = obbject.chart.fig if obbject.chart else None + if not export: + obbject.show() + elif session.settings.USE_INTERACTIVE_DF and not export: + obbject.charting.table() + else: + if isinstance(df.columns, pd.RangeIndex): + df.columns = [str(i) for i in df.columns] + + print_rich_table( + df=df, show_index=True, title=title, export=export + ) + + elif isinstance(obbject, dict): + df = pd.DataFrame.from_dict(obbject, orient="columns") + print_rich_table( + df=df, show_index=True, title=title, export=export + ) + + elif not isinstance(obbject, OBBject): + session.console.print(obbject) + + if export and not df.empty: + sheet_name = getattr(ns_parser, "sheet_name", None) + if sheet_name and isinstance(sheet_name, list): + sheet_name = sheet_name[0] + + export_data( + export_type=",".join(ns_parser.export), + dir_path=os.path.dirname(os.path.abspath(__file__)), + func_name=translator.func.__name__, + df=df, + sheet_name=sheet_name, + figure=fig, + ) + elif export and df.empty: + session.console.print("[yellow]No data to export.[/yellow]") + + except Exception as e: + session.console.print(f"[red]{e}[/]\n") + return + + # Bind the method to the class + bound_method = MethodType(method, self) + + # Update the wrapper and set the attribute + bound_method = update_wrapper( # type: ignore + partial(bound_method, translator=translator), method + ) + setattr(self, f"call_{name}", bound_method) + + def _generate_controller_call(self, controller, name, parent_path, translators): + """Generate controller call.""" + + def method(self, _, controller, name, parent_path, translators): + """Call the controller.""" + self.queue = self.load_class( + class_ins=controller, + name=name, + parent_path=parent_path, + translators=translators, + queue=self.queue, + ) + + # Bind the method to the class + bound_method = MethodType(method, self) + + # Update the wrapper and set the attribute + bound_method = update_wrapper( # type: ignore + partial( + bound_method, + name=name, + parent_path=parent_path, + translators=translators, + controller=controller, + ), + method, + ) + setattr(self, f"call_{name}", bound_method) + + def _get_command_description(self, command: str) -> str: + """Get command description.""" + command_description = ( + obb.reference["paths"] # type: ignore + .get(f"{self.PATH}{command}", {}) + .get("description", "") + ) + + if not command_description: + trl = self.translators.get( + f"{self._name}_{command}" + ) or self.translators.get(command) + if trl and hasattr(trl, "parser"): + command_description = trl.parser.description + + return command_description.split(".")[0].lower() + + def _get_menu_description(self, menu: str) -> str: + """Get menu description.""" + + def _get_sub_menu_commands(): + """Get sub menu commands.""" + sub_path = f"{self.PATH[1:].replace('/','_')}{menu}" + commands = [] + for trl in self.translators: + if sub_path in trl: + commands.append(trl.replace(f"{sub_path}_", "")) + return commands + + menu_description = ( + obb.reference["routers"] # type: ignore + .get(f"{self.PATH}{menu}", {}) + .get("description", "") + ) or "" + if menu_description: + return menu_description.split(".")[0].lower() + + # If no description is found, return the sub menu commands + return ", ".join(_get_sub_menu_commands()) + + def print_help(self): + """Print help.""" + mt = MenuText(self.PATH) + + if self.CHOICES_MENUS: + for menu in self.CHOICES_MENUS: + description = self._get_menu_description(menu) + mt.add_menu(name=menu, description=description) + + if self.CHOICES_COMMANDS: + mt.add_raw("\n") + + if self.CHOICES_COMMANDS: + for command in self.CHOICES_COMMANDS: + command_description = self._get_command_description(command) + mt.add_cmd( + name=command.replace(f"{self._name}_", ""), + description=command_description, + ) + + if session.obbject_registry.obbjects: + mt.add_info("\nCached Results") + for key, value in list(session.obbject_registry.all.items())[ + : session.settings.N_TO_DISPLAY_OBBJECT_REGISTRY + ]: + mt.add_raw( + f"[yellow]OBB{key}[/yellow]: {value['command']}", + left_spacing=True, + ) + + session.console.print(text=mt.menu_text, menu=self.PATH) + + if mt.warnings: + session.console.print("") + for w in mt.warnings: + w_str = str(w).replace("{", "").replace("}", "").replace("'", "") + session.console.print(f"[yellow]{w_str}[/yellow]") + session.console.print("") diff --git a/cli/openbb_cli/controllers/choices.py b/cli/openbb_cli/controllers/choices.py new file mode 100644 index 0000000000000000000000000000000000000000..f4e875520951ae9f2d08153d929a20fe3ddf33fd --- /dev/null +++ b/cli/openbb_cli/controllers/choices.py @@ -0,0 +1,344 @@ +"""This module contains functions to build the choice map for the controllers.""" + +from argparse import SUPPRESS, ArgumentParser +from contextlib import contextmanager +from inspect import isfunction, unwrap +from types import MethodType +from typing import Callable, List, Literal, Tuple +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 + +session = Session() + + +def __mock_parse_known_args_and_warn( + controller, # pylint: disable=unused-argument + parser: ArgumentParser, + other_args: List[str], + export_allowed: Literal[ + "no_export", "raw_data_only", "figures_only", "raw_data_and_figures" + ] = "no_export", + raw: bool = False, + limit: int = 0, +) -> None: + """Add arguments. + + Add the arguments that would have normally added by : + - openbb_cli.base_controller.BaseController.parse_known_args_and_warn + + Parameters + ---------- + parser: argparse.ArgumentParser + Parser with predefined arguments + other_args: List[str] + list of arguments to parse + export_allowed: Literal["no_export", "raw_data_only", "figures_only", "raw_data_and_figures"] + Export options + raw: bool + Add the --raw flag + limit: int + Add a --limit flag with this number default + """ + _ = other_args + + parser.add_argument( + "-h", "--help", action="store_true", help="show this help message" + ) + + if export_allowed != "no_export": + choices_export = [] + help_export = "Does not export!" + + if export_allowed == "raw_data_only": + choices_export = ["csv", "json", "xlsx"] + help_export = "Export raw data into csv, json or xlsx." + elif export_allowed == "figures_only": + choices_export = ["png", "jpg"] + help_export = "Export figure into png or jpg." + else: + choices_export = ["csv", "json", "xlsx", "png", "jpg"] + help_export = ( + "Export raw data into csv, json, xlsx and figure into png or jpg." + ) + + parser.add_argument( + "--export", + default="", + type=check_file_type_saved(choices_export), + dest="export", + help=help_export, + choices=choices_export, + ) + + if raw: + parser.add_argument( + "--raw", + dest="raw", + action="store_true", + default=False, + help="Flag to display raw data", + ) + if limit > 0: + parser.add_argument( + "-l", + "--limit", + dest="limit", + default=limit, + 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, + ) + + +def __mock_parse_simple_args(parser: ArgumentParser, other_args: List[str]) -> Tuple: + """Add arguments. + + Add the arguments that would have normally added by: + - openbb_cli.parent_classes.BaseController.parse_simple_args + + Parameters + ---------- + parser: argparse.ArgumentParser + Parser with predefined arguments + other_args: List[str] + List of arguments to parse + """ + parser.add_argument( + "-h", "--help", action="store_true", help="show this help message" + ) + _ = other_args + return None, None + + +def __get_command_func(controller, command: str): + """Get the function with the name `f"call_{command}"` from controller object. + + Parameters + ---------- + controller: BaseController + Instance of the CLI Controller. + command: str + A name from controller.CHOICES_COMMANDS + + Returns + ------- + Callable: Command function. + """ + if command not in controller.CHOICES_COMMANDS: + raise AttributeError( + f"The following command is not inside `CHOICES_COMMANDS` : '{command}'" + ) + + command = f"call_{command}" + command_func = getattr(controller, command) + command_func = unwrap(func=command_func) + + if isfunction(command_func): + command_func = MethodType(command_func, controller) + + return command_func + + +def contains_functions_to_patch(command_func: Callable) -> bool: + """Check command function. + + Check if a `command_func` actually contains the functions we want to mock, i.e.: + - parse_simple_args + - parse_known_args_and_warn + + Parameters + ---------- + command_func: Callable + Function to check. + + Returns + ------- + bool: Whether or not `command_func` contains the mocked functions. + """ + co_names = command_func.__code__.co_names + + return bool( + "parse_simple_args" in co_names or "parse_known_args_and_warn" in co_names + ) + + +@contextmanager +def __patch_controller_functions(controller): + """Patch controller functions. + + Patch the following function from a BaseController instance: + - parse_simple_args + - parse_known_args_and_warn + + These functions take an 'argparse.ArgumentParser' object as parameter. + We want to intercept this 'argparse.ArgumentParser' object. + + Parameters + ---------- + controller: BaseController + BaseController object that needs to be patched. + + Returns + ------- + List[Callable]: List of mocked functions. + """ + bound_mock_parse_known_args_and_warn = MethodType( + __mock_parse_known_args_and_warn, + controller, + ) + + rich = patch( + target="openbb_cli.config.console.Console.print", + return_value=None, + ) + + patcher_list = [ + patch.object( + target=controller, + attribute="parse_simple_args", + side_effect=__mock_parse_simple_args, + return_value=(None, None), + ), + patch.object( + target=controller, + attribute="parse_known_args_and_warn", + side_effect=bound_mock_parse_known_args_and_warn, + return_value=None, + ), + ] + + if not session.settings.DEBUG_MODE: + rich.start() + patched_function_list = [] + for patcher in patcher_list: + patched_function_list.append(patcher.start()) + + yield patched_function_list + + if not session.settings.DEBUG_MODE: + rich.stop() + for patcher in patcher_list: + patcher.stop() + + +def _get_argument_parser( + controller, + command: str, +) -> ArgumentParser: + """Intercept the ArgumentParser instance from the command function. + + A command function being a function starting with `call_`, like: + - call_help + - call_overview + - call_load + + Parameters + ---------- + controller: BaseController + Instance of the CLI Controller. + command: str + A name from `controller.CHOICES_COMMANDS`. + + Returns + ------- + ArgumentParser: ArgumentParser instance from the command function. + """ + command_func: Callable = __get_command_func(controller=controller, command=command) + + if not contains_functions_to_patch(command_func=command_func): + raise AssertionError( + f"One of these functions should be inside `call_{command}`:\n" + " - parse_simple_args\n" + " - parse_known_args_and_warn\n" + ) + + with __patch_controller_functions(controller=controller) as patched_function_list: + command_func([]) + + call_count = 0 + for patched_function in patched_function_list: + call_count += patched_function.call_count + if patched_function.call_count == 1: + args, kwargs = patched_function.call_args + argument_parser = ( + kwargs["parser"] if kwargs.get("parser", None) else args[0] + ) + + if call_count != 1: + raise AssertionError( + f"One of these functions should be called once inside `call_{command}`:\n" + " - parse_simple_args\n" + " - parse_known_args_and_warn\n" + ) + + # pylint: disable=possibly-used-before-assignment + return argument_parser + + +def _build_command_choice_map(argument_parser: ArgumentParser) -> dict: + """Build the choice map for a command.""" + choice_map: dict = {} + for action in argument_parser._actions: # pylint: disable=protected-access + if action.help == SUPPRESS: + continue + if len(action.option_strings) == 1: + long_name = action.option_strings[0] + short_name = "" + elif len(action.option_strings) == 2: + short_name = action.option_strings[0] + long_name = action.option_strings[1] + else: + raise AttributeError(f"Invalid argument_parser: {argument_parser}") + + if hasattr(action, "choices") and action.choices: + choice_map[long_name] = {str(c): {} for c in action.choices} + else: + choice_map[long_name] = {} + + if short_name and long_name: + choice_map[short_name] = long_name + + return choice_map + + +def build_controller_choice_map(controller) -> dict: + """Build the choice map for a controller.""" + command_list = controller.CHOICES_COMMANDS + controller_choice_map: dict = {c: {} for c in controller.controller_choices} + + for command in command_list: + try: + argument_parser = _get_argument_parser( + controller=controller, + command=command, + ) + controller_choice_map[command] = _build_command_choice_map( + argument_parser=argument_parser + ) + except Exception as exception: + if session.settings.DEBUG_MODE: + raise Exception( + f"On command : `{command}`.\n{str(exception)}" + ) from exception + + return controller_choice_map diff --git a/cli/openbb_cli/controllers/cli_controller.py b/cli/openbb_cli/controllers/cli_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..9bacd47950574083538392f762247ae55a0df2e7 --- /dev/null +++ b/cli/openbb_cli/controllers/cli_controller.py @@ -0,0 +1,944 @@ +#!/usr/bin/env python +"""Main CLI Module.""" + +import argparse +import contextlib +import difflib +import os +import re +import sys +import time +import webbrowser +from datetime import datetime +from functools import partial, update_wrapper +from pathlib import Path +from types import MethodType +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, + ENV_FILE_SETTINGS, + HOME_DIRECTORY, + REPOSITORY_DIRECTORY, +) +from openbb_cli.config.menu_text import MenuText +from openbb_cli.controllers.base_controller import BaseController +from openbb_cli.controllers.platform_controller_factory import ( + PlatformControllerFactory, +) +from openbb_cli.controllers.script_parser import is_reset, parse_openbb_script +from openbb_cli.controllers.utils import ( + bootup, + first_time_user, + get_flair_and_username, + parse_and_split_input, + print_goodbye, + print_rich_table, + reset, + suppress_stdout, + welcome_message, +) +from openbb_cli.session import Session +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.styles import Style +from pydantic import BaseModel + +PLATFORM_ROUTERS = { + d: "menu" if not isinstance(getattr(obb, d), BaseModel) else "command" + for d in dir(obb) + if "_" not in d +} +NON_DATA_ROUTERS = ["coverage", "account", "reference", "system", "user"] +DATA_PROCESSING_ROUTERS = ["technical", "quantitative", "econometrics"] + +# pylint: disable=too-many-public-methods,import-outside-toplevel, too-many-function-args +# pylint: disable=too-many-branches,no-member,C0302,too-many-return-statements, inconsistent-return-statements + +env_file = str(ENV_FILE_SETTINGS) +session = Session() + + +class CLIController(BaseController): + """CLI Controller class.""" + + CHOICES_COMMANDS = ["record", "stop", "exe", "results"] + CHOICES_MENUS = [ + "settings", + ] + + for router, value in PLATFORM_ROUTERS.items(): + if value == "menu": + CHOICES_MENUS.append(router) + else: + CHOICES_COMMANDS.append(router) + + PATH = "/" + CHOICES_GENERATION = False + + def __init__(self, jobs_cmds: Optional[List[str]] = None): + """Construct CLI controller.""" + self.ROUTINE_FILES: Dict[str, str] = dict() + self.ROUTINE_DEFAULT_FILES: Dict[str, str] = dict() + self.ROUTINE_PERSONAL_FILES: Dict[str, str] = dict() + self.ROUTINE_CHOICES: Dict[str, Any] = dict() + + super().__init__(jobs_cmds) + + self.queue: List[str] = list() + + if jobs_cmds: + self.queue = parse_and_split_input( + an_input=" ".join(jobs_cmds), custom_filters=[] + ) + + self.update_success = False + + self._generate_platform_commands() + + self.update_runtime_choices() + + def _generate_platform_commands(self): + """Generate Platform based commands/menus.""" + + def method_call_class(self, _, controller, name, parent_path, target): + self.queue = self.load_class( + controller, name, parent_path, target, self.queue + ) + + # pylint: disable=unused-argument + def method_call_command(self, _, router: str): + """Call command.""" + mdl = getattr(obb, router) + df = pd.DataFrame.from_dict(mdl.model_dump(), orient="index") + if isinstance(df.columns, pd.RangeIndex): + df.columns = [str(i) for i in df.columns] + return print_rich_table(df, show_index=True) + + for router, value in PLATFORM_ROUTERS.items(): + target = getattr(obb, router) + + if value == "menu": + pcf = PlatformControllerFactory( + target, reference=obb.reference["paths"] # type: ignore + ) + DynamicController = pcf.create() + + # Bind the method to the class + bound_method = MethodType(method_call_class, self) + + # Update the wrapper and set the attribute + bound_method = update_wrapper( # type: ignore + partial( + bound_method, + controller=DynamicController, + name=router, + target=target, + parent_path=self.path, + ), + method_call_class, + ) + else: + bound_method = MethodType(method_call_command, self) + bound_method = update_wrapper( # type: ignore + partial(bound_method, router=router), + method_call_command, + ) + + setattr(self, f"call_{router}", bound_method) + + def update_runtime_choices(self): + """Update runtime choices.""" + routines_directory = Path(session.user.preferences.export_directory, "routines") + + if session.prompt_session and session.settings.USE_PROMPT_TOOLKIT: + # choices: dict = self.choices_default + choices: dict = {c: {} for c in self.controller_choices} # type: ignore + + self.ROUTINE_FILES = { + filepath.name: filepath # type: ignore + for filepath in routines_directory.rglob("*.openbb") + } + self.ROUTINE_DEFAULT_FILES = { + filepath.name: filepath # type: ignore + for filepath in Path(routines_directory / "hub" / "default").rglob( + "*.openbb" + ) + } + self.ROUTINE_PERSONAL_FILES = { + filepath.name: filepath # type: ignore + for filepath in Path(routines_directory / "hub" / "personal").rglob( + "*.openbb" + ) + } + + choices["exe"] = { + "--file": { + filename: {} for filename in list(self.ROUTINE_FILES.keys()) + }, + "-f": "--file", + "--example": None, + "-e": "--example", + "--input": None, + "-i": "--input", + "--url": None, + "--help": None, + "-h": "--help", + } + choices["record"] = { + "--name": None, + "-n": "--name", + "--description": None, + "-d": "--description", + "--public": None, + "-p": "--public", + "--tag1": {c: None for c in constants.SCRIPT_TAGS}, + "--tag2": {c: None for c in constants.SCRIPT_TAGS}, + "--tag3": {c: None for c in constants.SCRIPT_TAGS}, + "--help": None, + "-h": "--help", + } + choices["stop"] = {"--help": None, "-h": "--help"} + choices["results"] = { + "--help": None, + "-h": "--help", + "--export": {c: None for c in ["csv", "json", "xlsx", "png", "jpg"]}, + "--index": None, + "--key": None, + "--chart": None, + "--sheet_name": None, + } + + self.update_completer(choices) + + def print_help(self): + """Print help.""" + mt = MenuText("") + + mt.add_info("Configure the platform and manage your account") + for router, value in PLATFORM_ROUTERS.items(): + if router not in NON_DATA_ROUTERS or router in ["reference", "coverage"]: + continue + if value == "menu": + menu_description = ( + obb.reference["routers"] # type: ignore + .get(f"{self.PATH}{router}", {}) + .get("description") + ) or "" + mt.add_menu( + name=router, + description=menu_description.split(".")[0].lower(), + ) + else: + mt.add_cmd(router) + + mt.add_info("\nConfigure your CLI") + mt.add_menu( + "settings", + description="enable and disable feature flags, preferences and settings", + ) + mt.add_raw("\n") + mt.add_info("Record and execute your own .openbb routine scripts") + mt.add_cmd("record", description="start recording current session") + mt.add_cmd( + "stop", description="stop session recording and convert to .openbb routine" + ) + mt.add_cmd( + "exe", + description="execute .openbb routine scripts (use exe --example for an example)", + ) + mt.add_raw("\n") + mt.add_info("Retrieve data from different asset classes and providers") + + for router, value in PLATFORM_ROUTERS.items(): + if router in NON_DATA_ROUTERS or router in DATA_PROCESSING_ROUTERS: + continue + if value == "menu": + menu_description = ( + obb.reference["routers"] # type: ignore + .get(f"{self.PATH}{router}", {}) + .get("description") + ) or "" + mt.add_menu( + name=router, + description=menu_description.split(".")[0].lower(), + ) + else: + mt.add_cmd(router) + + if any(router in PLATFORM_ROUTERS for router in DATA_PROCESSING_ROUTERS): + mt.add_info("\nAnalyze and process previously obtained data") + + for router, value in PLATFORM_ROUTERS.items(): + if router not in DATA_PROCESSING_ROUTERS: + continue + if value == "menu": + menu_description = ( + obb.reference["routers"] # type: ignore + .get(f"{self.PATH}{router}", {}) + .get("description") + ) or "" + mt.add_menu( + name=router, + description=menu_description.split(".")[0].lower(), + ) + else: + mt.add_cmd(router) + + mt.add_raw("\n") + mt.add_cmd("results") + if session.obbject_registry.obbjects: + mt.add_info("\nCached Results") + for key, value in list(session.obbject_registry.all.items())[ # type: ignore + : session.settings.N_TO_DISPLAY_OBBJECT_REGISTRY + ]: + mt.add_raw( + f"[yellow]OBB{key}[/yellow]: {value['command']}", # type: ignore[index] + left_spacing=True, + ) + + session.console.print(text=mt.menu_text, menu="Home") + self.update_runtime_choices() + + def parse_input(self, an_input: str) -> List: + """Overwrite the BaseController parse_input for `askobb` and 'exe'. + + This will allow us to search for something like "P/E" ratio. + """ + # Filtering out sorting parameters with forward slashes like P/E + sort_filter = r"((\ -q |\ --question|\ ).*?(/))" + # Filter out urls + url = r"(exe (--url )?(https?://)?my\.openbb\.(dev|co)/u/.*/routine/.*)" + custom_filters = [sort_filter, url] + return parse_and_split_input(an_input=an_input, custom_filters=custom_filters) + + def call_settings(self, _): + """Process settings command.""" + from openbb_cli.controllers.settings_controller import ( + SettingsController, + ) + + self.queue = self.load_class(SettingsController, self.queue) + + def call_exe(self, other_args: List[str]): + """Process exe command.""" + # Merge rest of string path to other_args and remove queue since it is a dir + other_args += self.queue + + if not other_args: + session.console.print( + "[info]Provide a path to the routine you wish to execute. For an example, please use " + "`exe --example`.\n[/info]" + ) + return + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="exe", + description="Execute automated routine script. For an example, please use " + "`exe --example`.", + ) + parser.add_argument( + "--file", + "-f", + help="The path or .openbb file to run.", + dest="file", + required="-h" not in other_args + and "--help" not in other_args + and "-e" not in other_args + and "--example" not in other_args + and "--url" not in other_args + and "my.openbb" not in other_args[0], + type=str, + nargs="+", + ) + parser.add_argument( + "-i", + "--input", + help="Select multiple inputs to be replaced in the routine and separated by commas. E.g. GME,AMC,BTC-USD", + dest="routine_args", + type=str, + ) + parser.add_argument( + "-e", + "--example", + help="Run an example script to understand how routines can be used.", + dest="example", + action="store_true", + default=False, + ) + parser.add_argument( + "--url", help="URL to run openbb script from.", dest="url", type=str + ) + if other_args and "-" not in other_args[0][0]: + if other_args[0].startswith("my.") or other_args[0].startswith("http"): + other_args.insert(0, "--url") + else: + other_args.insert(0, "--file") + ns_parser = self.parse_known_args_and_warn(parser, other_args) + if ns_parser: + if ns_parser.example: + routine_path = ASSETS_DIRECTORY / "routines" / "routine_example.openbb" + session.console.print( # TODO: Point to docs when ready + "[info]Executing an example, please visit our docs " + "to learn how to create your own script.[/info]\n" + ) + time.sleep(3) + elif ns_parser.url: + if not ns_parser.url.startswith( + "https" + ) and not ns_parser.url.startswith("http:"): + url = "https://" + ns_parser.url + elif ns_parser.url.startswith("http://"): + url = ns_parser.url.replace("http://", "https://") + else: + url = ns_parser.url + username = url.split("/")[-3] + script_name = url.split("/")[-1] + file_name = f"{username}_{script_name}.openbb" + final_url = f"{url}?raw=true" + response = requests.get(final_url, timeout=10) + if response.status_code != 200: + session.console.print( + "[red]Could not find the requested script.[/red]" + ) + return + routine_text = response.json()["script"] + file_path = Path(session.user.preferences.export_directory, "routines") + routine_path = file_path / file_name + with open(routine_path, "w") as file: + file.write(routine_text) + self.update_runtime_choices() + + elif ns_parser.file: + file_path = " ".join(ns_parser.file) # type: ignore + # if string is not in this format "default/file.openbb" then check for files in ROUTINE_FILES + full_path = file_path + hub_routine = file_path.split("/") # type: ignore + # Change with: my.openbb.co + if hub_routine[0] == "default": + routine_path = Path( + self.ROUTINE_DEFAULT_FILES.get(hub_routine[1], full_path) + ) + elif hub_routine[0] == "personal": + routine_path = Path( + self.ROUTINE_PERSONAL_FILES.get(hub_routine[1], full_path) + ) + else: + routine_path = Path(self.ROUTINE_FILES.get(file_path, full_path)) # type: ignore + else: + return + + try: + with open(routine_path) as fp: + raw_lines = list(fp) + + script_inputs = [] + # Capture ARGV either as list if args separated by commas or as single value + if routine_args := ns_parser.routine_args: + pattern = r"\[(.*?)\]" + matches = re.findall(pattern, routine_args) + + for match in matches: + routine_args = routine_args.replace(f"[{match}]", "") + script_inputs.append(match) + + script_inputs.extend( + [val for val in routine_args.split(",") if val] + ) + + err, parsed_script = parse_openbb_script( + raw_lines=raw_lines, script_inputs=script_inputs + ) + + # If there err output is not an empty string then it means there was an + # issue in parsing the routine and therefore we don't want to feed it + # to the terminal + if err: + session.console.print(err) + return + + self.queue = [ + val + for val in parse_and_split_input( + an_input=parsed_script, custom_filters=[] + ) + if val + ] + + if "export" in self.queue[0]: + export_path = self.queue[0].split(" ")[1] + # If the path selected does not start from the user root, give relative location from root + if export_path[0] == "~": + export_path = export_path.replace( + "~", HOME_DIRECTORY.as_posix() + ) + elif export_path[0] != "/": + export_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), export_path + ) + + # Check if the directory exists + if os.path.isdir(export_path): + session.console.print( + f"Export data to be saved in the selected folder: '{export_path}'" + ) + else: + os.makedirs(export_path) + session.console.print( + f"[green]Folder '{export_path}' successfully created.[/green]" + ) + self.queue = self.queue[1:] + + except FileNotFoundError: + session.console.print( + f"[red]File '{routine_path}' doesn't exist.[/red]" + ) + return + + +def handle_job_cmds(jobs_cmds: Optional[List[str]]) -> Optional[List[str]]: + """Handle job commands.""" + export_path = "" + if jobs_cmds and "export" in jobs_cmds[0]: + commands = jobs_cmds[0].split("/") + first_split = commands[0].split(" ") + if len(first_split) > 1: + export_path = first_split[1] + jobs_cmds = ["/".join(commands[1:])] + if not export_path: + return jobs_cmds + if export_path[0] == "~": + export_path = export_path.replace("~", HOME_DIRECTORY.as_posix()) + elif export_path[0] != "/": + export_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), export_path + ) + + # Check if the directory exists + if os.path.isdir(export_path): + session.console.print( + f"Export data to be saved in the selected folder: '{export_path}'" + ) + else: + os.makedirs(export_path) + session.console.print( + f"[green]Folder '{export_path}' successfully created.[/green]" + ) + return jobs_cmds + + +# pylint: disable=unused-argument +def run_cli(jobs_cmds: Optional[List[str]] = None, test_mode=False): + """Run the CLI menu.""" + ret_code = 1 + t_controller = CLIController(jobs_cmds) + an_input = "" + + jobs_cmds = handle_job_cmds(jobs_cmds) + + bootup() + if not jobs_cmds: + welcome_message() + + if first_time_user(): + with contextlib.suppress(EOFError): + webbrowser.open("https://docs.openbb.co/cli") + + t_controller.print_help() + + while ret_code: + # There is a command in the queue + if t_controller.queue and len(t_controller.queue) > 0: + # If the command is quitting the menu we want to return in here + if t_controller.queue[0] in ("q", "..", "quit"): + print_goodbye() + break + + # Consume 1 element from the queue + an_input = t_controller.queue[0] + t_controller.queue = t_controller.queue[1:] + + # Print the current location because this was an instruction and we want user to know what was the action + if an_input and an_input.split(" ")[0] in t_controller.CHOICES_COMMANDS: + session.console.print(f"{get_flair_and_username()} / $ {an_input}") + + # Get input command from user + else: + try: + # Get input from user using auto-completion + if session.prompt_session and session.settings.USE_PROMPT_TOOLKIT: + # Check if toolbar hint was enabled + if session.settings.TOOLBAR_HINT: + an_input = session.prompt_session.prompt( # type: ignore[union-attr] + f"{get_flair_and_username()} / $ ", + completer=t_controller.completer, + search_ignore_case=True, + bottom_toolbar=HTML( + ' help menu ' + ' return to previous menu ' + ' exit the program ' + ' ' + "see usage and available options " + ), + style=Style.from_dict( + { + "bottom-toolbar": "#ffffff bg:#333333", + } + ), + ) + else: + an_input = session.prompt_session.prompt( # type: ignore[union-attr] + f"{get_flair_and_username()} / $ ", + completer=t_controller.completer, + search_ignore_case=True, + ) + + # Get input from user without auto-completion + else: + an_input = input(f"{get_flair_and_username()} / $ ") + + except (KeyboardInterrupt, EOFError): + print_goodbye() + break + + try: + # Process the input command + t_controller.queue = t_controller.switch(an_input) + + if an_input in ("q", "quit", "..", "exit", "e"): + print_goodbye() + break + + # Check if the user wants to reset application + if an_input in ("r", "reset") or t_controller.update_success: + reset(t_controller.queue if t_controller.queue else []) + break + + except SystemExit: + session.console.print( + f"[red]The command '{an_input}' doesn't exist on the / menu.[/red]\n", + ) + similar_cmd = difflib.get_close_matches( + an_input.split(" ")[0] if " " in an_input else an_input, + t_controller.controller_choices, + n=1, + cutoff=0.7, + ) + if similar_cmd: + an_input = similar_cmd[0] + if " " in an_input: + candidate_input = ( + f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}" + ) + if candidate_input == an_input: + an_input = "" + t_controller.queue = [] + session.console.print("\n") + continue + an_input = candidate_input + + session.console.print(f"[green]Replacing by '{an_input}'.[/green]") + t_controller.queue.insert(0, an_input) + + +def insert_start_slash(cmds: List[str]) -> List[str]: + """Insert a slash at the beginning of a command sequence.""" + if not cmds[0].startswith("/"): + cmds[0] = f"/{cmds[0]}" + if cmds[0].startswith("/home"): + cmds[0] = f"/{cmds[0][5:]}" + return cmds + + +def run_scripts( + path: Path, + test_mode: bool = False, + verbose: bool = False, + routines_args: Optional[List[str]] = None, + special_arguments: Optional[Dict[str, str]] = None, + output: bool = True, +): + """Run given .openbb scripts. + + Parameters + ---------- + path : str + The location of the .openbb file + test_mode : bool + Whether the CLI is in test mode + verbose : bool + Whether to run tests in verbose mode + routines_args : List[str] + One or multiple inputs to be replaced in the routine and separated by commas. + E.g. GME,AMC,BTC-USD + special_arguments: Optional[Dict[str, str]] + Replace `${key=default}` with `value` for every key in the dictionary + output: bool + Whether to log tests to txt files + """ + if not path.exists(): + session.console.print(f"File '{path}' doesn't exist. Launching base CLI.\n") + if not test_mode: + run_cli() + + # THIS NEEDS TO BE REFACTORED!!! - ITS USED FOR TESTING + with path.open() as fp: + raw_lines = [x for x in fp if (not is_reset(x)) and ("#" not in x) and x] + raw_lines = [ + raw_line.strip("\n") for raw_line in raw_lines if raw_line.strip("\n") + ] + + if routines_args: + lines = [] + for rawline in raw_lines: + templine = rawline + for i, arg in enumerate(routines_args): + templine = templine.replace(f"$ARGV[{i}]", arg) + lines.append(templine) + # Handle new testing arguments: + elif special_arguments: + lines = [] + for line in raw_lines: + new_line = re.sub( + r"\${[^{]+=[^{]+}", + lambda x: replace_dynamic(x, special_arguments), # type: ignore + line, + ) + lines.append(new_line) + + else: + lines = raw_lines + + if test_mode and "exit" not in lines[-1]: + lines.append("exit") + + # Deals with the export with a path with "/" in it + export_folder = "" + if "export" in lines[0]: + export_folder = lines[0].split("export ")[1].rstrip() + lines = lines[1:] + + simulate_argv = f"/{'/'.join([line.rstrip() for line in lines])}" + file_cmds = simulate_argv.replace("//", "/home/").split() + file_cmds = insert_start_slash(file_cmds) if file_cmds else file_cmds + file_cmds = ( + [f"export {export_folder}{' '.join(file_cmds)}"] + if export_folder + else [" ".join(file_cmds)] + ) + + if not test_mode or verbose: + run_cli(file_cmds, test_mode=True) + else: + with suppress_stdout(): + session.console.print(f"To ensure: {output}") + if output: + timestamp = datetime.now().timestamp() + stamp_str = str(timestamp).replace(".", "") + whole_path = Path(REPOSITORY_DIRECTORY / "integration_test_output") + whole_path.mkdir(parents=True, exist_ok=True) + first_cmd = file_cmds[0].split("/")[1] + with open( + whole_path / f"{stamp_str}_{first_cmd}_output.txt", "w" + ) as output_file, contextlib.redirect_stdout(output_file): + run_cli(file_cmds, test_mode=True) + else: + run_cli(file_cmds, test_mode=True) + + +def replace_dynamic(match: re.Match, special_arguments: Dict[str, str]) -> str: + """Replace ${key=default} with value in special_arguments if it exists, else with default. + + Parameters + ---------- + match: re.Match[str] + The match object + special_arguments: Dict[str, str] + The key value pairs to replace in the scripts + + Returns + ---------- + str + The new string + """ + cleaned = match[0].replace("{", "").replace("}", "").replace("$", "") + key, default = cleaned.split("=") + dict_value = special_arguments.get(key, default) + if dict_value: + return dict_value + return default + + +def run_routine(file: str, routines_args=Optional[str]): + """Execute command routine from .openbb file.""" + user_routine_path = Path(session.user.preferences.export_directory, "routines") + default_routine_path = ASSETS_DIRECTORY / "routines" / file + + if user_routine_path.exists(): + run_scripts(path=user_routine_path, routines_args=routines_args) + elif default_routine_path.exists(): + run_scripts(path=default_routine_path, routines_args=routines_args) + else: + session.console.print( + f"Routine not found, please put your `.openbb` file into : {user_routine_path}." + ) + + +# pylint: disable=unused-argument +def main( + debug: bool, + dev: bool, + path_list: List[str], + routines_args: Optional[List[str]] = None, + **kwargs, +): + """Run the CLI with various options. + + Parameters + ---------- + debug : bool + Whether to run the CLI in debug mode + dev: + Points backend towards development environment instead of production + test : bool + Whether to run the CLI in integrated test mode + filtert : str + Filter test files with given string in name + paths : List[str] + The paths to run for scripts or to test + verbose : bool + Whether to show output from tests + routines_args : List[str] + One or multiple inputs to be replaced in the routine and separated by commas. + E.g. GME,AMC,BTC-USD + """ + if debug: + session.settings.DEBUG_MODE = True + + if dev: + session.settings.DEV_BACKEND = True + session.settings.BASE_URL = "https://payments.openbb.dev/" + session.settings.HUB_URL = "https://my.openbb.dev" + + if isinstance(path_list, list) and path_list[0].endswith(".openbb"): + run_routine(file=path_list[0], routines_args=routines_args) + elif path_list: + argv_cmds = list([" ".join(path_list).replace(" /", "/home/")]) + argv_cmds = insert_start_slash(argv_cmds) if argv_cmds else argv_cmds + run_cli(argv_cmds) + else: + run_cli() + + +def parse_args_and_run(): + """Parse input arguments and run CLI.""" + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="cli", + description="The OpenBB Platform CLI.", + ) + parser.add_argument( + "-d", + "--debug", + dest="debug", + action="store_true", + default=False, + help="Runs the CLI in debug mode.", + ) + parser.add_argument( + "--dev", + dest="dev", + action="store_true", + default=False, + help="Points backend towards development environment instead of production", + ) + parser.add_argument( + "--file", + help="The path or .openbb file to run.", + dest="path", + nargs="+", + default="", + type=str, + ) + parser.add_argument( + "-i", + "--input", + help=( + "Select multiple inputs to be replaced in the routine and separated by commas." + "E.g. GME,AMC,BTC-USD" + ), + dest="routine_args", + type=lambda s: [str(item) for item in s.split(",")], + default=None, + ) + parser.add_argument( + "-t", + "--test", + action="store_true", + help=( + "Run the CLI in testing mode. Also run this option and '-h'" + " to see testing argument options." + ), + ) + # The args -m, -f and --HistoryManager.hist_file are used only in reports menu + # by papermill and that's why they have suppress help. + parser.add_argument( + "-m", + help=argparse.SUPPRESS, + dest="module", + default="", + type=str, + ) + parser.add_argument( + "-f", + help=argparse.SUPPRESS, + dest="module_file", + default="", + type=str, + ) + parser.add_argument( + "--HistoryManager.hist_file", + help=argparse.SUPPRESS, + dest="module_hist_file", + default="", + type=str, + ) + if sys.argv[1:] and "-" not in sys.argv[1][0]: + sys.argv.insert(1, "--file") + ns_parser, unknown = parser.parse_known_args() + + # This ensures that if cli.py receives unknown args it will not start. + # Use -d flag if you want to see the unknown args. + if unknown: + if ns_parser.debug: + session.console.print(unknown) + else: + sys.exit(-1) + + main( + ns_parser.debug, + ns_parser.dev, + ns_parser.path, + ns_parser.routine_args, + module=ns_parser.module, + module_file=ns_parser.module_file, + module_hist_file=ns_parser.module_hist_file, + ) + + +def launch( + debug: bool = False, dev: bool = False, queue: Optional[List[str]] = None +) -> None: + """Launch CLI.""" + if queue: + main(debug, dev, queue, module="") + else: + parse_args_and_run() + + +if __name__ == "__main__": + parse_args_and_run() diff --git a/cli/openbb_cli/controllers/hub_service.py b/cli/openbb_cli/controllers/hub_service.py new file mode 100644 index 0000000000000000000000000000000000000000..8a63f701604c24e2a6e6095c5fc8a03814219fbc --- /dev/null +++ b/cli/openbb_cli/controllers/hub_service.py @@ -0,0 +1,107 @@ +"""Routines handler module.""" + +from typing import Optional + +import requests +from openbb_cli.config.constants import ( + CONNECTION_ERROR_MSG, + CONNECTION_TIMEOUT_MSG, + TIMEOUT, +) +from openbb_cli.session import Session + +# created dictionaries for personal and default routines with the structure +# {"file_name" :["script","personal/default"]} +# and stored dictionaries in list +# created new directory structure to account for personal and default routines + + +session = Session() + + +# pylint: disable=too-many-arguments +def upload_routine( + auth_header: str, + name: str = "", + description: str = "", + routine: str = "", + override: bool = False, + tags: str = "", + public: bool = False, + timeout: int = TIMEOUT, +) -> Optional[requests.Response]: + """Send a routine to the server. + + Parameters + ---------- + auth_header : str + The authorization header, e.g. "Bearer ". + name : str + The name of the routine. + routine : str + The routine. + override : bool + Whether to override the routine if it already exists. + tags : str + The tags of the routine. + public : bool + Whether to make the routine public or not. + timeout : int + The timeout, by default TIMEOUT + + Returns + ------- + Optional[requests.Response] + The response from the post request. + """ + data = { + "name": name, + "description": description, + "script": routine, + "override": override, + "tags": tags, + "version": session.settings.VERSION, + "public": public, + } + _console = session.console + try: + response = requests.post( + headers={"Authorization": auth_header}, + url=session.settings.BASE_URL + "/terminal/script", + json=data, + timeout=timeout, + ) + if response.status_code == 200: + username = getattr(session.user.profile.hub_session, "username", None) + if not username: + _console.print("[red]No username found.[/red]") + _console.print("[red]Failed to upload your routine.[/red]") + return None + _console.print("[green]Successfully uploaded your routine.[/]") + + hub_url = session.settings.HUB_URL + + if public: + _console.print( + f"\n[yellow]Share or edit it at {hub_url}/u/{username}/routine/{name.replace(' ', '-')}[/]" + ) + else: + _console.print(f"\n[yellow]Go to {hub_url} to edit this script,[/]") + _console.print( + f"[yellow]or even make it public so you can access it at " + f"{hub_url}/u/{username}/routine/{name.replace(' ', '-')}[/]" + ) + elif response.status_code != 409: # 409: routine already exists + _console.print( + "[red]" + response.json().get("detail", "Unknown error.") + "[/red]" + ) + return response + except requests.exceptions.ConnectionError: + _console.print(f"\n{CONNECTION_ERROR_MSG}") + return None + except requests.exceptions.Timeout: + _console.print(f"\n{CONNECTION_TIMEOUT_MSG}") + return None + except Exception: + _console.print("[red]Failed to upload your routine.[/red]") + return None diff --git a/cli/openbb_cli/controllers/platform_controller_factory.py b/cli/openbb_cli/controllers/platform_controller_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..76d43688c01aa8d951fdff985f37c308f9193a61 --- /dev/null +++ b/cli/openbb_cli/controllers/platform_controller_factory.py @@ -0,0 +1,58 @@ +"""Platform controller factory to create a platform controller.""" + +from typing import Dict, List, Union + +from openbb_cli.argparse_translator.argparse_class_processor import ( + ArgparseClassProcessor, +) +from openbb_cli.controllers.base_platform_controller import PlatformController + + +class PlatformControllerFactory: + """Factory to create a platform controller.""" + + def __init__(self, platform_router: type, **kwargs): + """Create the controller name.""" + self.platform_router = platform_router + self._translated_target = ArgparseClassProcessor( + target_class=self.platform_router, reference=kwargs.get("reference", {}) + ) + self.router_name = ( + str(type(self.platform_router)) + .rsplit(".", maxsplit=1)[-1] + .replace("'>", "") + .replace("ROUTER_", "") + .lower() + ) + self.controller_name = f"{self.router_name.capitalize()}Controller" + + def create(self) -> type: + """Create the platform controller.""" + ClassName = self.controller_name + Parents = (PlatformController,) + Attributes: Dict[str, Union[bool, List[str]]] = {"CHOICES_GENERATION": True} + + # Menu and Command choices generation + choices_menus: List[str] = [] + choices_commands: List[str] = [] + translators = self._translated_target.translators + paths = self._translated_target.paths + # menus + for key, value in paths.items(): + if value == "path": + continue + choices_menus.append(key) + # commands + for name, _ in translators.items(): + if any(f"{self.router_name}_{path}" in name for path in paths): + continue + new_name = name.replace(f"{self.router_name}_", "") + choices_commands.append(new_name) + + Attributes["CHOICES_MENUS"] = choices_menus + Attributes["CHOICES_COMMANDS"] = choices_commands + + # Use type to create the class + DynamicClass = type(ClassName, Parents, Attributes) + + return DynamicClass diff --git a/cli/openbb_cli/controllers/script_parser.py b/cli/openbb_cli/controllers/script_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..69134fc6d8f3924c4d6d66fb49ced05dfb8fb86f --- /dev/null +++ b/cli/openbb_cli/controllers/script_parser.py @@ -0,0 +1,488 @@ +"""Routine functions for OpenBB Platform CLI.""" + +import re +from datetime import datetime, timedelta +from typing import Dict, List, Match, Optional, Tuple, Union + +from dateutil.relativedelta import relativedelta +from openbb_cli.session import Session + +session = Session() + +# pylint: disable=too-many-statements,eval-used,consider-iterating-dictionary +# pylint: disable=too-many-branches,too-many-return-statements + +# Necessary for OpenBB keywords +MONTHS_VALUE = { + "JANUARY": 1, + "FEBRUARY": 2, + "MARCH": 3, + "APRIL": 4, + "MAY": 5, + "JUNE": 6, + "JULY": 7, + "AUGUST": 8, + "SEPTEMBER": 9, + "OCTOBER": 10, + "NOVEMBER": 11, + "DECEMBER": 12, +} + +WEEKDAY_VALUE = { + "MONDAY": 0, + "TUESDAY": 1, + "WEDNESDAY": 2, + "THURSDAY": 3, + "FRIDAY": 4, + "SATURDAY": 5, + "SUNDAY": 6, +} + + +def is_reset(command: str) -> bool: + """Test whether a command is a reset command. + + Parameters + ---------- + command : str + The command to test + + Returns + ------- + answer : bool + Whether the command is a reset command + """ + if "reset" in command: + return True + if command == "r": + return True + if command == "r\n": + return True + return False + + +def match_and_return_openbb_keyword_date(keyword: str) -> str: # noqa: PLR0911 + """Return OpenBB keyword into date. + + Parameters + ---------- + keyword : str + String with potential OpenBB keyword (e.g. 1MONTHAGO,LASTFRIDAY,3YEARSFROMNOW,NEXTTUESDAY) + + Returns + ---------- + str: Date with format YYYY-MM-DD + """ + now = datetime.now() + for i, regex in enumerate([r"^\$(\d+)([A-Z]+)AGO$", r"^\$(\d+)([A-Z]+)FROMNOW$"]): + match = re.match(regex, keyword) + if match: + integer_value = int(match.group(1)) + time_unit = match.group(2) + clean_time = time_unit.upper() + if "DAYS" in clean_time or "MONTHS" in clean_time or "YEARS" in clean_time: + kwargs = {time_unit.lower(): integer_value} + if i == 0: + return (now - relativedelta(**kwargs)).strftime("%Y-%m-%d") # type: ignore + return (now + relativedelta(**kwargs)).strftime("%Y-%m-%d") # type: ignore + + match = re.search(r"\$LAST(\w+)", keyword) + if match: + time_unit = match.group(1) + # Check if it corresponds to a month + if time_unit in list(MONTHS_VALUE.keys()): + the_year = now.year + # Calculate the year and month for last month date + if now.month <= MONTHS_VALUE[time_unit]: + # If the current month is greater than the last date month, it means it is this year + the_year = now.year - 1 + return datetime(the_year, MONTHS_VALUE[time_unit], 1).strftime("%Y-%m-%d") + + # Check if it corresponds to a week day + if time_unit in list(WEEKDAY_VALUE.keys()): + if datetime.weekday(now) > WEEKDAY_VALUE[time_unit]: + return ( + now + - timedelta(datetime.weekday(now)) + + timedelta(WEEKDAY_VALUE[time_unit]) + ).strftime("%Y-%m-%d") + return ( + now + - timedelta(7) + - timedelta(datetime.weekday(now)) + + timedelta(WEEKDAY_VALUE[time_unit]) + ).strftime("%Y-%m-%d") + + match = re.search(r"\$NEXT(\w+)", keyword) + if match: + time_unit = match.group(1) + # Check if it corresponds to a month + if time_unit in list(MONTHS_VALUE.keys()): + # Calculate the year and month for next month date + if now.month < MONTHS_VALUE[time_unit]: + # If the current month is greater than the last date month, it means it is this year + return datetime(now.year, MONTHS_VALUE[time_unit], 1).strftime( + "%Y-%m-%d" + ) + + return datetime(now.year + 1, MONTHS_VALUE[time_unit], 1).strftime( + "%Y-%m-%d" + ) + + # Check if it corresponds to a week day + if time_unit in list(WEEKDAY_VALUE.keys()): + if datetime.weekday(now) < WEEKDAY_VALUE[time_unit]: + return ( + now + - timedelta(datetime.weekday(now)) + + timedelta(WEEKDAY_VALUE[time_unit]) + ).strftime("%Y-%m-%d") + return ( + now + + timedelta(7) + - timedelta(datetime.weekday(now)) + + timedelta(WEEKDAY_VALUE[time_unit]) + ).strftime("%Y-%m-%d") + + return "" + + +def parse_openbb_script( # noqa: PLR0911,PLR0912 + raw_lines: List[str], + script_inputs: Optional[List[str]] = None, +) -> Tuple[str, str]: + """Parse .openbb script. + + Parameters + ---------- + raw_lines : List[str] + Lines from .openbb script + script_inputs: str, optional + Inputs to the script that come externally + + Returns + ------- + str + Error that occurred - if empty means no error + str + Processed string from .openbb script that can be run by the OpenBB Platform CLI + """ + ROUTINE_VARS: Dict[str, Union[str, List[str]]] = dict() + if script_inputs: + ROUTINE_VARS["$ARGV"] = script_inputs + + ## PRE PROCESSING + # Remove reset commands, comments, empty lines and trailing/leading whitespaces + raw_lines = [ + x.strip() + for x in raw_lines + if (not is_reset(x)) and ("#" not in x) and x.strip() + ] + + ## LOOK FOR NEW VARIABLES BEING DECLARED FROM USERS + lines_without_declarations = list() + for line in raw_lines: + # Check if this line has a variable attribution + # This currently allows user to override ARGV parameter + if "$" in line and "=" in line: + match = re.search(r"\$(\w+)\s*=\s*([\w\d,-.\s]+)", line) + if match: + VAR_NAME = match.group(1) + VAR_VALUES = match.group(2) + ROUTINE_VARS["$" + VAR_NAME] = ( + VAR_VALUES if "," not in VAR_VALUES else VAR_VALUES.split(",") + ) + + # Just throw a warning when user uses wrong convention + numdollars = len(re.findall(r"\$", line)) + if numdollars > 1: + session.console.print( + f"The variable {VAR_NAME} should not be declared as " + f"{'$' * numdollars}{VAR_NAME}. Instead it will be " + f"converted into ${VAR_NAME}." + ) + + else: + lines_without_declarations.append(line) + else: + lines_without_declarations.append(line) + + # At this stage our ROUTINE_VARS should be completed coming from external AND from internal + # Now we want to replace the ROUTINE_VARS to where applicable throughout the .openbb script + # Due to this implementation, a variable declared at the end will still be effective + + lines_with_vars_replaced = list() + foreach_loop_found = False + for line in lines_without_declarations: + # Save temporary line to ensure that all vars get replaced by correct vars + templine = line + + # Found 'end' keyword which means that a loop has terminated + if re.match(r"^\s*end\s*$", line, re.IGNORECASE): + # Check whether the foreach loop has started or not + if not foreach_loop_found: + return ( + "[red]The script has a foreach loop that terminates before it gets started. " + "Add the keyword 'foreach' to explicitly start loop[/red]", + "", + ) + foreach_loop_found = False + + else: + # Found 'foreach' keyword which means there needs to be a matching 'end' + if re.search(r"foreach", line, re.IGNORECASE): + foreach_loop_found = True + + # Regular expression pattern to match variables starting with $ + pattern = r"(? None: + """Toggle setting value.""" + field_name = field["field_name"] + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog=field["command"], + description=field["description"], + add_help=False, + ) + ns_parser, _ = self.parse_simple_args(parser, other_args) + if ns_parser: + session.settings.set_item( + field_name, not getattr(session.settings, field_name) + ) + + def _set(self, other_args: List[str], field=field) -> None: + """Set preference value.""" + field_name = field["field_name"] + annotation = field["annotation"] + command = field["command"] + type_ = str if get_origin(annotation) is Literal else annotation + choices = None + if get_origin(annotation) is Literal: + choices = annotation.__args__ + elif command == "console_style": + # To have updated choices for console style + choices = session.style.available_styles + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog=command, + description=field["description"], + add_help=False, + ) + parser.add_argument( + "-v", + "--value", + dest="value", + action="store", + required=False, + type=type_, # type: ignore[arg-type] + choices=choices, + ) + ns_parser, _ = self.parse_simple_args(parser, other_args) + if ns_parser: + if ns_parser.value: + # Console style is applied immediately + if command == "console_style": + session.style.apply(ns_parser.value) + session.settings.set_item(field_name, ns_parser.value) + session.console.print( + f"[info]Current value:[/info] {getattr(session.settings, field_name)}" + ) + elif not other_args: + session.console.print( + f"[info]Current value:[/info] {getattr(session.settings, field_name)}" + ) + + action = None + if action_type == "toggle": + action = _toggle + elif action_type == "set": + action = _set + else: + raise ValueError(f"Action type '{action_type}' not allowed.") + + bound_method = update_wrapper( + wrapper=partial(MethodType(action, self), field=field), wrapped=action + ) + setattr(self, f"call_{cmd_name}", bound_method) diff --git a/cli/openbb_cli/controllers/utils.py b/cli/openbb_cli/controllers/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..6b54ac94ded521853a7ba32531f6321df08e6393 --- /dev/null +++ b/cli/openbb_cli/controllers/utils.py @@ -0,0 +1,1032 @@ +"""Utils.""" + +import argparse +import os +import random +import re +import shutil +import sys +from contextlib import contextmanager +from datetime import ( + datetime, +) +from pathlib import Path +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union + +import numpy as np +import pandas as pd +import requests +from openbb_cli.config.constants import AVAILABLE_FLAIRS, ENV_FILE_SETTINGS +from openbb_cli.session import Session +from openbb_core.app.model.obbject import OBBject +from pytz import all_timezones, timezone +from rich.table import Table + +if TYPE_CHECKING: + from openbb_charting.core.openbb_figure import OpenBBFigure + +# pylint: disable=R1702,R0912 + + +# pylint: disable=too-many-statements,no-member,too-many-branches,C0302 + +session = Session() + + +def remove_file(path: Path) -> bool: + """Remove path. + + Parameters + ---------- + path : Path + The file path. + + Returns + ------- + bool + The status of the removal. + """ + # TODO: Check why module level import leads to circular import. + try: + if os.path.isfile(path): + os.remove(path) + elif os.path.isdir(path): + shutil.rmtree(path) + return True + except Exception: + session.console.print( + f"\n[bold red]Failed to remove {path}" + "\nPlease delete this manually![/bold red]" + ) + return False + + +def print_goodbye(): + """Print a goodbye message when quitting the terminal.""" + # LEGACY GOODBYE MESSAGES - You'll live in our hearts forever. + # "An informed ape, is a strong ape." + # "Remember that stonks only go up." + # "Diamond hands." + # "Apes together strong." + # "This is our way." + # "Keep the spacesuit ape, we haven't reached the moon yet." + # "I am not a cat. I'm an ape." + # "We like the terminal." + # "...when offered a flight to the moon, nobody asks about what seat." + + text = """ +[param]Thank you for using the OpenBB Platform CLI and being part of this journey.[/param] + +We hope you'll find the new OpenBB Platform CLI a valuable tool. + +To stay tuned, sign up for our newsletter: [cmds]https://openbb.co/newsletter.[/] + +Please feel free to check out our other products: + +[bold]OpenBB Workspace[/]: [cmds]https://openbb.co[/cmds] +[bold]OpenBB Platform:[/] [cmds]https://docs.openbb.co/platform[/cmds] +[bold]OpenBB Bot[/]: [cmds]https://docs.openbb.co/bot[/cmds] + """ + session.console.print(text) + + +def print_guest_block_msg(): + """Block guest users from using the cli.""" + if session.is_local(): + session.console.print( + "[info]You are currently logged as a guest.[/info]\n" + "[info]Login to use this feature.[/info]\n\n" + "[info]If you don't have an account, you can create one here: [/info]" + f"[cmds]{session.settings.HUB_URL + '/register'}\n[/cmds]" + ) + + +def bootup(): + """Bootup the cli.""" + if sys.platform == "win32": + # Enable VT100 Escape Sequence for WINDOWS 10 Ver. 1607 + os.system("") # nosec # noqa: S605,S607 + + try: + if os.name == "nt": + # pylint: disable=E1101 + sys.stdin.reconfigure(encoding="utf-8") # type: ignore + # pylint: disable=E1101 + sys.stdout.reconfigure(encoding="utf-8") # type: ignore + except Exception as e: + session.console.print(e, "\n") + + +def welcome_message(): + """Print the welcome message. + + Prints first welcome message, help and a notification if updates are available. + """ + session.console.print( + f"\nWelcome to OpenBB Platform CLI v{session.settings.VERSION}" + ) + + +def reset(queue: Optional[List[str]] = None): + """Reset the CLI. + + Allows for checking code without quitting. + """ + session.console.print("resetting...") + debug = session.settings.DEBUG_MODE + dev = session.settings.DEV_BACKEND + + try: + # remove the hub routines + if not session.is_local(): + remove_file( + Path(session.user.preferences.export_directory, "routines", "hub") + ) + + # if not get_current_user().profile.remember: + # Local.remove(HIST_FILE_PROMPT) + + # we clear all openbb_cli modules from sys.modules + for module in list(sys.modules.keys()): + parts = module.split(".") + if parts[0] == "openbb_cli": + del sys.modules[module] + + queue_list = ["/".join(queue) if len(queue) > 0 else ""] # type: ignore + # pylint: disable=import-outside-toplevel + # we run the cli again + if session.is_local(): + from openbb_cli.controllers.cli_controller import main + + main(debug, dev, queue_list, module="") # type: ignore + else: + from openbb_cli.controllers.cli_controller import launch + + launch(queue=queue_list) + + except Exception as e: + session.console.print(f"Unfortunately, resetting wasn't possible: {e}\n") + print_goodbye() + + +@contextmanager +def suppress_stdout(): + """Suppress the stdout.""" + with open(os.devnull, "w") as devnull: + old_stdout = sys.stdout + old_stderr = sys.stderr + sys.stdout = devnull + sys.stderr = devnull + try: + yield + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + + +def first_time_user() -> bool: + """Check whether a user is a first time user. + + A first time user is someone with an empty .env file. + If this is true, it also adds an env variable to make sure this does not run again. + + Returns + ------- + bool + Whether or not the user is a first time user + """ + if ENV_FILE_SETTINGS.stat().st_size == 0: + session.settings.set_item("PREVIOUS_USE", True) + return True + return False + + +def parse_and_split_input(an_input: str, custom_filters: List) -> List[str]: + """Filter and split the input queue. + + Uses regex to filters command arguments that have forward slashes so that it doesn't + break the execution of the command queue. + Currently handles unix paths and sorting settings for screener menus. + + Parameters + ---------- + an_input : str + User input as string + custom_filters : List + Additional regular expressions to match + + Returns + ------- + List[str] + Command queue as list + """ + # Make sure that the user can go back to the root when doing "/" + if an_input and an_input == "/": + an_input = "home" + + # everything from ` -f ` to the next known extension + file_flag = r"(\ -f |\ --file )" + up_to = r".*?" + known_extensions = r"(\.(xlsx|csv|xls|tsv|json|yaml|ini|openbb|ipynb))" + unix_path_arg_exp = f"({file_flag}{up_to}{known_extensions})" + + # Add custom expressions to handle edge cases of individual controllers + custom_filter = "" + for exp in custom_filters: + if exp is not None: + custom_filter += f"|{exp}" + del exp + + slash_filter_exp = f"({unix_path_arg_exp}){custom_filter}" + + filter_input = True + placeholders: Dict[str, str] = {} + while filter_input: + match = re.search(pattern=slash_filter_exp, string=an_input) + if match is not None: + placeholder = f"{{placeholder{len(placeholders)+1}}}" + placeholders[placeholder] = an_input[ + match.span()[0] : match.span()[1] # noqa:E203 + ] + an_input = ( + an_input[: match.span()[0]] + + placeholder + + an_input[match.span()[1] :] # noqa:E203 + ) + else: + filter_input = False + + commands = an_input.split("/") if "timezone" not in an_input else [an_input] + + for command_num, command in enumerate(commands): + if command == commands[-1] == "": + return list(filter(None, commands)) + matching_placeholders = [tag for tag in placeholders if tag in command] + if len(matching_placeholders) > 0: + for tag in matching_placeholders: + commands[command_num] = command.replace(tag, placeholders[tag]) + return commands + + +def return_colored_value(value: str): + """Return the string value based on condition. + + Return it with green, yellow, red or white color based on + whether the number is positive, negative, zero or other, respectively. + + Parameters + ---------- + value: str + string to be checked + + Returns + ------- + value: str + string with color based on value of number if it exists + """ + values = re.findall(r"[-+]?(?:\d*\.\d+|\d+)", value) + + # Finds exactly 1 number in the string + if len(values) == 1: + if float(values[0]) > 0: + return f"[green]{value}[/green]" + + if float(values[0]) < 0: + return f"[red]{value}[/red]" + + if float(values[0]) == 0: + return f"[yellow]{value}[/yellow]" + + return f"{value}" + + +# pylint: disable=too-many-arguments,too-many-positional-arguments +def print_rich_table( # noqa: PLR0912 + df: pd.DataFrame, + show_index: bool = False, + title: str = "", + index_name: str = "", + headers: Optional[Union[List[str], pd.Index]] = None, + floatfmt: Union[str, List[str]] = ".2f", + show_header: bool = True, + automatic_coloring: bool = False, + columns_to_auto_color: Optional[List[str]] = None, + rows_to_auto_color: Optional[List[str]] = None, + export: bool = False, + limit: Optional[int] = 1000, + columns_keep_types: Optional[List[str]] = None, + use_tabulate_df: bool = True, +): + """Prepare a table from df in rich. + + Parameters + ---------- + df: pd.DataFrame + Dataframe to turn into table + show_index: bool + Whether to include index + title: str + Title for table + index_name : str + Title for index column + headers: List[str] + Titles for columns + floatfmt: Union[str, List[str]] + Float number formatting specs as string or list of strings. Defaults to ".2f" + show_header: bool + Whether to show the header row. + automatic_coloring: bool + Automatically color a table based on positive and negative values + columns_to_auto_color: List[str] + Columns to automatically color + rows_to_auto_color: List[str] + Rows to automatically color + export: bool + Whether we are exporting the table to a file. If so, we don't want to print it. + limit: Optional[int] + Limit the number of rows to show. + columns_keep_types: Optional[List[str]] + Columns to keep their types, i.e. not convert to numeric + """ + if export: + return + + MAX_COLS = session.settings.ALLOWED_NUMBER_OF_COLUMNS + MAX_ROWS = session.settings.ALLOWED_NUMBER_OF_ROWS + + # Make a copy of the dataframe to avoid SettingWithCopyWarning + df = df.copy() + + show_index = not isinstance(df.index, pd.RangeIndex) and show_index + # convert non-str that are not timestamp or int into str + # eg) praw.models.reddit.subreddit.Subreddit + for col in df.columns: + if columns_keep_types is not None and col in columns_keep_types: + continue + try: + if not any( + isinstance(df[col].iloc[x], pd.Timestamp) + for x in range(min(10, len(df))) + ): + df[col] = df[col].apply(pd.to_numeric) + except (ValueError, TypeError): + df[col] = df[col].astype(str) + + def _get_headers(_headers: Union[List[str], pd.Index]) -> List[str]: + """Check if headers are valid and return them.""" + output = _headers + if isinstance(_headers, pd.Index): + output = list(_headers) + if len(output) != len(df.columns): + raise ValueError("Length of headers does not match length of DataFrame.") + return output # type: ignore + + if session.settings.USE_INTERACTIVE_DF: + df_outgoing = df.copy() + # If headers are provided, use them + if headers is not None: + # We check if headers are valid + df_outgoing.columns = _get_headers(headers) + + if show_index and index_name not in df_outgoing.columns: + # If index name is provided, we use it + df_outgoing.index.name = index_name or "Index" + df_outgoing = df_outgoing.reset_index() + + for col in df_outgoing.columns: + if col == "": + df_outgoing = df_outgoing.rename(columns={col: " "}) + + session._backend.send_table( # type: ignore # pylint: disable=protected-access + df_table=df_outgoing, + title=title, + theme=session.user.preferences.table_style, + ) + return + + df = df.copy() if not limit else df.copy().iloc[:limit] + if automatic_coloring: + if columns_to_auto_color: + for col in columns_to_auto_color: + # checks whether column exists + if col in df.columns: + df[col] = df[col].apply(lambda x: return_colored_value(str(x))) + if rows_to_auto_color: + for row in rows_to_auto_color: + # checks whether row exists + if row in df.index: + df.loc[row] = df.loc[row].apply( + lambda x: return_colored_value(str(x)) + ) + + if columns_to_auto_color is None and rows_to_auto_color is None: + df = df.applymap(lambda x: return_colored_value(str(x))) + + exceeds_allowed_columns = len(df.columns) > MAX_COLS + exceeds_allowed_rows = len(df) > MAX_ROWS + + if exceeds_allowed_columns: + original_columns = df.columns.tolist() + trimmed_columns = df.columns.tolist()[:MAX_COLS] + df = df[trimmed_columns] + trimmed_columns = [ + col for col in original_columns if col not in trimmed_columns + ] + + if exceeds_allowed_rows: + n_rows = len(df.index) + max_rows = MAX_ROWS + df = df[:max_rows] + trimmed_rows_count = n_rows - max_rows + + if use_tabulate_df: + table = Table(title=title, show_lines=True, show_header=show_header) + + if show_index: + table.add_column(index_name) + + if headers is not None: + headers = _get_headers(headers) + for header in headers: + table.add_column(str(header)) + else: + for column in df.columns: + table.add_column(str(column)) + + if isinstance(floatfmt, list) and len(floatfmt) != len(df.columns): + raise ( + ValueError( + "Length of floatfmt list does not match length of DataFrame columns." + ) + ) + if isinstance(floatfmt, str): + floatfmt = [floatfmt for _ in range(len(df.columns))] + + for idx, values in zip(df.index.tolist(), df.values.tolist()): + # remove hour/min/sec from timestamp index - Format: YYYY-MM-DD # make better + row_idx = [str(idx)] if show_index else [] + row_idx += [ + ( + str(x) + if not isinstance(x, float) and not isinstance(x, np.float64) + else ( + f"{x:{floatfmt[idx]}}" + if isinstance(floatfmt, list) + else ( + f"{x:.2e}" + if 0 < abs(float(x)) <= 0.0001 + else f"{x:floatfmt}" + ) + ) + ) + for idx, x in enumerate(values) + ] + table.add_row(*row_idx) + session.console.print(table) + else: + session.console.print(df.to_string(col_space=0)) + + if exceeds_allowed_columns: + session.console.print( + f"[yellow]\nAllowed number of columns exceeded ({session.settings.ALLOWED_NUMBER_OF_COLUMNS}).\n" + f"The following columns were removed from the output: {', '.join(trimmed_columns)}.\n[/yellow]" + ) + + if exceeds_allowed_rows: + session.console.print( + f"[yellow]\nAllowed number of rows exceeded ({session.settings.ALLOWED_NUMBER_OF_ROWS}).\n" + f"{trimmed_rows_count} rows were removed from the output.\n[/yellow]" + ) + + if exceeds_allowed_columns or exceeds_allowed_rows: + session.console.print( + "Use the `--export` flag to analyse the full output on a file." + ) + + +def check_non_negative(value) -> int: + """Argparse type to check non negative int.""" + new_value = int(value) + if new_value < 0: + raise argparse.ArgumentTypeError(f"{value} is negative") + return new_value + + +def check_positive(value) -> int: + """Argparse type to check positive int.""" + new_value = int(value) + if new_value <= 0: + raise argparse.ArgumentTypeError(f"{value} is an invalid positive int value") + 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 = [ + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:86.1) Gecko/20100101 Firefox/86.1", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:86.1) Gecko/20100101 Firefox/86.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:82.1) Gecko/20100101 Firefox/82.1", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:86.0) Gecko/20100101 Firefox/86.0", + "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:86.0) Gecko/20100101 Firefox/86.0", + "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.10; rv:83.0) Gecko/20100101 Firefox/83.0", + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:84.0) Gecko/20100101 Firefox/84.0", + ] + + return random.choice(user_agent_strings) # nosec # noqa: S311 + + +def get_flair() -> str: + """Get a flair icon.""" + current_flair = str(session.settings.FLAIR) + flair = AVAILABLE_FLAIRS.get(current_flair, current_flair) + return flair + + +def get_dtime() -> str: + """Get a datetime string.""" + dtime = "" + if session.settings.USE_DATETIME and get_user_timezone_or_invalid() != "INVALID": + dtime = datetime.now(timezone(get_user_timezone())).strftime("%Y %b %d, %H:%M") + return dtime + + +def get_flair_and_username() -> str: + """Get a flair icon and username.""" + flair = get_flair() + if dtime := get_dtime(): + dtime = f"{dtime} " + + username = getattr(session.user.profile.hub_session, "username", "") + if username: + username = f"[{username}] " + + return f"{dtime}{username}{flair}" + + +def is_timezone_valid(user_tz: str) -> bool: + """Check whether user timezone is valid. + + Parameters + ---------- + user_tz: str + Timezone to check for validity + + Returns + ------- + bool + True if timezone provided is valid + """ + return user_tz in all_timezones + + +def get_user_timezone() -> str: + """Get user timezone if it is a valid one. + + Returns + ------- + str + user timezone based on .env file + """ + return session.settings.TIMEZONE + + +def get_user_timezone_or_invalid() -> str: + """Get user timezone if it is a valid one. + + Returns + ------- + str + user timezone based on timezone.openbb file or INVALID + """ + user_tz = get_user_timezone() + if is_timezone_valid(user_tz): + return f"{user_tz}" + return "INVALID" + + +def check_file_type_saved(valid_types: Optional[List[str]] = None): + """Provide valid types for the user to be able to select. + + Parameters + ---------- + valid_types: List[str] + List of valid types to export data + + Returns + ------- + check_filenames: Optional[List[str]] + Function that returns list of filenames to export data + """ + + def check_filenames(filenames: str = "") -> str: + """Check if filenames are valid. + + Parameters + ---------- + filenames: str + filenames to be saved separated with comma + + Returns + ---------- + str + valid filenames separated with comma + """ + if not filenames or not valid_types: + return "" + valid_filenames = list() + for filename in filenames.split(","): + if filename.endswith(tuple(valid_types)): + valid_filenames.append(filename) + else: + session.console.print( + f"[red]Filename '{filename}' provided is not valid!\nPlease use one of the following file types:" + f"{','.join(valid_types)}[/red]\n" + ) + return ",".join(valid_filenames) + + return check_filenames + + +def remove_timezone_from_dataframe(df: pd.DataFrame) -> pd.DataFrame: + """Remove timezone information from a dataframe. + + Parameters + ---------- + df : pd.DataFrame + The dataframe to remove timezone information from + + Returns + ------- + pd.DataFrame + The dataframe with timezone information removed + """ + date_cols = [] + index_is_date = False + + # Find columns and index containing date data + if ( + df.index.dtype.kind == "M" + and hasattr(df.index.dtype, "tz") + and df.index.dtype.tz is not None + ): + index_is_date = True + + for col, dtype in df.dtypes.items(): + if dtype.kind == "M" and hasattr(df.index.dtype, "tz") and dtype.tz is not None: + date_cols.append(col) + + # Remove the timezone information + for col in date_cols: + df[col] = df[col].dt.date + + if index_is_date: + index_name = df.index.name + df.index = df.index.date + df.index.name = index_name + + return df + + +def compose_export_path(func_name: str, dir_path: str) -> Path: + """Compose export path for data from the terminal. + + Creates a path to a folder and a filename based on conditions. + + Parameters + ---------- + func_name : str + Name of the command that invokes this function + dir_path : str + Path of directory from where this function is called + + Returns + ------- + Path + Path variable containing the path of the exported file + """ + now = datetime.now() + # Resolving all symlinks and also normalizing path. + resolve_path = Path(dir_path).resolve() + # Getting the directory names from the path. Instead of using split/replace (Windows doesn't like that) + # check if this is done in a main context to avoid saving with openbb_cli + if resolve_path.parts[-2] == "openbb_cli": + path_cmd = f"{resolve_path.parts[-1]}" + else: + path_cmd = f"{resolve_path.parts[-2]}_{resolve_path.parts[-1]}" + + default_filename = f"{now.strftime('%Y%m%d_%H%M%S')}_{path_cmd}_{func_name}" + + full_path = Path(session.user.preferences.export_directory) / default_filename + + return full_path + + +def ask_file_overwrite(file_path: Path) -> Tuple[bool, bool]: + """Provide a prompt for overwriting existing files. + + Returns two values, the first is a boolean indicating if the file exists and the + second is a boolean indicating if the user wants to overwrite the file. + """ + if session.settings.FILE_OVERWRITE: + return False, True + if session.settings.TEST_MODE: + return False, True + if file_path.exists(): + overwrite = input("\nFile already exists. Overwrite? [y/n]: ").lower() + if overwrite == "y": + file_path.unlink(missing_ok=True) + # File exists and user wants to overwrite + return True, True + # File exists and user does not want to overwrite + return True, False + # File does not exist + return False, True + + +# This is a false positive on pylint and being tracked in pylint #3060 +# pylint: disable=abstract-class-instantiated,too-many-positional-arguments +def save_to_excel(df, saved_path, sheet_name, start_row=0, index=True, header=True): + """Save a Pandas DataFrame to an Excel file. + + Args: + df: A Pandas DataFrame. + saved_path: The path to the Excel file to save to. + sheet_name: The name of the sheet to save the DataFrame to. + start_row: The row number to start writing the DataFrame at. + index: Whether to write the DataFrame index to the Excel file. + header: Whether to write the DataFrame header to the Excel file. + """ + overwrite_options = { + "o": "replace", + "a": "overlay", + "n": "new", + } + + if not saved_path.exists(): + with pd.ExcelWriter(saved_path, engine="openpyxl") as writer: + df.to_excel(writer, sheet_name=sheet_name, index=index, header=header) + + else: + with pd.ExcelFile(saved_path) as reader: + overwrite_option = "n" + if sheet_name in reader.sheet_names: + overwrite_option = input( + "\nSheet already exists. Overwrite/Append/New? [o/a/n]: " + ).lower() + start_row = 0 + if overwrite_option == "a": + existing_df = pd.read_excel(saved_path, sheet_name=sheet_name) + start_row = existing_df.shape[0] + 1 + + with pd.ExcelWriter( + saved_path, + mode="a", + if_sheet_exists=overwrite_options[overwrite_option], + engine="openpyxl", + ) as writer: + df.to_excel( + writer, + sheet_name=sheet_name, + startrow=start_row, + index=index, + header=False if overwrite_option == "a" else header, + ) + + +# This is a false positive on pylint and being tracked in pylint #3060 +# pylint: disable=abstract-class-instantiated,too-many-positional-arguments +def export_data( + export_type: str, + dir_path: str, + func_name: str, + df: pd.DataFrame = pd.DataFrame(), + sheet_name: Optional[str] = None, + figure: Optional["OpenBBFigure"] = None, + margin: bool = True, +) -> None: + """Export data to a file. + + Parameters + ---------- + export_type : str + Type of export between: csv,json,xlsx,xls + dir_path : str + Path of directory from where this function is called + func_name : str + Name of the command that invokes this function + df : pd.Dataframe + Dataframe of data to save + sheet_name : str + If provided. The name of the sheet to save in excel file + figure : Optional[OpenBBFigure] + Figure object to save as image file + margin : bool + Automatically adjust subplot parameters to give specified padding. + """ + if export_type: + saved_path = compose_export_path(func_name, dir_path).resolve() + saved_path.parent.mkdir(parents=True, exist_ok=True) + for exp_type in export_type.split(","): + # In this scenario the path was provided, e.g. --export pt.csv, pt.jpg + if "." in exp_type: + saved_path = saved_path.with_name(exp_type) + # In this scenario we use the default filename + else: + if ".OpenBB_openbb_cli" in saved_path.name: + saved_path = saved_path.with_name( + saved_path.name.replace(".OpenBB_openbb_cli", "OpenBBCLI") + ) + saved_path = saved_path.with_suffix(f".{exp_type}") + + exists, overwrite = False, False + is_xlsx = exp_type.endswith("xlsx") + if sheet_name is None and is_xlsx or not is_xlsx: + exists, overwrite = ask_file_overwrite(saved_path) + + if exists and not overwrite: + existing = len(list(saved_path.parent.glob(saved_path.stem + "*"))) + saved_path = saved_path.with_stem(f"{saved_path.stem}_{existing + 1}") + + df = df.replace( + { + r"\[yellow\]": "", + r"\[/yellow\]": "", + r"\[green\]": "", + r"\[/green\]": "", + r"\[red\]": "", + r"\[/red\]": "", + r"\[magenta\]": "", + r"\[/magenta\]": "", + }, + regex=True, + ) + + if exp_type.endswith("csv"): + df.to_csv(saved_path) + elif exp_type.endswith("json"): + df.reset_index(drop=True, inplace=True) + df.to_json(saved_path) + elif exp_type.endswith("xlsx"): + # since xlsx does not support datetimes with timezones we need to remove it + df = remove_timezone_from_dataframe(df) + + if sheet_name is None: # noqa: SIM223 + df.to_excel( + saved_path, + index=True, + header=True, + ) + else: + save_to_excel(df, saved_path, sheet_name) + + elif saved_path.suffix in [".jpg", ".png"]: + if figure is None: + session.console.print("No plot to export.") + continue + figure.show(export_image=saved_path, margin=margin) + else: + session.console.print("Wrong export file specified.") + continue + + if saved_path.exists(): + session.console.print(f"Saved file: {saved_path}") + else: + session.console.print(f"Failed to save file: {saved_path}") + + if figure is not None: + figure._exported = True # pylint: disable=protected-access + + +def system_clear(): + """Clear screen.""" + os.system("cls||clear") # nosec # noqa: S605,S607 + + +# Write an abstract helper to make requests from a url with potential headers and params +def request( + url: str, method: str = "get", timeout: int = 0, **kwargs +) -> requests.Response: + """Make requests from a url with potential headers and params. + + Parameters + ---------- + url : str + Url to make the request to + method : str + HTTP method to use. Choose from: + delete, get, head, patch, post, put, by default "get" + timeout : int + How many seconds to wait for the server to send data + + Returns + ------- + requests.Response + Request response object + + Raises + ------ + ValueError + If invalid method is passed + """ + method = method.lower() + if method not in ["delete", "get", "head", "patch", "post", "put"]: + raise ValueError(f"Invalid method: {method}") + # We want to add a user agent to the request, so check if there are any headers + # If there are headers, check if there is a user agent, if not add one. + # Some requests seem to work only with a specific user agent, so we want to be able to override it. + headers = kwargs.pop("headers", {}) + timeout = timeout or session.user.preferences.request_timeout + + if "User-Agent" not in headers: + headers["User-Agent"] = get_user_agent() + func = getattr(requests, method) + return func( + url, + headers=headers, + timeout=timeout, + **kwargs, + ) + + +def parse_unknown_args_to_dict(unknown_args: Optional[List[str]]) -> Dict[str, str]: + """Parse unknown arguments to a dictionary.""" + unknown_args_dict = {} + if unknown_args: + for idx, arg in enumerate(unknown_args): + if arg.startswith("--"): + if idx + 1 < len(unknown_args): + try: + unknown_args_dict[arg.replace("--", "")] = ( + eval( # noqa: S307, E501 pylint: disable=eval-used + unknown_args[idx + 1] + ) + ) + except Exception: + unknown_args_dict[arg] = unknown_args[idx + 1] + else: + session.console.print( + f"Missing value for argument {arg}. Skipping this argument." + ) + return unknown_args_dict + + +def handle_obbject_display( + obbject: OBBject, + chart: bool = False, + export: str = "", + sheet_name: str = "", + **kwargs, +): + """Handle the display of an OBBject.""" + df: pd.DataFrame = pd.DataFrame() + fig: Optional[OpenBBFigure] = None + if chart: + try: + if obbject.chart: + obbject.show(**kwargs) + else: + obbject.charting.to_chart(**kwargs) # type: ignore + if export: + fig = obbject.chart.fig # type: ignore + df = obbject.to_dataframe() + except Exception as e: + session.console.print(f"Failed to display chart: {e}") + elif session.settings.USE_INTERACTIVE_DF: + obbject.charting.table() # type: ignore + else: + df = obbject.to_dataframe() + print_rich_table( + df=df, + show_index=True, + title=obbject.extra.get("command", ""), + export=bool(export), + ) + if export and not df.empty: + if sheet_name and isinstance(sheet_name, list): + sheet_name = sheet_name[0] + + func_name = ( + obbject.extra.get("command", "") + .replace("/", "_") + .replace(" ", "_") + .replace("--", "_") + ) + export_data( + export_type=",".join(export), + dir_path=os.path.dirname(os.path.abspath(__file__)), + func_name=func_name, + df=df, + sheet_name=sheet_name, + figure=fig, + ) + elif export and df.empty: + session.console.print("[yellow]No data to export.[/yellow]") diff --git a/cli/openbb_cli/models/settings.py b/cli/openbb_cli/models/settings.py new file mode 100644 index 0000000000000000000000000000000000000000..f0c9fa78b598b94b9bd07460a326bfa76f31e20e --- /dev/null +++ b/cli/openbb_cli/models/settings.py @@ -0,0 +1,167 @@ +"""Settings model.""" + +from enum import Enum +from typing import Any, Literal + +from dotenv import dotenv_values, set_key +from openbb_cli.config.constants import AVAILABLE_FLAIRS, ENV_FILE_SETTINGS +from openbb_core.app.version import get_package_version +from pydantic import BaseModel, ConfigDict, Field, model_validator +from pytz import all_timezones + +VERSION = get_package_version("openbb-cli") + + +class SettingGroups(Enum): + """Setting types.""" + + feature_flags = "feature_flag" + preferences = "preference" + + +class Settings(BaseModel): + """Settings model.""" + + # Platform CLI version + VERSION: str = VERSION + + # DEVELOPMENT FLAGS + TEST_MODE: bool = False + DEBUG_MODE: bool = False + DEV_BACKEND: bool = False + + # OPENBB + HUB_URL: str = "https://my.openbb.co" + BASE_URL: str = "https://payments.openbb.co" + + # GENERAL + PREVIOUS_USE: bool = False + + # FEATURE FLAGS + FILE_OVERWRITE: bool = Field( + default=False, + description="whether to overwrite Excel files if they already exists", + command="overwrite", + group=SettingGroups.feature_flags, + ) + SHOW_VERSION: bool = Field( + default=True, + description="whether to show the version in the bottom right corner", + command="version", + group=SettingGroups.feature_flags, + ) + USE_INTERACTIVE_DF: bool = Field( + default=True, + description="display tables in interactive window", + command="interactive", + group=SettingGroups.feature_flags, + ) + USE_CLEAR_AFTER_CMD: bool = Field( + default=False, + description="clear console after each command", + command="cls", + group=SettingGroups.feature_flags, + ) + USE_DATETIME: bool = Field( + default=True, + description="whether to show the date and time before the flair", + command="datetime", + group=SettingGroups.feature_flags, + ) + USE_PROMPT_TOOLKIT: bool = Field( + default=True, + description="enable prompt toolkit (autocomplete and history)", + command="promptkit", + group=SettingGroups.feature_flags, + ) + ENABLE_EXIT_AUTO_HELP: bool = Field( + default=True, + description="automatically print help when quitting menu", + command="exithelp", + group=SettingGroups.feature_flags, + ) + ENABLE_RICH_PANEL: bool = Field( + default=True, + description="enable colorful rich CLI panel", + command="richpanel", + group=SettingGroups.feature_flags, + ) + TOOLBAR_HINT: bool = Field( + default=True, + description="displays usage hints in the bottom toolbar", + command="tbhint", + group=SettingGroups.feature_flags, + ) + SHOW_MSG_OBBJECT_REGISTRY: bool = Field( + default=False, + description="show obbject registry message after a new result is added", + command="obbject_msg", + group=SettingGroups.feature_flags, + ) + + # PREFERENCES + TIMEZONE: Literal[tuple(all_timezones)] = Field( # type: ignore[valid-type] + default="America/New_York", + description="pick timezone", + command="timezone", + group=SettingGroups.preferences, + ) + FLAIR: Literal[tuple(AVAILABLE_FLAIRS)] = Field( # type: ignore[valid-type] + default=":openbb", + description="choose flair icon", + command="flair", + group=SettingGroups.preferences, + ) + N_TO_KEEP_OBBJECT_REGISTRY: int = Field( + default=10, + description="define the maximum number of obbjects allowed in the registry", + command="obbject_res", + group=SettingGroups.preferences, + ) + N_TO_DISPLAY_OBBJECT_REGISTRY: int = Field( + default=5, + description="define the maximum number of cached results to display on the help menu", + command="obbject_display", + group=SettingGroups.preferences, + ) + RICH_STYLE: str = Field( + default="dark", + description="apply a custom rich style to the CLI", + command="console_style", + group=SettingGroups.preferences, + ) + ALLOWED_NUMBER_OF_ROWS: int = Field( + default=20, + description="number of rows to show (when not using interactive tables).", + command="n_rows", + group=SettingGroups.preferences, + ) + ALLOWED_NUMBER_OF_COLUMNS: int = Field( + default=5, + description="number of columns to show (when not using interactive tables).", + command="n_cols", + group=SettingGroups.preferences, + ) + + model_config = ConfigDict(validate_assignment=True) + + def __repr__(self) -> str: + """Return a string representation of the model.""" + return f"{self.__class__.__name__}\n\n" + "\n".join( + f"{k}: {v}" for k, v in self.model_dump().items() + ) + + @model_validator(mode="before") + @classmethod + def from_env(cls, values: dict) -> dict: + """Load settings from .env.""" + settings = {} + settings.update(dotenv_values(ENV_FILE_SETTINGS)) + settings.update(values) + filtered = {k.replace("OPENBB_", ""): v for k, v in settings.items()} + return filtered + + def set_item(self, key: str, value: Any) -> None: + """Set an item in the model and save to .env.""" + setattr(self, key, value) + set_key(str(ENV_FILE_SETTINGS), "OPENBB_" + key, str(value)) diff --git a/cli/openbb_cli/session.py b/cli/openbb_cli/session.py new file mode 100644 index 0000000000000000000000000000000000000000..8b49537fb584cf96010c2ab9fec07e820b54296d --- /dev/null +++ b/cli/openbb_cli/session.py @@ -0,0 +1,108 @@ +"""Settings module.""" + +import sys +from pathlib import Path +from typing import Optional + +from openbb import obb +from openbb_charting.core.backend import create_backend, get_backend +from openbb_core.app.model.abstract.singleton import SingletonMeta +from openbb_core.app.model.charts.charting_settings import ChartingSettings +from openbb_core.app.model.user_settings import UserSettings as User +from prompt_toolkit import PromptSession + +from openbb_cli.argparse_translator.obbject_registry import Registry +from openbb_cli.config.completer import CustomFileHistory +from openbb_cli.config.console import Console +from openbb_cli.config.constants import HIST_FILE_PROMPT +from openbb_cli.config.style import Style +from openbb_cli.models.settings import Settings + + +def _get_backend(): + """Get the Platform charting backend.""" + try: + return get_backend() + except ValueError: + # backend might not be created yet + charting_settings = ChartingSettings( + system_settings=obb.system, user_settings=obb.user # type: ignore + ) + create_backend(charting_settings) + get_backend().start(debug=charting_settings.debug_mode) # type: ignore + return get_backend() + + +class Session(metaclass=SingletonMeta): + """Session class.""" + + def __init__(self): + """Initialize session.""" + + self._obb = obb + self._settings = Settings() + self._style = Style( + style=self._settings.RICH_STYLE, + directory=Path(self._obb.user.preferences.user_styles_directory), # type: ignore[union-attr] + ) + self._console = Console( + settings=self._settings, style=self._style.console_style + ) + self._prompt_session = self._get_prompt_session() + self._obbject_registry = Registry() + + self._backend = _get_backend() + + @property + def user(self) -> User: + """Get platform user.""" + return self._obb.user # type: ignore[union-attr] + + @property + def settings(self) -> Settings: + """Get CLI settings.""" + return self._settings + + @property + def style(self) -> Style: + """Get CLI style.""" + return self._style + + @property + def console(self) -> Console: + """Get console.""" + return self._console + + @property + def obbject_registry(self) -> Registry: + """Get obbject registry.""" + return self._obbject_registry + + @property + def prompt_session(self) -> Optional[PromptSession]: + """Get prompt session.""" + return self._prompt_session + + def _get_prompt_session(self) -> Optional[PromptSession]: + """Initialize prompt session.""" + try: + if sys.stdin.isatty(): + prompt_session: Optional[PromptSession] = PromptSession( + history=CustomFileHistory(str(HIST_FILE_PROMPT)) + ) + else: + prompt_session = None + except Exception: + prompt_session = None + + return prompt_session + + def is_local(self) -> bool: + """Check if user is local.""" + return not bool(self.user.profile.hub_session) + + def max_obbjects_exceeded(self) -> bool: + """Check if max obbjects exceeded.""" + return ( + len(self.obbject_registry.all) >= self.settings.N_TO_KEEP_OBBJECT_REGISTRY + ) diff --git a/cli/openbb_cli/utils/utils.py b/cli/openbb_cli/utils/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..b38c83a8dabfa67ac5d44e645f767f345f4da124 --- /dev/null +++ b/cli/openbb_cli/utils/utils.py @@ -0,0 +1,34 @@ +"""OpenBB Platform CLI utilities.""" + +import json +from pathlib import Path + +HOME_DIRECTORY = Path.home() +OPENBB_PLATFORM_DIRECTORY = Path(HOME_DIRECTORY, ".openbb_platform") +SYSTEM_SETTINGS_PATH = Path(OPENBB_PLATFORM_DIRECTORY, "system_settings.json") + + +def change_logging_sub_app() -> str: + """Build OpenBB Platform setting files.""" + with open(SYSTEM_SETTINGS_PATH) as file: + system_settings = json.load(file) + + initial_logging_sub_app = system_settings.get("logging_sub_app", "") + + system_settings["logging_sub_app"] = "cli" + + with open(SYSTEM_SETTINGS_PATH, "w") as file: + json.dump(system_settings, file, indent=4) + + return initial_logging_sub_app + + +def reset_logging_sub_app(initial_logging_sub_app: str): + """Reset OpenBB Platform setting files.""" + with open(SYSTEM_SETTINGS_PATH) as file: + system_settings = json.load(file) + + system_settings["logging_sub_app"] = initial_logging_sub_app + + with open(SYSTEM_SETTINGS_PATH, "w") as file: + json.dump(system_settings, file, indent=4) diff --git a/cli/poetry.lock b/cli/poetry.lock new file mode 100644 index 0000000000000000000000000000000000000000..1c80c93f704eca7dcb78cf4f08d7fb4a9a50943a --- /dev/null +++ b/cli/poetry.lock @@ -0,0 +1,5004 @@ +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.11.18" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96264854fedbea933a9ca4b7e0c745728f01380691687b7365d18d9e977179c4"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9602044ff047043430452bc3a2089743fa85da829e6fc9ee0025351d66c332b6"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5691dc38750fcb96a33ceef89642f139aa315c8a193bbd42a0c33476fd4a1609"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554c918ec43f8480b47a5ca758e10e793bd7410b83701676a4782672d670da55"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a4076a2b3ba5b004b8cffca6afe18a3b2c5c9ef679b4d1e9859cf76295f8d4f"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:767a97e6900edd11c762be96d82d13a1d7c4fc4b329f054e88b57cdc21fded94"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ddc9337a0fb0e727785ad4f41163cc314376e82b31846d3835673786420ef1"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f414f37b244f2a97e79b98d48c5ff0789a0b4b4609b17d64fa81771ad780e415"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fdb239f47328581e2ec7744ab5911f97afb10752332a6dd3d98e14e429e1a9e7"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2c50bad73ed629cc326cc0f75aed8ecfb013f88c5af116f33df556ed47143eb"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8d8f20c39d3fa84d1c28cdb97f3111387e48209e224408e75f29c6f8e0861d"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:106032eaf9e62fd6bc6578c8b9e6dc4f5ed9a5c1c7fb2231010a1b4304393421"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b491e42183e8fcc9901d8dcd8ae644ff785590f1727f76ca86e731c61bfe6643"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad8c745ff9460a16b710e58e06a9dec11ebc0d8f4dd82091cefb579844d69868"}, + {file = "aiohttp-3.11.18-cp310-cp310-win32.whl", hash = "sha256:8e57da93e24303a883146510a434f0faf2f1e7e659f3041abc4e3fb3f6702a9f"}, + {file = "aiohttp-3.11.18-cp310-cp310-win_amd64.whl", hash = "sha256:cc93a4121d87d9f12739fc8fab0a95f78444e571ed63e40bfc78cd5abe700ac9"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd"}, + {file = "aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d"}, + {file = "aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea"}, + {file = "aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8"}, + {file = "aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:474215ec618974054cf5dc465497ae9708543cbfc312c65212325d4212525811"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ced70adf03920d4e67c373fd692123e34d3ac81dfa1c27e45904a628567d804"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d9f6c0152f8d71361905aaf9ed979259537981f47ad099c8b3d81e0319814bd"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a35197013ed929c0aed5c9096de1fc5a9d336914d73ab3f9df14741668c0616c"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540b8a1f3a424f1af63e0af2d2853a759242a1769f9f1ab053996a392bd70118"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28c3f975e5ae3dbcbe95b7e3dcd30e51da561a0a0f2cfbcdea30fc1308d72137"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c28875e316c7b4c3e745172d882d8a5c835b11018e33432d281211af35794a93"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:13cd38515568ae230e1ef6919e2e33da5d0f46862943fcda74e7e915096815f3"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0e2a92101efb9f4c2942252c69c63ddb26d20f46f540c239ccfa5af865197bb8"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6d3e32b8753c8d45ac550b11a1090dd66d110d4ef805ffe60fa61495360b3b2"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d4df95ad522c53f2b9ebc07f12ccd2cb15550941e11a5bbc5ddca2ca56316d7"}, + {file = "aiohttp-3.11.18-cp313-cp313-win32.whl", hash = "sha256:cdd1bbaf1e61f0d94aced116d6e95fe25942f7a5f42382195fd9501089db5d78"}, + {file = "aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:469ac32375d9a716da49817cd26f1916ec787fc82b151c1c832f58420e6d3533"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3cec21dd68924179258ae14af9f5418c1ebdbba60b98c667815891293902e5e0"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b426495fb9140e75719b3ae70a5e8dd3a79def0ae3c6c27e012fc59f16544a4a"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2f41203e2808616292db5d7170cccf0c9f9c982d02544443c7eb0296e8b0c7"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc0ae0a5e9939e423e065a3e5b00b24b8379f1db46046d7ab71753dfc7dd0e1"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe7cdd3f7d1df43200e1c80f1aed86bb36033bf65e3c7cf46a2b97a253ef8798"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5199be2a2f01ffdfa8c3a6f5981205242986b9e63eb8ae03fd18f736e4840721"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ccec9e72660b10f8e283e91aa0295975c7bd85c204011d9f5eb69310555cf30"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1596ebf17e42e293cbacc7a24c3e0dc0f8f755b40aff0402cb74c1ff6baec1d3"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:eab7b040a8a873020113ba814b7db7fa935235e4cbaf8f3da17671baa1024863"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5d61df4a05476ff891cff0030329fee4088d40e4dc9b013fac01bc3c745542c2"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46533e6792e1410f9801d09fd40cbbff3f3518d1b501d6c3c5b218f427f6ff08"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c1b90407ced992331dd6d4f1355819ea1c274cc1ee4d5b7046c6761f9ec11829"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a2fd04ae4971b914e54fe459dd7edbbd3f2ba875d69e057d5e3c8e8cac094935"}, + {file = "aiohttp-3.11.18-cp39-cp39-win32.whl", hash = "sha256:b2f317d1678002eee6fe85670039fb34a757972284614638f82b903a03feacdc"}, + {file = "aiohttp-3.11.18-cp39-cp39-win_amd64.whl", hash = "sha256:5e7007b8d1d09bce37b54111f593d173691c530b80f27c6493b928dabed9e6ef"}, + {file = "aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiohttp-client-cache" +version = "0.11.1" +description = "Persistent cache for aiohttp requests" +optional = false +python-versions = "<4.0,>=3.8" +groups = ["main"] +files = [ + {file = "aiohttp_client_cache-0.11.1-py3-none-any.whl", hash = "sha256:06ea196e35219a6f1ecc2f96639106eeea5fc1ec9808c805aa3a2e5cbfa62df6"}, + {file = "aiohttp_client_cache-0.11.1.tar.gz", hash = "sha256:32e63ad210240f8224f3e12772fe53ac102cf24c7cf18ddb86acbb9fdf9e4b6f"}, +] + +[package.dependencies] +aiohttp = ">=3.8,<4.0" +attrs = ">=21.2" +itsdangerous = ">=2.0" +url-normalize = ">=1.4,<2.0" + +[package.extras] +all = ["aioboto3 (>=9.0)", "aiobotocore (>=2.0)", "aiofiles (>=0.6.0)", "aiosqlite (>=0.20)", "motor (>=3.1)", "redis (>=4.2)"] +docs = ["furo (>=2023.8,<2024.0)", "linkify-it-py (>=2.0)", "markdown-it-py (>=2.2)", "myst-parser (>=2.0)", "python-forge (>=18.6,<19.0)", "sphinx (==7.1.2)", "sphinx-autodoc-typehints (>=1.23,<2.0)", "sphinx-automodapi (>=0.15)", "sphinx-copybutton (>=0.3,<0.4)", "sphinx-inline-tabs (>=2023.4)", "sphinxcontrib-apidoc (>=0.3)"] +dynamodb = ["aioboto3 (>=9.0)", "aiobotocore (>=2.0)"] +filesystem = ["aiofiles (>=0.6.0)", "aiosqlite (>=0.20)"] +mongodb = ["motor (>=3.1)"] +redis = ["redis (>=4.2)"] +sqlite = ["aiosqlite (>=0.20)"] + +[[package]] +name = "aiosignal" +version = "1.3.2" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "aiosqlite" +version = "0.20.0" +description = "asyncio bridge to the standard sqlite3 module" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"}, + {file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"}, +] + +[package.dependencies] +typing_extensions = ">=4.0" + +[package.extras] +dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"] + +[[package]] +name = "annotated-types" +version = "0.7.0" +description = "Reusable constraint types to use with typing.Annotated" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, + {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, +] + +[[package]] +name = "anyio" +version = "4.9.0" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, + {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, +] + +[package.dependencies] +exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} +idna = ">=2.8" +sniffio = ">=1.1" +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} + +[package.extras] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] +trio = ["trio (>=0.26.1)"] + +[[package]] +name = "arch" +version = "7.2.0" +description = "ARCH for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "arch-7.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ae119847fae37bbbf7273b4aa1f709f49d4637497ab6a4d7e76417eb94cae4af"}, + {file = "arch-7.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb3cb8bcf7f28e3a18d4e2b6cb661a8dd883af844277633ebe533ee1df2cc12a"}, + {file = "arch-7.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0559cfe6da6e5c8627fcb84868de2aed14554ab155d717147a6d194ae3facd96"}, + {file = "arch-7.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63128bf500d67688532f3192bdb3fb67aa1231cb7ed56cbb5d4fce4c83db225e"}, + {file = "arch-7.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8f8aa11ab6e276d9e47fad412837eba97963b5b22a7686f04913a2f59a053655"}, + {file = "arch-7.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:d817acc22ee44f7bb227a88c494d12e74ad27ff0d045b59be36054b13d78c8a0"}, + {file = "arch-7.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b4c46704a811438d37f84b7e82b933de3666d8a482c062285e402f7b8411be7"}, + {file = "arch-7.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7363070f4733db29abeec305564b591f6075ed155ba210ebc45355ae8701f352"}, + {file = "arch-7.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3473ef75eedb0625fa396bc7d8a1d099eed9d462710268d1c1c11e4d6dd3891b"}, + {file = "arch-7.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dde9c7cd52370b53cc1f7df499a855dc25a83c1e69f64155210d16d436c1bc76"}, + {file = "arch-7.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc8abccb095d285c3e049c015ed8f2859196eb0e53f83ea8ec76e16ec114b4e8"}, + {file = "arch-7.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:74bf2768be83c171066c397640be5d937baf7395c8809f1d5fc843563cf47bbf"}, + {file = "arch-7.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb08153a2e38300f5244b9c31a179c8dff6c0c79539f2995c428c09a8b24121a"}, + {file = "arch-7.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2013c7be5a24aefd08b1eb88fb9dec0e05a8cea515669e384249b76bf1357768"}, + {file = "arch-7.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15d2069ff223bfcd9851e664da47103c7144a00f21a9de91414f233f981c048a"}, + {file = "arch-7.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c8939ebc83dea0cf22423669b4f523cc47a7f3f5d7af9d7af179aaf21c566f3"}, + {file = "arch-7.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a70170660dc0e97c980f4f5e75e5e5169f5d3c9a432b686e92994f0e5c376d38"}, + {file = "arch-7.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:f374d113f44e5f0a7cf7a4a5b08c2ebc603990750fa24f472a00ede96d8cf02e"}, + {file = "arch-7.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2a0c9a02afd470b3c7c0a507a6466ace7bf314bf0977818c58dff1d7a0b165cc"}, + {file = "arch-7.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e5251808057282b614bb588c704106e9ea3fcef11dda142d05dbf5f6a95745f6"}, + {file = "arch-7.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16e54bb679fbab801f788ba8a9ed93929e453b0f471501bbad8a674290ddeab0"}, + {file = "arch-7.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4523f9357de7c8f064610cda669d3da411d879ca3db7fff5851ee3f9a55d9d6"}, + {file = "arch-7.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d45fb7ddc8376e7e2b4037297f526da1d1dd75190cab9add01e1db7194b51ca"}, + {file = "arch-7.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7ea8a89e1f63222f28f49925e8d30886478161124d862dcc840e66e6bb986301"}, + {file = "arch-7.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c60e1fbaa4a286d7658ea577e0cbbd925b1ea33eb40cfc2cb770fb83c4c141c"}, + {file = "arch-7.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:55eef0542a3fc1c2e7bd958bb924c712f1796ff7baa51d510f97a5437a294c79"}, + {file = "arch-7.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8ffdfbfb6bf855fa9eb20b66ba328c1556790f2b9f37ced21c47c412f92541ae"}, + {file = "arch-7.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24977b2969225bbe039f1816253b1c06b1a03ad64a904679e2a1762bac03c0e4"}, + {file = "arch-7.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:d7f7d50c3122e58540c530c333b25f9c50317a32dc43dedfd105f07712c5d4ec"}, + {file = "arch-7.2.0.tar.gz", hash = "sha256:2c6128174e0e3ab4c2cad527e78eb3fbce4a4f8ba53580bdf0d02566eb2e953d"}, +] + +[package.dependencies] +numpy = ">=1.22.3" +pandas = ">=1.4" +scipy = ">=1.8" +statsmodels = ">=0.12" + +[[package]] +name = "async-lru" +version = "2.0.5" +description = "Simple LRU cache for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943"}, + {file = "async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb"}, +] + +[package.dependencies] +typing_extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + +[[package]] +name = "backoff" +version = "2.2.1" +description = "Function decoration for backoff and retry" +optional = false +python-versions = ">=3.7,<4.0" +groups = ["main"] +files = [ + {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, + {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, +] + +[[package]] +name = "beautifulsoup4" +version = "4.13.4" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["main"] +files = [ + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "cattrs" +version = "24.1.3" +description = "Composable complex class support for attrs and dataclasses." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "cattrs-24.1.3-py3-none-any.whl", hash = "sha256:adf957dddd26840f27ffbd060a6c4dd3b2192c5b7c2c0525ef1bd8131d8a83f5"}, + {file = "cattrs-24.1.3.tar.gz", hash = "sha256:981a6ef05875b5bb0c7fb68885546186d306f10f0f6718fe9b96c226e68821ff"}, +] + +[package.dependencies] +attrs = ">=23.1.0" +exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""} + +[package.extras] +bson = ["pymongo (>=4.4.0)"] +cbor2 = ["cbor2 (>=5.4.6)"] +msgpack = ["msgpack (>=1.0.5)"] +msgspec = ["msgspec (>=0.18.5) ; implementation_name == \"cpython\""] +orjson = ["orjson (>=3.9.2) ; implementation_name == \"cpython\""] +pyyaml = ["pyyaml (>=6.0)"] +tomlkit = ["tomlkit (>=0.11.8)"] +ujson = ["ujson (>=5.7.0)"] + +[[package]] +name = "certifi" +version = "2025.4.26" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, + {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, + {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, + {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, + {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, + {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, + {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, + {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, + {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, + {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, + {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, +] + +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main"] +markers = "platform_system == \"Windows\" or sys_platform == \"win32\"" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "courlan" +version = "1.3.2" +description = "Clean, filter and sample URLs to optimize data collection – includes spam, content type and language filters." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be"}, + {file = "courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190"}, +] + +[package.dependencies] +babel = ">=2.16.0" +tld = ">=0.13" +urllib3 = ">=1.26,<3" + +[package.extras] +dev = ["black", "flake8", "mypy", "pytest", "pytest-cov", "types-urllib3"] + +[[package]] +name = "cython" +version = "3.0.12" +description = "The Cython compiler for writing C extensions in the Python language." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" +groups = ["main"] +files = [ + {file = "Cython-3.0.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba67eee9413b66dd9fbacd33f0bc2e028a2a120991d77b5fd4b19d0b1e4039b9"}, + {file = "Cython-3.0.12-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee2717e5b5f7d966d0c6e27d2efe3698c357aa4d61bb3201997c7a4f9fe485a"}, + {file = "Cython-3.0.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cffc3464f641c8d0dda942c7c53015291beea11ec4d32421bed2f13b386b819"}, + {file = "Cython-3.0.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d3a8f81980ffbd74e52f9186d8f1654e347d0c44bfea6b5997028977f481a179"}, + {file = "Cython-3.0.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8d32856716c369d01f2385ad9177cdd1a11079ac89ea0932dc4882de1aa19174"}, + {file = "Cython-3.0.12-cp310-cp310-win32.whl", hash = "sha256:712c3f31adec140dc60d064a7f84741f50e2c25a8edd7ae746d5eb4d3ef7072a"}, + {file = "Cython-3.0.12-cp310-cp310-win_amd64.whl", hash = "sha256:d6945694c5b9170cfbd5f2c0d00ef7487a2de7aba83713a64ee4ebce7fad9e05"}, + {file = "Cython-3.0.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feb86122a823937cc06e4c029d80ff69f082ebb0b959ab52a5af6cdd271c5dc3"}, + {file = "Cython-3.0.12-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfdbea486e702c328338314adb8e80f5f9741f06a0ae83aaec7463bc166d12e8"}, + {file = "Cython-3.0.12-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563de1728c8e48869d2380a1b76bbc1b1b1d01aba948480d68c1d05e52d20c92"}, + {file = "Cython-3.0.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398d4576c1e1f6316282aa0b4a55139254fbed965cba7813e6d9900d3092b128"}, + {file = "Cython-3.0.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1e5eadef80143026944ea8f9904715a008f5108d1d644a89f63094cc37351e73"}, + {file = "Cython-3.0.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5a93cbda00a5451175b97dea5a9440a3fcee9e54b4cba7a7dbcba9a764b22aec"}, + {file = "Cython-3.0.12-cp311-cp311-win32.whl", hash = "sha256:3109e1d44425a2639e9a677b66cd7711721a5b606b65867cb2d8ef7a97e2237b"}, + {file = "Cython-3.0.12-cp311-cp311-win_amd64.whl", hash = "sha256:d4b70fc339adba1e2111b074ee6119fe9fd6072c957d8597bce9a0dd1c3c6784"}, + {file = "Cython-3.0.12-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fe030d4a00afb2844f5f70896b7f2a1a0d7da09bf3aa3d884cbe5f73fff5d310"}, + {file = "Cython-3.0.12-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7fec4f052b8fe173fe70eae75091389955b9a23d5cec3d576d21c5913b49d47"}, + {file = "Cython-3.0.12-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0faa5e39e5c8cdf6f9c3b1c3f24972826e45911e7f5b99cf99453fca5432f45e"}, + {file = "Cython-3.0.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d53de996ed340e9ab0fc85a88aaa8932f2591a2746e1ab1c06e262bd4ec4be7"}, + {file = "Cython-3.0.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea3a0e19ab77266c738aa110684a753a04da4e709472cadeff487133354d6ab8"}, + {file = "Cython-3.0.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c151082884be468f2f405645858a857298ac7f7592729e5b54788b5c572717ba"}, + {file = "Cython-3.0.12-cp312-cp312-win32.whl", hash = "sha256:3083465749911ac3b2ce001b6bf17f404ac9dd35d8b08469d19dc7e717f5877a"}, + {file = "Cython-3.0.12-cp312-cp312-win_amd64.whl", hash = "sha256:c0b91c7ebace030dd558ea28730de8c580680b50768e5af66db2904a3716c3e3"}, + {file = "Cython-3.0.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4ee6f1ea1bead8e6cbc4e64571505b5d8dbdb3b58e679d31f3a84160cebf1a1a"}, + {file = "Cython-3.0.12-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57aefa6d3341109e46ec1a13e3a763aaa2cbeb14e82af2485b318194be1d9170"}, + {file = "Cython-3.0.12-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:879ae9023958d63c0675015369384642d0afb9c9d1f3473df9186c42f7a9d265"}, + {file = "Cython-3.0.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36fcd584dae547de6f095500a380f4a0cce72b7a7e409e9ff03cb9beed6ac7a1"}, + {file = "Cython-3.0.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62b79dcc0de49efe9e84b9d0e2ae0a6fc9b14691a65565da727aa2e2e63c6a28"}, + {file = "Cython-3.0.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4aa255781b093a8401109d8f2104bbb2e52de7639d5896aefafddc85c30e0894"}, + {file = "Cython-3.0.12-cp313-cp313-win32.whl", hash = "sha256:77d48f2d4bab9fe1236eb753d18f03e8b2619af5b6f05d51df0532a92dfb38ab"}, + {file = "Cython-3.0.12-cp313-cp313-win_amd64.whl", hash = "sha256:86c304b20bd57c727c7357e90d5ba1a2b6f1c45492de2373814d7745ef2e63b4"}, + {file = "Cython-3.0.12-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ff5c0b6a65b08117d0534941d404833d516dac422eee88c6b4fd55feb409a5ed"}, + {file = "Cython-3.0.12-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:680f1d6ed4436ae94805db264d6155ed076d2835d84f20dcb31a7a3ad7f8668c"}, + {file = "Cython-3.0.12-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc24609613fa06d0d896309f7164ba168f7e8d71c1e490ed2a08d23351c3f41"}, + {file = "Cython-3.0.12-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1879c073e2b34924ce9b7ca64c212705dcc416af4337c45f371242b2e5f6d32"}, + {file = "Cython-3.0.12-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:bfb75123dd4ff767baa37d7036da0de2dfb6781ff256eef69b11b88b9a0691d1"}, + {file = "Cython-3.0.12-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:f39640f8df0400cde6882e23c734f15bb8196de0a008ae5dc6c8d1ec5957d7c8"}, + {file = "Cython-3.0.12-cp36-cp36m-win32.whl", hash = "sha256:8c9efe9a0895abee3cadfdad4130b30f7b5e57f6e6a51ef2a44f9fc66a913880"}, + {file = "Cython-3.0.12-cp36-cp36m-win_amd64.whl", hash = "sha256:63d840f2975e44d74512f8f34f1f7cb8121c9428e26a3f6116ff273deb5e60a2"}, + {file = "Cython-3.0.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:75c5acd40b97cff16fadcf6901a91586cbca5dcdba81f738efaf1f4c6bc8dccb"}, + {file = "Cython-3.0.12-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e62564457851db1c40399bd95a5346b9bb99e17a819bf583b362f418d8f3457a"}, + {file = "Cython-3.0.12-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ccd1228cc203b1f1b8a3d403f5a20ad1c40e5879b3fbf5851ce09d948982f2c"}, + {file = "Cython-3.0.12-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25529ee948f44d9a165ff960c49d4903267c20b5edf2df79b45924802e4cca6e"}, + {file = "Cython-3.0.12-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:90cf599372c5a22120609f7d3a963f17814799335d56dd0dcf8fe615980a8ae1"}, + {file = "Cython-3.0.12-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9f8c48748a9c94ea5d59c26ab49ad0fad514d36f894985879cf3c3ca0e600bf4"}, + {file = "Cython-3.0.12-cp37-cp37m-win32.whl", hash = "sha256:3e4fa855d98bc7bd6a2049e0c7dc0dcf595e2e7f571a26e808f3efd84d2db374"}, + {file = "Cython-3.0.12-cp37-cp37m-win_amd64.whl", hash = "sha256:120681093772bf3600caddb296a65b352a0d3556e962b9b147efcfb8e8c9801b"}, + {file = "Cython-3.0.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:731d719423e041242c9303c80cae4327467299b90ffe62d4cc407e11e9ea3160"}, + {file = "Cython-3.0.12-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3238a29f37999e27494d120983eca90d14896b2887a0bd858a381204549137a"}, + {file = "Cython-3.0.12-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b588c0a089a9f4dd316d2f9275230bad4a7271e5af04e1dc41d2707c816be44b"}, + {file = "Cython-3.0.12-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab9f5198af74eb16502cc143cdde9ca1cbbf66ea2912e67440dd18a36e3b5fa"}, + {file = "Cython-3.0.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8ee841c0e114efa1e849c281ac9b8df8aa189af10b4a103b1c5fd71cbb799679"}, + {file = "Cython-3.0.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:43c48b5789398b228ea97499f5b864843ba9b1ab837562a9227c6f58d16ede8b"}, + {file = "Cython-3.0.12-cp38-cp38-win32.whl", hash = "sha256:5e5f17c48a4f41557fbcc7ee660ccfebe4536a34c557f553b6893c1b3c83df2d"}, + {file = "Cython-3.0.12-cp38-cp38-win_amd64.whl", hash = "sha256:309c081057930bb79dc9ea3061a1af5086c679c968206e9c9c2ec90ab7cb471a"}, + {file = "Cython-3.0.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54115fcc126840926ff3b53cfd2152eae17b3522ae7f74888f8a41413bd32f25"}, + {file = "Cython-3.0.12-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:629db614b9c364596d7c975fa3fb3978e8c5349524353dbe11429896a783fc1e"}, + {file = "Cython-3.0.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af081838b0f9e12a83ec4c3809a00a64c817f489f7c512b0e3ecaf5f90a2a816"}, + {file = "Cython-3.0.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:34ce459808f7d8d5d4007bc5486fe50532529096b43957af6cbffcb4d9cc5c8d"}, + {file = "Cython-3.0.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d6c6cd6a75c8393e6805d17f7126b96a894f310a1a9ea91c47d141fb9341bfa8"}, + {file = "Cython-3.0.12-cp39-cp39-win32.whl", hash = "sha256:a4032e48d4734d2df68235d21920c715c451ac9de15fa14c71b378e8986b83be"}, + {file = "Cython-3.0.12-cp39-cp39-win_amd64.whl", hash = "sha256:dcdc3e5d4ce0e7a4af6903ed580833015641e968d18d528d8371e2435a34132c"}, + {file = "Cython-3.0.12-py2.py3-none-any.whl", hash = "sha256:0038c9bae46c459669390e53a1ec115f8096b2e4647ae007ff1bf4e6dee92806"}, + {file = "cython-3.0.12.tar.gz", hash = "sha256:b988bb297ce76c671e28c97d017b95411010f7c77fa6623dd0bb47eed1aee1bc"}, +] + +[[package]] +name = "dateparser" +version = "1.2.1" +description = "Date parsing library designed to parse dates from HTML pages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "dateparser-1.2.1-py3-none-any.whl", hash = "sha256:bdcac262a467e6260030040748ad7c10d6bacd4f3b9cdb4cfd2251939174508c"}, + {file = "dateparser-1.2.1.tar.gz", hash = "sha256:7e4919aeb48481dbfc01ac9683c8e20bfe95bb715a38c1e9f6af889f4f30ccc3"}, +] + +[package.dependencies] +python-dateutil = ">=2.7.0" +pytz = ">=2024.2" +regex = ">=2015.06.24,<2019.02.19 || >2019.02.19,<2021.8.27 || >2021.8.27" +tzlocal = ">=0.2" + +[package.extras] +calendars = ["convertdate (>=2.2.1)", "hijridate"] +fasttext = ["fasttext (>=0.9.1)", "numpy (>=1.19.3,<2)"] +langdetect = ["langdetect (>=1.0.0)"] + +[[package]] +name = "deepdiff" +version = "8.4.2" +description = "Deep Difference and Search of any Python object/data. Recreate objects by adding adding deltas to each other." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "deepdiff-8.4.2-py3-none-any.whl", hash = "sha256:7e39e5b26f3747c54f9d0e8b9b29daab670c3100166b77cc0185d5793121b099"}, + {file = "deepdiff-8.4.2.tar.gz", hash = "sha256:5c741c0867ebc7fcb83950ad5ed958369c17f424e14dee32a11c56073f4ee92a"}, +] + +[package.dependencies] +orderly-set = ">=5.3.0,<6" + +[package.extras] +cli = ["click (==8.1.8)", "pyyaml (==6.0.2)"] +optimize = ["orjson"] + +[[package]] +name = "defusedxml" +version = "0.8.0rc2" +description = "XML bomb protection for Python stdlib modules" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "defusedxml-0.8.0rc2-py2.py3-none-any.whl", hash = "sha256:1c812964311154c3bf4aaf3bc1443b31ee13530b7f255eaaa062c0553c76103d"}, + {file = "defusedxml-0.8.0rc2.tar.gz", hash = "sha256:138c7d540a78775182206c7c97fe65b246a2f40b29471e1a2f1b0da76e7a3942"}, +] + +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "et-xmlfile" +version = "2.0.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, + {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "exchange-calendars" +version = "4.5.6" +description = "Calendars for securities exchanges" +optional = false +python-versions = "~=3.9" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "exchange_calendars-4.5.6-py3-none-any.whl", hash = "sha256:5abf5ebcb8ceef0ced36fe4e20071d42517091bf081e6c44354cb343009d672b"}, + {file = "exchange_calendars-4.5.6.tar.gz", hash = "sha256:5db77178cf849f81dd6dcc99995e2163b928c0f45dcd0a2c395958beb1dbb145"}, +] + +[package.dependencies] +korean-lunar-calendar = "*" +numpy = "*" +pandas = ">=1.5" +pyluach = "*" +toolz = "*" +tzdata = "*" + +[package.extras] +dev = ["flake8", "hypothesis", "pip-tools", "pytest", "pytest-benchmark", "pytest-xdist"] + +[[package]] +name = "exchange-calendars" +version = "4.10" +description = "Calendars for securities exchanges" +optional = false +python-versions = "~=3.10" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "exchange_calendars-4.10-py3-none-any.whl", hash = "sha256:f2fbc3696b334db7bb5cc786dc77ae4d48b00dccfbb55fcb2ca3ea2a88b8886e"}, + {file = "exchange_calendars-4.10.tar.gz", hash = "sha256:3f25007e934c7e8c6f0fd43f4fd7959e64505f8aa6b1eb4d6c5da71ab8af054b"}, +] + +[package.dependencies] +korean_lunar_calendar = "*" +numpy = "*" +pandas = "*" +pyluach = "*" +toolz = "*" +tzdata = "*" + +[package.extras] +dev = ["flake8", "hypothesis", "pip-tools", "pytest", "pytest-benchmark", "pytest-xdist"] + +[[package]] +name = "fastapi" +version = "0.115.12" +description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, + {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, +] + +[package.dependencies] +pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" +starlette = ">=0.40.0,<0.47.0" +typing-extensions = ">=4.8.0" + +[package.extras] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] + +[[package]] +name = "fastjsonschema" +version = "2.21.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, + {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "finvizfinance" +version = "1.1.0" +description = "Finviz Finance. Information downloader." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "finvizfinance-1.1.0-py3-none-any.whl", hash = "sha256:d5a75f0f61ecb2ab2bafdcc17d29a06bead603479269d915e58020f308ff0bf5"}, + {file = "finvizfinance-1.1.0.tar.gz", hash = "sha256:4d08a40b62e5bec0150548a7d1e3e3c85e32de0a94df329fa06a7f792065e274"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +lxml = "*" +pandas = "*" +requests = "*" + +[[package]] +name = "formulaic" +version = "1.1.1" +description = "An implementation of Wilkinson formulas." +optional = false +python-versions = ">=3.7.2" +groups = ["main"] +files = [ + {file = "formulaic-1.1.1-py3-none-any.whl", hash = "sha256:bbb7e38f99e4bcdc62cb0a6a818ad33b370b4e98e9e4f0b276561448482c8268"}, + {file = "formulaic-1.1.1.tar.gz", hash = "sha256:ddf80e4bef976dd99698aa27512015276c7b86c314b601ae6fd360c7741b7231"}, +] + +[package.dependencies] +interface-meta = ">=1.2.0" +numpy = ">=1.16.5" +pandas = ">=1.0" +scipy = ">=1.6" +typing-extensions = ">=4.2.0" +wrapt = {version = ">=1.0", markers = "python_version < \"3.13\""} + +[package.extras] +arrow = ["pyarrow (>=1)"] +calculus = ["sympy (>=1.3,!=1.10)"] + +[[package]] +name = "freezegun" +version = "1.5.1" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + +[[package]] +name = "frozendict" +version = "2.4.6" +description = "A simple immutable dictionary" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "frozendict-2.4.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c3a05c0a50cab96b4bb0ea25aa752efbfceed5ccb24c007612bc63e51299336f"}, + {file = "frozendict-2.4.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f5b94d5b07c00986f9e37a38dd83c13f5fe3bf3f1ccc8e88edea8fe15d6cd88c"}, + {file = "frozendict-2.4.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4c789fd70879ccb6289a603cdebdc4953e7e5dea047d30c1b180529b28257b5"}, + {file = "frozendict-2.4.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da6a10164c8a50b34b9ab508a9420df38f4edf286b9ca7b7df8a91767baecb34"}, + {file = "frozendict-2.4.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9a8a43036754a941601635ea9c788ebd7a7efbed2becba01b54a887b41b175b9"}, + {file = "frozendict-2.4.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9905dcf7aa659e6a11b8051114c9fa76dfde3a6e50e6dc129d5aece75b449a2"}, + {file = "frozendict-2.4.6-cp310-cp310-win_amd64.whl", hash = "sha256:323f1b674a2cc18f86ab81698e22aba8145d7a755e0ac2cccf142ee2db58620d"}, + {file = "frozendict-2.4.6-cp310-cp310-win_arm64.whl", hash = "sha256:eabd21d8e5db0c58b60d26b4bb9839cac13132e88277e1376970172a85ee04b3"}, + {file = "frozendict-2.4.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:eddabeb769fab1e122d3a6872982c78179b5bcc909fdc769f3cf1964f55a6d20"}, + {file = "frozendict-2.4.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:377a65be0a700188fc21e669c07de60f4f6d35fae8071c292b7df04776a1c27b"}, + {file = "frozendict-2.4.6-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce1e9217b85eec6ba9560d520d5089c82dbb15f977906eb345d81459723dd7e3"}, + {file = "frozendict-2.4.6-cp36-cp36m-musllinux_1_2_aarch64.whl", hash = "sha256:7291abacf51798d5ffe632771a69c14fb423ab98d63c4ccd1aa382619afe2f89"}, + {file = "frozendict-2.4.6-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:e72fb86e48811957d66ffb3e95580af7b1af1e6fbd760ad63d7bd79b2c9a07f8"}, + {file = "frozendict-2.4.6-cp36-cp36m-win_amd64.whl", hash = "sha256:622301b1c29c4f9bba633667d592a3a2b093cb408ba3ce578b8901ace3931ef3"}, + {file = "frozendict-2.4.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a4e3737cb99ed03200cd303bdcd5514c9f34b29ee48f405c1184141bd68611c9"}, + {file = "frozendict-2.4.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49ffaf09241bc1417daa19362a2241a4aa435f758fd4375c39ce9790443a39cd"}, + {file = "frozendict-2.4.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d69418479bfb834ba75b0e764f058af46ceee3d655deb6a0dd0c0c1a5e82f09"}, + {file = "frozendict-2.4.6-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:c131f10c4d3906866454c4e89b87a7e0027d533cce8f4652aa5255112c4d6677"}, + {file = "frozendict-2.4.6-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:fc67cbb3c96af7a798fab53d52589752c1673027e516b702ab355510ddf6bdff"}, + {file = "frozendict-2.4.6-cp37-cp37m-win_amd64.whl", hash = "sha256:7730f8ebe791d147a1586cbf6a42629351d4597773317002181b66a2da0d509e"}, + {file = "frozendict-2.4.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:807862e14b0e9665042458fde692c4431d660c4219b9bb240817f5b918182222"}, + {file = "frozendict-2.4.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9647c74efe3d845faa666d4853cfeabbaee403b53270cabfc635b321f770e6b8"}, + {file = "frozendict-2.4.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:665fad3f0f815aa41294e561d98dbedba4b483b3968e7e8cab7d728d64b96e33"}, + {file = "frozendict-2.4.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f42e6b75254ea2afe428ad6d095b62f95a7ae6d4f8272f0bd44a25dddd20f67"}, + {file = "frozendict-2.4.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:02331541611f3897f260900a1815b63389654951126e6e65545e529b63c08361"}, + {file = "frozendict-2.4.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:18d50a2598350b89189da9150058191f55057581e40533e470db46c942373acf"}, + {file = "frozendict-2.4.6-cp38-cp38-win_amd64.whl", hash = "sha256:1b4a3f8f6dd51bee74a50995c39b5a606b612847862203dd5483b9cd91b0d36a"}, + {file = "frozendict-2.4.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a76cee5c4be2a5d1ff063188232fffcce05dde6fd5edd6afe7b75b247526490e"}, + {file = "frozendict-2.4.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ba5ef7328706db857a2bdb2c2a17b4cd37c32a19c017cff1bb7eeebc86b0f411"}, + {file = "frozendict-2.4.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669237c571856be575eca28a69e92a3d18f8490511eff184937283dc6093bd67"}, + {file = "frozendict-2.4.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0aaa11e7c472150efe65adbcd6c17ac0f586896096ab3963775e1c5c58ac0098"}, + {file = "frozendict-2.4.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b8f2829048f29fe115da4a60409be2130e69402e29029339663fac39c90e6e2b"}, + {file = "frozendict-2.4.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:94321e646cc39bebc66954a31edd1847d3a2a3483cf52ff051cd0996e7db07db"}, + {file = "frozendict-2.4.6-cp39-cp39-win_amd64.whl", hash = "sha256:74b6b26c15dddfefddeb89813e455b00ebf78d0a3662b89506b4d55c6445a9f4"}, + {file = "frozendict-2.4.6-cp39-cp39-win_arm64.whl", hash = "sha256:7088102345d1606450bd1801a61139bbaa2cb0d805b9b692f8d81918ea835da6"}, + {file = "frozendict-2.4.6-py311-none-any.whl", hash = "sha256:d065db6a44db2e2375c23eac816f1a022feb2fa98cbb50df44a9e83700accbea"}, + {file = "frozendict-2.4.6-py312-none-any.whl", hash = "sha256:49344abe90fb75f0f9fdefe6d4ef6d4894e640fadab71f11009d52ad97f370b9"}, + {file = "frozendict-2.4.6-py313-none-any.whl", hash = "sha256:7134a2bb95d4a16556bb5f2b9736dceb6ea848fa5b6f3f6c2d6dba93b44b4757"}, + {file = "frozendict-2.4.6.tar.gz", hash = "sha256:df7cd16470fbd26fc4969a208efadc46319334eb97def1ddf48919b351192b8e"}, +] + +[[package]] +name = "frozenlist" +version = "1.6.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9799257237d0479736e2b4c01ff26b5c7f7694ac9692a426cb717f3dc02fff9b"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a7bb0fe1f7a70fb5c6f497dc32619db7d2cdd53164af30ade2f34673f8b1fc"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:36d2fc099229f1e4237f563b2a3e0ff7ccebc3999f729067ce4e64a97a7f2869"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f27a9f9a86dcf00708be82359db8de86b80d029814e6693259befe82bb58a106"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ecee69073312951244f11b8627e3700ec2bfe07ed24e3a685a5979f0412d24"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2c7d5aa19714b1b01a0f515d078a629e445e667b9da869a3cd0e6fe7dec78bd"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69bbd454f0fb23b51cadc9bdba616c9678e4114b6f9fa372d462ff2ed9323ec8"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7daa508e75613809c7a57136dec4871a21bca3080b3a8fc347c50b187df4f00c"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:89ffdb799154fd4d7b85c56d5fa9d9ad48946619e0eb95755723fffa11022d75"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:920b6bd77d209931e4c263223381d63f76828bec574440f29eb497cf3394c249"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d3ceb265249fb401702fce3792e6b44c1166b9319737d21495d3611028d95769"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52021b528f1571f98a7d4258c58aa8d4b1a96d4f01d00d51f1089f2e0323cb02"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0f2ca7810b809ed0f1917293050163c7654cefc57a49f337d5cd9de717b8fad3"}, + {file = "frozenlist-1.6.0-cp310-cp310-win32.whl", hash = "sha256:0e6f8653acb82e15e5443dba415fb62a8732b68fe09936bb6d388c725b57f812"}, + {file = "frozenlist-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a39819a5a3e84304cd286e3dc62a549fe60985415851b3337b6f5cc91907f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e"}, + {file = "frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860"}, + {file = "frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770"}, + {file = "frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc"}, + {file = "frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e"}, + {file = "frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4"}, + {file = "frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:536a1236065c29980c15c7229fbb830dedf809708c10e159b8136534233545f0"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ed5e3a4462ff25ca84fb09e0fada8ea267df98a450340ead4c91b44857267d70"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e19c0fc9f4f030fcae43b4cdec9e8ab83ffe30ec10c79a4a43a04d1af6c5e1ad"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c608f833897501dac548585312d73a7dca028bf3b8688f0d712b7acfaf7fb3"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0dbae96c225d584f834b8d3cc688825911960f003a85cb0fd20b6e5512468c42"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:625170a91dd7261a1d1c2a0c1a353c9e55d21cd67d0852185a5fef86587e6f5f"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1db8b2fc7ee8a940b547a14c10e56560ad3ea6499dc6875c354e2335812f739d"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4da6fc43048b648275a220e3a61c33b7fff65d11bdd6dcb9d9c145ff708b804c"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef8e7e8f2f3820c5f175d70fdd199b79e417acf6c72c5d0aa8f63c9f721646f"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa733d123cc78245e9bb15f29b44ed9e5780dc6867cfc4e544717b91f980af3b"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ba7f8d97152b61f22d7f59491a781ba9b177dd9f318486c5fbc52cde2db12189"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:56a0b8dd6d0d3d971c91f1df75e824986667ccce91e20dca2023683814344791"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5c9e89bf19ca148efcc9e3c44fd4c09d5af85c8a7dd3dbd0da1cb83425ef4983"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1330f0a4376587face7637dfd245380a57fe21ae8f9d360c1c2ef8746c4195fa"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2187248203b59625566cac53572ec8c2647a140ee2738b4e36772930377a533c"}, + {file = "frozenlist-1.6.0-cp39-cp39-win32.whl", hash = "sha256:2b8cf4cfea847d6c12af06091561a89740f1f67f331c3fa8623391905e878530"}, + {file = "frozenlist-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:1255d5d64328c5a0d066ecb0f02034d086537925f1f04b50b1ae60d37afbf572"}, + {file = "frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191"}, + {file = "frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68"}, +] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml ; platform_python_implementation == \"CPython\""] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "htmldate" +version = "1.9.3" +description = "Fast and robust extraction of original and updated publication dates from URLs and web pages." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "htmldate-1.9.3-py3-none-any.whl", hash = "sha256:3fadc422cf3c10a5cdb5e1b914daf37ec7270400a80a1b37e2673ff84faaaff8"}, + {file = "htmldate-1.9.3.tar.gz", hash = "sha256:ac0caf4628c3ded4042011e2d60dc68dfb314c77b106587dd307a80d77e708e9"}, +] + +[package.dependencies] +charset_normalizer = ">=3.4.0" +dateparser = ">=1.1.2" +lxml = {version = ">=5.3.0,<6", markers = "platform_system != \"Darwin\" or python_version > \"3.8\""} +python-dateutil = ">=2.9.0.post0" +urllib3 = ">=1.26,<3" + +[package.extras] +all = ["htmldate[dev]", "htmldate[speed]"] +dev = ["black", "flake8", "mypy", "pytest", "pytest-cov", "types-dateparser", "types-lxml", "types-python-dateutil", "types-urllib3"] +speed = ["backports-datetime-fromisoformat ; python_version < \"3.11\"", "faust-cchardet (>=2.1.19)", "urllib3[brotli]"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-metadata" +version = "8.7.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "inflection" +version = "0.5.1" +description = "A port of Ruby on Rails inflector to Python" +optional = false +python-versions = ">=3.5" +groups = ["main"] +files = [ + {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, + {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "inscriptis" +version = "2.6.0" +description = "inscriptis - HTML to text converter." +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "inscriptis-2.6.0-py3-none-any.whl", hash = "sha256:654dbcd0551c2f6004f8069a05cafff3eed2d327d5057adc6e657ba2610f52af"}, + {file = "inscriptis-2.6.0.tar.gz", hash = "sha256:6f164bf45ea6972d61fd048a8e074d5125d215eaa837f8e70c158c97c31c3181"}, +] + +[package.dependencies] +lxml = ">=4.9.3" +requests = ">=2.32.2" + +[package.extras] +web-service = ["fastapi (>=0.115.11,<0.116.0)", "uvicorn (>=0.34.0,<0.35.0)"] + +[[package]] +name = "interface-meta" +version = "1.3.0" +description = "`interface_meta` provides a convenient way to expose an extensible API with enforced method signatures and consistent documentation." +optional = false +python-versions = ">=3.7,<4.0" +groups = ["main"] +files = [ + {file = "interface_meta-1.3.0-py3-none-any.whl", hash = "sha256:de35dc5241431886e709e20a14d6597ed07c9f1e8b4bfcffde2190ca5b700ee8"}, + {file = "interface_meta-1.3.0.tar.gz", hash = "sha256:8a4493f8bdb73fb9655dcd5115bc897e207319e36c8835f39c516a2d7e9d79a1"}, +] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, + {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "justext" +version = "3.0.2" +description = "Heuristic based boilerplate removal tool" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7"}, + {file = "justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05"}, +] + +[package.dependencies] +lxml = {version = ">=4.4.2", extras = ["html-clean"]} + +[[package]] +name = "korean-lunar-calendar" +version = "0.3.1" +description = "Korean Lunar Calendar" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "korean_lunar_calendar-0.3.1-py3-none-any.whl", hash = "sha256:392757135c492c4f42a604e6038042953c35c6f449dda5f27e3f86a7f9c943e5"}, + {file = "korean_lunar_calendar-0.3.1.tar.gz", hash = "sha256:eb2c485124a061016926bdea6d89efdf9b9fdbf16db55895b6cf1e5bec17b857"}, +] + +[[package]] +name = "linearmodels" +version = "6.1" +description = "Linear Panel, Instrumental Variable, Asset Pricing, and System Regression models for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "linearmodels-6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c9ab6f960fbd3060bccd28a20d9d4e29acda09158c1577e930c8c862af51a4a7"}, + {file = "linearmodels-6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:263e4d2bda42240a0e380a806296ca54bb5f1e10a293f81b8a2a142f7b6512d3"}, + {file = "linearmodels-6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1a2b33b10b5f9844219feb4e21b509cbaa923b3acc5456881f25b1504cbce8"}, + {file = "linearmodels-6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39b2445a4c75f8e5ce663d2219e5f34adeb110bccf40fd54c0b5106366fb0ab1"}, + {file = "linearmodels-6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fe72fff0ce415727a5a56f3c30b68b2493f1453fe3ad994942177f8e99a44a6a"}, + {file = "linearmodels-6.1-cp310-cp310-win_amd64.whl", hash = "sha256:e3b260dfdf8ba7f47d478d4cb37fb9743719166901e837f7686b014ab416e9ef"}, + {file = "linearmodels-6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c31fc62766a088a91969ad4fedf5c95eb5176fee67d595178642a2ebdc8757ce"}, + {file = "linearmodels-6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d68d09deda6a88134c2a37f5f3d9c9da01e999e7ec0520736d73365f5f438cd"}, + {file = "linearmodels-6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:151d48882005843935bf42fe9bd3b6ba3043320319701176a1f49db04a3b015a"}, + {file = "linearmodels-6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2688c1f359171b9a54ae4f1c9f5aae9858f878fc40c6cb647a3a76bdccafd6a7"}, + {file = "linearmodels-6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17822f49bbc02b4aea748c8be0fe86ac2bcd928a6f43566cd3a0d19cc61a1606"}, + {file = "linearmodels-6.1-cp311-cp311-win_amd64.whl", hash = "sha256:89bb4fdfa4aecad4f743fc06f9014c702a4a98a7ec5ad005cbaa6798ffad8381"}, + {file = "linearmodels-6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:39ef5f2a9280b6a11b4be073d860a1f2e0b4b7ee98a2fb07cfe903b5faa96e00"}, + {file = "linearmodels-6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f872ad46571f8f10f4d37006a2561470c42f6bc0553b717bae4bb1233951ae1"}, + {file = "linearmodels-6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:061788d634991d1bccf5f62cb6f7abcea15cdb4e66a4b1861f13e6ba9915c4ab"}, + {file = "linearmodels-6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04cee9532a1c3fa583dc906e0da575f43be6bb8b2078ed7a09282c0d47a7304b"}, + {file = "linearmodels-6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce5f44b5c1ff4110c69f02f2a41afec2cd46ed5e135c7adfb929322d82369fca"}, + {file = "linearmodels-6.1-cp312-cp312-win_amd64.whl", hash = "sha256:18b827f96db5c7406bbdfe00dab386385b93e8b8727a6cc033e725f53dbfa066"}, + {file = "linearmodels-6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a9e6f96ec3b048265befa38069c66a3a2a98612afddf62cd6a95026af445b9c"}, + {file = "linearmodels-6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79f1bb320ff6a5ac0fc350989d5818a7cd1f888975b04f38a8c10b90b194d718"}, + {file = "linearmodels-6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08f612bd0c2968beae4016a79b8a802bd91fcafb7149bb918bffca0d766ea46a"}, + {file = "linearmodels-6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27671f6a25bbf81a731630e6a66c3befc955ecc82e402f08b067d61a1ebf2a"}, + {file = "linearmodels-6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f020b98e852006ab2731b5508c4190017075197cf8563f0cd81838edf4b05e7d"}, + {file = "linearmodels-6.1-cp313-cp313-win_amd64.whl", hash = "sha256:628be681f59a07da0848174974cc0d331fc5daf2367d37f27aec94b7e8e16e70"}, + {file = "linearmodels-6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9db86e757dfcd03e0c95a654fba72a7f5c9b42e1b7fe73dd240fc929aefa854"}, + {file = "linearmodels-6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8eb8f2290608bd8c8e7965dec22399cf498a38a70692bb5d5a5b0dbddca4658e"}, + {file = "linearmodels-6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f5a430361707ba79fb91fd4bf5acd85c7d4b41f0c964747d864ff3409bbfff6"}, + {file = "linearmodels-6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d81a96566087c61955db44e402e181484582300f7a05b3e27d65a87538ce0f3"}, + {file = "linearmodels-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:c342b0a6aa5819901cde646f4d6a9da3387aad40e49bed792fcb5e57b6624246"}, + {file = "linearmodels-6.1.tar.gz", hash = "sha256:74ead48a054bc1b3ebec8e8d7187f17504058891b70c2e090372b4759eeb3e89"}, +] + +[package.dependencies] +Cython = ">=3.0.10" +formulaic = ">=1.0.0" +mypy-extensions = ">=0.4" +numpy = ">=1.22.3,<3" +pandas = ">=1.4.0" +pyhdfe = ">=0.1" +scipy = ">=1.8.0" +setuptools-scm = {version = ">=8.0.0,<9.0.0", extras = ["toml"]} +statsmodels = ">=0.13.0" + +[[package]] +name = "llvmlite" +version = "0.43.0" +description = "lightweight wrapper around basic LLVM functionality" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761"}, + {file = "llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc"}, + {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead"}, + {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a"}, + {file = "llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed"}, + {file = "llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98"}, + {file = "llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57"}, + {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2"}, + {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749"}, + {file = "llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91"}, + {file = "llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7"}, + {file = "llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7"}, + {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f"}, + {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844"}, + {file = "llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9"}, + {file = "llvmlite-0.43.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cd2a7376f7b3367019b664c21f0c61766219faa3b03731113ead75107f3b66c"}, + {file = "llvmlite-0.43.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18e9953c748b105668487b7c81a3e97b046d8abf95c4ddc0cd3c94f4e4651ae8"}, + {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74937acd22dc11b33946b67dca7680e6d103d6e90eeaaaf932603bec6fe7b03a"}, + {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9efc739cc6ed760f795806f67889923f7274276f0eb45092a1473e40d9b867"}, + {file = "llvmlite-0.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:47e147cdda9037f94b399bf03bfd8a6b6b1f2f90be94a454e3386f006455a9b4"}, + {file = "llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5"}, +] + +[[package]] +name = "llvmlite" +version = "0.44.0" +description = "lightweight wrapper around basic LLVM functionality" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "llvmlite-0.44.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:9fbadbfba8422123bab5535b293da1cf72f9f478a65645ecd73e781f962ca614"}, + {file = "llvmlite-0.44.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cccf8eb28f24840f2689fb1a45f9c0f7e582dd24e088dcf96e424834af11f791"}, + {file = "llvmlite-0.44.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7202b678cdf904823c764ee0fe2dfe38a76981f4c1e51715b4cb5abb6cf1d9e8"}, + {file = "llvmlite-0.44.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40526fb5e313d7b96bda4cbb2c85cd5374e04d80732dd36a282d72a560bb6408"}, + {file = "llvmlite-0.44.0-cp310-cp310-win_amd64.whl", hash = "sha256:41e3839150db4330e1b2716c0be3b5c4672525b4c9005e17c7597f835f351ce2"}, + {file = "llvmlite-0.44.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3"}, + {file = "llvmlite-0.44.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ace564d9fa44bb91eb6e6d8e7754977783c68e90a471ea7ce913bff30bd62427"}, + {file = "llvmlite-0.44.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5d22c3bfc842668168a786af4205ec8e3ad29fb1bc03fd11fd48460d0df64c1"}, + {file = "llvmlite-0.44.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610"}, + {file = "llvmlite-0.44.0-cp311-cp311-win_amd64.whl", hash = "sha256:d8489634d43c20cd0ad71330dde1d5bc7b9966937a263ff1ec1cebb90dc50955"}, + {file = "llvmlite-0.44.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:1d671a56acf725bf1b531d5ef76b86660a5ab8ef19bb6a46064a705c6ca80aad"}, + {file = "llvmlite-0.44.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f79a728e0435493611c9f405168682bb75ffd1fbe6fc360733b850c80a026db"}, + {file = "llvmlite-0.44.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0143a5ef336da14deaa8ec26c5449ad5b6a2b564df82fcef4be040b9cacfea9"}, + {file = "llvmlite-0.44.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d752f89e31b66db6f8da06df8b39f9b91e78c5feea1bf9e8c1fba1d1c24c065d"}, + {file = "llvmlite-0.44.0-cp312-cp312-win_amd64.whl", hash = "sha256:eae7e2d4ca8f88f89d315b48c6b741dcb925d6a1042da694aa16ab3dd4cbd3a1"}, + {file = "llvmlite-0.44.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:319bddd44e5f71ae2689859b7203080716448a3cd1128fb144fe5c055219d516"}, + {file = "llvmlite-0.44.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c58867118bad04a0bb22a2e0068c693719658105e40009ffe95c7000fcde88e"}, + {file = "llvmlite-0.44.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:46224058b13c96af1365290bdfebe9a6264ae62fb79b2b55693deed11657a8bf"}, + {file = "llvmlite-0.44.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa0097052c32bf721a4efc03bd109d335dfa57d9bffb3d4c24cc680711b8b4fc"}, + {file = "llvmlite-0.44.0-cp313-cp313-win_amd64.whl", hash = "sha256:2fb7c4f2fb86cbae6dca3db9ab203eeea0e22d73b99bc2341cdf9de93612e930"}, + {file = "llvmlite-0.44.0.tar.gz", hash = "sha256:07667d66a5d150abed9157ab6c0b9393c9356f229784a4385c02f99e94fc94d4"}, +] + +[[package]] +name = "lxml" +version = "5.4.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e7bc6df34d42322c5289e37e9971d6ed114e3776b45fa879f734bded9d1fea9c"}, + {file = "lxml-5.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6854f8bd8a1536f8a1d9a3655e6354faa6406621cf857dc27b681b69860645c7"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:696ea9e87442467819ac22394ca36cb3d01848dad1be6fac3fb612d3bd5a12cf"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef80aeac414f33c24b3815ecd560cee272786c3adfa5f31316d8b349bfade28"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b9c2754cef6963f3408ab381ea55f47dabc6f78f4b8ebb0f0b25cf1ac1f7609"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a62cc23d754bb449d63ff35334acc9f5c02e6dae830d78dab4dd12b78a524f4"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f82125bc7203c5ae8633a7d5d20bcfdff0ba33e436e4ab0abc026a53a8960b7"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:b67319b4aef1a6c56576ff544b67a2a6fbd7eaee485b241cabf53115e8908b8f"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:a8ef956fce64c8551221f395ba21d0724fed6b9b6242ca4f2f7beb4ce2f41997"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:0a01ce7d8479dce84fc03324e3b0c9c90b1ece9a9bb6a1b6c9025e7e4520e78c"}, + {file = "lxml-5.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:91505d3ddebf268bb1588eb0f63821f738d20e1e7f05d3c647a5ca900288760b"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a3bcdde35d82ff385f4ede021df801b5c4a5bcdfb61ea87caabcebfc4945dc1b"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aea7c06667b987787c7d1f5e1dfcd70419b711cdb47d6b4bb4ad4b76777a0563"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7fb111eef4d05909b82152721a59c1b14d0f365e2be4c742a473c5d7372f4f5"}, + {file = "lxml-5.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43d549b876ce64aa18b2328faff70f5877f8c6dede415f80a2f799d31644d776"}, + {file = "lxml-5.4.0-cp310-cp310-win32.whl", hash = "sha256:75133890e40d229d6c5837b0312abbe5bac1c342452cf0e12523477cd3aa21e7"}, + {file = "lxml-5.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:de5b4e1088523e2b6f730d0509a9a813355b7f5659d70eb4f319c76beea2e250"}, + {file = "lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9"}, + {file = "lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8"}, + {file = "lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd"}, + {file = "lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751"}, + {file = "lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4"}, + {file = "lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539"}, + {file = "lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4"}, + {file = "lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7"}, + {file = "lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f"}, + {file = "lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc"}, + {file = "lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f"}, + {file = "lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2"}, + {file = "lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0"}, + {file = "lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8"}, + {file = "lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b"}, + {file = "lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a"}, + {file = "lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82"}, + {file = "lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f"}, + {file = "lxml-5.4.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7be701c24e7f843e6788353c055d806e8bd8466b52907bafe5d13ec6a6dbaecd"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb54f7c6bafaa808f27166569b1511fc42701a7713858dddc08afdde9746849e"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97dac543661e84a284502e0cf8a67b5c711b0ad5fb661d1bd505c02f8cf716d7"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:c70e93fba207106cb16bf852e421c37bbded92acd5964390aad07cb50d60f5cf"}, + {file = "lxml-5.4.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9c886b481aefdf818ad44846145f6eaf373a20d200b5ce1a5c8e1bc2d8745410"}, + {file = "lxml-5.4.0-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:fa0e294046de09acd6146be0ed6727d1f42ded4ce3ea1e9a19c11b6774eea27c"}, + {file = "lxml-5.4.0-cp36-cp36m-win32.whl", hash = "sha256:61c7bbf432f09ee44b1ccaa24896d21075e533cd01477966a5ff5a71d88b2f56"}, + {file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"}, + {file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"}, + {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"}, + {file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"}, + {file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"}, + {file = "lxml-5.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eaf24066ad0b30917186420d51e2e3edf4b0e2ea68d8cd885b14dc8afdcf6556"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2b31a3a77501d86d8ade128abb01082724c0dfd9524f542f2f07d693c9f1175f"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e108352e203c7afd0eb91d782582f00a0b16a948d204d4dec8565024fafeea5"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11a96c3b3f7551c8a8109aa65e8594e551d5a84c76bf950da33d0fb6dfafab7"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:ca755eebf0d9e62d6cb013f1261e510317a41bf4650f22963474a663fdfe02aa"}, + {file = "lxml-5.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:4cd915c0fb1bed47b5e6d6edd424ac25856252f09120e3e8ba5154b6b921860e"}, + {file = "lxml-5.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:226046e386556a45ebc787871d6d2467b32c37ce76c2680f5c608e25823ffc84"}, + {file = "lxml-5.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b108134b9667bcd71236c5a02aad5ddd073e372fb5d48ea74853e009fe38acb6"}, + {file = "lxml-5.4.0-cp38-cp38-win32.whl", hash = "sha256:1320091caa89805df7dcb9e908add28166113dcd062590668514dbd510798c88"}, + {file = "lxml-5.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:073eb6dcdf1f587d9b88c8c93528b57eccda40209cf9be549d469b942b41d70b"}, + {file = "lxml-5.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bda3ea44c39eb74e2488297bb39d47186ed01342f0022c8ff407c250ac3f498e"}, + {file = "lxml-5.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ceaf423b50ecfc23ca00b7f50b64baba85fb3fb91c53e2c9d00bc86150c7e40"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:664cdc733bc87449fe781dbb1f309090966c11cc0c0cd7b84af956a02a8a4729"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67ed8a40665b84d161bae3181aa2763beea3747f748bca5874b4af4d75998f87"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b4a3bd174cc9cdaa1afbc4620c049038b441d6ba07629d89a83b408e54c35cd"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:b0989737a3ba6cf2a16efb857fb0dfa20bc5c542737fddb6d893fde48be45433"}, + {file = "lxml-5.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:dc0af80267edc68adf85f2a5d9be1cdf062f973db6790c1d065e45025fa26140"}, + {file = "lxml-5.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:639978bccb04c42677db43c79bdaa23785dc7f9b83bfd87570da8207872f1ce5"}, + {file = "lxml-5.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5a99d86351f9c15e4a901fc56404b485b1462039db59288b203f8c629260a142"}, + {file = "lxml-5.4.0-cp39-cp39-win32.whl", hash = "sha256:3e6d5557989cdc3ebb5302bbdc42b439733a841891762ded9514e74f60319ad6"}, + {file = "lxml-5.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:a8c9b7f16b63e65bbba889acb436a1034a82d34fa09752d754f88d708eca80e1"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1b717b00a71b901b4667226bba282dd462c42ccf618ade12f9ba3674e1fabc55"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27a9ded0f0b52098ff89dd4c418325b987feed2ea5cc86e8860b0f844285d740"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b7ce10634113651d6f383aa712a194179dcd496bd8c41e191cec2099fa09de5"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53370c26500d22b45182f98847243efb518d268374a9570409d2e2276232fd37"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c6364038c519dffdbe07e3cf42e6a7f8b90c275d4d1617a69bb59734c1a2d571"}, + {file = "lxml-5.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b12cb6527599808ada9eb2cd6e0e7d3d8f13fe7bbb01c6311255a15ded4c7ab4"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5f11a1526ebd0dee85e7b1e39e39a0cc0d9d03fb527f56d8457f6df48a10dc0c"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b4afaf38bf79109bb060d9016fad014a9a48fb244e11b94f74ae366a64d252"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de6f6bb8a7840c7bf216fb83eec4e2f79f7325eca8858167b68708b929ab2172"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5cca36a194a4eb4e2ed6be36923d3cffd03dcdf477515dea687185506583d4c9"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b7c86884ad23d61b025989d99bfdd92a7351de956e01c61307cb87035960bcb1"}, + {file = "lxml-5.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:53d9469ab5460402c19553b56c3648746774ecd0681b1b27ea74d5d8a3ef5590"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:56dbdbab0551532bb26c19c914848d7251d73edb507c3079d6805fa8bba5b706"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14479c2ad1cb08b62bb941ba8e0e05938524ee3c3114644df905d2331c76cd57"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32697d2ea994e0db19c1df9e40275ffe84973e4232b5c274f47e7c1ec9763cdd"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:24f6df5f24fc3385f622c0c9d63fe34604893bc1a5bdbb2dbf5870f85f9a404a"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:151d6c40bc9db11e960619d2bf2ec5829f0aaffb10b41dcf6ad2ce0f3c0b2325"}, + {file = "lxml-5.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4025bf2884ac4370a3243c5aa8d66d3cb9e15d3ddd0af2d796eccc5f0244390e"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9459e6892f59ecea2e2584ee1058f5d8f629446eab52ba2305ae13a32a059530"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47fb24cc0f052f0576ea382872b3fc7e1f7e3028e53299ea751839418ade92a6"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50441c9de951a153c698b9b99992e806b71c1f36d14b154592580ff4a9d0d877"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ab339536aa798b1e17750733663d272038bf28069761d5be57cb4a9b0137b4f8"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:9776af1aad5a4b4a1317242ee2bea51da54b2a7b7b48674be736d463c999f37d"}, + {file = "lxml-5.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:63e7968ff83da2eb6fdda967483a7a023aa497d85ad8f05c3ad9b1f2e8c84987"}, + {file = "lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd"}, +] + +[package.dependencies] +lxml_html_clean = {version = "*", optional = true, markers = "extra == \"html-clean\""} + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml_html_clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.11,<3.1.0)"] + +[[package]] +name = "lxml-html-clean" +version = "0.4.2" +description = "HTML cleaner from lxml project" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "lxml_html_clean-0.4.2-py3-none-any.whl", hash = "sha256:74ccfba277adcfea87a1e9294f47dd86b05d65b4da7c5b07966e3d5f3be8a505"}, + {file = "lxml_html_clean-0.4.2.tar.gz", hash = "sha256:91291e7b5db95430abf461bc53440964d58e06cc468950f9e47db64976cebcb3"}, +] + +[package.dependencies] +lxml = "*" + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "monotonic" +version = "1.6" +description = "An implementation of time.monotonic() for Python 2 & < 3.3" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, + {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, +] + +[[package]] +name = "more-itertools" +version = "10.7.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e"}, + {file = "more_itertools-10.7.0.tar.gz", hash = "sha256:9fddd5403be01a94b204faadcff459ec3568cf110265d3c54323e1e866ad29d3"}, +] + +[[package]] +name = "multidict" +version = "6.4.3" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32a998bd8a64ca48616eac5a8c1cc4fa38fb244a3facf2eeb14abe186e0f6cc5"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a54ec568f1fc7f3c313c2f3b16e5db346bf3660e1309746e7fccbbfded856188"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a7be07e5df178430621c716a63151165684d3e9958f2bbfcb644246162007ab7"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b128dbf1c939674a50dd0b28f12c244d90e5015e751a4f339a96c54f7275e291"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9cb19dfd83d35b6ff24a4022376ea6e45a2beba8ef3f0836b8a4b288b6ad685"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cf62f8e447ea2c1395afa289b332e49e13d07435369b6f4e41f887db65b40bf"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:909f7d43ff8f13d1adccb6a397094adc369d4da794407f8dd592c51cf0eae4b1"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bb8f8302fbc7122033df959e25777b0b7659b1fd6bcb9cb6bed76b5de67afef"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b79471b4f21169ea25ebc37ed6f058040c578e50ade532e2066562597b8a9"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a7bd27f7ab3204f16967a6f899b3e8e9eb3362c0ab91f2ee659e0345445e0078"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:99592bd3162e9c664671fd14e578a33bfdba487ea64bcb41d281286d3c870ad7"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a62d78a1c9072949018cdb05d3c533924ef8ac9bcb06cbf96f6d14772c5cd451"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ccdde001578347e877ca4f629450973c510e88e8865d5aefbcb89b852ccc666"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:eccb67b0e78aa2e38a04c5ecc13bab325a43e5159a181a9d1a6723db913cbb3c"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b6fcf6054fc4114a27aa865f8840ef3d675f9316e81868e0ad5866184a6cba5"}, + {file = "multidict-6.4.3-cp310-cp310-win32.whl", hash = "sha256:f92c7f62d59373cd93bc9969d2da9b4b21f78283b1379ba012f7ee8127b3152e"}, + {file = "multidict-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:b57e28dbc031d13916b946719f213c494a517b442d7b48b29443e79610acd887"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7"}, + {file = "multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378"}, + {file = "multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a"}, + {file = "multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124"}, + {file = "multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8"}, + {file = "multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3"}, + {file = "multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4"}, + {file = "multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5"}, + {file = "multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5427a2679e95a642b7f8b0f761e660c845c8e6fe3141cddd6b62005bd133fc21"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24a8caa26521b9ad09732972927d7b45b66453e6ebd91a3c6a46d811eeb7349b"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6b5a272bc7c36a2cd1b56ddc6bff02e9ce499f9f14ee4a45c45434ef083f2459"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf74dc5e212b8c75165b435c43eb0d5e81b6b300a938a4eb82827119115e840"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9f35de41aec4b323c71f54b0ca461ebf694fb48bec62f65221f52e0017955b39"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae93e0ff43b6f6892999af64097b18561691ffd835e21a8348a441e256592e1f"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3929269e9d7eff905d6971d8b8c85e7dbc72c18fb99c8eae6fe0a152f2e343"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6214fe1750adc2a1b801a199d64b5a67671bf76ebf24c730b157846d0e90d2"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d79cf5c0c6284e90f72123f4a3e4add52d6c6ebb4a9054e88df15b8d08444c6"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2427370f4a255262928cd14533a70d9738dfacadb7563bc3b7f704cc2360fc4e"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:fbd8d737867912b6c5f99f56782b8cb81f978a97b4437a1c476de90a3e41c9a1"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0ee1bf613c448997f73fc4efb4ecebebb1c02268028dd4f11f011f02300cf1e8"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578568c4ba5f2b8abd956baf8b23790dbfdc953e87d5b110bce343b4a54fc9e7"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a059ad6b80de5b84b9fa02a39400319e62edd39d210b4e4f8c4f1243bdac4752"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dd53893675b729a965088aaadd6a1f326a72b83742b056c1065bdd2e2a42b4df"}, + {file = "multidict-6.4.3-cp39-cp39-win32.whl", hash = "sha256:abcfed2c4c139f25c2355e180bcc077a7cae91eefbb8b3927bb3f836c9586f1f"}, + {file = "multidict-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:b1b389ae17296dd739015d5ddb222ee99fd66adeae910de21ac950e00979d897"}, + {file = "multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9"}, + {file = "multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "multitasking" +version = "0.0.11" +description = "Non-blocking Python methods using decorators" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "multitasking-0.0.11-py3-none-any.whl", hash = "sha256:1e5b37a5f8fc1e6cfaafd1a82b6b1cc6d2ed20037d3b89c25a84f499bd7b3dd4"}, + {file = "multitasking-0.0.11.tar.gz", hash = "sha256:4d6bc3cc65f9b2dca72fb5a787850a88dae8f620c2b36ae9b55248e51bcd6026"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, +] + +[[package]] +name = "nasdaq-data-link" +version = "1.0.4" +description = "Package for Nasdaq Data Link API access" +optional = false +python-versions = ">= 3.7" +groups = ["main"] +files = [ + {file = "Nasdaq Data Link-1.0.4.tar.gz", hash = "sha256:7beae38ff0b376db24a50d6b8445c94832ebdd88737f6aafbc81a7dbdda25ca1"}, + {file = "Nasdaq_Data_Link-1.0.4-py2.py3-none-any.whl", hash = "sha256:214a620551da1c7521476839fb96f932234d4d78e7ba44310722709ca37b0691"}, +] + +[package.dependencies] +inflection = ">=0.3.1" +more-itertools = "*" +numpy = ">=1.8" +pandas = ">=0.14" +python-dateutil = "*" +requests = ">=2.7.0" +six = "*" + +[[package]] +name = "nbformat" +version = "5.10.4" +description = "The Jupyter Notebook format" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, + {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, +] + +[package.dependencies] +fastjsonschema = ">=2.15" +jsonschema = ">=2.6" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +traitlets = ">=5.1" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "numba" +version = "0.60.0" +description = "compiling Python code using LLVM" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651"}, + {file = "numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b"}, + {file = "numba-0.60.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1527dc578b95c7c4ff248792ec33d097ba6bef9eda466c948b68dfc995c25781"}, + {file = "numba-0.60.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe0b28abb8d70f8160798f4de9d486143200f34458d34c4a214114e445d7124e"}, + {file = "numba-0.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:19407ced081d7e2e4b8d8c36aa57b7452e0283871c296e12d798852bc7d7f198"}, + {file = "numba-0.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a17b70fc9e380ee29c42717e8cc0bfaa5556c416d94f9aa96ba13acb41bdece8"}, + {file = "numba-0.60.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb02b344a2a80efa6f677aa5c40cd5dd452e1b35f8d1c2af0dfd9ada9978e4b"}, + {file = "numba-0.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f4fde652ea604ea3c86508a3fb31556a6157b2c76c8b51b1d45eb40c8598703"}, + {file = "numba-0.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4142d7ac0210cc86432b818338a2bc368dc773a2f5cf1e32ff7c5b378bd63ee8"}, + {file = "numba-0.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cac02c041e9b5bc8cf8f2034ff6f0dbafccd1ae9590dc146b3a02a45e53af4e2"}, + {file = "numba-0.60.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7da4098db31182fc5ffe4bc42c6f24cd7d1cb8a14b59fd755bfee32e34b8404"}, + {file = "numba-0.60.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38d6ea4c1f56417076ecf8fc327c831ae793282e0ff51080c5094cb726507b1c"}, + {file = "numba-0.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62908d29fb6a3229c242e981ca27e32a6e606cc253fc9e8faeb0e48760de241e"}, + {file = "numba-0.60.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ebaa91538e996f708f1ab30ef4d3ddc344b64b5227b67a57aa74f401bb68b9d"}, + {file = "numba-0.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:f75262e8fe7fa96db1dca93d53a194a38c46da28b112b8a4aca168f0df860347"}, + {file = "numba-0.60.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:01ef4cd7d83abe087d644eaa3d95831b777aa21d441a23703d649e06b8e06b74"}, + {file = "numba-0.60.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:819a3dfd4630d95fd574036f99e47212a1af41cbcb019bf8afac63ff56834449"}, + {file = "numba-0.60.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b983bd6ad82fe868493012487f34eae8bf7dd94654951404114f23c3466d34b"}, + {file = "numba-0.60.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c151748cd269ddeab66334bd754817ffc0cabd9433acb0f551697e5151917d25"}, + {file = "numba-0.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:3031547a015710140e8c87226b4cfe927cac199835e5bf7d4fe5cb64e814e3ab"}, + {file = "numba-0.60.0.tar.gz", hash = "sha256:5df6158e5584eece5fc83294b949fd30b9f1125df7708862205217e068aabf16"}, +] + +[package.dependencies] +llvmlite = "==0.43.*" +numpy = ">=1.22,<2.1" + +[[package]] +name = "numba" +version = "0.61.2" +description = "compiling Python code using LLVM" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numba-0.61.2-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:cf9f9fc00d6eca0c23fc840817ce9f439b9f03c8f03d6246c0e7f0cb15b7162a"}, + {file = "numba-0.61.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea0247617edcb5dd61f6106a56255baab031acc4257bddaeddb3a1003b4ca3fd"}, + {file = "numba-0.61.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ae8c7a522c26215d5f62ebec436e3d341f7f590079245a2f1008dfd498cc1642"}, + {file = "numba-0.61.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd1e74609855aa43661edffca37346e4e8462f6903889917e9f41db40907daa2"}, + {file = "numba-0.61.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae45830b129c6137294093b269ef0a22998ccc27bf7cf096ab8dcf7bca8946f9"}, + {file = "numba-0.61.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2"}, + {file = "numba-0.61.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:49c980e4171948ffebf6b9a2520ea81feed113c1f4890747ba7f59e74be84b1b"}, + {file = "numba-0.61.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3945615cd73c2c7eba2a85ccc9c1730c21cd3958bfcf5a44302abae0fb07bb60"}, + {file = "numba-0.61.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbfdf4eca202cebade0b7d43896978e146f39398909a42941c9303f82f403a18"}, + {file = "numba-0.61.2-cp311-cp311-win_amd64.whl", hash = "sha256:76bcec9f46259cedf888041b9886e257ae101c6268261b19fda8cfbc52bec9d1"}, + {file = "numba-0.61.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2"}, + {file = "numba-0.61.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4ddce10009bc097b080fc96876d14c051cc0c7679e99de3e0af59014dab7dfe8"}, + {file = "numba-0.61.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b1bb509d01f23d70325d3a5a0e237cbc9544dd50e50588bc581ba860c213546"}, + {file = "numba-0.61.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:48a53a3de8f8793526cbe330f2a39fe9a6638efcbf11bd63f3d2f9757ae345cd"}, + {file = "numba-0.61.2-cp312-cp312-win_amd64.whl", hash = "sha256:97cf4f12c728cf77c9c1d7c23707e4d8fb4632b46275f8f3397de33e5877af18"}, + {file = "numba-0.61.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:3a10a8fc9afac40b1eac55717cece1b8b1ac0b946f5065c89e00bde646b5b154"}, + {file = "numba-0.61.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d3bcada3c9afba3bed413fba45845f2fb9cd0d2b27dd58a1be90257e293d140"}, + {file = "numba-0.61.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bdbca73ad81fa196bd53dc12e3aaf1564ae036e0c125f237c7644fe64a4928ab"}, + {file = "numba-0.61.2-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:5f154aaea625fb32cfbe3b80c5456d514d416fcdf79733dd69c0df3a11348e9e"}, + {file = "numba-0.61.2-cp313-cp313-win_amd64.whl", hash = "sha256:59321215e2e0ac5fa928a8020ab00b8e57cda8a97384963ac0dfa4d4e6aa54e7"}, + {file = "numba-0.61.2.tar.gz", hash = "sha256:8750ee147940a6637b80ecf7f95062185ad8726c8c28a2295b8ec1160a196f7d"}, +] + +[package.dependencies] +llvmlite = "==0.44.*" +numpy = ">=1.24,<2.3" + +[[package]] +name = "numpy" +version = "2.0.2" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "numpy-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:51129a29dbe56f9ca83438b706e2e69a39892b5eda6cedcb6b0c9fdc9b0d3ece"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f15975dfec0cf2239224d80e32c3170b1d168335eaedee69da84fbe9f1f9cd04"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8c5713284ce4e282544c68d1c3b2c7161d38c256d2eefc93c1d683cf47683e66"}, + {file = "numpy-2.0.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:becfae3ddd30736fe1889a37f1f580e245ba79a5855bff5f2a29cb3ccc22dd7b"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2da5960c3cf0df7eafefd806d4e612c5e19358de82cb3c343631188991566ccd"}, + {file = "numpy-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:496f71341824ed9f3d2fd36cf3ac57ae2e0165c143b55c3a035ee219413f3318"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a61ec659f68ae254e4d237816e33171497e978140353c0c2038d46e63282d0c8"}, + {file = "numpy-2.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d731a1c6116ba289c1e9ee714b08a8ff882944d4ad631fd411106a30f083c326"}, + {file = "numpy-2.0.2-cp310-cp310-win32.whl", hash = "sha256:984d96121c9f9616cd33fbd0618b7f08e0cfc9600a7ee1d6fd9b239186d19d97"}, + {file = "numpy-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:c7b0be4ef08607dd04da4092faee0b86607f111d5ae68036f16cc787e250a131"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:49ca4decb342d66018b01932139c0961a8f9ddc7589611158cb3c27cbcf76448"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:11a76c372d1d37437857280aa142086476136a8c0f373b2e648ab2c8f18fb195"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:807ec44583fd708a21d4a11d94aedf2f4f3c3719035c76a2bbe1fe8e217bdc57"}, + {file = "numpy-2.0.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8cafab480740e22f8d833acefed5cc87ce276f4ece12fdaa2e8903db2f82897a"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a15f476a45e6e5a3a79d8a14e62161d27ad897381fecfa4a09ed5322f2085669"}, + {file = "numpy-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13e689d772146140a252c3a28501da66dfecd77490b498b168b501835041f951"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9ea91dfb7c3d1c56a0e55657c0afb38cf1eeae4544c208dc465c3c9f3a7c09f9"}, + {file = "numpy-2.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c1c9307701fec8f3f7a1e6711f9089c06e6284b3afbbcd259f7791282d660a15"}, + {file = "numpy-2.0.2-cp311-cp311-win32.whl", hash = "sha256:a392a68bd329eafac5817e5aefeb39038c48b671afd242710b451e76090e81f4"}, + {file = "numpy-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:286cd40ce2b7d652a6f22efdfc6d1edf879440e53e76a75955bc0c826c7e64dc"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:df55d490dea7934f330006d0f81e8551ba6010a5bf035a249ef61a94f21c500b"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8df823f570d9adf0978347d1f926b2a867d5608f434a7cff7f7908c6570dcf5e"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9a92ae5c14811e390f3767053ff54eaee3bf84576d99a2456391401323f4ec2c"}, + {file = "numpy-2.0.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:a842d573724391493a97a62ebbb8e731f8a5dcc5d285dfc99141ca15a3302d0c"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c05e238064fc0610c840d1cf6a13bf63d7e391717d247f1bf0318172e759e692"}, + {file = "numpy-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:96a55f64139912d61de9137f11bf39a55ec8faec288c75a54f93dfd39f7eb40c"}, + {file = "numpy-2.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec9852fb39354b5a45a80bdab5ac02dd02b15f44b3804e9f00c556bf24b4bded"}, + {file = "numpy-2.0.2-cp312-cp312-win32.whl", hash = "sha256:671bec6496f83202ed2d3c8fdc486a8fc86942f2e69ff0e986140339a63bcbe5"}, + {file = "numpy-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:cfd41e13fdc257aa5778496b8caa5e856dc4896d4ccf01841daee1d96465467a"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9059e10581ce4093f735ed23f3b9d283b9d517ff46009ddd485f1747eb22653c"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:423e89b23490805d2a5a96fe40ec507407b8ee786d66f7328be214f9679df6dd"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:2b2955fa6f11907cf7a70dab0d0755159bca87755e831e47932367fc8f2f2d0b"}, + {file = "numpy-2.0.2-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:97032a27bd9d8988b9a97a8c4d2c9f2c15a81f61e2f21404d7e8ef00cb5be729"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e795a8be3ddbac43274f18588329c72939870a16cae810c2b73461c40718ab1"}, + {file = "numpy-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26b258c385842546006213344c50655ff1555a9338e2e5e02a0756dc3e803dd"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fec9451a7789926bcf7c2b8d187292c9f93ea30284802a0ab3f5be8ab36865d"}, + {file = "numpy-2.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9189427407d88ff25ecf8f12469d4d39d35bee1db5d39fc5c168c6f088a6956d"}, + {file = "numpy-2.0.2-cp39-cp39-win32.whl", hash = "sha256:905d16e0c60200656500c95b6b8dca5d109e23cb24abc701d41c02d74c6b3afa"}, + {file = "numpy-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:a3f4ab0caa7f053f6797fcd4e1e25caee367db3112ef2b6ef82d749530768c73"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7f0a0c6f12e07fa94133c8a67404322845220c06a9e80e85999afe727f7438b8"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-macosx_14_0_x86_64.whl", hash = "sha256:312950fdd060354350ed123c0e25a71327d3711584beaef30cdaa93320c392d4"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26df23238872200f63518dd2aa984cfca675d82469535dc7162dc2ee52d9dd5c"}, + {file = "numpy-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a46288ec55ebbd58947d31d72be2c63cbf839f0a63b49cb755022310792a3385"}, + {file = "numpy-2.0.2.tar.gz", hash = "sha256:883c987dee1880e2a864ab0dc9892292582510604156762362d9326444636e78"}, +] + +[[package]] +name = "numpy" +version = "2.2.5" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26"}, + {file = "numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a"}, + {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f"}, + {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba"}, + {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3"}, + {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57"}, + {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c"}, + {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1"}, + {file = "numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88"}, + {file = "numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54"}, + {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610"}, + {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b"}, + {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be"}, + {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906"}, + {file = "numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175"}, + {file = "numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa"}, + {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571"}, + {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073"}, + {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8"}, + {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae"}, + {file = "numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb"}, + {file = "numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191"}, + {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372"}, + {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d"}, + {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7"}, + {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73"}, + {file = "numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b"}, + {file = "numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376"}, + {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19"}, + {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0"}, + {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a"}, + {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066"}, + {file = "numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e"}, + {file = "numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169"}, + {file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"}, +] + +[[package]] +name = "openbb" +version = "4.4.4" +description = "Investment research for everyone, anywhere." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb-4.4.4-py3-none-any.whl", hash = "sha256:74e1e5bcf5bcf08747475fce0964f92b477d0f83364fa2ff70c71a6827209e6a"}, + {file = "openbb-4.4.4.tar.gz", hash = "sha256:b9c779d1d9c91322872d96f3dfdf68d811a0928bde46dd71b7d0d8117b50771a"}, +] + +[package.dependencies] +openbb-alpha-vantage = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"alpha-vantage\" or extra == \"all\""} +openbb-benzinga = ">=1.4.1,<2.0.0" +openbb-biztoc = {version = ">=1.4.2,<2.0.0", optional = true, markers = "extra == \"biztoc\" or extra == \"all\""} +openbb-bls = ">=1.1.2,<2.0.0" +openbb-cboe = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"cboe\" or extra == \"all\""} +openbb-cftc = ">=1.1.1,<2.0.0" +openbb-charting = {version = ">=2.3.4,<3.0.0", optional = true, markers = "extra == \"charting\" or extra == \"all\""} +openbb-commodity = ">=1.3.1,<2.0.0" +openbb-core = ">=1.4.6,<2.0.0" +openbb-crypto = ">=1.4.1,<2.0.0" +openbb-currency = ">=1.4.1,<2.0.0" +openbb-deribit = {version = ">=1.0.1,<2.0.0", optional = true, markers = "extra == \"all\""} +openbb-derivatives = ">=1.4.1,<2.0.0" +openbb-ecb = {version = ">=1.4.2,<2.0.0", optional = true, markers = "extra == \"ecb\" or extra == \"all\""} +openbb-econdb = ">=1.3.1,<2.0.0" +openbb-econometrics = {version = ">=1.5.3,<2.0.0", optional = true, markers = "extra == \"econometrics\" or extra == \"all\""} +openbb-economy = ">=1.4.2,<2.0.0" +openbb-equity = ">=1.4.1,<2.0.0" +openbb-etf = ">=1.4.1,<2.0.0" +openbb-federal-reserve = ">=1.4.2,<2.0.0" +openbb-finra = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"finra\" or extra == \"all\""} +openbb-finviz = {version = ">=1.3.1,<2.0.0", optional = true, markers = "extra == \"finviz\" or extra == \"all\""} +openbb-fixedincome = ">=1.4.3,<2.0.0" +openbb-fmp = ">=1.4.2,<2.0.0" +openbb-fred = ">=1.4.4,<2.0.0" +openbb-government-us = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"government-us\" or extra == \"all\""} +openbb-imf = ">=1.1.1,<2.0.0" +openbb-index = ">=1.4.1,<2.0.0" +openbb-intrinio = ">=1.4.1,<2.0.0" +openbb-multpl = {version = ">=1.1.1,<2.0.0", optional = true, markers = "extra == \"multpl\" or extra == \"all\""} +openbb-nasdaq = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"nasdaq\" or extra == \"all\""} +openbb-news = ">=1.4.1,<2.0.0" +openbb-oecd = ">=1.4.1,<2.0.0" +openbb-platform-api = ">=1.1.6,<2.0.0" +openbb-polygon = ">=1.4.1,<2.0.0" +openbb-quantitative = {version = ">=1.4.3,<2.0.0", optional = true, markers = "extra == \"quantitative\" or extra == \"all\""} +openbb-regulators = ">=1.4.2,<2.0.0" +openbb-sec = ">=1.4.3,<2.0.0" +openbb-seeking-alpha = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"seeking-alpha\" or extra == \"all\""} +openbb-stockgrid = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"stockgrid\" or extra == \"all\""} +openbb-technical = {version = ">=1.4.3,<2.0.0", optional = true, markers = "extra == \"technical\" or extra == \"all\""} +openbb-tiingo = ">=1.4.1,<2.0.0" +openbb-tmx = {version = ">=1.3.2,<2.0.0", optional = true, markers = "extra == \"tmx\" or extra == \"all\""} +openbb-tradier = {version = ">=1.3.1,<2.0.0", optional = true, markers = "extra == \"tradier\" or extra == \"all\""} +openbb-tradingeconomics = ">=1.4.1,<2.0.0" +openbb-us-eia = ">=1.1.1,<2.0.0" +openbb-wsj = {version = ">=1.4.1,<2.0.0", optional = true, markers = "extra == \"wsj\" or extra == \"all\""} +openbb-yfinance = ">=1.4.3,<2.0.0" + +[package.extras] +all = ["openbb-alpha-vantage (>=1.4.1,<2.0.0)", "openbb-biztoc (>=1.4.2,<2.0.0)", "openbb-cboe (>=1.4.1,<2.0.0)", "openbb-charting (>=2.3.4,<3.0.0)", "openbb-deribit (>=1.0.1,<2.0.0)", "openbb-ecb (>=1.4.2,<2.0.0)", "openbb-econometrics (>=1.5.3,<2.0.0)", "openbb-finra (>=1.4.1,<2.0.0)", "openbb-finviz (>=1.3.1,<2.0.0)", "openbb-government-us (>=1.4.1,<2.0.0)", "openbb-multpl (>=1.1.1,<2.0.0)", "openbb-nasdaq (>=1.4.1,<2.0.0)", "openbb-quantitative (>=1.4.3,<2.0.0)", "openbb-seeking-alpha (>=1.4.1,<2.0.0)", "openbb-stockgrid (>=1.4.1,<2.0.0)", "openbb-technical (>=1.4.3,<2.0.0)", "openbb-tmx (>=1.3.2,<2.0.0)", "openbb-tradier (>=1.3.1,<2.0.0)", "openbb-wsj (>=1.4.1,<2.0.0)"] +alpha-vantage = ["openbb-alpha-vantage (>=1.4.1,<2.0.0)"] +biztoc = ["openbb-biztoc (>=1.4.2,<2.0.0)"] +cboe = ["openbb-cboe (>=1.4.1,<2.0.0)"] +charting = ["openbb-charting (>=2.3.4,<3.0.0)"] +ecb = ["openbb-ecb (>=1.4.2,<2.0.0)"] +econometrics = ["openbb-econometrics (>=1.5.3,<2.0.0)"] +finra = ["openbb-finra (>=1.4.1,<2.0.0)"] +finviz = ["openbb-finviz (>=1.3.1,<2.0.0)"] +government-us = ["openbb-government-us (>=1.4.1,<2.0.0)"] +multpl = ["openbb-multpl (>=1.1.1,<2.0.0)"] +nasdaq = ["openbb-nasdaq (>=1.4.1,<2.0.0)"] +quantitative = ["openbb-quantitative (>=1.4.3,<2.0.0)"] +seeking-alpha = ["openbb-seeking-alpha (>=1.4.1,<2.0.0)"] +stockgrid = ["openbb-stockgrid (>=1.4.1,<2.0.0)"] +technical = ["openbb-technical (>=1.4.3,<2.0.0)"] +tmx = ["openbb-tmx (>=1.3.2,<2.0.0)"] +tradier = ["openbb-tradier (>=1.3.1,<2.0.0)"] +wsj = ["openbb-wsj (>=1.4.1,<2.0.0)"] + +[[package]] +name = "openbb-alpha-vantage" +version = "1.4.1" +description = "Alpha Vantage extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_alpha_vantage-1.4.1-py3-none-any.whl", hash = "sha256:a7f77fa0ac158de68626387b9410d0b340518aae780faadb6db69a66f4cb9924"}, + {file = "openbb_alpha_vantage-1.4.1.tar.gz", hash = "sha256:a4bd601c8e06609022428b2f752e9d66825d53eafc372d689a4cecb154d781f3"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-benzinga" +version = "1.4.1" +description = "Benzinga extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_benzinga-1.4.1-py3-none-any.whl", hash = "sha256:4b447801f22b4e339eec5d30bfe605e29228d9154543c372614012ae04b9005b"}, + {file = "openbb_benzinga-1.4.1.tar.gz", hash = "sha256:89525ae38c3fc1bd85cf19a37ca75efb207c27d9356a837b6e17ca8b8746ca75"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-biztoc" +version = "1.4.2" +description = "Biztoc Provider for OpenBB Platform" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_biztoc-1.4.2-py3-none-any.whl", hash = "sha256:0bb18439af96f6ecf2c4e97b85756b63cf274af874267b08d61f314eaea4d324"}, + {file = "openbb_biztoc-1.4.2.tar.gz", hash = "sha256:643c950c80df72a20ad9277a4a5b241b2f188f76543d5a3607d30b4f94719f49"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-bls" +version = "1.1.2" +description = "The Bureau of Labor Statistics' (BLS) Public Data Application Programming Interface (API) gives the public access to economic data from all BLS programs. It is the Bureau's hope that talented developers and programmers will use the BLS Public Data API to create original, inventive applications with published BLS data." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_bls-1.1.2-py3-none-any.whl", hash = "sha256:4146831c473f4e6010e53dcdfdd558a773bf076ed3fe5741d52413bd16450b94"}, + {file = "openbb_bls-1.1.2.tar.gz", hash = "sha256:7613ec2452479ed44d268097255c9059b80f798afaec7f815b25ceb0bc041852"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-cboe" +version = "1.4.1" +description = "CBOE extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_cboe-1.4.1-py3-none-any.whl", hash = "sha256:d082f1fb14426b165c39bb50abd3e5a68099a078b474816560aa5072066ca81d"}, + {file = "openbb_cboe-1.4.1.tar.gz", hash = "sha256:786cd44e78edc07278bba8e70e6e8f58bd05ab36fdff4e704b5aea1af91fc632"}, +] + +[package.dependencies] +aiohttp-client-cache = ">=0.11.0,<0.12.0" +aiosqlite = ">=0.20.0,<0.21.0" +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-cftc" +version = "1.1.1" +description = "The mission of the Commodity Futures Trading Commission (CFTC) is to promote the integrity, resilience, and vibrancy of the U.S. derivatives markets through sound regulation." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_cftc-1.1.1-py3-none-any.whl", hash = "sha256:93fbe7fbbb691c4c08d6b083cb375ea105c2c7c359ae8e58866aee8e7becfde5"}, + {file = "openbb_cftc-1.1.1.tar.gz", hash = "sha256:7388a02594309f84d9929f8a2b528ca93a6e0afac51543c850fb71a396d943a5"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-charting" +version = "2.3.4" +description = "Charting extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_charting-2.3.4-py3-none-any.whl", hash = "sha256:746a9cd8644f9960831126bd2a5f13296a91176a8d9ccbc4dcffc3e164612e96"}, +] + +[package.dependencies] +nbformat = ">=5.10.0,<6.0.0" +openbb-core = ">=1.4.6,<2.0.0" +pandas-ta-openbb = ">=0.4.20,<0.5.0" +plotly = ">=5.24.1,<6.0.0" + +[package.extras] +pywry = ["pywry (>=0.6.2,<0.7.0)"] + +[[package]] +name = "openbb-commodity" +version = "1.3.1" +description = "Commodity extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_commodity-1.3.1-py3-none-any.whl", hash = "sha256:76af2699f8d5481636fdbd12ae6bb7fa604602ff42d347b6aab7119af8dd5938"}, + {file = "openbb_commodity-1.3.1.tar.gz", hash = "sha256:af7bbd3a6820d3280cc231d10967fff0a440b5671e661d6689c7136a77faae79"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-core" +version = "1.4.6" +description = "OpenBB package with core functionality." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_core-1.4.6-py3-none-any.whl", hash = "sha256:f54aeb3e1049018571299709a7853723581b7ebd435b0d597435fcafd77ca605"}, + {file = "openbb_core-1.4.6.tar.gz", hash = "sha256:b046e1ecc6c9ab7e8cfdae342d2574d352d1ff9374e287ac9e78c84688f9eccf"}, +] + +[package.dependencies] +aiohttp = ">=3.11.11,<4.0.0" +fastapi = ">=0.115,<0.116" +html5lib = ">=1.1,<2.0" +importlib-metadata = ">=6.8.0" +pandas = ">=1.5.3" +posthog = ">=3.3.1,<4.0.0" +pydantic = ">=2.5.1,<3.0.0" +pyjwt = ">=2.10.1,<3.0.0" +python-dotenv = ">=1.0.0,<2.0.0" +python-multipart = ">=0.0.20,<0.0.21" +requests = ">=2.32.1,<3.0.0" +ruff = ">=0.7,<0.8" +uuid7 = ">=0.1.0,<0.2.0" +uvicorn = ">=0.34.2,<0.35.0" +websockets = ">=15.0,<16.0" + +[[package]] +name = "openbb-crypto" +version = "1.4.1" +description = "Crypto extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_crypto-1.4.1-py3-none-any.whl", hash = "sha256:e5e4b8486af726c688b2671906cc319df5f990d2a3db3e546bdf70f22101ca79"}, + {file = "openbb_crypto-1.4.1.tar.gz", hash = "sha256:c9a93102395066304c4bb59c679c953806ac632b89a1b620b52d7e61d2809658"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-currency" +version = "1.4.1" +description = "Currency extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_currency-1.4.1-py3-none-any.whl", hash = "sha256:f2812117a271cd171d40131bd54a9a6e9dd70ae66e24efe498e5de13a7f71c28"}, + {file = "openbb_currency-1.4.1.tar.gz", hash = "sha256:a16af23594a1b64343a35611701208f8e56379731fb91e3d4f92dcbf677f229b"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-deribit" +version = "1.0.1" +description = "Deribit is a crypto-native derivatives exchange." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_deribit-1.0.1-py3-none-any.whl", hash = "sha256:84b4446cc058b75e1baf1fa5c219e4aaea713314ba35d7fcc62fa03d4dc76ea6"}, + {file = "openbb_deribit-1.0.1.tar.gz", hash = "sha256:f72aa88b97f74874b5138c813586d71cdea6c11b44f4cce403f8f1f90b62af71"}, +] + +[package.dependencies] +async-lru = ">=2.0.4,<3.0.0" +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-derivatives" +version = "1.4.1" +description = "Derivatives extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_derivatives-1.4.1-py3-none-any.whl", hash = "sha256:b6e946544a82b7f1ecfc6f9939a06fdcb69819001434c2e4902d93b96fb43502"}, + {file = "openbb_derivatives-1.4.1.tar.gz", hash = "sha256:f3eea5d404ef94fe182d6f85a176a04e622d7b5b4311f0c57ec98d23699d62ec"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-ecb" +version = "1.4.2" +description = "ECB extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_ecb-1.4.2-py3-none-any.whl", hash = "sha256:8c3e1c81d761cd9f76bc013fe442b609263f253f5c4022ac9635124f6e67a7e8"}, + {file = "openbb_ecb-1.4.2.tar.gz", hash = "sha256:ebb5e00f1ce202f6de249fc37ead57579a11fdf30b1fc8dd9e4e7a6a9d5af748"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +xmltodict = ">=0.13.0,<0.14.0" + +[[package]] +name = "openbb-econdb" +version = "1.3.1" +description = "EconDB extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_econdb-1.3.1-py3-none-any.whl", hash = "sha256:3167ac368c06ac1e2db527fd1005437ffde6b06057114268f89684ceb57321a1"}, + {file = "openbb_econdb-1.3.1.tar.gz", hash = "sha256:245c418c05120f7cd1b13ee9eb00e07f5d8f49fc357ed54670ff2f43dca97eb4"}, +] + +[package.dependencies] +aiohttp-client-cache = ">=0.11.0,<0.12.0" +aiosqlite = ">=0.20.0,<0.21.0" +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-econometrics" +version = "1.5.3" +description = "Econometrics Toolkit for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_econometrics-1.5.3-py3-none-any.whl", hash = "sha256:6cef6e3e7b997504295d5380141d4a52e5fc0c3ca55ccdb71bf9fa994ef9d1b8"}, + {file = "openbb_econometrics-1.5.3.tar.gz", hash = "sha256:36b3f6eb2c2010fc0f7fed55c0310665fcefaf601c583c9ba8c29b2f198f1105"}, +] + +[package.dependencies] +arch = ">=7.2,<8.0" +linearmodels = ">=6,<7" +openbb-core = ">=1.4.6,<2.0.0" +pandas-ta-openbb = ">=0.4.20,<0.5.0" + +[[package]] +name = "openbb-economy" +version = "1.4.2" +description = "Economy extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_economy-1.4.2-py3-none-any.whl", hash = "sha256:850288346d8545fcf2e29a50e7ca48d9a8bc381567c0f27b3c6e6a2b9bf0ad79"}, + {file = "openbb_economy-1.4.2.tar.gz", hash = "sha256:27e87caeea1d2c3562422b89fe2b25319e138063e71d2b7367dea325ab577aa0"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-equity" +version = "1.4.1" +description = "Equity extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_equity-1.4.1-py3-none-any.whl", hash = "sha256:962e71ddff9c9a793b266a24c407adba42deb539df8794275b2f3573848e1021"}, + {file = "openbb_equity-1.4.1.tar.gz", hash = "sha256:56e47b4f5525e4f4a44761f1907c17a60fea40a18739acaadf766fe051a58762"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-etf" +version = "1.4.1" +description = "ETF extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_etf-1.4.1-py3-none-any.whl", hash = "sha256:9d3647d726ad143062462ccaa62d438a4863d047cdd524fef1bae65976719de5"}, + {file = "openbb_etf-1.4.1.tar.gz", hash = "sha256:72f5f0a1a0c386e0d109c8cc1716d764e6d50f96626e9d889504f2db8108fc2d"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-federal-reserve" +version = "1.4.2" +description = "US Federal Reserve Data Extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_federal_reserve-1.4.2-py3-none-any.whl", hash = "sha256:0015ebf0b8197f0c17734fbd91e1b8a12d324da512ffe03659fa9b0a7dc55c1a"}, + {file = "openbb_federal_reserve-1.4.2.tar.gz", hash = "sha256:e086e9ca97366bcf2bc933301369e3dbc7dd721d3e0b28301cfc71d1ab723a60"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-finra" +version = "1.4.1" +description = "FINRA extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_finra-1.4.1-py3-none-any.whl", hash = "sha256:818fe0d93b63425adf5192d2e6809405ce21a60f470a701cb61dd5abecc3888d"}, + {file = "openbb_finra-1.4.1.tar.gz", hash = "sha256:412a948f62d833b01188a9978378bd072524926f7d1ba4d14af9b1d89dd4aec0"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-finviz" +version = "1.3.1" +description = "Finviz extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_finviz-1.3.1-py3-none-any.whl", hash = "sha256:9bd7b2c2829be29e38b210d6a69f973f579a89dc36c8dd8248616dd93f223c14"}, + {file = "openbb_finviz-1.3.1.tar.gz", hash = "sha256:a839472d260aaa69c00ed36e0bcee91be5dee9aecc8b457806e7bceb3c2c6320"}, +] + +[package.dependencies] +finvizfinance = ">=1.1.0,<2.0.0" +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-fixedincome" +version = "1.4.3" +description = "Fixed income extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_fixedincome-1.4.3-py3-none-any.whl", hash = "sha256:87dda025991b6de392767d6465c6fdadc8144f188f50494c4a6756a9849920d5"}, + {file = "openbb_fixedincome-1.4.3.tar.gz", hash = "sha256:4a17536f0a250279cb30287ee658cc89173370bc343eb7051471a61faa8a66e4"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-fmp" +version = "1.4.2" +description = "FMP extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_fmp-1.4.2-py3-none-any.whl", hash = "sha256:0081f0335a2786b03116d038cb2c95a55fe1b73dc341da9ccb62edf3496c1414"}, + {file = "openbb_fmp-1.4.2.tar.gz", hash = "sha256:eb781312d99789fe67de883a4c149b1ba33c5a2ee6276c0702e9c1f81ee54fc4"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-fred" +version = "1.4.4" +description = "FRED extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_fred-1.4.4-py3-none-any.whl", hash = "sha256:9e705012fe09f87cf263b3cfc17782a2338a8d3172f4a87064074e55c2673d30"}, + {file = "openbb_fred-1.4.4.tar.gz", hash = "sha256:d9541635e3395029f893406e60c55b4131ff404316644952fa280d8f13366d56"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-government-us" +version = "1.4.1" +description = "US Government Data Extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_government_us-1.4.1-py3-none-any.whl", hash = "sha256:0a0e7f740802be647e3bb74446597439585821be6c4a36f4eddb3147c5914cc1"}, + {file = "openbb_government_us-1.4.1.tar.gz", hash = "sha256:6edef4c30326419ac66f126dbc7681efc78ac874bf5a4029d99f3880ffdef80f"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +random-user-agent = ">=1.0.1,<2.0.0" + +[[package]] +name = "openbb-imf" +version = "1.1.1" +description = "https://datahelp.imf.org/knowledgebase/articles/630877-api" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_imf-1.1.1-py3-none-any.whl", hash = "sha256:8d479e38298b0efa9ab398ada469b0817c907d92021859efcb29b3fcfde35e7d"}, + {file = "openbb_imf-1.1.1.tar.gz", hash = "sha256:668348e391e43dbeab89ad5e841addca24730121502d9c8d59b262eaaef8c477"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-index" +version = "1.4.1" +description = "Index extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_index-1.4.1-py3-none-any.whl", hash = "sha256:17e9109a3f5efb10916d2a13156248cbe55c0e403ae40c2519834ad3a81f715c"}, + {file = "openbb_index-1.4.1.tar.gz", hash = "sha256:d92020cbe7af4810af0b3c9a43f9e729cc3223d23ea9920d8a4923c6f10b2add"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-intrinio" +version = "1.4.1" +description = "Intrinio extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_intrinio-1.4.1-py3-none-any.whl", hash = "sha256:124d68a77dca39f2018533eebfb566fcee1fe6d3fdc4b893a44ea49ef9b39671"}, + {file = "openbb_intrinio-1.4.1.tar.gz", hash = "sha256:69fab0eaced94f11f9ac845ae5d5f6637e30ac31526ac599c708123c9cefa882"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +requests-cache = ">=1.1.0,<2.0.0" + +[[package]] +name = "openbb-multpl" +version = "1.1.1" +description = "Public data on historical S&P Multiples." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_multpl-1.1.1-py3-none-any.whl", hash = "sha256:b808f2c40e4781c9e3d9c8a104c0842a9f0f875bb777fec91fe26d3131b83989"}, + {file = "openbb_multpl-1.1.1.tar.gz", hash = "sha256:07fb9497bd4aa9e77d7909c7c2f50e3b84cc36783f34826ff3e2b42941edc3ec"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-nasdaq" +version = "1.4.1" +description = "Nasdaq extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_nasdaq-1.4.1-py3-none-any.whl", hash = "sha256:403dfa1d0b41df45f660320bfd0b3c66bd9aa809926585351c0aa91b33fb6460"}, + {file = "openbb_nasdaq-1.4.1.tar.gz", hash = "sha256:0563ab65b9c6a155b4cd4087ec1ad0017cb50a12339d8ff97daa0bf389165ae5"}, +] + +[package.dependencies] +nasdaq-data-link = ">=1.0.4,<2.0.0" +openbb-core = ">=1.4.6,<2.0.0" +random-user-agent = ">=1.0.1,<2.0.0" + +[[package]] +name = "openbb-news" +version = "1.4.1" +description = "News extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_news-1.4.1-py3-none-any.whl", hash = "sha256:f27b636ab67321f3e18a1cf1164838ca1dfca33e7fe888fcf6c7c10281ebd3b9"}, + {file = "openbb_news-1.4.1.tar.gz", hash = "sha256:79635875c297b9aae4e11d8bd1c70bd7df492d0d90e0f0efeb5c923bff2ed97d"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-oecd" +version = "1.4.1" +description = "OECD extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_oecd-1.4.1-py3-none-any.whl", hash = "sha256:99af4415d91c015d1c7e61ea1e7df14f610b746cf5f45ed5404d89f71120fa23"}, + {file = "openbb_oecd-1.4.1.tar.gz", hash = "sha256:ce02954fb57a5d2687c5a3bdff6052dda3cbdc9a5a6f65b813b3380e4bf4f593"}, +] + +[package.dependencies] +defusedxml = ">=0.8.0rc2,<0.9.0" +openbb-core = ">=1.4.6,<2.0.0" +urllib3 = ">1.26.16" + +[[package]] +name = "openbb-platform-api" +version = "1.1.6" +description = "OpenBB Platform API: Launch script and widgets builder for the OpenBB Platform API and Workspace Backend Connector." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_platform_api-1.1.6-py3-none-any.whl", hash = "sha256:a2ab1537dbc267380581d81e07257a22a0a8ba37adb2a9aeda39e408760f009d"}, + {file = "openbb_platform_api-1.1.6.tar.gz", hash = "sha256:df036090aa19fefd79bd0a582b98ebfe14fb6d3e91a7020a14d9930c21835e26"}, +] + +[package.dependencies] +deepdiff = "*" +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-polygon" +version = "1.4.1" +description = "Polygon extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_polygon-1.4.1-py3-none-any.whl", hash = "sha256:846f6af22b1179c0ba65e36331fdb984618f5bd4fa8ef8482b7928c7610fc179"}, + {file = "openbb_polygon-1.4.1.tar.gz", hash = "sha256:95e9687c66d409ed16adcdafe3f9d26ce0376cd5a80c152f0334908c86ab369f"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-quantitative" +version = "1.4.3" +description = "Quantitative Analysis extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_quantitative-1.4.3-py3-none-any.whl", hash = "sha256:50116f92a24ef1ca38fcdf75f644d165d0ea7d50b69040244be4b48181ad7f0e"}, + {file = "openbb_quantitative-1.4.3.tar.gz", hash = "sha256:2b7251c1fdd656ae53335b559573b1e360459e959d5ccb7bacfe392a6ff18ad7"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +pandas-ta-openbb = ">=0.4.20,<0.5.0" + +[[package]] +name = "openbb-regulators" +version = "1.4.2" +description = "Markets and Agency Regulators extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_regulators-1.4.2-py3-none-any.whl", hash = "sha256:4869201cdade1c9cb31cc81f0f6fffd65f64e3a8d0ae4395194de4113c072d1d"}, + {file = "openbb_regulators-1.4.2.tar.gz", hash = "sha256:0619750f3fc1d9c16cb861a3be1a0c0dba9a762ead1347fb878804fa45c2251f"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-sec" +version = "1.4.3" +description = "SEC extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_sec-1.4.3-py3-none-any.whl", hash = "sha256:70da4cc69f179cb48e2272eb9b39228cbd90a518c4207f433ce86b4112aac28d"}, + {file = "openbb_sec-1.4.3.tar.gz", hash = "sha256:eaf0e0452d712ab0d333fddcb746440d6dada71de74e3236214cc0ab8e83a788"}, +] + +[package.dependencies] +aiohttp-client-cache = ">=0.11.0,<0.12.0" +aiosqlite = ">=0.20.0,<0.21.0" +beautifulsoup4 = ">=4.12,<5.0" +inscriptis = ">=2.5.3,<3.0.0" +lxml = ">=5.2.1,<6.0.0" +openbb-core = ">=1.4.6,<2.0.0" +trafilatura = ">=2.0,<3.0" +xmltodict = ">=0.13.0,<0.14.0" + +[[package]] +name = "openbb-seeking-alpha" +version = "1.4.1" +description = "Seeking Alpha extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_seeking_alpha-1.4.1-py3-none-any.whl", hash = "sha256:5ae9cc8839b93c8efdd7e9ec8871389293612792c353c9a0cc6872c00ee3f4c5"}, + {file = "openbb_seeking_alpha-1.4.1.tar.gz", hash = "sha256:af01366a18c31ebe8492e99c5672e584aa2527362a216c90690f62bb387cb9db"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-stockgrid" +version = "1.4.1" +description = "stockgrid extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_stockgrid-1.4.1-py3-none-any.whl", hash = "sha256:9990fe3cd5649ff8b623f842fd5fb4ae804eef5ee2f4d9cecc006b429831d08d"}, + {file = "openbb_stockgrid-1.4.1.tar.gz", hash = "sha256:39787bffc178b5ac4fa1aece47a95dc0c55fa8617dfa1ef6a3a07a6dd5d038c8"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +pytest-freezegun = ">=0.4.2,<0.5.0" + +[[package]] +name = "openbb-technical" +version = "1.4.3" +description = "Technical Analysis extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_technical-1.4.3-py3-none-any.whl", hash = "sha256:921bdd82b86e73970c78296244ae5bc19e772be2399f73165a565f23aa5a86c8"}, + {file = "openbb_technical-1.4.3.tar.gz", hash = "sha256:a4dacf2a8653015d5a1f81884506e1017b62f1b16433d21588fc87b32cf80a78"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +pandas-ta-openbb = ">=0.4.20,<0.5.0" +scikit-learn = ">=1.6.0,<2.0.0" + +[[package]] +name = "openbb-tiingo" +version = "1.4.1" +description = "Tiingo extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_tiingo-1.4.1-py3-none-any.whl", hash = "sha256:1d5ba6518e662b55cb54693a95546b3d38754ea7c84976738944db2c4399414a"}, + {file = "openbb_tiingo-1.4.1.tar.gz", hash = "sha256:683f309a806690195645a46067c5281ce2b3802da769f170005554cc936cd075"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-tmx" +version = "1.3.2" +description = "Unofficial TMX data provider extension for the OpenBB Platform - Public Canadian markets data for Python and Fast API." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_tmx-1.3.2-py3-none-any.whl", hash = "sha256:678da8ef442fc955d37f40fd5d1137ba9df6a73e2d08ad4201f8d5df3db5de78"}, + {file = "openbb_tmx-1.3.2.tar.gz", hash = "sha256:d5ebf0b4a2efc413d725276577ea6ad93ba6962fc2ae9268b0a0f9cff7426767"}, +] + +[package.dependencies] +aiohttp-client-cache = ">=0.11.0,<0.12.0" +aiosqlite = ">=0.20.0,<0.21.0" +exchange-calendars = ">=4.5.4,<5.0.0" +openbb-core = ">=1.4.6,<2.0.0" +random-user-agent = ">=1.0.1,<2.0.0" + +[[package]] +name = "openbb-tradier" +version = "1.3.1" +description = "Tradier Provider Extension for the OpenBB Platform" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_tradier-1.3.1-py3-none-any.whl", hash = "sha256:4f3ff3a98c7f81e14dad0eeb98c4f8273429256911cd25bcf90331e47f207160"}, + {file = "openbb_tradier-1.3.1.tar.gz", hash = "sha256:15c99da818c78569374ffc310b607e15d30fbe0316427fbe714092ea79ec8ec3"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-tradingeconomics" +version = "1.4.1" +description = "Trading Economics extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_tradingeconomics-1.4.1-py3-none-any.whl", hash = "sha256:cc27608acfc1148a9866425de2dfdfbf40e2a8332f5306dada834cca32e5d1ae"}, + {file = "openbb_tradingeconomics-1.4.1.tar.gz", hash = "sha256:6862be50199eecf573279fec13d37d5b64844ff66dd2fcda0a70ed7d0b461606"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-us-eia" +version = "1.1.1" +description = "The U.S. Energy Information Administration is committed to its free and open data by making it available through an Application Programming Interface (API) and its open data tools. See https://www.eia.gov/opendata/ for more information." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_us_eia-1.1.1-py3-none-any.whl", hash = "sha256:840804734b62f8058a08aeb4f380b9fa2b4771daaadf02bfe0ddd3c9dffb81e4"}, + {file = "openbb_us_eia-1.1.1.tar.gz", hash = "sha256:8339b8dec25d50811242ec3cbf3fbadb682dbd9474b78b0ae72af3dcd9d82355"}, +] + +[package.dependencies] +async-lru = ">=2.0.4,<3.0.0" +openbb-core = ">=1.4.6,<2.0.0" +openpyxl = ">=3.1.5,<4.0.0" +xlrd = ">=2.0.1,<3.0.0" + +[[package]] +name = "openbb-wsj" +version = "1.4.1" +description = "wsj extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_wsj-1.4.1-py3-none-any.whl", hash = "sha256:44366792db38a4ea486f4b0a08cb4195f78716311123a2f2f08845ff686185f4"}, + {file = "openbb_wsj-1.4.1.tar.gz", hash = "sha256:080fa03d28c330115f34a79677022e12d09a61da68d32fcd9bb97eff595ecd23"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" + +[[package]] +name = "openbb-yfinance" +version = "1.4.3" +description = "yfinance extension for OpenBB" +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "openbb_yfinance-1.4.3-py3-none-any.whl", hash = "sha256:e6f9afd24543009a5d8f2b6404bfeff361d9a999320ca89cb7b6383c2ff06a42"}, + {file = "openbb_yfinance-1.4.3.tar.gz", hash = "sha256:53339ff96c0c591e22d78fbeb41436dd8c9c0179d00d3b6aaac4460abc19268d"}, +] + +[package.dependencies] +openbb-core = ">=1.4.6,<2.0.0" +yfinance = ">=0.2.56,<0.3.0" + +[[package]] +name = "openpyxl" +version = "3.1.5" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, + {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "orderly-set" +version = "5.4.0" +description = "Orderly set" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "orderly_set-5.4.0-py3-none-any.whl", hash = "sha256:f0192a7f9ae3385b587b71688353fae491d1ca45878496eb71ea118be1623639"}, + {file = "orderly_set-5.4.0.tar.gz", hash = "sha256:c8ff5ba824abe4eebcbbdd3f646ff3648ad0dd52239319d90056d8d30b6cccdd"}, +] + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "pandas" +version = "2.2.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.7" + +[package.extras] +all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] +aws = ["s3fs (>=2022.11.0)"] +clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] +compression = ["zstandard (>=0.19.0)"] +computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] +consortium-standard = ["dataframe-api-compat (>=0.1.7)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] +feather = ["pyarrow (>=10.0.1)"] +fss = ["fsspec (>=2022.11.0)"] +gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] +hdf5 = ["tables (>=3.8.0)"] +html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] +mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] +parquet = ["pyarrow (>=10.0.1)"] +performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] +plot = ["matplotlib (>=3.6.3)"] +postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] +spss = ["pyreadstat (>=1.2.0)"] +sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] +test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.9.2)"] + +[[package]] +name = "pandas-ta-openbb" +version = "0.4.21" +description = "Fork of pandas-ta for use with NumPy 2 and OpenBB." +optional = false +python-versions = "<3.13,>=3.9.21" +groups = ["main"] +files = [ + {file = "pandas_ta_openbb-0.4.21-py3-none-any.whl", hash = "sha256:5628b6097d284ca72d65ce789ffb766ad4b3dfe66f6a380fdc635b4b548e719f"}, + {file = "pandas_ta_openbb-0.4.21.tar.gz", hash = "sha256:8161e22066f26f1fa4bd780f94252dd30a31857b4991563d1b6e109f552786d7"}, +] + +[package.dependencies] +numba = ">=0.59.0" +numpy = ">=1.26.4" +pandas = ">=2.2.0" +scipy = ">=1.13" +statsmodels = ">=0.14.0,<0.15.0" + +[[package]] +name = "patsy" +version = "1.0.1" +description = "A Python package for describing statistical models and for building design matrices." +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "patsy-1.0.1-py2.py3-none-any.whl", hash = "sha256:751fb38f9e97e62312e921a1954b81e1bb2bcda4f5eeabaf94db251ee791509c"}, + {file = "patsy-1.0.1.tar.gz", hash = "sha256:e786a9391eec818c054e359b737bbce692f051aee4c661f4141cc88fb459c0c4"}, +] + +[package.dependencies] +numpy = ">=1.4" + +[package.extras] +test = ["pytest", "pytest-cov", "scipy"] + +[[package]] +name = "peewee" +version = "3.18.1" +description = "a little orm" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "peewee-3.18.1.tar.gz", hash = "sha256:a76a694b3b3012ce22f00d51fd83e55bf80b595275a90ed62cd36eb45496cf1d"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] + +[[package]] +name = "plotly" +version = "5.24.1" +description = "An open-source, interactive data visualization library for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"}, + {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"}, +] + +[package.dependencies] +packaging = "*" +tenacity = ">=6.2.0" + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "posthog" +version = "3.25.0" +description = "Integrate PostHog into any python application." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "posthog-3.25.0-py2.py3-none-any.whl", hash = "sha256:85db78c13d1ecb11aed06fad53759c4e8fb3633442c2f3d0336bc0ce8a585d30"}, + {file = "posthog-3.25.0.tar.gz", hash = "sha256:9168f3e7a0a5571b6b1065c41b3c171fbc68bfe72c3ac0bfd6e3d2fcdb7df2ca"}, +] + +[package.dependencies] +backoff = ">=1.10.0" +distro = ">=1.5.0" +monotonic = ">=1.5" +python-dateutil = ">2.1" +requests = ">=2.7,<3.0" +six = ">=1.5" + +[package.extras] +dev = ["black", "django-stubs", "flake8", "flake8-print", "isort", "lxml", "mypy", "mypy-baseline", "pre-commit", "pydantic", "types-mock", "types-python-dateutil", "types-requests", "types-setuptools", "types-six"] +langchain = ["langchain (>=0.2.0)"] +sentry = ["django", "sentry-sdk"] +test = ["anthropic", "coverage", "django", "flake8", "freezegun (==1.5.1)", "langchain-anthropic (>=0.2.0)", "langchain-community (>=0.2.0)", "langchain-openai (>=0.2.0)", "langgraph", "mock (>=2.0.0)", "openai", "parameterized (>=0.8.1)", "pydantic", "pylint", "pytest", "pytest-asyncio", "pytest-timeout"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.51" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "propcache" +version = "0.3.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136"}, + {file = "propcache-0.3.1-cp310-cp310-win32.whl", hash = "sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42"}, + {file = "propcache-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9"}, + {file = "propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005"}, + {file = "propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7"}, + {file = "propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b"}, + {file = "propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef"}, + {file = "propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24"}, + {file = "propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a"}, + {file = "propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d"}, + {file = "propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe"}, + {file = "propcache-0.3.1-cp39-cp39-win32.whl", hash = "sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64"}, + {file = "propcache-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566"}, + {file = "propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40"}, + {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, +] + +[[package]] +name = "pydantic" +version = "2.11.4" +description = "Data validation using Python type hints" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb"}, + {file = "pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d"}, +] + +[package.dependencies] +annotated-types = ">=0.6.0" +pydantic-core = "2.33.2" +typing-extensions = ">=4.12.2" +typing-inspection = ">=0.4.0" + +[package.extras] +email = ["email-validator (>=2.0.0)"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] + +[[package]] +name = "pydantic-core" +version = "2.33.2" +description = "Core functionality for Pydantic validation and serialization" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, +] + +[package.dependencies] +typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" + +[[package]] +name = "pygments" +version = "2.19.1" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, + {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pyhdfe" +version = "0.2.0" +description = "High dimensional fixed effect absorption with Python 3" +optional = false +python-versions = ">=3.6" +groups = ["main"] +files = [ + {file = "pyhdfe-0.2.0-py3-none-any.whl", hash = "sha256:5be73689101b97ff9e6e563874747257cdf86cb683159de8e16a5457130fb532"}, + {file = "pyhdfe-0.2.0.tar.gz", hash = "sha256:8cddc5f5a09148d3281fca3c787146a85ecc5a7517be3ae5762bfe507907b7fb"}, +] + +[package.dependencies] +numpy = ">=1.12.0" +scipy = ">=1.0.0" + +[package.extras] +docs = ["astunparse", "docutils (==0.17)", "ipython", "jinja2 (>=2.11,<3.0)", "nbsphinx (==0.5.0)", "sphinx (==2.0.0)", "sphinx-rtd-theme (==0.4.3)"] +tests = ["pytest", "pytest-xdist"] + +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pyluach" +version = "2.2.0" +description = "A Python package for dealing with Hebrew (Jewish) calendar dates." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "pyluach-2.2.0-py3-none-any.whl", hash = "sha256:d1eb49d6292087e9290f4661ae01b60c8c933704ec8c9cef82673b349ff96adf"}, + {file = "pyluach-2.2.0.tar.gz", hash = "sha256:9063a25387cd7624276fd0656508bada08aa8a6f22e8db352844cd858e69012b"}, +] + +[package.extras] +doc = ["sphinx (>=6.1.3,<6.2.0)", "sphinx_rtd_theme (>=1.2.0,<1.3.0)"] +test = ["beautifulsoup4", "flake8", "pytest", "pytest-cov"] + +[[package]] +name = "pytest" +version = "8.3.5" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, + {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-freezegun" +version = "0.4.2" +description = "Wrap tests with fixtures in freeze_time" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytest-freezegun-0.4.2.zip", hash = "sha256:19c82d5633751bf3ec92caa481fb5cffaac1787bd485f0df6436fd6242176949"}, + {file = "pytest_freezegun-0.4.2-py2.py3-none-any.whl", hash = "sha256:5318a6bfb8ba4b709c8471c94d0033113877b3ee02da5bfcd917c1889cde99a7"}, +] + +[package.dependencies] +freezegun = ">0.3" +pytest = ">=3.0.0" + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "1.1.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, + {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-multipart" +version = "0.0.20" +description = "A streaming multipart parser for Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"}, + {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, +] + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + +[[package]] +name = "pywin32" +version = "310" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +groups = ["main"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" +files = [ + {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, + {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, + {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, + {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, + {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, + {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, + {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, + {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, + {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, + {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, + {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, + {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, + {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, + {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, + {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, + {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, +] + +[[package]] +name = "pywry" +version = "0.6.2" +description = "" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pywry-0.6.2-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:45d6bb827bf76b2532a9d70b539209d70f37dfb13e9862549b7bff8500ad2495"}, + {file = "pywry-0.6.2-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:1d9ffd826a3a08c132843340e6d896efb7b972b301d045e3239a7dc08d9cac2f"}, + {file = "pywry-0.6.2-py3-none-win_amd64.whl", hash = "sha256:4f0e5b502555ee8b8e799baeaebe63243a84b7ce51df01a1c439dbc4e8227b9e"}, + {file = "pywry-0.6.2.tar.gz", hash = "sha256:9bd88c36ab0860728d9e64360010f8abcede43645656030e4a63e69e81a98c95"}, +] + +[package.dependencies] +setproctitle = "*" + +[package.extras] +dev = ["auditwheel", "wheel"] + +[[package]] +name = "random-user-agent" +version = "1.0.1" +description = "A package to get random user agents based filters provided by user" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "random_user_agent-1.0.1-py3-none-any.whl", hash = "sha256:535636a55fb63fe3d74fd0260d854c241d9f2946447026464e578e68eac17dac"}, + {file = "random_user_agent-1.0.1.tar.gz", hash = "sha256:8f8ca26ec8cb1d24ad1758d8b8f700d154064d641dbe9a255cfec42960fbd012"}, +] + +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-cache" +version = "1.2.1" +description = "A persistent cache for python requests" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, + {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, +] + +[package.dependencies] +attrs = ">=21.2" +cattrs = ">=22.2" +platformdirs = ">=2.5" +requests = ">=2.22" +url-normalize = ">=1.4" +urllib3 = ">=1.25.5" + +[package.extras] +all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=6.0.1)", "redis (>=3)", "ujson (>=5.4)"] +bson = ["bson (>=0.5)"] +docs = ["furo (>=2023.3,<2024.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.9)"] +dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] +json = ["ujson (>=5.4)"] +mongodb = ["pymongo (>=3)"] +redis = ["redis (>=3)"] +security = ["itsdangerous (>=2.0)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "rich" +version = "14.0.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.8.0" +groups = ["main"] +files = [ + {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, + {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rpds-py" +version = "0.24.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, + {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875"}, + {file = "rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07"}, + {file = "rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718"}, + {file = "rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a"}, + {file = "rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56"}, + {file = "rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30"}, + {file = "rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9"}, + {file = "rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143"}, + {file = "rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45"}, + {file = "rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103"}, + {file = "rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f"}, + {file = "rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e"}, +] + +[[package]] +name = "ruff" +version = "0.7.4" +description = "An extremely fast Python linter and code formatter, written in Rust." +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, + {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, + {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, + {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, + {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, + {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, + {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, + {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, + {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, +] + +[[package]] +name = "scikit-learn" +version = "1.6.1" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e"}, + {file = "scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36"}, + {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5"}, + {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b"}, + {file = "scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002"}, + {file = "scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33"}, + {file = "scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d"}, + {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2"}, + {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8"}, + {file = "scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415"}, + {file = "scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b"}, + {file = "scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2"}, + {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f"}, + {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86"}, + {file = "scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52"}, + {file = "scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322"}, + {file = "scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1"}, + {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348"}, + {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97"}, + {file = "scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691"}, + {file = "scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f"}, + {file = "scikit_learn-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1"}, + {file = "scikit_learn-1.6.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e"}, + {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107"}, + {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422"}, + {file = "scikit_learn-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b"}, + {file = "scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.5.1)", "scikit-image (>=0.17.2)"] + +[[package]] +name = "scipy" +version = "1.13.1" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "scipy-1.13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:20335853b85e9a49ff7572ab453794298bcf0354d8068c5f6775a0eabf350aca"}, + {file = "scipy-1.13.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d605e9c23906d1994f55ace80e0125c587f96c020037ea6aa98d01b4bd2e222f"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa31f1def5c819b19ecc3a8b52d28ffdcc7ed52bb20c9a7589669dd3c250989"}, + {file = "scipy-1.13.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f26264b282b9da0952a024ae34710c2aff7d27480ee91a2e82b7b7073c24722f"}, + {file = "scipy-1.13.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:eccfa1906eacc02de42d70ef4aecea45415f5be17e72b61bafcfd329bdc52e94"}, + {file = "scipy-1.13.1-cp310-cp310-win_amd64.whl", hash = "sha256:2831f0dc9c5ea9edd6e51e6e769b655f08ec6db6e2e10f86ef39bd32eb11da54"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:27e52b09c0d3a1d5b63e1105f24177e544a222b43611aaf5bc44d4a0979e32f9"}, + {file = "scipy-1.13.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:54f430b00f0133e2224c3ba42b805bfd0086fe488835effa33fa291561932326"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e89369d27f9e7b0884ae559a3a956e77c02114cc60a6058b4e5011572eea9299"}, + {file = "scipy-1.13.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a78b4b3345f1b6f68a763c6e25c0c9a23a9fd0f39f5f3d200efe8feda560a5fa"}, + {file = "scipy-1.13.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45484bee6d65633752c490404513b9ef02475b4284c4cfab0ef946def50b3f59"}, + {file = "scipy-1.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:5713f62f781eebd8d597eb3f88b8bf9274e79eeabf63afb4a737abc6c84ad37b"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5d72782f39716b2b3509cd7c33cdc08c96f2f4d2b06d51e52fb45a19ca0c86a1"}, + {file = "scipy-1.13.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:017367484ce5498445aade74b1d5ab377acdc65e27095155e448c88497755a5d"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:949ae67db5fa78a86e8fa644b9a6b07252f449dcf74247108c50e1d20d2b4627"}, + {file = "scipy-1.13.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de3ade0e53bc1f21358aa74ff4830235d716211d7d077e340c7349bc3542e884"}, + {file = "scipy-1.13.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2ac65fb503dad64218c228e2dc2d0a0193f7904747db43014645ae139c8fad16"}, + {file = "scipy-1.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:cdd7dacfb95fea358916410ec61bbc20440f7860333aee6d882bb8046264e949"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:436bbb42a94a8aeef855d755ce5a465479c721e9d684de76bf61a62e7c2b81d5"}, + {file = "scipy-1.13.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:8335549ebbca860c52bf3d02f80784e91a004b71b059e3eea9678ba994796a24"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d533654b7d221a6a97304ab63c41c96473ff04459e404b83275b60aa8f4b7004"}, + {file = "scipy-1.13.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:637e98dcf185ba7f8e663e122ebf908c4702420477ae52a04f9908707456ba4d"}, + {file = "scipy-1.13.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a014c2b3697bde71724244f63de2476925596c24285c7a637364761f8710891c"}, + {file = "scipy-1.13.1-cp39-cp39-win_amd64.whl", hash = "sha256:392e4ec766654852c25ebad4f64e4e584cf19820b980bc04960bca0b0cd6eaa2"}, + {file = "scipy-1.13.1.tar.gz", hash = "sha256:095a87a0312b08dfd6a6155cbbd310a8c51800fc931b8c0b84003014b874ed3c"}, +] + +[package.dependencies] +numpy = ">=1.22.4,<2.3" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy", "pycodestyle", "pydevtool", "rich-click", "ruff", "types-psutil", "typing_extensions"] +doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.12.0)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0)", "sphinx-design (>=0.4.0)"] +test = ["array-api-strict", "asv", "gmpy2", "hypothesis (>=6.30)", "mpmath", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "scipy" +version = "1.15.2" +description = "Fundamental algorithms for scientific computing in Python" +optional = false +python-versions = ">=3.10" +groups = ["main"] +markers = "python_version >= \"3.11\"" +files = [ + {file = "scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9"}, + {file = "scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5"}, + {file = "scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e"}, + {file = "scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9"}, + {file = "scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3"}, + {file = "scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d"}, + {file = "scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58"}, + {file = "scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa"}, + {file = "scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971"}, + {file = "scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655"}, + {file = "scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e"}, + {file = "scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0"}, + {file = "scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40"}, + {file = "scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462"}, + {file = "scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93"}, + {file = "scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20"}, + {file = "scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e"}, + {file = "scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8"}, + {file = "scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11"}, + {file = "scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53"}, + {file = "scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d"}, + {file = "scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb"}, + {file = "scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27"}, + {file = "scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0"}, + {file = "scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32"}, + {file = "scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d"}, + {file = "scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6"}, + {file = "scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af"}, + {file = "scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274"}, + {file = "scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776"}, + {file = "scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828"}, + {file = "scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28"}, + {file = "scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db"}, + {file = "scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec"}, +] + +[package.dependencies] +numpy = ">=1.23.5,<2.5" + +[package.extras] +dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "setproctitle" +version = "1.3.6" +description = "A Python module to customize the process title" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "setproctitle-1.3.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ebcf34b69df4ca0eabaaaf4a3d890f637f355fed00ba806f7ebdd2d040658c26"}, + {file = "setproctitle-1.3.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1aa1935aa2195b76f377e5cb018290376b7bf085f0b53f5a95c0c21011b74367"}, + {file = "setproctitle-1.3.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13624d9925bb481bc0ccfbc7f533da38bfbfe6e80652314f789abc78c2e513bd"}, + {file = "setproctitle-1.3.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97a138fa875c6f281df7720dac742259e85518135cd0e3551aba1c628103d853"}, + {file = "setproctitle-1.3.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c86e9e82bfab579327dbe9b82c71475165fbc8b2134d24f9a3b2edaf200a5c3d"}, + {file = "setproctitle-1.3.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6af330ddc2ec05a99c3933ab3cba9365357c0b8470a7f2fa054ee4b0984f57d1"}, + {file = "setproctitle-1.3.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:109fc07b1cd6cef9c245b2028e3e98e038283342b220def311d0239179810dbe"}, + {file = "setproctitle-1.3.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:7df5fcc48588f82b6cc8073db069609ddd48a49b1e9734a20d0efb32464753c4"}, + {file = "setproctitle-1.3.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2407955dc359d735a20ac6e797ad160feb33d529a2ac50695c11a1ec680eafab"}, + {file = "setproctitle-1.3.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:38ca045626af693da042ac35d7332e7b9dbd52e6351d6973b310612e3acee6d6"}, + {file = "setproctitle-1.3.6-cp310-cp310-win32.whl", hash = "sha256:9483aa336687463f5497dd37a070094f3dff55e2c888994f8440fcf426a1a844"}, + {file = "setproctitle-1.3.6-cp310-cp310-win_amd64.whl", hash = "sha256:4efc91b437f6ff2578e89e3f17d010c0a0ff01736606473d082913ecaf7859ba"}, + {file = "setproctitle-1.3.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1d856b0f4e4a33e31cdab5f50d0a14998f3a2d726a3fd5cb7c4d45a57b28d1b"}, + {file = "setproctitle-1.3.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:50706b9c0eda55f7de18695bfeead5f28b58aa42fd5219b3b1692d554ecbc9ec"}, + {file = "setproctitle-1.3.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af188f3305f0a65c3217c30c6d4c06891e79144076a91e8b454f14256acc7279"}, + {file = "setproctitle-1.3.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cce0ed8b3f64c71c140f0ec244e5fdf8ecf78ddf8d2e591d4a8b6aa1c1214235"}, + {file = "setproctitle-1.3.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70100e2087fe05359f249a0b5f393127b3a1819bf34dec3a3e0d4941138650c9"}, + {file = "setproctitle-1.3.6-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1065ed36bd03a3fd4186d6c6de5f19846650b015789f72e2dea2d77be99bdca1"}, + {file = "setproctitle-1.3.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4adf6a0013fe4e0844e3ba7583ec203ca518b9394c6cc0d3354df2bf31d1c034"}, + {file = "setproctitle-1.3.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:eb7452849f6615871eabed6560ffedfe56bc8af31a823b6be4ce1e6ff0ab72c5"}, + {file = "setproctitle-1.3.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a094b7ce455ca341b59a0f6ce6be2e11411ba6e2860b9aa3dbb37468f23338f4"}, + {file = "setproctitle-1.3.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ad1c2c2baaba62823a7f348f469a967ece0062140ca39e7a48e4bbb1f20d54c4"}, + {file = "setproctitle-1.3.6-cp311-cp311-win32.whl", hash = "sha256:8050c01331135f77ec99d99307bfbc6519ea24d2f92964b06f3222a804a3ff1f"}, + {file = "setproctitle-1.3.6-cp311-cp311-win_amd64.whl", hash = "sha256:9b73cf0fe28009a04a35bb2522e4c5b5176cc148919431dcb73fdbdfaab15781"}, + {file = "setproctitle-1.3.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af44bb7a1af163806bbb679eb8432fa7b4fb6d83a5d403b541b675dcd3798638"}, + {file = "setproctitle-1.3.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cca16fd055316a48f0debfcbfb6af7cea715429fc31515ab3fcac05abd527d8"}, + {file = "setproctitle-1.3.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea002088d5554fd75e619742cefc78b84a212ba21632e59931b3501f0cfc8f67"}, + {file = "setproctitle-1.3.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb465dd5825356c1191a038a86ee1b8166e3562d6e8add95eec04ab484cfb8a2"}, + {file = "setproctitle-1.3.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d2c8e20487b3b73c1fa72c56f5c89430617296cd380373e7af3a538a82d4cd6d"}, + {file = "setproctitle-1.3.6-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d6252098e98129a1decb59b46920d4eca17b0395f3d71b0d327d086fefe77d"}, + {file = "setproctitle-1.3.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cf355fbf0d4275d86f9f57be705d8e5eaa7f8ddb12b24ced2ea6cbd68fdb14dc"}, + {file = "setproctitle-1.3.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e288f8a162d663916060beb5e8165a8551312b08efee9cf68302687471a6545d"}, + {file = "setproctitle-1.3.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b2e54f4a2dc6edf0f5ea5b1d0a608d2af3dcb5aa8c8eeab9c8841b23e1b054fe"}, + {file = "setproctitle-1.3.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b6f4abde9a2946f57e8daaf1160b2351bcf64274ef539e6675c1d945dbd75e2a"}, + {file = "setproctitle-1.3.6-cp312-cp312-win32.whl", hash = "sha256:db608db98ccc21248370d30044a60843b3f0f3d34781ceeea67067c508cd5a28"}, + {file = "setproctitle-1.3.6-cp312-cp312-win_amd64.whl", hash = "sha256:082413db8a96b1f021088e8ec23f0a61fec352e649aba20881895815388b66d3"}, + {file = "setproctitle-1.3.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e2a9e62647dc040a76d55563580bf3bb8fe1f5b6ead08447c2ed0d7786e5e794"}, + {file = "setproctitle-1.3.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:751ba352ed922e0af60458e961167fa7b732ac31c0ddd1476a2dfd30ab5958c5"}, + {file = "setproctitle-1.3.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7890e291bf4708e3b61db9069ea39b3ab0651e42923a5e1f4d78a7b9e4b18301"}, + {file = "setproctitle-1.3.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2b17855ed7f994f3f259cf2dfbfad78814538536fa1a91b50253d84d87fd88d"}, + {file = "setproctitle-1.3.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e51ec673513465663008ce402171192a053564865c2fc6dc840620871a9bd7c"}, + {file = "setproctitle-1.3.6-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63cc10352dc6cf35a33951656aa660d99f25f574eb78132ce41a85001a638aa7"}, + {file = "setproctitle-1.3.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0dba8faee2e4a96e934797c9f0f2d093f8239bf210406a99060b3eabe549628e"}, + {file = "setproctitle-1.3.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e3e44d08b61de0dd6f205528498f834a51a5c06689f8fb182fe26f3a3ce7dca9"}, + {file = "setproctitle-1.3.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:de004939fc3fd0c1200d26ea9264350bfe501ffbf46c8cf5dc7f345f2d87a7f1"}, + {file = "setproctitle-1.3.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3f8194b4d631b003a1176a75d1acd545e04b1f54b821638e098a93e6e62830ef"}, + {file = "setproctitle-1.3.6-cp313-cp313-win32.whl", hash = "sha256:d714e002dd3638170fe7376dc1b686dbac9cb712cde3f7224440af722cc9866a"}, + {file = "setproctitle-1.3.6-cp313-cp313-win_amd64.whl", hash = "sha256:b70c07409d465f3a8b34d52f863871fb8a00755370791d2bd1d4f82b3cdaf3d5"}, + {file = "setproctitle-1.3.6-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:23a57d3b8f1549515c2dbe4a2880ebc1f27780dc126c5e064167563e015817f5"}, + {file = "setproctitle-1.3.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:81c443310831e29fabbd07b75ebbfa29d0740b56f5907c6af218482d51260431"}, + {file = "setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88c63bd395c787b0aa81d8bbc22c1809f311032ce3e823a6517b711129818e4"}, + {file = "setproctitle-1.3.6-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73f14b86d0e2858ece6bf5807c9889670e392c001d414b4293d0d9b291942c3"}, + {file = "setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3393859eb8f19f5804049a685bf286cb08d447e28ba5c6d8543c7bf5500d5970"}, + {file = "setproctitle-1.3.6-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:785cd210c0311d9be28a70e281a914486d62bfd44ac926fcd70cf0b4d65dff1c"}, + {file = "setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c051f46ed1e13ba8214b334cbf21902102807582fbfaf0fef341b9e52f0fafbf"}, + {file = "setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:49498ebf68ca3e75321ffe634fcea5cc720502bfaa79bd6b03ded92ce0dc3c24"}, + {file = "setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4431629c178193f23c538cb1de3da285a99ccc86b20ee91d81eb5f1a80e0d2ba"}, + {file = "setproctitle-1.3.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d136fbf8ad4321716e44d6d6b3d8dffb4872626010884e07a1db54b7450836cf"}, + {file = "setproctitle-1.3.6-cp313-cp313t-win32.whl", hash = "sha256:d483cc23cc56ab32911ea0baa0d2d9ea7aa065987f47de847a0a93a58bf57905"}, + {file = "setproctitle-1.3.6-cp313-cp313t-win_amd64.whl", hash = "sha256:74973aebea3543ad033b9103db30579ec2b950a466e09f9c2180089e8346e0ec"}, + {file = "setproctitle-1.3.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3884002b3a9086f3018a32ab5d4e1e8214dd70695004e27b1a45c25a6243ad0b"}, + {file = "setproctitle-1.3.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6a1d3aa13acfe81f355b0ce4968facc7a19b0d17223a0f80c011a1dba8388f37"}, + {file = "setproctitle-1.3.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f24d5b9383318cbd1a5cd969377937d66cf0542f24aa728a4f49d9f98f9c0da8"}, + {file = "setproctitle-1.3.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a4ae2ea9afcfdd2b931ddcebf1cf82532162677e00326637b31ed5dff7d985ca"}, + {file = "setproctitle-1.3.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:805bb33e92fc3d8aa05674db3068d14d36718e3f2c5c79b09807203f229bf4b5"}, + {file = "setproctitle-1.3.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b20a5f4164cec7007be55c9cf18d2cd08ed7c3bf6769b3cd6d044ad888d74b"}, + {file = "setproctitle-1.3.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:793a23e8d9cb6c231aa3023d700008224c6ec5b8fd622d50f3c51665e3d0a190"}, + {file = "setproctitle-1.3.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:57bc54763bf741813a99fbde91f6be138c8706148b7b42d3752deec46545d470"}, + {file = "setproctitle-1.3.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:b0174ca6f3018ddeaa49847f29b69612e590534c1d2186d54ab25161ecc42975"}, + {file = "setproctitle-1.3.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:807796fe301b7ed76cf100113cc008c119daf4fea2f9f43c578002aef70c3ebf"}, + {file = "setproctitle-1.3.6-cp38-cp38-win32.whl", hash = "sha256:5313a4e9380e46ca0e2c681ba739296f9e7c899e6f4d12a6702b2dc9fb846a31"}, + {file = "setproctitle-1.3.6-cp38-cp38-win_amd64.whl", hash = "sha256:d5a6c4864bb6fa9fcf7b57a830d21aed69fd71742a5ebcdbafda476be673d212"}, + {file = "setproctitle-1.3.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:391bb6a29c4fe7ccc9c30812e3744060802d89b39264cfa77f3d280d7f387ea5"}, + {file = "setproctitle-1.3.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:156795b3db976611d09252fc80761fcdb65bb7c9b9581148da900851af25ecf4"}, + {file = "setproctitle-1.3.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdd7315314b0744a7dd506f3bd0f2cf90734181529cdcf75542ee35ad885cab7"}, + {file = "setproctitle-1.3.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d50bfcc1d1692dc55165b3dd2f0b9f8fb5b1f7b571a93e08d660ad54b9ca1a5"}, + {file = "setproctitle-1.3.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:163dba68f979c61e4e2e779c4d643e968973bdae7c33c3ec4d1869f7a9ba8390"}, + {file = "setproctitle-1.3.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d5a369eb7ec5b2fdfa9927530b5259dd21893fa75d4e04a223332f61b84b586"}, + {file = "setproctitle-1.3.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:18d0667bafaaae4c1dee831e2e59841c411ff399b9b4766822ba2685d419c3be"}, + {file = "setproctitle-1.3.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f33fbf96b52d51c23b6cff61f57816539c1c147db270cfc1cc3bc012f4a560a9"}, + {file = "setproctitle-1.3.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:543f59601a4e32daf44741b52f9a23e0ee374f9f13b39c41d917302d98fdd7b0"}, + {file = "setproctitle-1.3.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2156d55308431ac3b3ec4e5e05b1726d11a5215352d6a22bb933171dee292f8c"}, + {file = "setproctitle-1.3.6-cp39-cp39-win32.whl", hash = "sha256:17d7c833ed6545ada5ac4bb606b86a28f13a04431953d4beac29d3773aa00b1d"}, + {file = "setproctitle-1.3.6-cp39-cp39-win_amd64.whl", hash = "sha256:2940cf13f4fc11ce69ad2ed37a9f22386bfed314b98d8aebfd4f55459aa59108"}, + {file = "setproctitle-1.3.6-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3cde5b83ec4915cd5e6ae271937fd60d14113c8f7769b4a20d51769fe70d8717"}, + {file = "setproctitle-1.3.6-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:797f2846b546a8741413c57d9fb930ad5aa939d925c9c0fa6186d77580035af7"}, + {file = "setproctitle-1.3.6-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3eb04bcf0119aadc6235a2c162bae5ed5f740e3d42273a7228b915722de20"}, + {file = "setproctitle-1.3.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e6b5633c94c5111f7137f875e8f1ff48f53b991d5d5b90932f27dc8c1fa9ae4"}, + {file = "setproctitle-1.3.6-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ded9e86397267732a0641d4776c7c663ea16b64d7dbc4d9cc6ad8536363a2d29"}, + {file = "setproctitle-1.3.6-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae82507fe458f7c0c8227017f2158111a4c9e7ce94de05178894a7ea9fefc8a1"}, + {file = "setproctitle-1.3.6-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fc97805f9d74444b027babff710bf39df1541437a6a585a983d090ae00cedde"}, + {file = "setproctitle-1.3.6-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:83066ffbf77a5f82b7e96e59bdccbdda203c8dccbfc3f9f0fdad3a08d0001d9c"}, + {file = "setproctitle-1.3.6-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:9b50700785eccac0819bea794d968ed8f6055c88f29364776b7ea076ac105c5d"}, + {file = "setproctitle-1.3.6-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92df0e70b884f5da35f2e01489dca3c06a79962fb75636985f1e3a17aec66833"}, + {file = "setproctitle-1.3.6-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8834ab7be6539f1bfadec7c8d12249bbbe6c2413b1d40ffc0ec408692232a0c6"}, + {file = "setproctitle-1.3.6-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a5963b663da69ad25fa1559ee064584935570def665917918938c1f1289f5ebc"}, + {file = "setproctitle-1.3.6.tar.gz", hash = "sha256:c9f32b96c700bb384f33f7cf07954bb609d35dd82752cef57fb2ee0968409169"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "setuptools" +version = "80.1.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "setuptools-80.1.0-py3-none-any.whl", hash = "sha256:ea0e7655c05b74819f82e76e11a85b31779fee7c4969e82f72bab0664e8317e4"}, + {file = "setuptools-80.1.0.tar.gz", hash = "sha256:2e308396e1d83de287ada2c2fd6e64286008fe6aca5008e0b6a8cb0e2c86eedd"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\"", "ruff (>=0.8.0) ; sys_platform != \"cygwin\""] +core = ["importlib_metadata (>=6) ; python_version < \"3.10\"", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1) ; python_version < \"3.11\"", "wheel (>=0.43.0)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"] +type = ["importlib_metadata (>=7.0.2) ; python_version < \"3.10\"", "jaraco.develop (>=7.21) ; sys_platform != \"cygwin\"", "mypy (==1.14.*)", "pytest-mypy"] + +[[package]] +name = "setuptools-scm" +version = "8.3.1" +description = "the blessed package to manage your versions by scm tags" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "setuptools_scm-8.3.1-py3-none-any.whl", hash = "sha256:332ca0d43791b818b841213e76b1971b7711a960761c5bea5fc5cdb5196fbce3"}, + {file = "setuptools_scm-8.3.1.tar.gz", hash = "sha256:3d555e92b75dacd037d32bafdf94f97af51ea29ae8c7b234cf94b7a5bd242a63"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.6", markers = "python_version < \"3.10\""} +packaging = ">=20" +setuptools = "*" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["entangled-cli (>=2.0,<3.0)", "mkdocs", "mkdocs-entangled-plugin", "mkdocs-include-markdown-plugin", "mkdocs-material", "mkdocstrings[python]", "pygments"] +rich = ["rich"] +test = ["build", "pytest", "rich", "typing-extensions ; python_version < \"3.11\"", "wheel"] + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +description = "Sniff out which async library your code is running under" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, + {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, +] + +[[package]] +name = "soupsieve" +version = "2.7" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, +] + +[[package]] +name = "starlette" +version = "0.46.2" +description = "The little ASGI library that shines." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, + {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, +] + +[package.dependencies] +anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] + +[[package]] +name = "statsmodels" +version = "0.14.4" +description = "Statistical computations and models for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "statsmodels-0.14.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7a62f1fc9086e4b7ee789a6f66b3c0fc82dd8de1edda1522d30901a0aa45e42b"}, + {file = "statsmodels-0.14.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:46ac7ddefac0c9b7b607eed1d47d11e26fe92a1bc1f4d9af48aeed4e21e87981"}, + {file = "statsmodels-0.14.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a337b731aa365d09bb0eab6da81446c04fde6c31976b1d8e3d3a911f0f1e07b"}, + {file = "statsmodels-0.14.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:631bb52159117c5da42ba94bd94859276b68cab25dc4cac86475bc24671143bc"}, + {file = "statsmodels-0.14.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3bb2e580d382545a65f298589809af29daeb15f9da2eb252af8f79693e618abc"}, + {file = "statsmodels-0.14.4-cp310-cp310-win_amd64.whl", hash = "sha256:9729642884147ee9db67b5a06a355890663d21f76ed608a56ac2ad98b94d201a"}, + {file = "statsmodels-0.14.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ed7e118e6e3e02d6723a079b8c97eaadeed943fa1f7f619f7148dfc7862670f"}, + {file = "statsmodels-0.14.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f5f537f7d000de4a1708c63400755152b862cd4926bb81a86568e347c19c364b"}, + {file = "statsmodels-0.14.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa74aaa26eaa5012b0a01deeaa8a777595d0835d3d6c7175f2ac65435a7324d2"}, + {file = "statsmodels-0.14.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e332c2d9b806083d1797231280602340c5c913f90d4caa0213a6a54679ce9331"}, + {file = "statsmodels-0.14.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9c8fa28dfd75753d9cf62769ba1fecd7e73a0be187f35cc6f54076f98aa3f3f"}, + {file = "statsmodels-0.14.4-cp311-cp311-win_amd64.whl", hash = "sha256:a6087ecb0714f7c59eb24c22781491e6f1cfffb660b4740e167625ca4f052056"}, + {file = "statsmodels-0.14.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5221dba7424cf4f2561b22e9081de85f5bb871228581124a0d1b572708545199"}, + {file = "statsmodels-0.14.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:17672b30c6b98afe2b095591e32d1d66d4372f2651428e433f16a3667f19eabb"}, + {file = "statsmodels-0.14.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5e6312213b8cfb9dca93dd46a0f4dccb856541f91d3306227c3d92f7659245"}, + {file = "statsmodels-0.14.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bbb150620b53133d6cd1c5d14c28a4f85701e6c781d9b689b53681effaa655f"}, + {file = "statsmodels-0.14.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb695c2025d122a101c2aca66d2b78813c321b60d3a7c86bb8ec4467bb53b0f9"}, + {file = "statsmodels-0.14.4-cp312-cp312-win_amd64.whl", hash = "sha256:7f7917a51766b4e074da283c507a25048ad29a18e527207883d73535e0dc6184"}, + {file = "statsmodels-0.14.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5a24f5d2c22852d807d2b42daf3a61740820b28d8381daaf59dcb7055bf1a79"}, + {file = "statsmodels-0.14.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df4f7864606fa843d7e7c0e6af288f034a2160dba14e6ccc09020a3cf67cb092"}, + {file = "statsmodels-0.14.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91341cbde9e8bea5fb419a76e09114e221567d03f34ca26e6d67ae2c27d8fe3c"}, + {file = "statsmodels-0.14.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1322286a7bfdde2790bf72d29698a1b76c20b8423a55bdcd0d457969d0041f72"}, + {file = "statsmodels-0.14.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e31b95ac603415887c9f0d344cb523889cf779bc52d68e27e2d23c358958fec7"}, + {file = "statsmodels-0.14.4-cp313-cp313-win_amd64.whl", hash = "sha256:81030108d27aecc7995cac05aa280cf8c6025f6a6119894eef648997936c2dd0"}, + {file = "statsmodels-0.14.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4793b01b7a5f5424f5a1dbcefc614c83c7608aa2b035f087538253007c339d5d"}, + {file = "statsmodels-0.14.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d330da34f59f1653c5193f9fe3a3a258977c880746db7f155fc33713ea858db5"}, + {file = "statsmodels-0.14.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e9ddefba1d4e1107c1f20f601b0581421ea3ad9fd75ce3c2ba6a76b6dc4682c"}, + {file = "statsmodels-0.14.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f43da7957e00190104c5dd0f661bfc6dfc68b87313e3f9c4dbd5e7d222e0aeb"}, + {file = "statsmodels-0.14.4-cp39-cp39-win_amd64.whl", hash = "sha256:8286f69a5e1d0e0b366ffed5691140c83d3efc75da6dbf34a3d06e88abfaaab6"}, + {file = "statsmodels-0.14.4.tar.gz", hash = "sha256:5d69e0f39060dc72c067f9bb6e8033b6dccdb0bae101d76a7ef0bcc94e898b67"}, +] + +[package.dependencies] +numpy = ">=1.22.3,<3" +packaging = ">=21.3" +pandas = ">=1.4,<2.1.0 || >2.1.0" +patsy = ">=0.5.6" +scipy = ">=1.8,<1.9.2 || >1.9.2" + +[package.extras] +build = ["cython (>=3.0.10)"] +develop = ["colorama", "cython (>=3.0.10)", "cython (>=3.0.10,<4)", "flake8", "isort", "joblib", "matplotlib (>=3)", "pytest (>=7.3.0,<8)", "pytest-cov", "pytest-randomly", "pytest-xdist", "pywinpty ; os_name == \"nt\"", "setuptools-scm[toml] (>=8.0,<9.0)"] +docs = ["ipykernel", "jupyter-client", "matplotlib", "nbconvert", "nbformat", "numpydoc", "pandas-datareader", "sphinx"] + +[[package]] +name = "tenacity" +version = "9.1.2" +description = "Retry code until it succeeds" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, + {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, +] + +[package.extras] +doc = ["reno", "sphinx"] +test = ["pytest", "tornado (>=4.5)", "typeguard"] + +[[package]] +name = "threadpoolctl" +version = "3.6.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, + {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, +] + +[[package]] +name = "tld" +version = "0.13" +description = "Extract the top-level domain (TLD) from the URL given." +optional = false +python-versions = ">=3.7, <4" +groups = ["main"] +files = [ + {file = "tld-0.13-py2.py3-none-any.whl", hash = "sha256:f75b2be080f767ed17c2338a339eaa4fab5792586319ca819119da252f9f3749"}, + {file = "tld-0.13.tar.gz", hash = "sha256:93dde5e1c04bdf1844976eae440706379d21f4ab235b73c05d7483e074fb5629"}, +] + +[[package]] +name = "tomli" +version = "2.2.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "python_version < \"3.11\"" +files = [ + {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, + {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, + {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, + {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, + {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, + {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, + {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, + {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, + {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, + {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, + {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, + {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, + {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, + {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, + {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, + {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, + {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, + {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, +] + +[[package]] +name = "toolz" +version = "1.0.0" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"}, + {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"}, +] + +[[package]] +name = "trafilatura" +version = "2.0.0" +description = "Python & Command-line tool to gather text and metadata on the Web: Crawling, scraping, extraction, output as CSV, JSON, HTML, MD, TXT, XML." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d"}, + {file = "trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247"}, +] + +[package.dependencies] +certifi = "*" +charset_normalizer = ">=3.4.0" +courlan = ">=1.3.2" +htmldate = ">=1.9.2" +justext = ">=3.0.1" +lxml = {version = ">=5.3.0", markers = "platform_system != \"Darwin\" or python_version > \"3.8\""} +urllib3 = ">=1.26,<3" + +[package.extras] +all = ["brotli", "cchardet (>=2.1.7) ; python_version < \"3.11\"", "faust-cchardet (>=2.1.19) ; python_version >= \"3.11\"", "htmldate[speed] (>=1.9.2)", "py3langid (>=0.3.0)", "pycurl (>=7.45.3)", "urllib3[socks]", "zstandard (>=0.23.0)"] +dev = ["flake8", "mypy", "pytest", "pytest-cov", "types-lxml", "types-urllib3"] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +description = "Runtime typing introspection tools" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, + {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, +] + +[package.dependencies] +typing-extensions = ">=4.12.0" + +[[package]] +name = "tzdata" +version = "2025.2" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +groups = ["main"] +files = [ + {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, + {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d"}, + {file = "tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd"}, +] + +[package.dependencies] +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + +[[package]] +name = "url-normalize" +version = "1.4.3" +description = "URL normalization for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main"] +files = [ + {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, + {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "urllib3" +version = "2.4.0" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, + {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "uuid7" +version = "0.1.0" +description = "UUID version 7, generating time-sorted UUIDs with 200ns time resolution and 48 bits of randomness" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "uuid7-0.1.0-py2.py3-none-any.whl", hash = "sha256:5e259bb63c8cb4aded5927ff41b444a80d0c7124e8a0ced7cf44efa1f5cccf61"}, + {file = "uuid7-0.1.0.tar.gz", hash = "sha256:8c57aa32ee7456d3cc68c95c4530bc571646defac01895cfc73545449894a63c"}, +] + +[[package]] +name = "uvicorn" +version = "0.34.2" +description = "The lightning-fast ASGI server." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "uvicorn-0.34.2-py3-none-any.whl", hash = "sha256:deb49af569084536d269fe0a6d67e3754f104cf03aba7c11c40f01aadf33c403"}, + {file = "uvicorn-0.34.2.tar.gz", hash = "sha256:0e929828f6186353a80b58ea719861d2629d766293b6d19baf086ba31d4f3328"}, +] + +[package.dependencies] +click = ">=7.0" +h11 = ">=0.8" +typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "websockets" +version = "15.0.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, + {file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"}, + {file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"}, + {file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"}, + {file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"}, + {file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"}, + {file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"}, + {file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"}, + {file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"}, + {file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"}, + {file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"}, + {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"}, + {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"}, + {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"}, + {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"}, + {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"}, + {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"}, + {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"}, + {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"}, + {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"}, + {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"}, + {file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"}, + {file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"}, + {file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"}, + {file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"}, + {file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"}, + {file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"}, + {file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"}, + {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"}, + {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"}, +] + +[[package]] +name = "wrapt" +version = "1.17.2" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, + {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, + {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, + {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, + {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, + {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, + {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, + {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, + {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, + {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, + {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, + {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, + {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, + {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, + {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, + {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, + {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, + {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, + {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, + {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, + {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, + {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, + {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, + {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, + {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, + {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, + {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, + {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, + {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, + {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, + {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, + {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, + {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, + {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, + {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, + {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, + {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, + {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, +] + +[[package]] +name = "xlrd" +version = "2.0.1" +description = "Library for developers to extract data from Microsoft Excel (tm) .xls spreadsheet files" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +groups = ["main"] +files = [ + {file = "xlrd-2.0.1-py2.py3-none-any.whl", hash = "sha256:6a33ee89877bd9abc1158129f6e94be74e2679636b8a205b43b85206c3f0bbdd"}, + {file = "xlrd-2.0.1.tar.gz", hash = "sha256:f72f148f54442c6b056bf931dbc34f986fd0c3b0b6b5a58d013c9aef274d0c88"}, +] + +[package.extras] +build = ["twine", "wheel"] +docs = ["sphinx"] +test = ["pytest", "pytest-cov"] + +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.4" +groups = ["main"] +files = [ + {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, + {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, +] + +[[package]] +name = "yarl" +version = "1.20.0" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f6670b9ae3daedb325fa55fbe31c22c8228f6e0b513772c2e1c623caa6ab22"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85a231fa250dfa3308f3c7896cc007a47bc76e9e8e8595c20b7426cac4884c62"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a06701b647c9939d7019acdfa7ebbfbb78ba6aa05985bb195ad716ea759a569"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7595498d085becc8fb9203aa314b136ab0516c7abd97e7d74f7bb4eb95042abe"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af5607159085dcdb055d5678fc2d34949bd75ae6ea6b4381e784bbab1c3aa195"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95b50910e496567434cb77a577493c26bce0f31c8a305135f3bda6a2483b8e10"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b594113a301ad537766b4e16a5a6750fcbb1497dcc1bc8a4daae889e6402a634"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:083ce0393ea173cd37834eb84df15b6853b555d20c52703e21fbababa8c129d2"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1a350a652bbbe12f666109fbddfdf049b3ff43696d18c9ab1531fbba1c977a"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fb0caeac4a164aadce342f1597297ec0ce261ec4532bbc5a9ca8da5622f53867"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d88cc43e923f324203f6ec14434fa33b85c06d18d59c167a0637164863b8e995"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e52d6ed9ea8fd3abf4031325dc714aed5afcbfa19ee4a89898d663c9976eb487"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ce360ae48a5e9961d0c730cf891d40698a82804e85f6e74658fb175207a77cb2"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:06d06c9d5b5bc3eb56542ceeba6658d31f54cf401e8468512447834856fb0e61"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c27d98f4e5c4060582f44e58309c1e55134880558f1add7a87c1bc36ecfade19"}, + {file = "yarl-1.20.0-cp310-cp310-win32.whl", hash = "sha256:f4d3fa9b9f013f7050326e165c3279e22850d02ae544ace285674cb6174b5d6d"}, + {file = "yarl-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:bc906b636239631d42eb8a07df8359905da02704a868983265603887ed68c076"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5"}, + {file = "yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6"}, + {file = "yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b"}, + {file = "yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64"}, + {file = "yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384"}, + {file = "yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62"}, + {file = "yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f"}, + {file = "yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac"}, + {file = "yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:119bca25e63a7725b0c9d20ac67ca6d98fa40e5a894bd5d4686010ff73397914"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35d20fb919546995f1d8c9e41f485febd266f60e55383090010f272aca93edcc"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:484e7a08f72683c0f160270566b4395ea5412b4359772b98659921411d32ad26"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d8a3d54a090e0fff5837cd3cc305dd8a07d3435a088ddb1f65e33b322f66a94"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f0cf05ae2d3d87a8c9022f3885ac6dea2b751aefd66a4f200e408a61ae9b7f0d"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a884b8974729e3899d9287df46f015ce53f7282d8d3340fa0ed57536b440621c"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8d8aa8dd89ffb9a831fedbcb27d00ffd9f4842107d52dc9d57e64cb34073d5c"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4e88d6c3c8672f45a30867817e4537df1bbc6f882a91581faf1f6d9f0f1b5a"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdb77efde644d6f1ad27be8a5d67c10b7f769804fff7a966ccb1da5a4de4b656"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4ba5e59f14bfe8d261a654278a0f6364feef64a794bd456a8c9e823071e5061c"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d0bf955b96ea44ad914bc792c26a0edcd71b4668b93cbcd60f5b0aeaaed06c64"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:27359776bc359ee6eaefe40cb19060238f31228799e43ebd3884e9c589e63b20"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:04d9c7a1dc0a26efb33e1acb56c8849bd57a693b85f44774356c92d610369efa"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:faa709b66ae0e24c8e5134033187a972d849d87ed0a12a0366bedcc6b5dc14a5"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:44869ee8538208fe5d9342ed62c11cc6a7a1af1b3d0bb79bb795101b6e77f6e0"}, + {file = "yarl-1.20.0-cp39-cp39-win32.whl", hash = "sha256:b7fa0cb9fd27ffb1211cde944b41f5c67ab1c13a13ebafe470b1e206b8459da8"}, + {file = "yarl-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4fad6e5189c847820288286732075f213eabf81be4d08d6cc309912e62be5b7"}, + {file = "yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124"}, + {file = "yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + +[[package]] +name = "yfinance" +version = "0.2.57" +description = "Download market data from Yahoo! Finance API" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "yfinance-0.2.57-py2.py3-none-any.whl", hash = "sha256:b630c333da74798090fab5ee11d0961fd6e4b394ad150be7a6bd8f7f52990536"}, + {file = "yfinance-0.2.57.tar.gz", hash = "sha256:7b16349a97b1148d4611d24bcaab45ccb643144001b51ecb093607950264f3b1"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.11.1" +frozendict = ">=2.3.4" +multitasking = ">=0.0.7" +numpy = ">=1.16.5" +pandas = ">=1.3.0" +peewee = ">=3.16.2" +platformdirs = ">=2.0.0" +pytz = ">=2022.5" +requests = ">=2.31" + +[package.extras] +nospam = ["requests_cache (>=1.0)", "requests_ratelimiter (>=0.3.1)"] +repair = ["scipy (>=1.6.3)"] + +[[package]] +name = "zipp" +version = "3.21.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.1" +python-versions = "^3.9.21,<3.13" +content-hash = "104bd0d53d3be18eafbd4d2f7d200128f6164247786ebb52412c8d5a3a739063" diff --git a/cli/pyproject.toml b/cli/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..76fb3fbf83aeb2bfcb3acbbc4d5f09ff7832ffab --- /dev/null +++ b/cli/pyproject.toml @@ -0,0 +1,31 @@ +[tool.poetry] +name = "openbb-cli" +version = "1.1.9" +description = "Investment Research for Everyone, Anywhere." +authors = ["OpenBB "] +packages = [{ include = "openbb_cli" }] +license = "AGPL-3.0-only" +readme = "README.md" +homepage = "https://openbb.co" +repository = "https://github.com/OpenBB-finance/OpenBB" +documentation = "https://docs.openbb.co/cli" + +[tool.poetry.scripts] +openbb = 'openbb_cli.cli:main' + +[tool.poetry.dependencies] +python = "^3.9.21,<3.13" + +# OpenBB dependencies +openbb = { version = "^4.4.4", extras = ["all"] } + +# CLI dependencies +prompt-toolkit = "^3.0.50" +rich = "^14.0.0" +python-dotenv = "^1.0.1" +openpyxl = "^3.1.5" +pywry = "^0.6.2" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/cli/tests/__init__.py b/cli/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/cli/tests/test_argparse_translator.py b/cli/tests/test_argparse_translator.py new file mode 100644 index 0000000000000000000000000000000000000000..be7b2315fac33c05277ec30fbbf09aa7768a8cd5 --- /dev/null +++ b/cli/tests/test_argparse_translator.py @@ -0,0 +1,96 @@ +"""Test the Argparse Translator.""" + +from argparse import ArgumentParser + +import pytest +from openbb_cli.argparse_translator.argparse_argument import ( + ArgparseArgumentGroupModel, + ArgparseArgumentModel, +) +from openbb_cli.argparse_translator.argparse_translator import ( + ArgparseTranslator, +) + +# pylint: disable=protected-access + + +def test_custom_argument_action_validation(): + """Test that CustomArgument raises an error for invalid actions.""" + with pytest.raises(ValueError) as excinfo: + ArgparseArgumentModel( + name="test", + type=bool, + dest="test", + default=False, + required=True, + action="store", + help="Test argument", + nargs=None, + choices=None, + ) + assert 'action must be "store_true"' in str(excinfo.value) + + +def test_custom_argument_remove_props_on_store_true(): + """Test that CustomArgument removes type, nargs, and choices on store_true.""" + argument = ArgparseArgumentModel( + name="verbose", + type=None, + dest="verbose", + default=None, + required=False, + action="store_true", + help="Verbose output", + nargs=None, + choices=None, + ) + assert argument.type is None + assert argument.nargs is None + assert argument.choices is None + + +def test_custom_argument_group(): + """Test the CustomArgumentGroup class.""" + args = [ + ArgparseArgumentModel( + name="test", + type=int, + dest="test", + default=1, + required=True, + action="store", + help="Test argument", + nargs=None, + choices=None, + ) + ] + group = ArgparseArgumentGroupModel(name="Test Group", arguments=args) + assert group.name == "Test Group" + assert len(group.arguments) == 1 + assert group.arguments[0].name == "test" + + +def test_argparse_translator_setup(): + """Test the ArgparseTranslator setup.""" + + def test_function(test_arg: int): + """A test function.""" + return test_arg * 2 + + translator = ArgparseTranslator(func=test_function) + parser = translator.parser + assert isinstance(parser, ArgumentParser) + assert "--test_arg" in parser._option_string_actions + + +def test_argparse_translator_execution(): + """Test the ArgparseTranslator execution.""" + + def test_function(test_arg: int) -> int: + """A test function.""" + return test_arg * 2 + + translator = ArgparseTranslator(func=test_function) + parsed_args = translator.parser.parse_args(["--test_arg", "3"]) + result = translator.execute_func(parsed_args) + assert result == 6 diff --git a/cli/tests/test_argparse_translator_obbject_registry.py b/cli/tests/test_argparse_translator_obbject_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..53a4a1cea80f24c057026da2fa3f32df7a6ed4f9 --- /dev/null +++ b/cli/tests/test_argparse_translator_obbject_registry.py @@ -0,0 +1,89 @@ +"""Test OBBject Registry.""" + +from unittest.mock import Mock + +import pytest +from openbb_cli.argparse_translator.obbject_registry import Registry +from openbb_core.app.model.obbject import OBBject + +# pylint: disable=redefined-outer-name, protected-access + + +@pytest.fixture +def registry(): + """Fixture to create a Registry instance for testing.""" + return Registry() + + +@pytest.fixture +def mock_obbject(): + """Fixture to create a mock OBBject for testing.""" + + class MockModel: + """Mock model for testing.""" + + def __init__(self, value): + self.mock_value = value + self._model_json_schema = "mock_json_schema" + + def model_json_schema(self): + return self._model_json_schema + + obb = Mock(spec=OBBject) + obb.id = "123" + obb.provider = "test_provider" + obb.extra = {"command": "test_command"} + obb._route = "/test/route" + obb._standard_params = Mock() + obb._standard_params = {} + obb.results = [MockModel(1), MockModel(2)] + return obb + + +def test_listing_all_obbjects(registry, mock_obbject): + """Test listing all obbjects with additional properties.""" + registry.register(mock_obbject) + + all_obbjects = registry.all + assert len(all_obbjects) == 1 + assert all_obbjects[0]["command"] == "test_command" + assert all_obbjects[0]["provider"] == "test_provider" + + +def test_registry_initialization(registry): + """Test the Registry is initialized correctly.""" + assert registry.obbjects == [] + + +def test_register_new_obbject(registry, mock_obbject): + """Test registering a new OBBject.""" + registry.register(mock_obbject) + assert mock_obbject in registry.obbjects + + +def test_register_duplicate_obbject(registry, mock_obbject): + """Test that duplicate OBBjects are not added.""" + registry.register(mock_obbject) + registry.register(mock_obbject) + assert len(registry.obbjects) == 1 + + +def test_get_obbject_by_index(registry, mock_obbject): + """Test retrieving an obbject by its index.""" + registry.register(mock_obbject) + retrieved = registry.get(0) + assert retrieved == mock_obbject + + +def test_remove_obbject_by_index(registry, mock_obbject): + """Test removing an obbject by index.""" + registry.register(mock_obbject) + registry.remove(0) + assert mock_obbject not in registry.obbjects + + +def test_remove_last_obbject_by_default(registry, mock_obbject): + """Test removing the last obbject by default.""" + registry.register(mock_obbject) + registry.remove() + assert not registry.obbjects diff --git a/cli/tests/test_cli.py b/cli/tests/test_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..39936df00e2d0af61683726c2852ebed6254e861 --- /dev/null +++ b/cli/tests/test_cli.py @@ -0,0 +1,45 @@ +"""Test the CLI module.""" + +from unittest.mock import patch + +from openbb_cli.cli import main + + +@patch("openbb_cli.config.setup.bootstrap") +@patch("openbb_cli.controllers.cli_controller.launch") +@patch("sys.argv", ["openbb", "--dev", "--debug"]) +def test_main_with_dev_and_debug(mock_launch, mock_bootstrap): + """Test the main function with dev and debug flags.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(True, True) + + +@patch("openbb_cli.config.setup.bootstrap") +@patch("openbb_cli.controllers.cli_controller.launch") +@patch("sys.argv", ["openbb"]) +def test_main_without_arguments(mock_launch, mock_bootstrap): + """Test the main function without arguments.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(False, False) + + +@patch("openbb_cli.config.setup.bootstrap") +@patch("openbb_cli.controllers.cli_controller.launch") +@patch("sys.argv", ["openbb", "--dev"]) +def test_main_with_dev_only(mock_launch, mock_bootstrap): + """Test the main function with dev flag only.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(True, False) + + +@patch("openbb_cli.config.setup.bootstrap") +@patch("openbb_cli.controllers.cli_controller.launch") +@patch("sys.argv", ["openbb", "--debug"]) +def test_main_with_debug_only(mock_launch, mock_bootstrap): + """Test the main function with debug flag only.""" + main() + mock_bootstrap.assert_called_once() + mock_launch.assert_called_once_with(False, True) diff --git a/cli/tests/test_config_completer.py b/cli/tests/test_config_completer.py new file mode 100644 index 0000000000000000000000000000000000000000..71efad99d5b86644cf4cd4c9c30f46caaff222a4 --- /dev/null +++ b/cli/tests/test_config_completer.py @@ -0,0 +1,78 @@ +"""Test the Config completer.""" + +import pytest +from openbb_cli.config.completer import WordCompleter +from prompt_toolkit.completion import CompleteEvent +from prompt_toolkit.document import Document + +# pylint: disable=redefined-outer-name, import-outside-toplevel + + +@pytest.fixture +def word_completer(): + """Return a simple word completer.""" + words = ["test", "example", "demo"] + return WordCompleter(words, ignore_case=True) + + +def test_word_completer_simple(word_completer): + """Test the word completer with a simple word list.""" + doc = Document(text="ex", cursor_position=2) + completions = list(word_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 1 + assert completions[0].text == "example" + + +def test_word_completer_case_insensitive(word_completer): + """Test the word completer with case-insensitive matching.""" + doc = Document(text="Ex", cursor_position=2) + completions = list(word_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 1 + assert completions[0].text == "example" + + +def test_word_completer_no_match(word_completer): + """Test the word completer with no matches.""" + doc = Document(text="xyz", cursor_position=3) + completions = list(word_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 0 + + +@pytest.fixture +def nested_completer(): + """Return a nested completer.""" + from openbb_cli.config.completer import NestedCompleter + + data = { + "show": { + "version": None, + "interfaces": None, + "clock": None, + "ip": {"interface": {"brief": None}}, + }, + "exit": None, + "enable": None, + } + return NestedCompleter.from_nested_dict(data) + + +def test_nested_completer_root_command(nested_completer): + """Test the nested completer with a root command.""" + doc = Document(text="sh", cursor_position=2) + completions = list(nested_completer.get_completions(doc, CompleteEvent())) + assert "show" in [c.text for c in completions] + + +def test_nested_completer_sub_command(nested_completer): + """Test the nested completer with a sub-command.""" + doc = Document(text="show ", cursor_position=5) + completions = list(nested_completer.get_completions(doc, CompleteEvent())) + assert "version" in [c.text for c in completions] + assert "interfaces" in [c.text for c in completions] + + +def test_nested_completer_no_match(nested_completer): + """Test the nested completer with no matches.""" + doc = Document(text="random ", cursor_position=7) + completions = list(nested_completer.get_completions(doc, CompleteEvent())) + assert len(completions) == 0 diff --git a/cli/tests/test_config_console.py b/cli/tests/test_config_console.py new file mode 100644 index 0000000000000000000000000000000000000000..60ff91a88ac1c9b3a8dcaf8b23925e35c6e740f7 --- /dev/null +++ b/cli/tests/test_config_console.py @@ -0,0 +1,47 @@ +"""Test Config Console.""" + +from unittest.mock import patch + +import pytest +from openbb_cli.config.console import Console +from rich.text import Text + +# pylint: disable=redefined-outer-name, unused-argument, unused-variable, protected-access + + +@pytest.fixture +def mock_settings(): + """Mock settings to inject into Console.""" + + class MockSettings: + TEST_MODE = False + ENABLE_RICH_PANEL = True + SHOW_VERSION = True + VERSION = "1.0" + + return MockSettings() + + +@pytest.fixture +def console(mock_settings): + """Create a Console instance with mocked settings.""" + with patch("rich.console.Console") as MockRichConsole: # noqa: F841 + return Console(settings=mock_settings) + + +def test_print_without_panel(console, mock_settings): + """Test printing without a rich panel when disabled.""" + mock_settings.ENABLE_RICH_PANEL = False + with patch.object(console._console, "print") as mock_print: + console.print(text="Hello, world!", menu="Home Menu") + mock_print.assert_called_once_with("Hello, world!") + + +def test_blend_text(): + """Test blending text colors.""" + message = "Hello" + color1 = (255, 0, 0) # Red + color2 = (0, 0, 255) # Blue + blended_text = Console._blend_text(message, color1, color2) + assert isinstance(blended_text, Text) + assert "Hello" in blended_text.plain diff --git a/cli/tests/test_config_menu_text.py b/cli/tests/test_config_menu_text.py new file mode 100644 index 0000000000000000000000000000000000000000..10956ccbac559e18a433f9ef304c5ee6fa1968b8 --- /dev/null +++ b/cli/tests/test_config_menu_text.py @@ -0,0 +1,68 @@ +"""Test Config Menu Text.""" + +import pytest +from openbb_cli.config.menu_text import MenuText + +# pylint: disable=redefined-outer-name, protected-access + + +@pytest.fixture +def menu_text(): + """Fixture to create a MenuText instance for testing.""" + return MenuText(path="/test/path") + + +def test_initialization(menu_text): + """Test initialization of the MenuText class.""" + assert menu_text.menu_text == "" + assert menu_text.menu_path == "/test/path" + assert menu_text.warnings == [] + + +def test_add_raw(menu_text): + """Test adding raw text.""" + menu_text.add_raw("Example raw text") + assert "Example raw text" in menu_text.menu_text + + +def test_add_info(menu_text): + """Test adding informational text.""" + menu_text.add_info("Info text") + assert "[info]Info text:[/info]" in menu_text.menu_text + + +def test_add_cmd(menu_text): + """Test adding a command.""" + menu_text.add_cmd("command", "Performs an action") + assert "command" in menu_text.menu_text + assert "Performs an action" in menu_text.menu_text + + +def test_format_cmd_name(menu_text): + """Test formatting of command names that are too long.""" + long_name = "x" * 50 # Assuming CMD_NAME_LENGTH is 23 + formatted_name = menu_text._format_cmd_name(long_name) + assert len(formatted_name) <= menu_text.CMD_NAME_LENGTH + assert menu_text.warnings # Check that a warning was added + + +def test_format_cmd_description(menu_text): + """Test truncation of long descriptions.""" + long_description = "y" * 100 # Assuming CMD_DESCRIPTION_LENGTH is 65 + formatted_description = menu_text._format_cmd_description("cmd", long_description) + assert len(formatted_description) <= menu_text.CMD_DESCRIPTION_LENGTH + + +def test_add_menu(menu_text): + """Test adding a menu item.""" + menu_text.add_menu("Settings", "Configure your settings") + assert "Settings" in menu_text.menu_text + assert "Configure your settings" in menu_text.menu_text + + +def test_add_setting(menu_text): + """Test adding a setting.""" + menu_text.add_setting("Enable Feature", True, "Feature description") + assert "Enable Feature" in menu_text.menu_text + assert "Feature description" in menu_text.menu_text + assert "[green]" in menu_text.menu_text diff --git a/cli/tests/test_config_setup.py b/cli/tests/test_config_setup.py new file mode 100644 index 0000000000000000000000000000000000000000..7c01469063b47086f38b34c445604f186a7efb57 --- /dev/null +++ b/cli/tests/test_config_setup.py @@ -0,0 +1,49 @@ +"""Test the Config Setup.""" + +from unittest.mock import patch + +import pytest +from openbb_cli.config.setup import bootstrap + +# pylint: disable=unused-variable + + +def test_bootstrap_creates_directory_and_file(): + """Test that bootstrap creates the settings directory and environment file.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch: + bootstrap() + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_touch.assert_called_once_with(exist_ok=True) + + +def test_bootstrap_directory_exists(): + """Test bootstrap when the directory already exists.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch: + bootstrap() + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_touch.assert_called_once_with(exist_ok=True) + + +def test_bootstrap_file_exists(): + """Test bootstrap when the environment file already exists.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch: + bootstrap() + mock_mkdir.assert_called_once_with(parents=True, exist_ok=True) + mock_touch.assert_called_once_with(exist_ok=True) + + +def test_bootstrap_permission_error(): + """Test bootstrap handles permission errors gracefully.""" + with patch("pathlib.Path.mkdir") as mock_mkdir, patch( + "pathlib.Path.touch" + ) as mock_touch, pytest.raises( # noqa: F841 + PermissionError + ): + mock_mkdir.side_effect = PermissionError("No permission to create directory") + bootstrap() # Expecting to raise a PermissionError and be caught by pytest.raises diff --git a/cli/tests/test_config_style.py b/cli/tests/test_config_style.py new file mode 100644 index 0000000000000000000000000000000000000000..72e9c2a2f38f9361bb767e7a07cfe4178790feca --- /dev/null +++ b/cli/tests/test_config_style.py @@ -0,0 +1,60 @@ +"""Test Config Style.""" + +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.config.style import Style + +# pylint: disable=redefined-outer-name, protected-access + + +@pytest.fixture +def mock_style_directory(tmp_path): + """Fixture to create a mock styles directory.""" + (tmp_path / "styles" / "default").mkdir(parents=True, exist_ok=True) + return tmp_path / "styles" + + +@pytest.fixture +def style(mock_style_directory): + """Fixture to create a Style instance for testing.""" + return Style(directory=mock_style_directory) + + +def test_initialization(style): + """Test that Style class initializes with default properties.""" + assert style.line_width == 1.5 + assert isinstance(style.console_style, dict) + + +@patch("pathlib.Path.exists", MagicMock(return_value=True)) +@patch("pathlib.Path.rglob") +def test_load_styles(mock_rglob, style, mock_style_directory): + """Test loading styles from directories.""" + mock_rglob.return_value = [mock_style_directory / "default" / "dark.richstyle.json"] + style._load(mock_style_directory) + assert "dark" in style.console_styles_available + + +@patch("builtins.open", new_callable=MagicMock) +@patch("json.load", MagicMock(return_value={"background": "black"})) +def test_from_json(mock_open, style, mock_style_directory): + """Test loading style from a JSON file.""" + json_file = mock_style_directory / "dark.richstyle.json" + result = style._from_json(json_file) + assert result == {"background": "black"} + mock_open.assert_called_once_with(json_file) + + +def test_apply_invalid_style(style, mock_style_directory, capsys): + """Test applying an invalid style and falling back to default.""" + style.apply("nonexistent", mock_style_directory) + captured = capsys.readouterr() + assert "Invalid console style" in captured.out + + +def test_available_styles(style): + """Test listing available styles.""" + style.console_styles_available = {"dark": Path("/path/to/dark.richstyle.json")} + assert "dark" in style.available_styles diff --git a/cli/tests/test_controllers_base_controller.py b/cli/tests/test_controllers_base_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..465a5b61e5e665edd2d252a76b615ffd96a34b5c --- /dev/null +++ b/cli/tests/test_controllers_base_controller.py @@ -0,0 +1,79 @@ +"""Test the base controller.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.base_controller import BaseController + +# pylint: disable=unused-argument, unused-variable + + +class TestableBaseController(BaseController): + """Testable Base Controller.""" + + def __init__(self, queue=None): + """Initialize the TestableBaseController.""" + self.PATH = "/valid/path/" + super().__init__(queue=queue) + + def print_help(self): + """Print help.""" + + +def test_base_controller_initialization(): + """Test the initialization of the base controller.""" + with patch.object(TestableBaseController, "check_path", return_value=None): + controller = TestableBaseController() + assert controller.path == ["valid", "path"] # Checking for correct path split + + +def test_path_validation(): + """Test the path validation method.""" + controller = TestableBaseController() + + with pytest.raises(ValueError): + controller.PATH = "invalid/path" + controller.check_path() + + with pytest.raises(ValueError): + controller.PATH = "/invalid/path" + controller.check_path() + + with pytest.raises(ValueError): + controller.PATH = "/Invalid/Path/" + controller.check_path() + + controller.PATH = "/valid/path/" + + +def test_parse_input(): + """Test the parse input method.""" + controller = TestableBaseController() + input_str = "cmd1/cmd2/cmd3" + expected = ["cmd1", "cmd2", "cmd3"] + result = controller.parse_input(input_str) + assert result == expected + + +def test_switch(): + """Test the switch method.""" + controller = TestableBaseController() + with patch.object(controller, "call_exit", MagicMock()) as mock_exit: + controller.queue = ["exit"] + controller.switch("exit") + mock_exit.assert_called_once() + + +def test_call_help(): + """Test the call help method.""" + controller = TestableBaseController() + with patch("openbb_cli.controllers.base_controller.session.console.print"): + controller.call_help(None) + + +def test_call_exit(): + """Test the call exit method.""" + controller = TestableBaseController() + with patch.object(controller, "save_class", MagicMock()): + controller.queue = ["quit"] + controller.call_exit(None) diff --git a/cli/tests/test_controllers_base_platform_controller.py b/cli/tests/test_controllers_base_platform_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..cabfe44cd6bd8ce10f517dfa738e912286fc625f --- /dev/null +++ b/cli/tests/test_controllers_base_platform_controller.py @@ -0,0 +1,70 @@ +"""Test the BasePlatformController.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.base_platform_controller import PlatformController, Session + +# pylint: disable=redefined-outer-name, protected-access, unused-argument, unused-variable + + +@pytest.fixture +def mock_session(): + """Mock session fixture.""" + with patch( + "openbb_cli.controllers.base_platform_controller.session", + MagicMock(spec=Session), + ) as mock: + yield mock + + +def test_initialization_with_valid_params(mock_session): + """Test the initialization of the BasePlatformController.""" + translators = {"dummy_translator": MagicMock()} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + assert controller._name == "test" + assert controller.translators == translators + + +def test_initialization_without_required_params(): + """Test the initialization of the BasePlatformController without required params.""" + with pytest.raises(ValueError): + PlatformController(name="test", parent_path=["parent"]) + + +def test_command_generation(mock_session): + """Test the command generation method.""" + translator = MagicMock() + translators = {"test_command": translator} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + + # Check if command function is correctly linked + assert "test_command" in controller.translators + + +def test_print_help(mock_session): + """Test the print help method.""" + translators = {"test_command": MagicMock()} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + + with patch( + "openbb_cli.controllers.base_platform_controller.MenuText" + ) as mock_menu_text: + controller.print_help() + mock_menu_text.assert_called_once_with("/parent/test/") + + +def test_sub_controller_generation(mock_session): + """Test the sub controller generation method.""" + translators = {"test_menu_item": MagicMock()} + controller = PlatformController( + name="test", parent_path=["parent"], translators=translators + ) + + assert "test_menu_item" in controller.translators diff --git a/cli/tests/test_controllers_choices.py b/cli/tests/test_controllers_choices.py new file mode 100644 index 0000000000000000000000000000000000000000..6d604751b1f6bbc45fdb5c1c6686cb7a2f18ee4c --- /dev/null +++ b/cli/tests/test_controllers_choices.py @@ -0,0 +1,51 @@ +"""Test the choices controller.""" + +from argparse import ArgumentParser +from unittest.mock import patch + +import pytest +from openbb_cli.controllers.choices import ( + build_controller_choice_map, +) + +# pylint: disable=redefined-outer-name, protected-access, unused-argument, unused-variable + + +class MockController: + """Mock controller class for testing.""" + + CHOICES_COMMANDS = ["test_command"] + controller_choices = ["test_command", "help"] + + def call_test_command(self, args): + """Mock function for test_command.""" + parser = ArgumentParser() + parser.add_argument( + "--example", choices=["option1", "option2"], help="Example argument." + ) + return parser.parse_args(args) + + +@pytest.fixture +def mock_controller(): + """Mock controller fixture.""" + return MockController() + + +def test_build_command_choice_map(mock_controller): + """Test the building of a command choice map.""" + with patch( + "openbb_cli.controllers.choices._get_argument_parser" + ) as mock_get_parser: + parser = ArgumentParser() + parser.add_argument( + "--option", choices=["opt1", "opt2"], help="A choice option." + ) + mock_get_parser.return_value = parser + + choice_map = build_controller_choice_map(controller=mock_controller) + + assert "test_command" in choice_map + assert "--option" in choice_map["test_command"] + assert "opt1" in choice_map["test_command"]["--option"] + assert "opt2" in choice_map["test_command"]["--option"] diff --git a/cli/tests/test_controllers_cli_controller.py b/cli/tests/test_controllers_cli_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..7cb7c6578a9293aed05fdcbcaf857d359f3edde8 --- /dev/null +++ b/cli/tests/test_controllers_cli_controller.py @@ -0,0 +1,79 @@ +"""Test the CLI controller.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.cli_controller import ( + CLIController, + handle_job_cmds, + parse_and_split_input, + run_cli, +) + +# pylint: disable=redefined-outer-name, unused-argument + + +def test_parse_and_split_input_custom_filters(): + """Test the parse_and_split_input function with custom filters.""" + input_cmd = "query -q AAPL/P" + result = parse_and_split_input( + input_cmd, custom_filters=[r"((\ -q |\ --question|\ ).*?(/))"] + ) + assert ( + "AAPL/P" not in result + ), "Should filter out terms that look like a sorting parameter" + + +@patch("openbb_cli.controllers.cli_controller.CLIController.print_help") +def test_cli_controller_print_help(mock_print_help): + """Test the CLIController print_help method.""" + controller = CLIController() + controller.print_help() + mock_print_help.assert_called_once() + + +@pytest.mark.parametrize( + "controller_input, expected_output", + [ + ("settings", True), + ("random_command", False), + ], +) +def test_CLIController_has_command(controller_input, expected_output): + """Test the CLIController has_command method.""" + controller = CLIController() + assert hasattr(controller, f"call_{controller_input}") == expected_output + + +def test_handle_job_cmds_with_export_path(): + """Test the handle_job_cmds function with an export path.""" + jobs_cmds = ["export /path/to/export some_command"] + result = handle_job_cmds(jobs_cmds) + expected = "some_command" + assert expected in result[0] # type: ignore + + +@patch("openbb_cli.controllers.cli_controller.CLIController.switch", return_value=[]) +@patch("openbb_cli.controllers.cli_controller.print_goodbye") +def test_run_cli_quit_command(mock_print_goodbye, mock_switch): + """Test the run_cli function with the quit command.""" + run_cli(["quit"], test_mode=True) + mock_print_goodbye.assert_called_once() + + +@pytest.mark.skip("This test is not working as expected") +def test_execute_openbb_routine_with_mocked_requests(): + """Test the call_exe function with mocked requests.""" + with patch("requests.get") as mock_get: + response = MagicMock() + response.status_code = 200 + response.json.return_value = {"script": "print('Hello World')"} + mock_get.return_value = response + # Here we need to call the correct function, assuming it's something like `call_exe` for URL-based scripts + controller = CLIController() + controller.call_exe( + ["--url", "https://my.openbb.co/u/test/routine/test.openbb"] + ) + mock_get.assert_called_with( + "https://my.openbb.co/u/test/routine/test.openbb?raw=true", timeout=10 + ) diff --git a/cli/tests/test_controllers_controller_factory.py b/cli/tests/test_controllers_controller_factory.py new file mode 100644 index 0000000000000000000000000000000000000000..8832885b47af8f1ec2a062b82cb761c65ada24df --- /dev/null +++ b/cli/tests/test_controllers_controller_factory.py @@ -0,0 +1,61 @@ +"""Test the Controller Factory.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.platform_controller_factory import ( + PlatformControllerFactory, +) + +# pylint: disable=redefined-outer-name, unused-argument + + +@pytest.fixture +def mock_processor(): + """Fixture to mock ArgparseClassProcessor.""" + with patch( + "openbb_cli.controllers.platform_controller_factory.ArgparseClassProcessor" + ) as mock: + instance = mock.return_value + instance.paths = {"settings": "menu"} + instance.translators = {"test_router_settings": MagicMock()} + yield instance + + +@pytest.fixture +def platform_router(): + """Fixture to provide a mock platform_router class.""" + + class MockRouter: + pass + + return MockRouter + + +@pytest.fixture +def factory(platform_router, mock_processor): + """Fixture to create a PlatformControllerFactory with mocked dependencies.""" + return PlatformControllerFactory( + platform_router=platform_router, reference={"test": "ref"} + ) + + +def test_init(mock_processor): + """Test the initialization of the PlatformControllerFactory.""" + factory = PlatformControllerFactory( + platform_router=MagicMock(), reference={"test": "ref"} + ) + assert factory.router_name.lower() == "magicmock" + assert factory.controller_name == "MagicmockController" + + +def test_create_controller(factory): + """Test the creation of a controller class.""" + ControllerClass = factory.create() + + assert "PlatformController" in [base.__name__ for base in ControllerClass.__bases__] + assert ControllerClass.CHOICES_GENERATION + assert "settings" in ControllerClass.CHOICES_MENUS + assert "test_router_settings" not in [ + cmd.replace("test_router_", "") for cmd in ControllerClass.CHOICES_COMMANDS + ] diff --git a/cli/tests/test_controllers_script_parser.py b/cli/tests/test_controllers_script_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..488eab6b288043bad5c1cc2d826c6f0b43583b20 --- /dev/null +++ b/cli/tests/test_controllers_script_parser.py @@ -0,0 +1,102 @@ +"""Test Script parser.""" + +from datetime import datetime, timedelta + +import pytest +from openbb_cli.controllers.script_parser import ( + match_and_return_openbb_keyword_date, + parse_openbb_script, +) + +# pylint: disable=import-outside-toplevel, unused-variable, line-too-long + + +@pytest.mark.parametrize( + "command, expected", + [ + ("reset", True), + ("r", True), + ("r\n", True), + ("restart", False), + ], +) +def test_is_reset(command, expected): + """Test the is_reset function.""" + from openbb_cli.controllers.script_parser import is_reset + + assert is_reset(command) == expected + + +def test_match_and_return_openbb_keyword_date(): + """Test the match_and_return_openbb_keyword_date function.""" + keyword = "$LASTFRIDAY" + result = match_and_return_openbb_keyword_date(keyword) + expected = "" + if keyword == "$LASTFRIDAY": + today = datetime.now() + expected = today - timedelta(days=(today.weekday() + 3) % 7) + if expected >= today: + expected -= timedelta(days=7) + expected = expected.strftime("%Y-%m-%d") + assert result == expected + + +def test_parse_openbb_script_basic(): + """Test the parse_openbb_script function.""" + raw_lines = ["echo 'Hello World'"] + error, script = parse_openbb_script(raw_lines) + assert error == "" + assert script == "/echo 'Hello World'" + + +def test_parse_openbb_script_with_variable(): + """Test the parse_openbb_script function.""" + raw_lines = ["$VAR = 2022-01-01", "echo $VAR"] + error, script = parse_openbb_script(raw_lines) + assert error == "" + assert script == "/echo 2022-01-01" + + +def test_parse_openbb_script_with_foreach_loop(): + """Test the parse_openbb_script function.""" + raw_lines = ["foreach $$DATE in 2022-01-01,2022-01-02", "echo $$DATE", "end"] + error, script = parse_openbb_script(raw_lines) + assert error == "" + assert script == "/echo 2022-01-01/echo 2022-01-02" + + +def test_parse_openbb_script_with_error(): + """Test the parse_openbb_script function.""" + raw_lines = ["$VAR = ", "echo $VAR"] + error, script = parse_openbb_script(raw_lines) + assert "Variable $VAR not given" in error + + +@pytest.mark.parametrize( + "line, expected", + [ + ( + "foreach $$VAR in 2022-01-01", + "[red]The script has a foreach loop that doesn't terminate. Add the keyword 'end' to explicitly terminate loop[/red]", # noqa: E501 + ), + ("echo Hello World", ""), + ( + "end", + "[red]The script has a foreach loop that terminates before it gets started. Add the keyword 'foreach' to explicitly start loop[/red]", # noqa: E501 + ), + ], +) +def test_parse_openbb_script_foreach_errors(line, expected): + """Test the parse_openbb_script function.""" + error, script = parse_openbb_script([line]) + assert error == expected + + +def test_date_keyword_last_friday(): + """Test the match_and_return_openbb_keyword_date function.""" + today = datetime.now() + last_friday = today - timedelta(days=(today.weekday() - 4 + 7) % 7) + if last_friday >= today: + last_friday -= timedelta(days=7) + expected_date = last_friday.strftime("%Y-%m-%d") + assert match_and_return_openbb_keyword_date("$LASTFRIDAY") == expected_date diff --git a/cli/tests/test_controllers_settings_controller.py b/cli/tests/test_controllers_settings_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..6b2389d491fe7256f36b703166035dcaaeda5334 --- /dev/null +++ b/cli/tests/test_controllers_settings_controller.py @@ -0,0 +1,128 @@ +"""Test the SettingsController class.""" + +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.settings_controller import SettingsController + +# pylint: disable=redefined-outer-name, unused-argument, no-member + + +@pytest.fixture +def mock_session(): + with patch("openbb_cli.controllers.settings_controller.session") as mock: + mock.settings.USE_INTERACTIVE_DF = False + mock.settings.ALLOWED_NUMBER_OF_ROWS = 20 + mock.settings.TIMEZONE = "UTC" + mock.style.available_styles = ["dark"] + mock.settings.set_item = MagicMock() + + yield mock + + +def test_call_interactive(mock_session): + controller = SettingsController() + controller.call_interactive(None) + mock_session.settings.set_item.assert_called_once_with("USE_INTERACTIVE_DF", True) + + +@pytest.mark.parametrize( + "input_rows, expected", + [ + (10, 10), + (15, 15), + ], +) +def test_call_n_rows(input_rows, expected, mock_session): + controller = SettingsController() + args = ["--value", str(input_rows)] + controller.call_n_rows(args) + mock_session.settings.set_item.assert_called_with( + "ALLOWED_NUMBER_OF_ROWS", expected + ) + + +def test_call_n_rows_no_args_provided(mock_session): + controller = SettingsController() + controller.call_n_rows([]) + mock_session.console.print.assert_called_with("[info]Current value:[/info] 20") + + +@pytest.mark.parametrize( + "timezone, valid", + [ + ("UTC", True), + ("Mars/Phobos", False), + ], +) +def test_call_timezone(timezone, valid, mock_session): + controller = SettingsController() + args = ["--value", timezone] + controller.call_timezone(args) + if valid: + mock_session.settings.set_item.assert_called_with("TIMEZONE", timezone) + else: + mock_session.settings.set_item.assert_not_called() + + +def test_call_console_style(mock_session): + controller = SettingsController() + args = ["--value", "dark"] + controller.call_console_style(args) + mock_session.console.print.assert_called() + + +def test_call_console_style_no_args(mock_session): + mock_session.settings.RICH_STYLE = "default" + controller = SettingsController() + controller.call_console_style([]) + mock_session.console.print.assert_called_with("[info]Current value:[/info] default") + + +def test_call_flair(mock_session): + controller = SettingsController() + args = ["--value", "rocket"] + controller.call_flair(args) + + +def test_call_flair_no_args(mock_session): + mock_session.settings.FLAIR = "bug" + controller = SettingsController() + controller.call_flair([]) + mock_session.console.print.assert_called_with("[info]Current value:[/info] bug") + + +def test_call_obbject_display(mock_session): + controller = SettingsController() + args = ["--value", "5"] + controller.call_obbject_display(args) + mock_session.settings.set_item.assert_called_once_with( + "N_TO_DISPLAY_OBBJECT_REGISTRY", 5 + ) + + +def test_call_obbject_display_no_args(mock_session): + mock_session.settings.N_TO_DISPLAY_OBBJECT_REGISTRY = 10 + controller = SettingsController() + controller.call_obbject_display([]) + mock_session.console.print.assert_called_with("[info]Current value:[/info] 10") + + +@pytest.mark.parametrize( + "args, expected", + [ + (["--value", "50"], 50), + (["--value", "100"], 100), + ([], 20), + ], +) +def test_call_n_rows_v2(args, expected, mock_session): + mock_session.settings.ALLOWED_NUMBER_OF_ROWS = 20 + controller = SettingsController() + controller.call_n_rows(args) + if args: + mock_session.settings.set_item.assert_called_with( + "ALLOWED_NUMBER_OF_ROWS", expected + ) + else: + mock_session.console.print.assert_called_with("[info]Current value:[/info] 20") diff --git a/cli/tests/test_controllers_utils.py b/cli/tests/test_controllers_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e5a842170a64e686b47f6eb09921cde4f2c19b66 --- /dev/null +++ b/cli/tests/test_controllers_utils.py @@ -0,0 +1,147 @@ +"""Test the Controller utils.""" + +import argparse +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.controllers.utils import ( + check_non_negative, + check_positive, + get_flair_and_username, + get_user_agent, + parse_and_split_input, + print_goodbye, + print_guest_block_msg, + remove_file, + welcome_message, +) + +# pylint: disable=redefined-outer-name, unused-argument + + +@pytest.fixture +def mock_session(): + """Mock the session and its dependencies.""" + with patch("openbb_cli.controllers.utils.session") as mock_session: + mock_session.console.print = MagicMock() + mock_session.is_local = MagicMock(return_value=True) + mock_session.settings.VERSION = "1.0" + mock_session.user.profile.hub_session.username = "testuser" + mock_session.settings.FLAIR = "rocket" + yield mock_session + + +def test_remove_file_existing_file(): + """Test removing an existing file.""" + with patch("os.path.isfile", return_value=True), patch("os.remove") as mock_remove: + assert remove_file(Path("/path/to/file")) + mock_remove.assert_called_once() + + +def test_remove_file_directory(): + """Test removing a directory.""" + with patch("os.path.isfile", return_value=False), patch( + "os.path.isdir", return_value=True + ), patch("shutil.rmtree") as mock_rmtree: + assert remove_file(Path("/path/to/directory")) + mock_rmtree.assert_called_once() + + +def test_remove_file_failure(mock_session): + """Test removing a file that fails.""" + with patch("os.path.isfile", return_value=True), patch( + "os.remove", side_effect=Exception("Error") + ): + assert not remove_file(Path("/path/to/file")) + mock_session.console.print.assert_called() + + +def test_print_goodbye(mock_session): + """Test printing the goodbye message.""" + print_goodbye() + mock_session.console.print.assert_called() + + +def test_parse_and_split_input(): + """Test parsing and splitting user input.""" + user_input = "ls -f /home/user/docs/document.xlsx" + result = parse_and_split_input(user_input, []) + assert "ls" in result[0] + + +@pytest.mark.parametrize( + "input_command, expected_output", + [ + ("/", ["home"]), + ("ls -f /path/to/file.txt", ["ls -f ", "path", "to", "file.txt"]), + ("rm -f /home/user/docs", ["rm -f ", "home", "user", "docs"]), + ], +) +def test_parse_and_split_input_special_cases(input_command, expected_output): + """Test parsing and splitting user input with special cases.""" + result = parse_and_split_input(input_command, []) + assert result == expected_output + + +def test_print_guest_block_msg(mock_session): + """Test printing the guest block message.""" + print_guest_block_msg() + mock_session.console.print.assert_called() + + +def test_welcome_message(mock_session): + """Test printing the welcome message.""" + welcome_message() + mock_session.console.print.assert_called_with( + "\nWelcome to OpenBB Platform CLI v1.0" + ) + + +def test_get_flair_and_username(mock_session): + """Test getting the flair and username.""" + result = get_flair_and_username() + assert "testuser" in result + assert "rocket" in result + + +@pytest.mark.parametrize( + "value, expected", + [ + ("10", 10), + ("0", 0), + ("-1", pytest.raises(argparse.ArgumentTypeError)), + ("text", pytest.raises(ValueError)), + ], +) +def test_check_non_negative(value, expected): + """Test checking for a non-negative value.""" + if isinstance(expected, int): + assert check_non_negative(value) == expected + else: + with expected: + check_non_negative(value) + + +@pytest.mark.parametrize( + "value, expected", + [ + ("1", 1), + ("0", pytest.raises(argparse.ArgumentTypeError)), + ("-1", pytest.raises(argparse.ArgumentTypeError)), + ("text", pytest.raises(ValueError)), + ], +) +def test_check_positive(value, expected): + """Test checking for a positive value.""" + if isinstance(expected, int): + assert check_positive(value) == expected + else: + with expected: + check_positive(value) + + +def test_get_user_agent(): + """Test getting the user agent.""" + result = get_user_agent() + assert result.startswith("Mozilla/5.0") diff --git a/cli/tests/test_models_settings.py b/cli/tests/test_models_settings.py new file mode 100644 index 0000000000000000000000000000000000000000..4e65144531688d99946f5c071d83121b71a29579 --- /dev/null +++ b/cli/tests/test_models_settings.py @@ -0,0 +1,69 @@ +"""Test the Models Settings module.""" + +from unittest.mock import mock_open, patch + +from openbb_cli.models.settings import Settings + +# pylint: disable=unused-argument + + +def test_default_values(): + """Test the default values of the settings model.""" + fields = Settings.model_fields + assert fields["TEST_MODE"].default is False + assert fields["DEBUG_MODE"].default is False + assert fields["DEV_BACKEND"].default is False + assert fields["FILE_OVERWRITE"].default is False + assert fields["SHOW_VERSION"].default is True + assert fields["USE_INTERACTIVE_DF"].default is True + assert fields["USE_CLEAR_AFTER_CMD"].default is False + assert fields["USE_DATETIME"].default is True + assert fields["USE_PROMPT_TOOLKIT"].default is True + assert fields["ENABLE_EXIT_AUTO_HELP"].default is True + assert fields["ENABLE_RICH_PANEL"].default is True + assert fields["TOOLBAR_HINT"].default is True + assert fields["SHOW_MSG_OBBJECT_REGISTRY"].default is False + assert fields["TIMEZONE"].default == "America/New_York" + assert fields["FLAIR"].default == ":openbb" + assert fields["PREVIOUS_USE"].default is False + assert fields["N_TO_KEEP_OBBJECT_REGISTRY"].default == 10 + assert fields["N_TO_DISPLAY_OBBJECT_REGISTRY"].default == 5 + assert fields["RICH_STYLE"].default == "dark" + assert fields["ALLOWED_NUMBER_OF_ROWS"].default == 20 + assert fields["ALLOWED_NUMBER_OF_COLUMNS"].default == 5 + assert fields["HUB_URL"].default == "https://my.openbb.co" + assert fields["BASE_URL"].default == "https://payments.openbb.co" + + +# Test __repr__ output +def test_repr(): + """Test the __repr__ method of the settings model.""" + settings = Settings() + repr_str = settings.__repr__() # pylint: disable=C2801 + assert "Settings\n\n" in repr_str + + +# Test loading from environment variables +@patch( + "openbb_cli.models.settings.dotenv_values", + return_value={"OPENBB_TEST_MODE": "True", "OPENBB_VERSION": "2.0.0"}, +) +def test_from_env(mock_dotenv_values): + """Test loading settings from environment variables.""" + settings = Settings.from_env({}) # type: ignore + assert settings["TEST_MODE"] == "True" + assert settings["VERSION"] == "2.0.0" + + +# Test setting an item and updating .env +@patch("openbb_cli.models.settings.set_key") +@patch( + "openbb_cli.models.settings.open", + new_callable=mock_open, + read_data="TEST_MODE=False\n", +) +def test_set_item(mock_file, mock_set_key): + """Test setting an item and updating the .env file.""" + settings = Settings() + settings.set_item("TEST_MODE", True) + assert settings.TEST_MODE is True diff --git a/cli/tests/test_session.py b/cli/tests/test_session.py new file mode 100644 index 0000000000000000000000000000000000000000..66ca9fccab8be6f3072a825891bf025e8945a266 --- /dev/null +++ b/cli/tests/test_session.py @@ -0,0 +1,44 @@ +"Test the Session class." +from unittest.mock import MagicMock, patch + +import pytest +from openbb_cli.models.settings import Settings +from openbb_cli.session import Session, sys + +# pylint: disable=redefined-outer-name, unused-argument, protected-access + + +def mock_isatty(return_value): + """Mock the isatty method.""" + original_isatty = sys.stdin.isatty + sys.stdin.isatty = MagicMock(return_value=return_value) # type: ignore + return original_isatty + + +@pytest.fixture +def session(): + """Session fixture.""" + return Session() + + +def test_session_initialization(session): + """Test the initialization of the Session class.""" + assert session.settings is not None + assert session.style is not None + assert session.console is not None + assert session.obbject_registry is not None + assert isinstance(session.settings, Settings) + + +@patch("sys.stdin.isatty", return_value=True) +def test_get_prompt_session_true(mock_isatty, session): + "Test get_prompt_session method." + prompt_session = session._get_prompt_session() + assert prompt_session is not None + + +@patch("sys.stdin.isatty", return_value=False) +def test_get_prompt_session_false(mock_isatty, session): + "Test get_prompt_session method." + prompt_session = session._get_prompt_session() + assert prompt_session is None