diff --git a/packages/packages.json b/packages/packages.json index cb67b674..f11b9096 100644 --- a/packages/packages.json +++ b/packages/packages.json @@ -16,14 +16,14 @@ "contract/valory/staking_token/0.1.0": "bafybeiep4r6qyilbfgzdvx6t7zvpgaioxqktmxm7puwtnbpb2ftlib43gy", "contract/valory/relayer/0.1.0": "bafybeicawmds6czx7db2lcktvexwrp245jpekgulndtos5s5zdid3ilvq4", "skill/valory/market_manager_abci/0.1.0": "bafybeiaru2d32wpmcgqs64eepxud4idgubc3vmsbdwbia7gygipql2mmqi", - "skill/valory/decision_maker_abci/0.1.0": "bafybeiddnmcquiuznts67ridhpnaqw2y3rrt4nfau5kjm74zhk5lhjxi2q", - "skill/valory/trader_abci/0.1.0": "bafybeiemjz3ca7la7jkeqdr7hxo7fa77xe2fkfadzb53gdkji7abpl2eiu", - "skill/valory/tx_settlement_multiplexer_abci/0.1.0": "bafybeietwknem7iiood6pwkfup322ywwjmdrmdapllrcms6jpeev5w2qfe", + "skill/valory/decision_maker_abci/0.1.0": "bafybeig5oivc24sqhgyxfhjbl2xsoa5yssv72lcu5ezunbcpwu3xo4jglm", + "skill/valory/trader_abci/0.1.0": "bafybeicmtcs4gaabwbyr4im2ux7co73y5n6ugj4abk6dayxzew7jly2zhe", + "skill/valory/tx_settlement_multiplexer_abci/0.1.0": "bafybeifpf3sk5evrvwxyht7mswcml3qy4nhzo5ddvjvgklapwsjwq6mera", "skill/valory/staking_abci/0.1.0": "bafybeicupccurmrg7qesivonlyt3nryarsmk5qf5yh6auno64wn45bybvq", "skill/valory/check_stop_trading_abci/0.1.0": "bafybeieduekpd4zbvjztyxyooppqnmjvup6jfp74uo6hhupvtvzzscdzkq", - "agent/valory/trader/0.1.0": "bafybeihq257kwjybsvrpzhjx4wbretrsurodpckjqdhv3idlnbu4mqvfnq", - "service/valory/trader/0.1.0": "bafybeihr6m4lec5r6kuf2zikusgyqilqwhwlpyehufyndjuziylr73dlqe", - "service/valory/trader_pearl/0.1.0": "bafybeibfqhsrtekx6nvwpz2nbbjyobljcahe6wz6jikyebt7opzxlal45u" + "agent/valory/trader/0.1.0": "bafybeih3r5xefwfqfdmmwx3e5n3i4df7pv3mvhqlk7qen2i75anj27ds4u", + "service/valory/trader/0.1.0": "bafybeid2xxc5plszxy644kbhcqxjwmpc6ay6xqhewc2c6bb3dqv3llt63u", + "service/valory/trader_pearl/0.1.0": "bafybeiaihaivsiyotduxvnmecvdf23t42bwnphwmswxcpfdhi3k6w57gze" }, "third_party": { "protocol/open_aea/signing/1.0.0": "bafybeihv62fim3wl2bayavfcg3u5e5cxu3b7brtu4cn5xoxd6lqwachasi", diff --git a/packages/valory/agents/trader/aea-config.yaml b/packages/valory/agents/trader/aea-config.yaml index 5471b02e..e3ca80bf 100644 --- a/packages/valory/agents/trader/aea-config.yaml +++ b/packages/valory/agents/trader/aea-config.yaml @@ -45,10 +45,10 @@ skills: - valory/reset_pause_abci:0.1.0:bafybeigrdlxed3xlsnxtjhnsbl3cojruihxcqx4jxhgivkd5i2fkjncgba - valory/termination_abci:0.1.0:bafybeib5l7jhew5ic6iq24dd23nidcoimzqkrk556gqywhoziatj33zvwm - valory/transaction_settlement_abci:0.1.0:bafybeic7q7recyka272udwcupblwbkc3jkodgp74fvcdxb7urametg5dae -- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeietwknem7iiood6pwkfup322ywwjmdrmdapllrcms6jpeev5w2qfe +- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeifpf3sk5evrvwxyht7mswcml3qy4nhzo5ddvjvgklapwsjwq6mera - valory/market_manager_abci:0.1.0:bafybeiaru2d32wpmcgqs64eepxud4idgubc3vmsbdwbia7gygipql2mmqi -- valory/decision_maker_abci:0.1.0:bafybeiddnmcquiuznts67ridhpnaqw2y3rrt4nfau5kjm74zhk5lhjxi2q -- valory/trader_abci:0.1.0:bafybeiemjz3ca7la7jkeqdr7hxo7fa77xe2fkfadzb53gdkji7abpl2eiu +- valory/decision_maker_abci:0.1.0:bafybeig5oivc24sqhgyxfhjbl2xsoa5yssv72lcu5ezunbcpwu3xo4jglm +- valory/trader_abci:0.1.0:bafybeicmtcs4gaabwbyr4im2ux7co73y5n6ugj4abk6dayxzew7jly2zhe - valory/staking_abci:0.1.0:bafybeicupccurmrg7qesivonlyt3nryarsmk5qf5yh6auno64wn45bybvq - valory/check_stop_trading_abci:0.1.0:bafybeieduekpd4zbvjztyxyooppqnmjvup6jfp74uo6hhupvtvzzscdzkq - valory/mech_interact_abci:0.1.0:bafybeid6m3i5ofq7vuogqapdnoshhq7mswmudhvfcr2craw25fdwtoe3lm @@ -246,6 +246,9 @@ models: requester_staking_instance_address: ${str:0x0000000000000000000000000000000000000000} response_timeout: ${int:300} expected_mech_response_time: ${int:300} + mech_invalid_response: ${str:Invalid Response} + mech_consecutive_failures_threshold: ${int:2} + tool_quarantine_duration: ${int:18000} benchmarking_mode: args: enabled: ${bool:false} @@ -271,6 +274,8 @@ models: requests: ${str:total_requests} accuracy: ${str:tool_accuracy} sep: ${str:,} + max: ${str:max} + datetime_format: ${str:%Y-%m-%d %H:%M:%S} network_subgraph: args: headers: diff --git a/packages/valory/services/trader/service.yaml b/packages/valory/services/trader/service.yaml index 3f33c0a3..e0b11b58 100644 --- a/packages/valory/services/trader/service.yaml +++ b/packages/valory/services/trader/service.yaml @@ -7,7 +7,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeigtuothskwyvrhfosps2bu6suauycolj67dpuxqvnicdrdu7yhtvq fingerprint_ignore_patterns: [] -agent: valory/trader:0.1.0:bafybeihq257kwjybsvrpzhjx4wbretrsurodpckjqdhv3idlnbu4mqvfnq +agent: valory/trader:0.1.0:bafybeih3r5xefwfqfdmmwx3e5n3i4df7pv3mvhqlk7qen2i75anj27ds4u number_of_agents: 4 deployment: agent: @@ -146,6 +146,9 @@ type: skill mech_interaction_sleep_time: ${MECH_INTERACTION_SLEEP_TIME:int:10} policy_store_update_offset: ${POLICY_STORE_UPDATE_OFFSET:int:259200} expected_mech_response_time: ${EXPECTED_MECH_RESPONSE_TIME:int:300} + mech_invalid_response: ${MECH_INVALID_RESPONSE:str:Invalid Response} + mech_consecutive_failures_threshold: ${MECH_CONSECUTIVE_FAILURES_THRESHOLD:int:2} + tool_quarantine_duration: ${TOOL_QUARANTINE_DURATION:int:18000} benchmark_tool: &id004 args: log_dir: ${LOG_DIR:str:/benchmarks} @@ -174,6 +177,8 @@ type: skill requests: ${ACC_INFO_FIELDS_REQUESTS:str:total_requests} accuracy: ${ACC_INFO_FIELDS_ACCURACY:str:tool_accuracy} sep: ${ACC_INFO_FIELDS_SEP:str:,} + max: ${ACC_INFO_FIELDS_MAX:str:max} + datetime_format: ${ACC_INFO_FIELDS_DATETIME_FORMAT:str:%Y-%m-%d %H:%M:%S} network_subgraph: &id006 args: headers: @@ -368,6 +373,9 @@ type: skill mech_interaction_sleep_time: ${MECH_INTERACTION_SLEEP_TIME:int:10} policy_store_update_offset: ${POLICY_STORE_UPDATE_OFFSET:int:259200} expected_mech_response_time: ${EXPECTED_MECH_RESPONSE_TIME:int:300} + mech_invalid_response: ${MECH_INVALID_RESPONSE:str:Invalid Response} + mech_consecutive_failures_threshold: ${MECH_CONSECUTIVE_FAILURES_THRESHOLD:int:2} + tool_quarantine_duration: ${TOOL_QUARANTINE_DURATION:int:18000} benchmark_tool: *id004 acc_info_fields: *id005 network_subgraph: *id006 @@ -486,6 +494,9 @@ type: skill mech_interaction_sleep_time: ${MECH_INTERACTION_SLEEP_TIME:int:10} policy_store_update_offset: ${POLICY_STORE_UPDATE_OFFSET:int:259200} expected_mech_response_time: ${EXPECTED_MECH_RESPONSE_TIME:int:300} + mech_invalid_response: ${MECH_INVALID_RESPONSE:str:Invalid Response} + mech_consecutive_failures_threshold: ${MECH_CONSECUTIVE_FAILURES_THRESHOLD:int:2} + tool_quarantine_duration: ${TOOL_QUARANTINE_DURATION:int:18000} benchmark_tool: *id004 acc_info_fields: *id005 network_subgraph: *id006 @@ -604,6 +615,9 @@ type: skill mech_interaction_sleep_time: ${MECH_INTERACTION_SLEEP_TIME:int:10} policy_store_update_offset: ${POLICY_STORE_UPDATE_OFFSET:int:259200} expected_mech_response_time: ${EXPECTED_MECH_RESPONSE_TIME:int:300} + mech_invalid_response: ${MECH_INVALID_RESPONSE:str:Invalid Response} + mech_consecutive_failures_threshold: ${MECH_CONSECUTIVE_FAILURES_THRESHOLD:int:2} + tool_quarantine_duration: ${TOOL_QUARANTINE_DURATION:int:18000} benchmark_tool: *id004 acc_info_fields: *id005 network_subgraph: *id006 diff --git a/packages/valory/services/trader_pearl/service.yaml b/packages/valory/services/trader_pearl/service.yaml index 88e6cefe..fcc4ed44 100644 --- a/packages/valory/services/trader_pearl/service.yaml +++ b/packages/valory/services/trader_pearl/service.yaml @@ -8,7 +8,7 @@ license: Apache-2.0 fingerprint: README.md: bafybeibg7bdqpioh4lmvknw3ygnllfku32oca4eq5pqtvdrdsgw6buko7e fingerprint_ignore_patterns: [] -agent: valory/trader:0.1.0:bafybeihq257kwjybsvrpzhjx4wbretrsurodpckjqdhv3idlnbu4mqvfnq +agent: valory/trader:0.1.0:bafybeih3r5xefwfqfdmmwx3e5n3i4df7pv3mvhqlk7qen2i75anj27ds4u number_of_agents: 1 deployment: agent: @@ -104,6 +104,9 @@ models: response_timeout: ${RESPONSE_TIMEOUT:int:300} policy_store_update_offset: ${POLICY_STORE_UPDATE_OFFSET:int:259200} expected_mech_response_time: ${EXPECTED_MECH_RESPONSE_TIME:int:300} + mech_invalid_response: ${MECH_INVALID_RESPONSE:str:Invalid Response} + mech_consecutive_failures_threshold: ${MECH_CONSECUTIVE_FAILURES_THRESHOLD:int:2} + tool_quarantine_duration: ${TOOL_QUARANTINE_DURATION:int:18000} benchmark_tool: args: log_dir: /benchmarks diff --git a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py index 4aba1476..baf8a856 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/blacklisting.py @@ -28,6 +28,9 @@ from packages.valory.skills.decision_maker_abci.states.blacklisting import ( BlacklistingRound, ) +from packages.valory.skills.decision_maker_abci.states.handle_failed_tx import ( + HandleFailedTxRound, +) class BlacklistingBehaviour(DecisionMakerBaseBehaviour): @@ -47,7 +50,7 @@ def _blacklist(self) -> None: sampled_bet = self.bets[sampled_bet_index] # the question is blacklisted, i.e., we did not place a bet on it, - # therefore, we bump to the queue status to next status + # therefore, we bump the queue's status to the next one sampled_bet.queue_status = sampled_bet.queue_status.next_status() def setup(self) -> None: @@ -69,6 +72,14 @@ def async_act(self) -> Generator: bets_hash = ( None if self.benchmarking_mode.enabled else self.hash_stored_bets() ) + if ( + self.synchronized_data.tx_submitter + != HandleFailedTxRound.auto_round_id() + ): + # if we are here, then the tool has responded with an error + self.policy.tool_responded( + self.synchronized_data.mech_tool, self.synced_timestamp + ) policy = self.policy.serialize() payload = BlacklistingPayload(self.context.agent_address, bets_hash, policy) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py b/packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py index 5ad86dec..7e17bb43 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py @@ -27,9 +27,11 @@ from typing import Any, Dict, Generator, List, Optional, Tuple, Union from packages.valory.skills.decision_maker_abci.behaviours.base import ( - DecisionMakerBaseBehaviour, remove_fraction_wei, ) +from packages.valory.skills.decision_maker_abci.behaviours.storage_manager import ( + StorageManagerBehaviour, +) from packages.valory.skills.decision_maker_abci.io_.loader import ComponentPackageLoader from packages.valory.skills.decision_maker_abci.models import ( BenchmarkingMockData, @@ -59,7 +61,7 @@ TOKEN_PRECISION = 10**18 -class DecisionReceiveBehaviour(DecisionMakerBaseBehaviour): +class DecisionReceiveBehaviour(StorageManagerBehaviour): """A behaviour in which the agents receive the mech response.""" matching_round = DecisionReceiveRound @@ -93,6 +95,17 @@ def mech_response(self) -> MechInteractionResponse: return MechInteractionResponse(error=error) return self._mech_response + @property + def is_invalid_response(self) -> bool: + """Check if the response is invalid.""" + if self.mech_response.result is None: + self.context.logger.warning( + "Trying to check whether the mech's response is invalid but no response has been detected! " + "Assuming invalid response." + ) + return True + return self.mech_response.result == self.params.mech_invalid_response + def _next_dataset_row(self) -> Optional[Dict[str, str]]: """Read the next row from the input dataset which is used during the benchmarking mode. @@ -527,12 +540,17 @@ def async_act(self) -> Generator: """Do the action.""" with self.context.benchmark_tool.measure(self.behaviour_id).local(): + success = yield from self._setup_policy_and_tools() + if not success: + return None + prediction_response = self._get_decision() is_profitable = None bet_amount = None next_mock_data_row = None bets_hash = None decision_received_timestamp = None + policy = None if prediction_response is not None and prediction_response.vote is not None: is_profitable, bet_amount = yield from self._is_profitable( prediction_response @@ -552,6 +570,14 @@ def async_act(self) -> Generator: bet_amount, ) + if prediction_response is not None: + self.policy.tool_responded( + self.synchronized_data.mech_tool, + self.synced_timestamp, + self.is_invalid_response, + ) + policy = self.policy.serialize() + # always remove the processed trade from the benchmarking input file # now there is one reader pointer per market if self.benchmarking_mode.enabled: @@ -572,7 +598,9 @@ def async_act(self) -> Generator: prediction_response.confidence if prediction_response else None, bet_amount, next_mock_data_row, + policy, decision_received_timestamp, ) + self._store_all() yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/handle_failed_tx.py b/packages/valory/skills/decision_maker_abci/behaviours/handle_failed_tx.py index 0ee2299a..52a93b73 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/handle_failed_tx.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/handle_failed_tx.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # -# Copyright 2023 Valory AG +# Copyright 2023-2024 Valory AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -24,11 +24,14 @@ from packages.valory.skills.decision_maker_abci.behaviours.base import ( DecisionMakerBaseBehaviour, ) -from packages.valory.skills.decision_maker_abci.payloads import VotingPayload +from packages.valory.skills.decision_maker_abci.payloads import HandleFailedTxPayload +from packages.valory.skills.decision_maker_abci.states.bet_placement import ( + BetPlacementRound, +) from packages.valory.skills.decision_maker_abci.states.handle_failed_tx import ( HandleFailedTxRound, ) -from packages.valory.skills.decision_maker_abci.states.redeem import RedeemRound +from packages.valory.skills.mech_interact_abci.states.request import MechRequestRound class HandleFailedTxBehaviour(DecisionMakerBaseBehaviour): @@ -40,9 +43,13 @@ def async_act(self) -> Generator: """Do the action.""" with self.context.benchmark_tool.measure(self.behaviour_id).local(): - after_redeeming = ( - self.synchronized_data.tx_submitter == RedeemRound.auto_round_id() + after_bet_attempt = self.synchronized_data.tx_submitter in ( + MechRequestRound.auto_round_id(), + BetPlacementRound.auto_round_id(), + ) + submitter = HandleFailedTxRound.auto_round_id() + payload = HandleFailedTxPayload( + self.context.agent_address, after_bet_attempt, submitter ) - payload = VotingPayload(self.context.agent_address, not after_redeeming) yield from self.finish_behaviour(payload) diff --git a/packages/valory/skills/decision_maker_abci/behaviours/storage_manager.py b/packages/valory/skills/decision_maker_abci/behaviours/storage_manager.py index 378b5659..0fd9a836 100644 --- a/packages/valory/skills/decision_maker_abci/behaviours/storage_manager.py +++ b/packages/valory/skills/decision_maker_abci/behaviours/storage_manager.py @@ -24,7 +24,7 @@ from abc import ABC from datetime import datetime from io import StringIO -from typing import Any, Dict, Generator, List, Optional +from typing import Any, Dict, Generator, List, Optional, Tuple from packages.valory.contracts.agent_registry.contract import AgentRegistryContract from packages.valory.protocols.contract_api import ContractApiMessage @@ -41,13 +41,11 @@ ) -POLICY_STORE = "policy_store_multi_bet.json" +POLICY_STORE = "policy_store_multi_bet_failure_adjusting.json" AVAILABLE_TOOLS_STORE = "available_tools_store.json" UTILIZED_TOOLS_STORE = "utilized_tools.json" GET = "GET" OK_CODE = 200 -MAX_STR = "max" -DATETIME_FORMAT_STR = "%Y-%m-%d %H:%M:%S" class StorageManagerBehaviour(DecisionMakerBaseBehaviour, ABC): @@ -234,8 +232,13 @@ def _try_recover_policy(self) -> Optional[EGreedyPolicy]: try: policy_path = self.params.store_path / POLICY_STORE with open(policy_path, "r") as f: - policy = f.read() - return EGreedyPolicy.deserialize(policy) + policy_raw = f.read() + policy = EGreedyPolicy.deserialize(policy_raw) + # overwrite the configurable parameters + policy.eps = self.params.epsilon + policy.consecutive_failures_threshold = self.params.policy_threshold + policy.quarantine_duration = self.params.tool_quarantine_duration + return policy except Exception as e: self.context.logger.warning(f"Could not recover the policy: {e}.") return None @@ -243,7 +246,11 @@ def _try_recover_policy(self) -> Optional[EGreedyPolicy]: def _get_init_policy(self) -> EGreedyPolicy: """Get the initial policy.""" # try to read the policy from the policy store, and if we cannot recover the policy, we create a new one - return self._try_recover_policy() or EGreedyPolicy(self.params.epsilon) + return self._try_recover_policy() or EGreedyPolicy( + self.params.epsilon, + self.params.policy_threshold, + self.params.tool_quarantine_duration, + ) def _fetch_accuracy_info(self) -> Generator[None, None, bool]: """Fetch the latest accuracy information available.""" @@ -270,97 +277,121 @@ def _fetch_accuracy_info(self) -> Generator[None, None, bool]: return True - def _fetch_remote_tool_date(self) -> int: - """Fetch the max transaction date from the remote accuracy storage.""" - self.context.logger.info("Checking remote accuracy information date... ") - self.context.logger.info("Trying to read max date in file...") - accuracy_information = self.remote_accuracy_information - - max_transaction_date = None - - if accuracy_information: - sep = self.acc_info_fields.sep - accuracy_information.seek(0) # Ensure we’re at the beginning - reader = csv.DictReader(accuracy_information.readlines(), delimiter=sep) - - # try to read the maximum transaction date in the remote accuracy info - try: - for row in reader: - current_transaction_date = row.get(MAX_STR) - if ( - max_transaction_date is None - or current_transaction_date > max_transaction_date - ): - max_transaction_date = current_transaction_date - - except TypeError: - self.context.logger.warning( - "Invalid transaction date found. Continuing with local accuracy information..." - ) - return 0 - - if max_transaction_date: - self.context.logger.info(f"Maximum date found: {max_transaction_date}") - max_datetime = datetime.strptime(max_transaction_date, DATETIME_FORMAT_STR) - unix_timestamp = int(max_datetime.timestamp()) - return unix_timestamp - - self.context.logger.info("No maximum date found.") - return 0 - - def _check_local_policy_store_overwrite(self) -> bool: - """Compare the local and remote policy store dates and decide which to use.""" - - local_policy_store_date = self.policy.updated_ts - remote_policy_store_date = self._fetch_remote_tool_date() - policy_store_update_offset = self.params.policy_store_update_offset - - self.context.logger.info("Comparing tool accuracy dates...") - - overwrite = ( - True - if remote_policy_store_date - > (local_policy_store_date - policy_store_update_offset) - else False - ) - self.context.logger.info(f"Local policy store overwrite: {overwrite}.") - return overwrite + def _remove_irrelevant_tools(self) -> None: + """Remove irrelevant tools from the accuracy store.""" + accuracy_store = self.policy.accuracy_store + for tool in accuracy_store.copy(): + if tool not in self.mech_tools: + accuracy_store.pop(tool, None) + + def _global_info_date_to_unix(self, tool_transaction_date: str) -> Optional[int]: + """Convert the global information date to unix.""" + datetime_format = self.acc_info_fields.datetime_format + try: + tool_transaction_datetime = datetime.strptime( + tool_transaction_date, datetime_format + ) + except (ValueError, TypeError): + self.context.logger.warning( + f"Could not parse the global info date {tool_transaction_date!r} using format {datetime_format!r}!" + ) + return None + + return int(tool_transaction_datetime.timestamp()) - def _update_accuracy_store(self) -> None: - """Update the accuracy store file with the latest information available""" - self.context.logger.info("Updating accuracy information of the policy...") + def _parse_global_info_row( + self, + row: Dict[str, str], + max_transaction_date: int, + tool_to_global_info: Dict[str, Dict[str, str]], + ) -> int: + """Parse a row of the global information.""" + tool = row[self.acc_info_fields.tool] + if tool not in self.mech_tools: + # skip irrelevant tools + return max_transaction_date + + # store the global information + tool_to_global_info[tool] = row + + # find the latest transaction date + tool_transaction_date = row[self.acc_info_fields.max] + tool_transaction_unix = self._global_info_date_to_unix(tool_transaction_date) + if ( + tool_transaction_unix is not None + and tool_transaction_unix > max_transaction_date + ): + return tool_transaction_unix + + return max_transaction_date + + def _parse_global_info(self) -> Tuple[int, Dict[str, Dict[str, str]]]: + """Parse the global information of the tools.""" sep = self.acc_info_fields.sep - self.remote_accuracy_information.seek( - 0 - ) # Ensure the file pointer is at the start reader: csv.DictReader = csv.DictReader( self.remote_accuracy_information, delimiter=sep ) - accuracy_store = self.policy.accuracy_store - # remove tools which are irrelevant - for tool in accuracy_store.copy(): - if tool not in self.mech_tools: - accuracy_store.pop(tool, None) - - # update the accuracy store using the latest accuracy information (only entered during the first period) + max_transaction_date = 0 + tool_to_global_info: Dict[str, Dict[str, str]] = {} for row in reader: - tool = row[self.acc_info_fields.tool] - if tool not in self.mech_tools: - continue + max_transaction_date = self._parse_global_info_row( + row, max_transaction_date, tool_to_global_info + ) + + return max_transaction_date, tool_to_global_info + + def _should_use_global_info(self, global_update_timestamp: int) -> bool: + """Whether we should use the global information of the tools.""" + local_update_timestamp = self.policy.updated_ts + local_update_offset = self.params.policy_store_update_offset + return global_update_timestamp > local_update_timestamp - local_update_offset + + def _overwrite_local_info( + self, tool_to_global_info: Dict[str, Dict[str, str]] + ) -> None: + """Overwrite the local information with the global information.""" + self.context.logger.info( + "The local policy store will be overwritten with global information." + ) - # overwrite local with global information (naturally, no global information is available for pending) + accuracy_store = self.policy.accuracy_store + for tool, row in tool_to_global_info.items(): accuracy_store[tool] = AccuracyInfo( int(row[self.acc_info_fields.requests]), - # set the pending using the local policy if this information exists + # naturally, no global information is available for pending. + # set it using the local policy if this information exists accuracy_store.get(tool, AccuracyInfo()).pending, float(row[self.acc_info_fields.accuracy]), ) + self.policy.updated_ts = int(datetime.now().timestamp()) - # update the accuracy store by adding tools which we do not have any global information about yet - for tool in self.mech_tools: - accuracy_store.setdefault(tool, AccuracyInfo()) + def _update_accuracy_store( + self, + global_update_timestamp: int, + tool_to_global_info: Dict[str, Dict[str, str]], + ) -> None: + """ + Update the accuracy store using the latest accuracy information. + The current method should only be called at the first period. + + :param global_update_timestamp: the timestamp of the latest global information update + :param tool_to_global_info: the global information of the tools + """ + if self._should_use_global_info(global_update_timestamp): + self._overwrite_local_info(tool_to_global_info) + + # update the accuracy store by adding tools for which we do not have any global information yet + for tool in self.mech_tools: + self.policy.accuracy_store.setdefault(tool, AccuracyInfo()) + + def _update_policy_tools(self) -> None: + """Update the policy's tools and their accuracy with the latest information available if `with_global_info`.""" + self.context.logger.info("Updating information of the policy...") + self._remove_irrelevant_tools() + global_info = self._parse_global_info() + self._update_accuracy_store(*global_info) self.policy.update_weighted_accuracy() def _set_policy(self) -> Generator: @@ -377,11 +408,9 @@ def _set_policy(self) -> Generator: yield from self.wait_for_condition_with_sleep( self._fetch_accuracy_info, sleep_time_override=self.params.sleep_time ) - overwrite_local_store = self._check_local_policy_store_overwrite() - if self.is_first_period and overwrite_local_store: - self.policy.updated_ts = int(datetime.now().timestamp()) - self._update_accuracy_store() + if self.is_first_period: + self._update_policy_tools() def _try_recover_utilized_tools(self) -> Dict[str, str]: """Try to recover the utilized tools from the tools store.""" diff --git a/packages/valory/skills/decision_maker_abci/fsm_specification.yaml b/packages/valory/skills/decision_maker_abci/fsm_specification.yaml index b3f24712..ac300954 100644 --- a/packages/valory/skills/decision_maker_abci/fsm_specification.yaml +++ b/packages/valory/skills/decision_maker_abci/fsm_specification.yaml @@ -81,10 +81,10 @@ transition_func: (BlacklistingRound, ROUND_TIMEOUT): BlacklistingRound (CheckBenchmarkingModeRound, BENCHMARKING_DISABLED): BenchmarkingModeDisabledRound (CheckBenchmarkingModeRound, BENCHMARKING_ENABLED): BenchmarkingRandomnessRound - (CheckBenchmarkingModeRound, BLACKLIST): ImpossibleRound + (CheckBenchmarkingModeRound, DONE): ImpossibleRound (CheckBenchmarkingModeRound, NO_MAJORITY): CheckBenchmarkingModeRound - (CheckBenchmarkingModeRound, NO_OP): ImpossibleRound (CheckBenchmarkingModeRound, ROUND_TIMEOUT): CheckBenchmarkingModeRound + (CheckBenchmarkingModeRound, SUBSCRIPTION_ERROR): ImpossibleRound (ClaimRound, DONE): ToolSelectionRound (ClaimRound, NO_MAJORITY): ClaimRound (ClaimRound, ROUND_TIMEOUT): ClaimRound diff --git a/packages/valory/skills/decision_maker_abci/models.py b/packages/valory/skills/decision_maker_abci/models.py index 4ecfaa23..8d761678 100644 --- a/packages/valory/skills/decision_maker_abci/models.py +++ b/packages/valory/skills/decision_maker_abci/models.py @@ -476,6 +476,15 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.expected_mech_response_time = self._ensure( "expected_mech_response_time", kwargs, int ) + self.mech_invalid_response: str = self._ensure( + "mech_invalid_response", kwargs, str + ) + self.policy_threshold: int = self._ensure( + "mech_consecutive_failures_threshold", kwargs, int + ) + self.tool_quarantine_duration: int = self._ensure( + "tool_quarantine_duration", kwargs, int + ) super().__init__(*args, **kwargs) @property @@ -559,6 +568,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: self.requests: str = self._ensure("requests", kwargs, str) self.accuracy: str = self._ensure("accuracy", kwargs, str) self.sep: str = self._ensure("sep", kwargs, str) + self.max: str = self._ensure("max", kwargs, str) + self.datetime_format: str = self._ensure("datetime_format", kwargs, str) super().__init__(*args, **kwargs) diff --git a/packages/valory/skills/decision_maker_abci/payloads.py b/packages/valory/skills/decision_maker_abci/payloads.py index 33ec183b..b15c02b0 100644 --- a/packages/valory/skills/decision_maker_abci/payloads.py +++ b/packages/valory/skills/decision_maker_abci/payloads.py @@ -35,6 +35,7 @@ class DecisionReceivePayload(UpdateBetsPayload): confidence: Optional[float] bet_amount: Optional[int] next_mock_data_row: Optional[int] + policy: Optional[str] decision_received_timestamp: Optional[int] @@ -119,3 +120,10 @@ class BetPlacementPayload(MultisigTxPayload): """Represents a transaction payload for placing a bet.""" wallet_balance: Optional[int] = None + + +@dataclass(frozen=True) +class HandleFailedTxPayload(VotingPayload): + """Represents a transaction payload for placing a bet.""" + + tx_submitter: str diff --git a/packages/valory/skills/decision_maker_abci/policy.py b/packages/valory/skills/decision_maker_abci/policy.py index 0b3db3a6..fd3e9f24 100644 --- a/packages/valory/skills/decision_maker_abci/policy.py +++ b/packages/valory/skills/decision_maker_abci/policy.py @@ -22,7 +22,8 @@ import json import random from dataclasses import asdict, dataclass, field, is_dataclass -from typing import Any, Dict, List, Optional, Union +from time import time +from typing import Any, Dict, List, Optional, Tuple, Union from packages.valory.skills.decision_maker_abci.utils.scaling import scale_value @@ -44,8 +45,8 @@ def default(self, o: Any) -> Any: return super().default(o) -def argmax(li: List) -> int: - """Get the index of the max value within the provided list.""" +def argmax(li: Union[Tuple, List]) -> int: + """Get the index of the max value within the provided tuple or list.""" return li.index((max(li))) @@ -61,6 +62,31 @@ class AccuracyInfo: accuracy: float = 0.0 +@dataclass +class ConsecutiveFailures: + """The consecutive failures of a tool.""" + + n_failures: int = 0 + timestamp: int = 0 + + def increase(self, timestamp: int) -> None: + """Increase the number of consecutive failures.""" + self.n_failures += 1 + self.timestamp = timestamp + + def reset(self, timestamp: int) -> None: + """Reset the number of consecutive failures.""" + self.n_failures = 0 + self.timestamp = timestamp + + def update_status(self, timestamp: int, has_failed: bool) -> None: + """Update the number of consecutive failures.""" + if has_failed: + self.increase(timestamp) + else: + self.reset(timestamp) + + class EGreedyPolicyDecoder(json.JSONDecoder): """A custom JSON decoder for the e greedy policy.""" @@ -71,17 +97,17 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: @staticmethod def hook( data: Dict[str, Any] - ) -> Union["EGreedyPolicy", AccuracyInfo, Dict[str, "EGreedyPolicy"]]: + ) -> Union[ + "EGreedyPolicy", + AccuracyInfo, + ConsecutiveFailures, + Dict[str, "EGreedyPolicy"], + Dict[str, ConsecutiveFailures], + ]: """Perform the custom decoding.""" - for cls_ in (AccuracyInfo, EGreedyPolicy): + for cls_ in (AccuracyInfo, ConsecutiveFailures, EGreedyPolicy): cls_attributes = cls_.__annotations__.keys() # pylint: disable=no-member - if sorted(cls_attributes) == sorted(data.keys()) or ( - cls_ == EGreedyPolicy - and sorted(cls_attributes - {"updated_ts"}) == sorted(data.keys()) - ): - # If EGreedyPolicy and 'updated_ts' is missing, set it to 0 - if cls_ == EGreedyPolicy and "updated_ts" not in data: - data["updated_ts"] = 0 + if sorted(cls_attributes) == sorted(data.keys()): # if the attributes match the ones of the current class, use it to perform the deserialization return cls_(**data) @@ -93,8 +119,11 @@ class EGreedyPolicy: """An e-Greedy policy for the tool selection based on tool accuracy.""" eps: float + consecutive_failures_threshold: int + quarantine_duration: int accuracy_store: Dict[str, AccuracyInfo] = field(default_factory=dict) weighted_accuracy: Dict[str, float] = field(default_factory=dict) + consecutive_failures: Dict[str, ConsecutiveFailures] = field(default_factory=dict) updated_ts: int = 0 def __post_init__(self) -> None: @@ -111,7 +140,7 @@ def deserialize(cls, policy: str) -> "EGreedyPolicy": @property def tools(self) -> List[str]: - """Get the number of the policy's tools.""" + """Get the policy's tools.""" return list(self.accuracy_store.keys()) @property @@ -137,12 +166,45 @@ def random_tool(self) -> str: """Get the name of a tool randomly.""" return random.choice(list(self.accuracy_store.keys())) # nosec + def is_quarantined(self, tool: str) -> bool: + """Check if the policy is valid.""" + if tool not in self.consecutive_failures: + return False + + failures = self.consecutive_failures[tool] + return ( + failures.n_failures > self.consecutive_failures_threshold + and failures.timestamp + self.quarantine_duration > int(time()) + ) + + @property + def valid_tools(self) -> List[str]: + """Get the policy's tools.""" + return list( + tool for tool in self.accuracy_store.keys() if not self.is_quarantined(tool) + ) + + @property + def valid_weighted_accuracy(self) -> Dict[str, float]: + """Get the valid weighted accuracy.""" + return { + tool: acc + for tool, acc in self.weighted_accuracy.items() + if not self.is_quarantined(tool) + } + @property def best_tool(self) -> str: - """Get the best tool.""" - weighted_accuracy = list(self.weighted_accuracy.values()) - best = argmax(weighted_accuracy) - return self.tools[best] + """Get the best non-quarantined tool.""" + valid_tools, valid_weighted_accuracies = zip( + *self.valid_weighted_accuracy.items() + ) + if not valid_weighted_accuracies: + # if there are no unquarantined tools, then consider them all valid + valid_tools, valid_weighted_accuracies = self.weighted_accuracy.items() + + best = argmax(valid_weighted_accuracies) + return valid_tools[best] def update_weighted_accuracy(self) -> None: """Update the weighted accuracy for each tool.""" @@ -177,6 +239,12 @@ def tool_used(self, tool: str) -> None: self.accuracy_store[tool].pending += 1 self.update_weighted_accuracy() + def tool_responded(self, tool: str, timestamp: int, failed: bool = True) -> None: + """Update the policy based on the given tool's response.""" + if tool not in self.consecutive_failures: + self.consecutive_failures[tool] = ConsecutiveFailures() + self.consecutive_failures[tool].update_status(timestamp, failed) + def update_accuracy_store(self, tool: str, winning: bool) -> None: """Update the accuracy store for the given tool.""" acc_info = self.accuracy_store[tool] @@ -200,11 +268,12 @@ def stats_report(self) -> str: report = "Policy statistics so far (only for resolved markets):\n" stats = ( - f"{tool} tool:\n" - f"\tTimes used: {self.accuracy_store[tool].requests}\n" - f"\tWeighted Accuracy: {self.weighted_accuracy[tool]}" + f"\t{tool} tool:\n" + f"\t\tQuarantined: {self.is_quarantined(tool)}\n" + f"\t\tTimes used: {self.accuracy_store[tool].requests}\n" + f"\t\tWeighted Accuracy: {self.weighted_accuracy[tool]}" for tool in self.tools ) report += "\n".join(stats) - report += f"\nBest tool so far is {self.best_tool!r}." + report += f"\nBest non-quarantined tool so far is {self.best_tool!r}." return report diff --git a/packages/valory/skills/decision_maker_abci/rounds.py b/packages/valory/skills/decision_maker_abci/rounds.py index 46aacc9a..2a942e2e 100644 --- a/packages/valory/skills/decision_maker_abci/rounds.py +++ b/packages/valory/skills/decision_maker_abci/rounds.py @@ -93,8 +93,8 @@ class DecisionMakerAbciApp(AbciApp[Event]): - benchmarking disabled: 14. - no majority: 0. - round timeout: 0. - - no op: 20. - - blacklist: 20. + - done: 20. + - subscription error: 20. 1. BenchmarkingRandomnessRound - done: 3. - round timeout: 1. @@ -202,8 +202,8 @@ class DecisionMakerAbciApp(AbciApp[Event]): Event.ROUND_TIMEOUT: CheckBenchmarkingModeRound, # added because of `autonomy analyse fsm-specs` # falsely reporting them as missing from the transition - Event.NO_OP: ImpossibleRound, - Event.BLACKLIST: ImpossibleRound, + Event.DONE: ImpossibleRound, + Event.SUBSCRIPTION_ERROR: ImpossibleRound, }, BenchmarkingRandomnessRound: { Event.DONE: SamplingRound, diff --git a/packages/valory/skills/decision_maker_abci/skill.yaml b/packages/valory/skills/decision_maker_abci/skill.yaml index cf113fe6..f2c06eb1 100644 --- a/packages/valory/skills/decision_maker_abci/skill.yaml +++ b/packages/valory/skills/decision_maker_abci/skill.yaml @@ -14,39 +14,39 @@ fingerprint: behaviours/__init__.py: bafybeih6ddz2ocvm6x6ytvlbcz6oi4snb5ee5xh5h65nq4w2qf7fd7zfky behaviours/base.py: bafybeieqvxpzjnwjgq4ffhjojle2q7onif44nefrlza6pxriewisqcm324 behaviours/bet_placement.py: bafybeia4listbfzsk4n4wkc4ycaftxgywjnl3mmpcqhuo3nwwia4n3oufu - behaviours/blacklisting.py: bafybeicah7vcruhbakrhpsxdmw3ftq72o5e3plfhhqsnvhk3vk6iud6og4 + behaviours/blacklisting.py: bafybeieuqoup2vrmrtvjfqnr5mzrvkegc7afb2oeujzq2itsbhcsham2se behaviours/check_benchmarking.py: bafybeiao2lyj7apezkqrpgsyzb3dwvrdgsrgtprf6iuhsmlsufvxfl5bci behaviours/claim_subscription.py: bafybeigbqkhc6mb73rbwaks32tfiqx6u2xza43uiy6rvbtrnqd6m4fru3e - behaviours/decision_receive.py: bafybeifrsnxref7yyl4hq4nmh4xa4rden4wygv5y5ugvqb5ji7zgj7mv5m + behaviours/decision_receive.py: bafybeibsthk6fjydkengwzgpgz5v44xpta67nnlyxt6uugx2voitqgojpq behaviours/decision_request.py: bafybeia22omb7tvocyfe3z2ucn5au5mcas7dg37ha42u7znefzrewjpk7y - behaviours/handle_failed_tx.py: bafybeidxpc6u575ymct5tdwutvzov6zqfdoio5irgldn3fw7q3lg36mmxm + behaviours/handle_failed_tx.py: bafybeiashwlfp6ty3g6ukgmliaghwu6yiunbqpjmyrzheokw3pbcr2ckaq behaviours/order_subscription.py: bafybeib3maqohhx35wzryy4otdcjp5thkr4sbp27ksvwidy3pwm444itra behaviours/randomness.py: bafybeiaoj3awyyg2onhpsdsn3dyczs23gr4smuzqcbw3e5ocljwxswjkce behaviours/reedem.py: bafybeidjmhh6c6shbg25d7exmc4nnp4heqbqselwuxj7rp2ss665lrytxe behaviours/round_behaviour.py: bafybeih63hpia2bwwzu563hxs5yd3t5ycvxvkfnhvxbzghbyy3mw3xjl3i behaviours/sampling.py: bafybeicvtxjv5rxlsdrmbtetqwzzau6is47guystvw245grd6s2qs5pxea - behaviours/storage_manager.py: bafybeiez6daaj2bufxdcsghtmqybyrzdh74z26cc4ajsqsiy5krgjo2tla + behaviours/storage_manager.py: bafybeic6wca37fkwonbsrwme55xnklfbqtheknroudayzfxdge4pxdbm7y behaviours/tool_selection.py: bafybeienlxcgjs3ogyofli3d7q3p5rst3mcxxcnwqf7qolqjeefjtixeke dialogues.py: bafybeigpwuzku3we7axmxeamg7vn656maww6emuztau5pg3ebsoquyfdqm - fsm_specification.yaml: bafybeigwlvvi6fav72wg4wz22xjekegenzjnub5efwz5xu6qsrjnxluspq + fsm_specification.yaml: bafybeidlnqnd7dwg7r2vhsgm2balrqhxm36qdflkmr7csze2kdeu3a7sbu handlers.py: bafybeibf42562x3d5i66yf5p3vi6a2oolhwwxr32pjqtuxz5w4gmg3r4oa io_/__init__.py: bafybeifxgmmwjqzezzn3e6keh2bfo4cyo7y5dq2ept3stfmgglbrzfl5rq io_/loader.py: bafybeih3sdsx5dhe4kzhtoafexjgkutsujwqy3zcdrlrkhtdks45bc7exa - models.py: bafybeidhdqgwcgn4rrncufpdmkbcye7xfqd6ytow7zx3hbcpsgpjmxgfmm - payloads.py: bafybeif3d4qgj635rbnp6a5lgwhgbyilta6mtytjcej2jccorckxbuaev4 - policy.py: bafybeihlzs4o5e7yfmfzcvvrzkf4bhxfsg5gxnzsrpepwgfugh45gafye4 + models.py: bafybeibifjp45ii5amaghmdk5yjha7if5qpyreg2axpcxcj3ek23ys4fxm + payloads.py: bafybeieygushjlrzwzpnhagjgpbs3goot3pnfheh6yawuwctrk3uoeesfm + policy.py: bafybeidya6626uuvztdf3vhmrazjg2yxafa2iqrxmeh5gddzjjcbano5bi redeem_info.py: bafybeifiiix4gihfo4avraxt34sfw35v6dqq45do2drrssei2shbps63mm - rounds.py: bafybeiazjcsukgefair52aw37hhvxzlopnzqqmi4ntqrinakljlcm4kt4a + rounds.py: bafybeidjve7efycfkkbignqky4x6awvrobn4w32grxiubxxiiparr7xd2i states/__init__.py: bafybeid23llnyp6j257dluxmrnztugo5llsrog7kua53hllyktz4dqhqoy - states/base.py: bafybeiatr6cqa3juxkhm6p4h7dhol7tfmxh2fo6nr2gtc6wuwyrtu3k3xm + states/base.py: bafybeiglqvym3ri6hurx4k7hrnykzbmslxe3vuj23djt6hai4czii4vbqq states/bet_placement.py: bafybeih5eopyxubczys5u5t3bdxbxpc7mmfdyqrpqsbm2uha5jc2phza4i states/blacklisting.py: bafybeiapelgjhbjjn4uq4z5gspyirqzwzgccg5anktrp5kxdwamfnfw5mi - states/check_benchmarking.py: bafybeiabv6pq7q45jd3nkor5afmlycqgec5ctuwcfbdukkjjm4imesv4ni - states/claim_subscription.py: bafybeiampifhdoztggwj6gthl2hfzecmjcwnm6nic2o47q4je7j4x3ujne - states/decision_receive.py: bafybeib3eahaehvmcrdojnlrz34q2crdb3ao6qun2wohokdf3t3ywwfa4y + states/check_benchmarking.py: bafybeifvto757zbfzy7mpehblyjd7zqboarxesjfiobtnbxew4nkltfkim + states/claim_subscription.py: bafybeidlubctpk5djsredvvwdhubs34rztud3lw7pwp5kj6ggzah6dvyly + states/decision_receive.py: bafybeiexc26g7z7by6eziawjld52nglljiwlj6oam5ramtbyo6un2tqs5y states/decision_request.py: bafybeiarv3r5j7cfvxmudki2llbdl2pvf24p5mvsva6bdgrylnwdyag5xy states/final_states.py: bafybeicjrrojo3gmfaxzicwloyorlnqgzl6a2avevo4nvhoh424zwzmbti - states/handle_failed_tx.py: bafybeihewm2vernvhktuorljdupjqcg2p5vs6wvsira2d62wkoyo5xlzjm + states/handle_failed_tx.py: bafybeiha5wkl4u4jlj7txpmhuzfnibnu3ix5zw4vmufjunoyuq6s6ubhnu states/order_subscription.py: bafybeihl3pwrbccaitiukbigygd5u3weyih34pvzql3c6n5k7gjj47f2be states/randomness.py: bafybeiceoo4nx3t4dofpwczw3v5mclramwmzpwjs6hv7l56arodrjx4l5u states/redeem.py: bafybeica6cn4xg7shea2wjhbqnddgxe5zao2hkmceltze7qknxdhtsoaxe @@ -59,15 +59,15 @@ fingerprint: tests/behaviours/dummy_strategy/dummy_strategy.py: bafybeig5e3xfr7gxsakfj4stbxqcwdiljl7klvgahkuwe3obzxgkg3qt2e tests/behaviours/test_base.py: bafybeif6pglmr7pvojylatfzaxtlk65igx6a2omyrbxfihnnft6o7p75p4 tests/conftest.py: bafybeidy5hw56kw5mxudnfbhvogofn6k4rqb4ux2bd45baedrrhmgyrude - tests/states/test_base.py: bafybeigiuctxda3npkbvx7nsq4jvqpckvbzgqlj76hdpk2ntc52ppc4vnm + tests/states/test_base.py: bafybeieqy7wz5677jathwnolsgrt7zdifauammly3aeoq6dk4hdsqo5fte tests/states/test_bet_placement.py: bafybeibvc37n2cluep4tasvgmvwxwne2deais6ptirducpogk67v4gj4ga tests/states/test_blacklising.py: bafybeihm2ex6l7fhorgi3mjj2epztu2r7bqbg56unpgpzfzymghshchqzy - tests/states/test_check_benchmarking.py: bafybeifwpi5f4fhreqptfxdsnyv3nptkqytkwbukfuqkrjo4eww7cv3sxy + tests/states/test_check_benchmarking.py: bafybeiaauepn46l5z5kx2ifzsqcuravg3zvddecuhdgp4eaeous6vaqyoq tests/states/test_claim_subscription.py: bafybeiclkxjhceb3ehgmg6klt4uywew5drk5b3w6no7mwxetpubxqrejfy - tests/states/test_decision_receive.py: bafybeiddj42tdkghvga574qxvixh7nmf6bpdjitlk3eliimnq2r4n6qlrm + tests/states/test_decision_receive.py: bafybeibkxalkfxuyokb6y5hkyu4pdlg4yfusbpgtkfr66cprygu7cgyd2y tests/states/test_decision_request.py: bafybeigqbakm2olkwvcngertjplhnmu6on6tp6hxn7lxygi2gf5a5eurbe tests/states/test_final_states.py: bafybeiftfd3ovaqpfe7t5ry7maiziavk74wl66d6zo6ikhgodznormd2nm - tests/states/test_handle_failed_tx.py: bafybeibuepj6fko7ba3bef6nybzetilni2iwgkxd5xeazqskadbad3l2zq + tests/states/test_handle_failed_tx.py: bafybeiebqgfhncmdexgq7khgkmcsym35547x3j6tr3mmybuyhocv6h5zpq tests/states/test_order_subscription.py: bafybeidx2tzivsxhpr5xx5e5h2xmpjyewfogt2mujv4sq3hbaeksmcbvhy tests/states/test_randomness.py: bafybeib3eqjv6mhlprzda7d4viddn5alrfqteq6juyg3ccejseoywcsbey tests/states/test_redeem.py: bafybeiezdnfrxukb2xpwffrr357g2anmdkwy7wo3nphvlggipq5xrdzr7a @@ -75,7 +75,7 @@ fingerprint: tests/states/test_tool_selection.py: bafybeib7js3dj7647t33o5ybfqftwytxktwrvhbri5yuyymg6znj6y7xxa tests/test_dialogues.py: bafybeibulo64tgfrq4e5qbcqnmifrlehkqciwuavublints353zaj2mlpa tests/test_handlers.py: bafybeihpkgtjjm3uegpup6zkznpoaxqpu6kmp3ujiggrzbe73p5fzlq7im - tests/test_payloads.py: bafybeic6h4jxlb6rxyx4fde753xvfdo5lszfcl2j6wlb266v3cxjrluqgi + tests/test_payloads.py: bafybeiggbcppj4j54r23qvg423elsnd7dcxl3sfo534ek5sd3g65ua57nq tests/test_rounds.py: bafybeigifftusd4ew42tyvyrr55o2uehhcik2gdq3atkpjwwlqdeskedty utils/__init__.py: bafybeiazrfg3kwfdl5q45azwz6b6mobqxngxpf4hazmrnkhinpk4qhbbf4 utils/nevermined.py: bafybeigallaqxhqopznhjhefr6bukh4ojkz5vdtqyzod5dksshrf24fjgi @@ -317,6 +317,9 @@ models: response_timeout: 300 agent_balance_threshold: 10000000000000000 expected_mech_response_time: 300 + mech_invalid_response: Invalid Response + mech_consecutive_failures_threshold: 2 + tool_quarantine_duration: 18000 class_name: DecisionMakerParams benchmarking_mode: args: @@ -350,6 +353,8 @@ models: requests: total_requests accuracy: tool_accuracy sep: ',' + max: max + datetime_format: '%Y-%m-%d %H:%M:%S' class_name: AccuracyInfoFields trades_subgraph: args: diff --git a/packages/valory/skills/decision_maker_abci/states/base.py b/packages/valory/skills/decision_maker_abci/states/base.py index 76dd9a67..421eec4c 100644 --- a/packages/valory/skills/decision_maker_abci/states/base.py +++ b/packages/valory/skills/decision_maker_abci/states/base.py @@ -198,6 +198,11 @@ def participant_to_tx_prep(self) -> DeserializedCollection: """Get the participants to bet-placement.""" return self._get_deserialized("participant_to_tx_prep") + @property + def participant_to_handle_failed_tx(self) -> DeserializedCollection: + """Get the participants to `HandleFailedTxRound`.""" + return self._get_deserialized("participant_to_handle_failed_tx") + @property def agreement_id(self) -> str: """Get the agreement id.""" @@ -273,6 +278,11 @@ def service_staking_state(self) -> StakingState: """Get the service's staking state.""" return StakingState(self.db.get("service_staking_state", 0)) + @property + def after_bet_attempt(self) -> bool: + """Get the service's staking state.""" + return bool(self.db.get("after_bet_attempt", False)) + class TxPreparationRound(CollectSameUntilThresholdRound): """A round for preparing a transaction.""" diff --git a/packages/valory/skills/decision_maker_abci/states/check_benchmarking.py b/packages/valory/skills/decision_maker_abci/states/check_benchmarking.py index 97d3bdc7..e50d6478 100644 --- a/packages/valory/skills/decision_maker_abci/states/check_benchmarking.py +++ b/packages/valory/skills/decision_maker_abci/states/check_benchmarking.py @@ -19,14 +19,16 @@ """This module contains a state of the decision-making abci app which checks if the benchmarking mode is enabled.""" +from packages.valory.skills.decision_maker_abci.payloads import VotingPayload from packages.valory.skills.decision_maker_abci.states.base import Event -from packages.valory.skills.decision_maker_abci.states.handle_failed_tx import ( - HandleFailedTxRound, +from packages.valory.skills.decision_maker_abci.states.claim_subscription import ( + ClaimRound, ) -class CheckBenchmarkingModeRound(HandleFailedTxRound): +class CheckBenchmarkingModeRound(ClaimRound): """A round for checking whether the benchmarking mode is enabled.""" + payload_class = VotingPayload done_event = Event.BENCHMARKING_ENABLED negative_event = Event.BENCHMARKING_DISABLED diff --git a/packages/valory/skills/decision_maker_abci/states/claim_subscription.py b/packages/valory/skills/decision_maker_abci/states/claim_subscription.py index 53b41baf..40331ef3 100644 --- a/packages/valory/skills/decision_maker_abci/states/claim_subscription.py +++ b/packages/valory/skills/decision_maker_abci/states/claim_subscription.py @@ -19,7 +19,13 @@ """This module contains the decision receiving state of the decision-making abci app.""" -from packages.valory.skills.abstract_round_abci.base import VotingRound, get_name +from typing import Type + +from packages.valory.skills.abstract_round_abci.base import ( + BaseTxPayload, + VotingRound, + get_name, +) from packages.valory.skills.decision_maker_abci.payloads import ClaimPayload from packages.valory.skills.decision_maker_abci.states.base import ( Event, @@ -30,7 +36,7 @@ class ClaimRound(VotingRound): """A round for preparing a transaction.""" - payload_class = ClaimPayload + payload_class: Type[BaseTxPayload] = ClaimPayload synchronized_data_class = SynchronizedData done_event = Event.DONE negative_event = Event.SUBSCRIPTION_ERROR diff --git a/packages/valory/skills/decision_maker_abci/states/decision_receive.py b/packages/valory/skills/decision_maker_abci/states/decision_receive.py index 678b6b2d..282e48c0 100644 --- a/packages/valory/skills/decision_maker_abci/states/decision_receive.py +++ b/packages/valory/skills/decision_maker_abci/states/decision_receive.py @@ -49,6 +49,7 @@ class DecisionReceiveRound(CollectSameUntilThresholdRound): get_name(SynchronizedData.confidence), get_name(SynchronizedData.bet_amount), get_name(SynchronizedData.next_mock_data_row), + get_name(SynchronizedData.policy), ) collection_key = get_name(SynchronizedData.participant_to_decision) diff --git a/packages/valory/skills/decision_maker_abci/states/handle_failed_tx.py b/packages/valory/skills/decision_maker_abci/states/handle_failed_tx.py index cce575c8..0f4c8412 100644 --- a/packages/valory/skills/decision_maker_abci/states/handle_failed_tx.py +++ b/packages/valory/skills/decision_maker_abci/states/handle_failed_tx.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # ------------------------------------------------------------------------------ # -# Copyright 2023 Valory AG +# Copyright 2023-2024 Valory AG # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,20 +19,48 @@ """This module contains the blacklisting state of the decision-making abci app.""" -from packages.valory.skills.abstract_round_abci.base import VotingRound, get_name -from packages.valory.skills.decision_maker_abci.payloads import VotingPayload +from enum import Enum +from typing import Optional, Tuple, cast + +from packages.valory.skills.abstract_round_abci.base import ( + BaseSynchronizedData, + CollectSameUntilThresholdRound, + get_name, +) +from packages.valory.skills.decision_maker_abci.payloads import HandleFailedTxPayload from packages.valory.skills.decision_maker_abci.states.base import ( Event, SynchronizedData, ) -class HandleFailedTxRound(VotingRound): +class HandleFailedTxRound(CollectSameUntilThresholdRound): """A round for updating the bets after blacklisting the sampled one.""" - payload_class = VotingPayload + payload_class = HandleFailedTxPayload synchronized_data_class = SynchronizedData done_event = Event.BLACKLIST - negative_event = Event.NO_OP + no_op_event = Event.NO_OP + none_event = Event.NO_OP no_majority_event = Event.NO_MAJORITY - collection_key = get_name(SynchronizedData.participant_to_votes) + selection_key = ( + get_name(SynchronizedData.after_bet_attempt), + get_name(SynchronizedData.tx_submitter), + ) + collection_key = get_name(SynchronizedData.participant_to_handle_failed_tx) + + def end_block(self) -> Optional[Tuple[BaseSynchronizedData, Enum]]: + """Process the end of the block.""" + res = super().end_block() + if res is None: + return None + + synced_data, event = cast(Tuple[SynchronizedData, Enum], res) + + if event != self.done_event: + return res + + if synced_data.after_bet_attempt: + return synced_data, self.done_event + + return synced_data, self.no_op_event diff --git a/packages/valory/skills/decision_maker_abci/tests/states/test_base.py b/packages/valory/skills/decision_maker_abci/tests/states/test_base.py index 805fa8ca..ad41e0e8 100644 --- a/packages/valory/skills/decision_maker_abci/tests/states/test_base.py +++ b/packages/valory/skills/decision_maker_abci/tests/states/test_base.py @@ -168,7 +168,9 @@ def test_policy_property( mock_policy_serialized = "serialized_policy_string" mocked_db.get_strict.return_value = mock_policy_serialized - expected_policy = EGreedyPolicy(eps=0.1) + expected_policy = EGreedyPolicy( + eps=0.1, consecutive_failures_threshold=1, quarantine_duration=0 + ) mock_deserialize.return_value = expected_policy result = sync_data.policy @@ -198,7 +200,10 @@ def test_weighted_accuracy(sync_data: SynchronizedData, mocked_db: MagicMock) -> selected_mech_tool = "tool1" policy_db_name = "policy" policy_mock = EGreedyPolicy( - eps=0.1, accuracy_store={selected_mech_tool: AccuracyInfo(requests=1)} + eps=0.1, + consecutive_failures_threshold=1, + quarantine_duration=0, + accuracy_store={selected_mech_tool: AccuracyInfo(requests=1)}, ).serialize() mocked_db.get_strict = lambda name: ( policy_mock if name == policy_db_name else selected_mech_tool diff --git a/packages/valory/skills/decision_maker_abci/tests/states/test_check_benchmarking.py b/packages/valory/skills/decision_maker_abci/tests/states/test_check_benchmarking.py index f21cb005..ebc2b3c0 100644 --- a/packages/valory/skills/decision_maker_abci/tests/states/test_check_benchmarking.py +++ b/packages/valory/skills/decision_maker_abci/tests/states/test_check_benchmarking.py @@ -23,8 +23,8 @@ from packages.valory.skills.decision_maker_abci.rounds import CheckBenchmarkingModeRound from packages.valory.skills.decision_maker_abci.states.base import Event -from packages.valory.skills.decision_maker_abci.states.handle_failed_tx import ( - HandleFailedTxRound, +from packages.valory.skills.decision_maker_abci.states.claim_subscription import ( + ClaimRound, ) @@ -36,8 +36,8 @@ def test_check_benchmarking_mode_round_initialization() -> None: assert round_instance.done_event == Event.BENCHMARKING_ENABLED assert round_instance.negative_event == Event.BENCHMARKING_DISABLED - # Check that it inherits from HandleFailedTxRound - assert isinstance(round_instance, HandleFailedTxRound) + # Check that it inherits from ClaimRound + assert isinstance(round_instance, ClaimRound) def test_check_benchmarking_mode_round_events() -> None: diff --git a/packages/valory/skills/decision_maker_abci/tests/states/test_decision_receive.py b/packages/valory/skills/decision_maker_abci/tests/states/test_decision_receive.py index 064c891a..bcf00f1b 100644 --- a/packages/valory/skills/decision_maker_abci/tests/states/test_decision_receive.py +++ b/packages/valory/skills/decision_maker_abci/tests/states/test_decision_receive.py @@ -63,6 +63,7 @@ def get_payloads( next_mock_data_row: Optional[int], is_profitable: Optional[bool], bets_hash: str, + policy: str, ) -> Mapping[str, UpdateBetsPayload]: """Get payloads.""" return { @@ -73,7 +74,8 @@ def get_payloads( bet_amount=bet_amount, next_mock_data_row=next_mock_data_row, is_profitable=is_profitable, - bets_hash=bets_hash, # Added bets_hash parameter + bets_hash=bets_hash, + policy=policy, decision_received_timestamp=int(datetime.datetime.utcnow().timestamp()), ) for participant in get_participants() @@ -110,6 +112,7 @@ class TestDecisionReceiveRound(BaseCollectSameUntilThresholdRoundTest): bet_amount=100, next_mock_data_row=1, is_profitable=True, + policy="", bets_hash=DUMMY_BETS_HASH, # Added bets_hash ), final_data={ @@ -131,6 +134,7 @@ class TestDecisionReceiveRound(BaseCollectSameUntilThresholdRoundTest): bet_amount=50, next_mock_data_row=2, is_profitable=False, + policy="", bets_hash=DUMMY_BETS_HASH, # Added bets_hash ), final_data={ @@ -152,6 +156,7 @@ class TestDecisionReceiveRound(BaseCollectSameUntilThresholdRoundTest): bet_amount=None, next_mock_data_row=None, is_profitable=True, + policy="", bets_hash=DUMMY_BETS_HASH, # Added bets_hash ), final_data={}, @@ -168,6 +173,7 @@ class TestDecisionReceiveRound(BaseCollectSameUntilThresholdRoundTest): bet_amount=None, next_mock_data_row=None, is_profitable=True, + policy="", bets_hash=DUMMY_BETS_HASH, # Added bets_hash ), final_data={}, @@ -184,6 +190,7 @@ class TestDecisionReceiveRound(BaseCollectSameUntilThresholdRoundTest): bet_amount=None, next_mock_data_row=None, is_profitable=True, + policy="", bets_hash=DUMMY_BETS_HASH, # Added bets_hash ), final_data={}, diff --git a/packages/valory/skills/decision_maker_abci/tests/states/test_handle_failed_tx.py b/packages/valory/skills/decision_maker_abci/tests/states/test_handle_failed_tx.py index b88bad2c..53fe7db7 100644 --- a/packages/valory/skills/decision_maker_abci/tests/states/test_handle_failed_tx.py +++ b/packages/valory/skills/decision_maker_abci/tests/states/test_handle_failed_tx.py @@ -20,21 +20,16 @@ """This package contains the tests for Decision Maker""" -import json -from typing import Any, Callable, Dict, FrozenSet, Type +from typing import Dict, Optional from unittest.mock import MagicMock import pytest -from packages.valory.skills.abstract_round_abci.base import ( - CollectionRound, - VotingRound, - get_name, -) +from packages.valory.skills.abstract_round_abci.base import CollectionRound from packages.valory.skills.abstract_round_abci.test_tools.rounds import ( - BaseVotingRoundTest, + BaseCollectSameUntilThresholdRoundTest, ) -from packages.valory.skills.decision_maker_abci.payloads import VotingPayload +from packages.valory.skills.decision_maker_abci.payloads import HandleFailedTxPayload from packages.valory.skills.decision_maker_abci.states.base import ( Event, SynchronizedData, @@ -44,141 +39,56 @@ ) -# Helper functions -def get_participants() -> FrozenSet[str]: - """Get participants for the test.""" - return frozenset([f"agent_{i}" for i in range(MAX_PARTICIPANTS)]) - +class TestEstimateConsensusRound(BaseCollectSameUntilThresholdRoundTest): + """Test EstimateConsensusRound.""" -def get_participant_to_votes( - participants: FrozenSet[str], vote: bool -) -> Dict[str, VotingPayload]: - """Map participants to votes.""" - return { - participant: VotingPayload(sender=participant, vote=vote) - for participant in participants - } + _synchronized_data_class = SynchronizedData + _event_class = Event + def get_participant_to_handle( + self, vote: Optional[bool] + ) -> Dict[str, HandleFailedTxPayload]: + """Map participants to votes.""" + return { + participant: HandleFailedTxPayload( + sender=participant, vote=vote, tx_submitter="tx_submitter" # type: ignore + ) + for participant in self.participants + } -def get_participant_to_votes_serialized( - participants: FrozenSet[str], vote: bool -) -> Dict[str, Dict[str, Any]]: - """Get serialized votes from participants.""" - return CollectionRound.serialize_collection( - get_participant_to_votes(participants, vote) + @pytest.mark.parametrize( + "vote, expected_event", + ( + (None, HandleFailedTxRound.none_event), + (True, HandleFailedTxRound.done_event), + (False, HandleFailedTxRound.no_op_event), + ), ) - - -# Dummy Payload Data -DUMMY_PAYLOAD_DATA = {"vote": True} -MAX_PARTICIPANTS = 4 - - -# Base test class for HandleFailedTxRound -class BaseHandleFailedTxRoundTest(BaseVotingRoundTest): - """Base Test Class for HandleFailedTxRound""" - - test_class: Type[VotingRound] - test_payload: Type[VotingPayload] - - def _test_voting_round( - self, vote: bool, expected_event: Any, threshold_check: Callable + def test_run( + self, + vote: Optional[bool], + expected_event: Event, ) -> None: - """Helper method to test voting rounds with positive or negative votes.""" + """Runs test.""" - test_round = self.test_class( + test_round = HandleFailedTxRound( synchronized_data=self.synchronized_data, context=MagicMock() ) - + participant_to_handle = self.get_participant_to_handle(vote) self._complete_run( self._test_round( test_round=test_round, - round_payloads=get_participant_to_votes(self.participants, vote=vote), - synchronized_data_update_fn=lambda synchronized_data, test_round: synchronized_data.update( - participant_to_votes=get_participant_to_votes_serialized( - self.participants, vote=vote - ) + round_payloads=participant_to_handle, + synchronized_data_update_fn=lambda _synchronized_data, _test_round: _synchronized_data.update( + participant_to_handle_failed_tx=CollectionRound.serialize_collection( + participant_to_handle + ), + most_voted_estimate=_test_round.most_voted_payload, ), synchronized_data_attr_checks=[ - lambda synchronized_data: synchronized_data.participant_to_votes.keys() - ] - if vote - else [], + lambda _synchronized_data: _synchronized_data.participant_to_handle_failed_tx.keys() + ], + most_voted_payload=vote, exit_event=expected_event, - threshold_check=threshold_check, ) ) - - def test_positive_votes(self) -> None: - """Test HandleFailedTxRound with positive votes.""" - self._test_voting_round( - vote=True, - expected_event=self._event_class.BLACKLIST, - threshold_check=lambda x: x.positive_vote_threshold_reached, - ) - - def test_negative_votes(self) -> None: - """Test HandleFailedTxRound with negative votes.""" - self._test_voting_round( - vote=False, - expected_event=self._event_class.NO_OP, - threshold_check=lambda x: x.negative_vote_threshold_reached, - ) - - -# Test class for HandleFailedTxRound -class TestHandleFailedTxRound(BaseHandleFailedTxRoundTest): - """Tests for HandleFailedTxRound.""" - - test_class = HandleFailedTxRound - _event_class = Event - _synchronized_data_class = SynchronizedData - - @pytest.mark.parametrize( - "test_case", - ( - # Parametrized test case for successful vote (BLACKLIST) - { - "name": "Happy path", - "initial_data": {}, - "payloads": get_participant_to_votes(get_participants(), True), - "final_data": {}, - "event": Event.BLACKLIST, - "most_voted_payload": json.dumps(DUMMY_PAYLOAD_DATA, sort_keys=True), - "synchronized_data_attr_checks": [ - lambda sync_data: sync_data.db.get( - get_name(SynchronizedData.participant_to_votes) - ) - == CollectionRound.deserialize_collection( - json.loads(json.dumps(DUMMY_PAYLOAD_DATA, sort_keys=True)) - ) - ], - }, - # Parametrized test case for no operation (NO_OP) - { - "name": "No majority", - "initial_data": {}, - "payloads": get_participant_to_votes(get_participants(), False), - "final_data": {}, - "event": Event.NO_OP, - "most_voted_payload": json.dumps(DUMMY_PAYLOAD_DATA, sort_keys=True), - "synchronized_data_attr_checks": [], - }, - ), - ) - def test_run(self, test_case: dict) -> None: - """Run the parameterized tests.""" - if test_case["event"] == Event.BLACKLIST: - self.test_positive_votes() - elif test_case["event"] == Event.NO_OP: - self.test_negative_votes() - - -# Additional tests for state initialization -class TestFinishedHandleFailedTxRound: - """Tests for FinishedHandleFailedTxRound.""" - - def test_initialization(self) -> None: - """Test the initialization of FinishedHandleFailedTxRound.""" - round_ = HandleFailedTxRound(synchronized_data=MagicMock(), context=MagicMock()) - assert isinstance(round_, HandleFailedTxRound) diff --git a/packages/valory/skills/decision_maker_abci/tests/test_payloads.py b/packages/valory/skills/decision_maker_abci/tests/test_payloads.py index a8197400..f406adc7 100644 --- a/packages/valory/skills/decision_maker_abci/tests/test_payloads.py +++ b/packages/valory/skills/decision_maker_abci/tests/test_payloads.py @@ -50,6 +50,7 @@ "confidence": 0.90, "bet_amount": 1, "next_mock_data_row": 1, + "policy": "dummy policy", "decision_received_timestamp": int(datetime.utcnow().timestamp()), }, ), diff --git a/packages/valory/skills/trader_abci/fsm_specification.yaml b/packages/valory/skills/trader_abci/fsm_specification.yaml index f00240b0..0778e7f0 100644 --- a/packages/valory/skills/trader_abci/fsm_specification.yaml +++ b/packages/valory/skills/trader_abci/fsm_specification.yaml @@ -117,10 +117,10 @@ transition_func: (CallCheckpointRound, SERVICE_NOT_STAKED): ResetAndPauseRound (CheckBenchmarkingModeRound, BENCHMARKING_DISABLED): UpdateBetsRound (CheckBenchmarkingModeRound, BENCHMARKING_ENABLED): BenchmarkingRandomnessRound - (CheckBenchmarkingModeRound, BLACKLIST): ImpossibleRound + (CheckBenchmarkingModeRound, DONE): ImpossibleRound (CheckBenchmarkingModeRound, NO_MAJORITY): CheckBenchmarkingModeRound - (CheckBenchmarkingModeRound, NO_OP): ImpossibleRound (CheckBenchmarkingModeRound, ROUND_TIMEOUT): CheckBenchmarkingModeRound + (CheckBenchmarkingModeRound, SUBSCRIPTION_ERROR): ImpossibleRound (CheckLateTxHashesRound, CHECK_LATE_ARRIVING_MESSAGE): SynchronizeLateMessagesRound (CheckLateTxHashesRound, CHECK_TIMEOUT): CheckLateTxHashesRound (CheckLateTxHashesRound, DONE): PostTxSettlementRound diff --git a/packages/valory/skills/trader_abci/skill.yaml b/packages/valory/skills/trader_abci/skill.yaml index 96451c90..90f21231 100644 --- a/packages/valory/skills/trader_abci/skill.yaml +++ b/packages/valory/skills/trader_abci/skill.yaml @@ -11,7 +11,7 @@ fingerprint: behaviours.py: bafybeigc6hszbu66ccajny5eh7thfgsrlr36je4mzziwp4mupgvtaeu6aa composition.py: bafybeifxerfvssuhodqmtvkz6umlmrmdqjv5ptpszhnwlavzxaavdpdyly dialogues.py: bafybeiebofyykseqp3fmif36cqmmyf3k7d2zbocpl6t6wnlpv4szghrxbm - fsm_specification.yaml: bafybeiea2w6rhdxwc2ogvdnoxw2wbklnlspsoxyzhpz6h6x6dnctqurnoi + fsm_specification.yaml: bafybeigfs3ffn5r3uo4d4aif7uwe3u7z5eqdx42zegifz6wswrsdx4d6xm handlers.py: bafybeibbxybbi66em63ad33cllymypr3za3f5xvor3m2krhuxoyxnqjnxu models.py: bafybeih2vkf4ln7n7ar27iemho7w7sdr4clmhbnhbcznmsri6mc2skkky4 tests/__init__.py: bafybeiadatapyjh3e7ucg2ehz77oms3ihrbutwb2cs2tkjehy54utwvuyi @@ -27,8 +27,8 @@ skills: - valory/transaction_settlement_abci:0.1.0:bafybeic7q7recyka272udwcupblwbkc3jkodgp74fvcdxb7urametg5dae - valory/termination_abci:0.1.0:bafybeib5l7jhew5ic6iq24dd23nidcoimzqkrk556gqywhoziatj33zvwm - valory/market_manager_abci:0.1.0:bafybeiaru2d32wpmcgqs64eepxud4idgubc3vmsbdwbia7gygipql2mmqi -- valory/decision_maker_abci:0.1.0:bafybeiddnmcquiuznts67ridhpnaqw2y3rrt4nfau5kjm74zhk5lhjxi2q -- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeietwknem7iiood6pwkfup322ywwjmdrmdapllrcms6jpeev5w2qfe +- valory/decision_maker_abci:0.1.0:bafybeig5oivc24sqhgyxfhjbl2xsoa5yssv72lcu5ezunbcpwu3xo4jglm +- valory/tx_settlement_multiplexer_abci:0.1.0:bafybeifpf3sk5evrvwxyht7mswcml3qy4nhzo5ddvjvgklapwsjwq6mera - valory/staking_abci:0.1.0:bafybeicupccurmrg7qesivonlyt3nryarsmk5qf5yh6auno64wn45bybvq - valory/check_stop_trading_abci:0.1.0:bafybeieduekpd4zbvjztyxyooppqnmjvup6jfp74uo6hhupvtvzzscdzkq - valory/mech_interact_abci:0.1.0:bafybeid6m3i5ofq7vuogqapdnoshhq7mswmudhvfcr2craw25fdwtoe3lm @@ -257,6 +257,9 @@ models: requester_staking_instance_address: '0x0000000000000000000000000000000000000000' response_timeout: 300 expected_mech_response_time: 300 + mech_invalid_response: Invalid Response + mech_consecutive_failures_threshold: 2 + tool_quarantine_duration: 18000 class_name: TraderParams benchmarking_mode: args: @@ -290,6 +293,8 @@ models: requests: total_requests accuracy: tool_accuracy sep: ',' + max: max + datetime_format: '%Y-%m-%d %H:%M:%S' class_name: AccuracyInfoFields network_subgraph: args: diff --git a/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml b/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml index 367792b9..3178b371 100644 --- a/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml +++ b/packages/valory/skills/tx_settlement_multiplexer_abci/skill.yaml @@ -23,7 +23,7 @@ protocols: - valory/ledger_api:1.0.0:bafybeihdk6psr4guxmbcrc26jr2cbgzpd5aljkqvpwo64bvaz7tdti2oni skills: - valory/abstract_round_abci:0.1.0:bafybeib733xfbndtpvkf44mtk7oyodnficgloo6xhn7xmqxxeos33es65u -- valory/decision_maker_abci:0.1.0:bafybeiddnmcquiuznts67ridhpnaqw2y3rrt4nfau5kjm74zhk5lhjxi2q +- valory/decision_maker_abci:0.1.0:bafybeig5oivc24sqhgyxfhjbl2xsoa5yssv72lcu5ezunbcpwu3xo4jglm - valory/staking_abci:0.1.0:bafybeicupccurmrg7qesivonlyt3nryarsmk5qf5yh6auno64wn45bybvq - valory/mech_interact_abci:0.1.0:bafybeid6m3i5ofq7vuogqapdnoshhq7mswmudhvfcr2craw25fdwtoe3lm behaviours: diff --git a/poetry.lock b/poetry.lock index 7be6dc09..decf62ab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,13 +2,13 @@ [[package]] name = "aiohappyeyeballs" -version = "2.4.3" +version = "2.4.4" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, + {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, + {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, ] [[package]] @@ -194,19 +194,19 @@ files = [ [[package]] name = "attrs" -version = "24.2.0" +version = "24.3.0" description = "Classes Without Boilerplate" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, - {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, + {file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"}, + {file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"}, ] [package.extras] benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] @@ -238,38 +238,36 @@ tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", " [[package]] name = "bcrypt" -version = "4.2.0" +version = "4.2.1" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.7" files = [ - {file = "bcrypt-4.2.0-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:096a15d26ed6ce37a14c1ac1e48119660f21b24cba457f160a4b830f3fe6b5cb"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c02d944ca89d9b1922ceb8a46460dd17df1ba37ab66feac4870f6862a1533c00"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d84cf6d877918620b687b8fd1bf7781d11e8a0998f576c7aa939776b512b98d"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1bb429fedbe0249465cdd85a58e8376f31bb315e484f16e68ca4c786dcc04291"}, - {file = "bcrypt-4.2.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:655ea221910bcac76ea08aaa76df427ef8625f92e55a8ee44fbf7753dbabb328"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:1ee38e858bf5d0287c39b7a1fc59eec64bbf880c7d504d3a06a96c16e14058e7"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0da52759f7f30e83f1e30a888d9163a81353ef224d82dc58eb5bb52efcabc399"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3698393a1b1f1fd5714524193849d0c6d524d33523acca37cd28f02899285060"}, - {file = "bcrypt-4.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:762a2c5fb35f89606a9fde5e51392dad0cd1ab7ae64149a8b935fe8d79dd5ed7"}, - {file = "bcrypt-4.2.0-cp37-abi3-win32.whl", hash = "sha256:5a1e8aa9b28ae28020a3ac4b053117fb51c57a010b9f969603ed885f23841458"}, - {file = "bcrypt-4.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:8f6ede91359e5df88d1f5c1ef47428a4420136f3ce97763e31b86dd8280fbdf5"}, - {file = "bcrypt-4.2.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:c52aac18ea1f4a4f65963ea4f9530c306b56ccd0c6f8c8da0c06976e34a6e841"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bbbfb2734f0e4f37c5136130405332640a1e46e6b23e000eeff2ba8d005da68"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3413bd60460f76097ee2e0a493ccebe4a7601918219c02f503984f0a7ee0aebe"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:8d7bb9c42801035e61c109c345a28ed7e84426ae4865511eb82e913df18f58c2"}, - {file = "bcrypt-4.2.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3d3a6d28cb2305b43feac298774b997e372e56c7c7afd90a12b3dc49b189151c"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9c1c4ad86351339c5f320ca372dfba6cb6beb25e8efc659bedd918d921956bae"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:27fe0f57bb5573104b5a6de5e4153c60814c711b29364c10a75a54bb6d7ff48d"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8ac68872c82f1add6a20bd489870c71b00ebacd2e9134a8aa3f98a0052ab4b0e"}, - {file = "bcrypt-4.2.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cb2a8ec2bc07d3553ccebf0746bbf3d19426d1c6d1adbd4fa48925f66af7b9e8"}, - {file = "bcrypt-4.2.0-cp39-abi3-win32.whl", hash = "sha256:77800b7147c9dc905db1cba26abe31e504d8247ac73580b4aa179f98e6608f34"}, - {file = "bcrypt-4.2.0-cp39-abi3-win_amd64.whl", hash = "sha256:61ed14326ee023917ecd093ee6ef422a72f3aec6f07e21ea5f10622b735538a9"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:39e1d30c7233cfc54f5c3f2c825156fe044efdd3e0b9d309512cc514a263ec2a"}, - {file = "bcrypt-4.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f4f4acf526fcd1c34e7ce851147deedd4e26e6402369304220250598b26448db"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:1ff39b78a52cf03fdf902635e4c81e544714861ba3f0efc56558979dd4f09170"}, - {file = "bcrypt-4.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:373db9abe198e8e2c70d12b479464e0d5092cc122b20ec504097b5f2297ed184"}, - {file = "bcrypt-4.2.0.tar.gz", hash = "sha256:cf69eaf5185fd58f268f805b505ce31f9b9fc2d64b376642164e9244540c1221"}, + {file = "bcrypt-4.2.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:1340411a0894b7d3ef562fb233e4b6ed58add185228650942bdc885362f32c17"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1ee315739bc8387aa36ff127afc99120ee452924e0df517a8f3e4c0187a0f5f"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dbd0747208912b1e4ce730c6725cb56c07ac734b3629b60d4398f082ea718ad"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:aaa2e285be097050dba798d537b6efd9b698aa88eef52ec98d23dcd6d7cf6fea"}, + {file = "bcrypt-4.2.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:76d3e352b32f4eeb34703370e370997065d28a561e4a18afe4fef07249cb4396"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:b7703ede632dc945ed1172d6f24e9f30f27b1b1a067f32f68bf169c5f08d0425"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89df2aea2c43be1e1fa066df5f86c8ce822ab70a30e4c210968669565c0f4685"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:04e56e3fe8308a88b77e0afd20bec516f74aecf391cdd6e374f15cbed32783d6"}, + {file = "bcrypt-4.2.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:cfdf3d7530c790432046c40cda41dfee8c83e29482e6a604f8930b9930e94139"}, + {file = "bcrypt-4.2.1-cp37-abi3-win32.whl", hash = "sha256:adadd36274510a01f33e6dc08f5824b97c9580583bd4487c564fc4617b328005"}, + {file = "bcrypt-4.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:8c458cd103e6c5d1d85cf600e546a639f234964d0228909d8f8dbeebff82d526"}, + {file = "bcrypt-4.2.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:8ad2f4528cbf0febe80e5a3a57d7a74e6635e41af1ea5675282a33d769fba413"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:909faa1027900f2252a9ca5dfebd25fc0ef1417943824783d1c8418dd7d6df4a"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cde78d385d5e93ece5479a0a87f73cd6fa26b171c786a884f955e165032b262c"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:533e7f3bcf2f07caee7ad98124fab7499cb3333ba2274f7a36cf1daee7409d99"}, + {file = "bcrypt-4.2.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:687cf30e6681eeda39548a93ce9bfbb300e48b4d445a43db4298d2474d2a1e54"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:041fa0155c9004eb98a232d54da05c0b41d4b8e66b6fc3cb71b4b3f6144ba837"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f85b1ffa09240c89aa2e1ae9f3b1c687104f7b2b9d2098da4e923f1b7082d331"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c6f5fa3775966cca251848d4d5393ab016b3afed251163c1436fefdec3b02c84"}, + {file = "bcrypt-4.2.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:807261df60a8b1ccd13e6599c779014a362ae4e795f5c59747f60208daddd96d"}, + {file = "bcrypt-4.2.1-cp39-abi3-win32.whl", hash = "sha256:b588af02b89d9fad33e5f98f7838bf590d6d692df7153647724a7f20c186f6bf"}, + {file = "bcrypt-4.2.1-cp39-abi3-win_amd64.whl", hash = "sha256:e84e0e6f8e40a242b11bce56c313edc2be121cec3e0ec2d76fce01f6af33c07c"}, + {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76132c176a6d9953cdc83c296aeaed65e1a708485fd55abf163e0d9f8f16ce0e"}, + {file = "bcrypt-4.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e158009a54c4c8bc91d5e0da80920d048f918c61a581f0a63e4e93bb556d362f"}, + {file = "bcrypt-4.2.1.tar.gz", hash = "sha256:6765386e3ab87f569b276988742039baab087b2cdb01e809d74e74503c2faafe"}, ] [package.extras] @@ -927,97 +925,111 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "cytoolz" -version = "1.0.0" +version = "1.0.1" description = "Cython implementation of Toolz: High performance functional utilities" optional = false python-versions = ">=3.8" files = [ - {file = "cytoolz-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ecf5a887acb8f079ab1b81612b1c889bcbe6611aa7804fd2df46ed310aa5a345"}, - {file = "cytoolz-1.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef0ef30c1e091d4d59d14d8108a16d50bd227be5d52a47da891da5019ac2f8e4"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7df2dfd679f0517a96ced1cdd22f5c6c6aeeed28d928a82a02bf4c3fd6fd7ac4"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c51452c938e610f57551aa96e34924169c9100c0448bac88c2fb395cbd3538c"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6433f03910c5e5345d82d6299457c26bf33821224ebb837c6b09d9cdbc414a6c"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:389ec328bb535f09e71dfe658bf0041f17194ca4cedaacd39bafe7893497a819"}, - {file = "cytoolz-1.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c64658e1209517ce4b54c1c9269a508b289d8d55fc742760e4b8579eacf09a33"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f6039a9bd5bb988762458b9ca82b39e60ca5e5baae2ba93913990dcc5d19fa88"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:85c9c8c4465ed1b2c8d67003809aec9627b129cb531d2f6cf0bbfe39952e7e4d"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:49375aad431d76650f94877afb92f09f58b6ff9055079ef4f2cd55313f5a1b39"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4c45106171c824a61e755355520b646cb35a1987b34bbf5789443823ee137f63"}, - {file = "cytoolz-1.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3b319a7f0fed5db07d189db4046162ebc183c108df3562a65ba6ebe862d1f634"}, - {file = "cytoolz-1.0.0-cp310-cp310-win32.whl", hash = "sha256:9770e1b09748ad0d751853d994991e2592a9f8c464a87014365f80dac2e83faa"}, - {file = "cytoolz-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:20194dd02954c00c1f0755e636be75a20781f91a4ac9270c7f747e82d3c7f5a5"}, - {file = "cytoolz-1.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dffc22fd2c91be64dbdbc462d0786f8e8ac9a275cfa1869a1084d1867d4f67e0"}, - {file = "cytoolz-1.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a99e7e29274e293f4ffe20e07f76c2ac753a78f1b40c1828dfc54b2981b2f6c4"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c507a3e0a45c41d66b43f96797290d75d1e7a8549aa03a4a6b8854fdf3f7b8d8"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:643a593ec272ef7429099e1182a22f64ec2696c00d295d2a5be390db1b7ff176"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ce38e2e42cbae30446190c59b92a8a9029e1806fd79eaf88f48b0fe33003893"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:810a6a168b8c5ecb412fbae3dd6f7ed6c6253a63caf4174ee9794ebd29b2224f"}, - {file = "cytoolz-1.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ce8a2a85c0741c1b19b16e6782c4a5abc54c3caecda66793447112ab2fa9884"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ea4ac72e6b830861035c4c7999af8e55813f57c6d1913a3d93cc4a6babc27bf7"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a09cdfb21dfb38aa04df43e7546a41f673377eb5485da88ceb784e327ec7603b"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:658dd85deb375ff7af990a674e5c9058cef1c9d1f5dc89bc87b77be499348144"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9715d1ff5576919d10b68f17241375f6a1eec8961c25b78a83e6ef1487053f39"}, - {file = "cytoolz-1.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f370a1f1f1afc5c1c8cc5edc1cfe0ba444263a0772af7ce094be8e734f41769d"}, - {file = "cytoolz-1.0.0-cp311-cp311-win32.whl", hash = "sha256:dbb2ec1177dca700f3db2127e572da20de280c214fc587b2a11c717fc421af56"}, - {file = "cytoolz-1.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:0983eee73df86e54bb4a79fcc4996aa8b8368fdbf43897f02f9c3bf39c4dc4fb"}, - {file = "cytoolz-1.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:10e3986066dc379e30e225b230754d9f5996aa8d84c2accc69c473c21d261e46"}, - {file = "cytoolz-1.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:16576f1bb143ee2cb9f719fcc4b845879fb121f9075c7c5e8a5ff4854bd02fc6"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3faa25a1840b984315e8b3ae517312375f4273ffc9a2f035f548b7f916884f37"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781fce70a277b20fd95dc66811d1a97bb07b611ceea9bda8b7dd3c6a4b05d59a"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7a562c25338eb24d419d1e80a7ae12133844ce6fdeb4ab54459daf250088a1b2"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f29d8330aaf070304f7cd5cb7e73e198753624eb0aec278557cccd460c699b5b"}, - {file = "cytoolz-1.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98a96c54aa55ed9c7cdb23c2f0df39a7b4ee518ac54888480b5bdb5ef69c7ef0"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:287d6d7f475882c2ddcbedf8da9a9b37d85b77690779a2d1cdceb5ae3998d52e"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:05a871688df749b982839239fcd3f8ec3b3b4853775d575ff9cd335fa7c75035"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:28bb88e1e2f7d6d4b8e0890b06d292c568984d717de3e8381f2ca1dd12af6470"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:576a4f1fc73d8836b10458b583f915849da6e4f7914f4ecb623ad95c2508cad5"}, - {file = "cytoolz-1.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:509ed3799c47e4ada14f63e41e8f540ac6e2dab97d5d7298934e6abb9d3830ec"}, - {file = "cytoolz-1.0.0-cp312-cp312-win32.whl", hash = "sha256:9ce25f02b910630f6dc2540dd1e26c9326027ddde6c59f8cab07c56acc70714c"}, - {file = "cytoolz-1.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e53cfcce87e05b7f0ae2fb2b3e5820048cd0bb7b701e92bd8f75c9fbb7c9ae9"}, - {file = "cytoolz-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7d56569dfe67a39ce74ffff0dc12cf0a3d1aae709667a303fe8f2dd5fd004fdf"}, - {file = "cytoolz-1.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:035c8bb4706dcf93a89fb35feadff67e9301935bf6bb864cd2366923b69d9a29"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27c684799708bdc7ee7acfaf464836e1b4dec0996815c1d5efd6a92a4356a562"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44ab57cfc922b15d94899f980d76759ef9e0256912dfab70bf2561bea9cd5b19"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:478af5ecc066da093d7660b23d0b465a7f44179739937afbded8af00af412eb6"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da1f82a7828a42468ea2820a25b6e56461361390c29dcd4d68beccfa1b71066b"}, - {file = "cytoolz-1.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c371b3114d38ee717780b239179e88d5d358fe759a00dcf07691b8922bbc762"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:90b343b2f3b3e77c3832ba19b0b17e95412a5b2e715b05c23a55ba525d1fca49"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89a554a9ba112403232a54e15e46ff218b33020f3f45c4baf6520ab198b7ad93"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:0d603f5e2b1072166745ecdd81384a75757a96a704a5642231eb51969f919d5f"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:122ef2425bd3c0419e6e5260d0b18cd25cf74de589cd0184e4a63b24a4641e2e"}, - {file = "cytoolz-1.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8819f1f97ebe36efcaf4b550e21677c46ac8a41bed482cf66845f377dd20700d"}, - {file = "cytoolz-1.0.0-cp38-cp38-win32.whl", hash = "sha256:fcddbb853770dd6e270d89ea8742f0aa42c255a274b9e1620eb04e019b79785e"}, - {file = "cytoolz-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:ca526905a014a38cc23ae78635dc51d0462c5c24425b22c08beed9ff2ee03845"}, - {file = "cytoolz-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:05df5ff1cdd198fb57e7368623662578c950be0b14883cadfb9ee4098415e1e5"}, - {file = "cytoolz-1.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04a84778f48ebddb26948971dc60948907c876ba33b13f9cbb014fe65b341fc2"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f65283b618b4c4df759f57bcf8483865a73f7f268e6d76886c743407c8d26c1c"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388cd07ee9a9e504c735a0a933e53c98586a1c301a64af81f7aa7ff40c747520"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:06d09e9569cfdfc5c082806d4b4582db8023a3ce034097008622bcbac7236f38"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9502bd9e37779cc9893cbab515a474c2ab6af61ed22ac2f7e16033db18fcaa85"}, - {file = "cytoolz-1.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:364c2fda148def38003b2c86e8adde1d2aab12411dd50872c244a815262e2fda"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:9b2e945617325242687189966335e785dc0fae316f4c1825baacf56e5a97e65f"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f16907fdc724c55b16776bdb7e629deae81d500fe48cfc3861231753b271355"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d3206c81ca3ba2d7b8fe78f2e116e3028e721148be753308e88dcbbc370bca52"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:becce4b13e110b5ac6b23753dcd0c977f4fdccffa31898296e13fd1109e517e3"}, - {file = "cytoolz-1.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69a7e5e98fd446079b8b8ec5987aec9a31ec3570a6f494baefa6800b783eaf22"}, - {file = "cytoolz-1.0.0-cp39-cp39-win32.whl", hash = "sha256:b1707b6c3a91676ac83a28a231a14b337dbb4436b937e6b3e4fd44209852a48b"}, - {file = "cytoolz-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:11d48b8521ef5fe92e099f4fc00717b5d0789c3c90d5d84031b6d3b17dee1700"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e672712d5dc3094afc6fb346dd4e9c18c1f3c69608ddb8cf3b9f8428f9c26a5c"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86fb208bfb7420e1d0d20065d661310e4a8a6884851d4044f47d37ed4cd7410e"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6dbe5fe3b835859fc559eb59bf2775b5a108f7f2cfab0966f3202859d787d8fd"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cace092dfda174eed09ed871793beb5b65633963bcda5b1632c73a5aceea1ce"}, - {file = "cytoolz-1.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f7a9d816af3be9725c70efe0a6e4352a45d3877751b395014b8eb2f79d7d8d9d"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:caa7ef840847a23b379e6146760e3a22f15f445656af97e55a435c592125cfa5"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921082fff09ff6e40c12c87b49be044492b2d6bb01d47783995813b76680c7b2"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a32f1356f3b64dda883583383966948604ac69ca0b7fbcf5f28856e5f9133b4e"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9af793b1738e4191d15a92e1793f1ffea9f6461022c7b2442f3cb1ea0a4f758a"}, - {file = "cytoolz-1.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:51dfda3983fcc59075c534ce54ca041bb3c80e827ada5d4f25ff7b4049777f94"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:acfb8780c04d29423d14aaab74cd1b7b4beaba32f676e7ace02c9acfbf532aba"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99f39dcc46416dca3eb23664b73187b77fb52cd8ba2ddd8020a292d8f449db67"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d56b3721977806dcf1a68b0ecd56feb382fdb0f632af1a9fc5ab9b662b32c6"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d346620abc8c83ae634136e700432ad6202faffcc24c5ab70b87392dcda8a1"}, - {file = "cytoolz-1.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:df0c81197fc130de94c09fc6f024a6a19c98ba8fe55c17f1e45ebba2e9229079"}, - {file = "cytoolz-1.0.0.tar.gz", hash = "sha256:eb453b30182152f9917a5189b7d99046b6ce90cdf8aeb0feff4b2683e600defd"}, + {file = "cytoolz-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cec9af61f71fc3853eb5dca3d42eb07d1f48a4599fa502cbe92adde85f74b042"}, + {file = "cytoolz-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:140bbd649dbda01e91add7642149a5987a7c3ccc251f2263de894b89f50b6608"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e90124bdc42ff58b88cdea1d24a6bc5f776414a314cc4d94f25c88badb3a16d1"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e74801b751e28f7c5cc3ad264c123954a051f546f2fdfe089f5aa7a12ccfa6da"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:582dad4545ddfb5127494ef23f3fa4855f1673a35d50c66f7638e9fb49805089"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd7bd0618e16efe03bd12f19c2a26a27e6e6b75d7105adb7be1cd2a53fa755d8"}, + {file = "cytoolz-1.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d74cca6acf1c4af58b2e4a89cc565ed61c5e201de2e434748c93e5a0f5c541a5"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:823a3763828d8d457f542b2a45d75d6b4ced5e470b5c7cf2ed66a02f508ed442"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:51633a14e6844c61db1d68c1ffd077cf949f5c99c60ed5f1e265b9e2966f1b52"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f3ec9b01c45348f1d0d712507d54c2bfd69c62fbd7c9ef555c9d8298693c2432"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:1855022b712a9c7a5bce354517ab4727a38095f81e2d23d3eabaf1daeb6a3b3c"}, + {file = "cytoolz-1.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9930f7288c4866a1dc1cc87174f0c6ff4cad1671eb1f6306808aa6c445857d78"}, + {file = "cytoolz-1.0.1-cp310-cp310-win32.whl", hash = "sha256:a9baad795d72fadc3445ccd0f122abfdbdf94269157e6d6d4835636dad318804"}, + {file = "cytoolz-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:ad95b386a84e18e1f6136f6d343d2509d4c3aae9f5a536f3dc96808fcc56a8cf"}, + {file = "cytoolz-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d958d4f04d9d7018e5c1850790d9d8e68b31c9a2deebca74b903706fdddd2b6"}, + {file = "cytoolz-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f445b8b731fc0ecb1865b8e68a070084eb95d735d04f5b6c851db2daf3048ab"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f546a96460a7e28eb2ec439f4664fa646c9b3e51c6ebad9a59d3922bbe65e30"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0317681dd065532d21836f860b0563b199ee716f55d0c1f10de3ce7100c78a3b"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c0ef52febd5a7821a3fd8d10f21d460d1a3d2992f724ba9c91fbd7a96745d41"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5ebaf419acf2de73b643cf96108702b8aef8e825cf4f63209ceb078d5fbbbfd"}, + {file = "cytoolz-1.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f7f04eeb4088947585c92d6185a618b25ad4a0f8f66ea30c8db83cf94a425e3"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f61928803bb501c17914b82d457c6f50fe838b173fb40d39c38d5961185bd6c7"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d2960cb4fa01ccb985ad1280db41f90dc97a80b397af970a15d5a5de403c8c61"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b2b407cc3e9defa8df5eb46644f6f136586f70ba49eba96f43de67b9a0984fd3"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8245f929144d4d3bd7b972c9593300195c6cea246b81b4c46053c48b3f044580"}, + {file = "cytoolz-1.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e37385db03af65763933befe89fa70faf25301effc3b0485fec1c15d4ce4f052"}, + {file = "cytoolz-1.0.1-cp311-cp311-win32.whl", hash = "sha256:50f9c530f83e3e574fc95c264c3350adde8145f4f8fc8099f65f00cc595e5ead"}, + {file = "cytoolz-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:b7f6b617454b4326af7bd3c7c49b0fc80767f134eb9fd6449917a058d17a0e3c"}, + {file = "cytoolz-1.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fcb8f7d0d65db1269022e7e0428471edee8c937bc288ebdcb72f13eaa67c2fe4"}, + {file = "cytoolz-1.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:207d4e4b445e087e65556196ff472ff134370d9a275d591724142e255f384662"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21cdf6bac6fd843f3b20280a66fd8df20dea4c58eb7214a2cd8957ec176f0bb3"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a55ec098036c0dea9f3bdc021f8acd9d105a945227d0811589f0573f21c9ce1"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a13ab79ff4ce202e03ab646a2134696988b554b6dc4b71451e948403db1331d8"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2d944799026e1ff08a83241f1027a2d9276c41f7a74224cd98b7df6e03957d"}, + {file = "cytoolz-1.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88ba85834cd523b91fdf10325e1e6d71c798de36ea9bdc187ca7bd146420de6f"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a750b1af7e8bf6727f588940b690d69e25dc47cce5ce467925a76561317eaf7"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44a71870f7eae31d263d08b87da7c2bf1176f78892ed8bdade2c2850478cb126"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c8231b9abbd8e368e036f4cc2e16902c9482d4cf9e02a6147ed0e9a3cd4a9ab0"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:aa87599ccc755de5a096a4d6c34984de6cd9dc928a0c5eaa7607457317aeaf9b"}, + {file = "cytoolz-1.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67cd16537df51baabde3baa770ab7b8d16839c4d21219d5b96ac59fb012ebd2d"}, + {file = "cytoolz-1.0.1-cp312-cp312-win32.whl", hash = "sha256:fb988c333f05ee30ad4693fe4da55d95ec0bb05775d2b60191236493ea2e01f9"}, + {file = "cytoolz-1.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:8f89c48d8e5aec55ffd566a8ec858706d70ed0c6a50228eca30986bfa5b4da8b"}, + {file = "cytoolz-1.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6944bb93b287032a4c5ca6879b69bcd07df46f3079cf8393958cf0b0454f50c0"}, + {file = "cytoolz-1.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e027260fd2fc5cb041277158ac294fc13dca640714527219f702fb459a59823a"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88662c0e07250d26f5af9bc95911e6137e124a5c1ec2ce4a5d74de96718ab242"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309dffa78b0961b4c0cf55674b828fbbc793cf2d816277a5c8293c0c16155296"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:edb34246e6eb40343c5860fc51b24937698e4fa1ee415917a73ad772a9a1746b"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a54da7a8e4348a18d45d4d5bc84af6c716d7f131113a4f1cc45569d37edff1b"}, + {file = "cytoolz-1.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:241c679c3b1913c0f7259cf1d9639bed5084c86d0051641d537a0980548aa266"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5bfc860251a8f280ac79696fc3343cfc3a7c30b94199e0240b6c9e5b6b01a2a5"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c8edd1547014050c1bdad3ff85d25c82bd1c2a3c96830c6181521eb78b9a42b3"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b349bf6162e8de215403d7f35f8a9b4b1853dc2a48e6e1a609a5b1a16868b296"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1b18b35256219b6c3dd0fa037741b85d0bea39c552eab0775816e85a52834140"}, + {file = "cytoolz-1.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:738b2350f340ff8af883eb301054eb724997f795d20d90daec7911c389d61581"}, + {file = "cytoolz-1.0.1-cp313-cp313-win32.whl", hash = "sha256:9cbd9c103df54fcca42be55ef40e7baea624ac30ee0b8bf1149f21146d1078d9"}, + {file = "cytoolz-1.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:90e577e08d3a4308186d9e1ec06876d4756b1e8164b92971c69739ea17e15297"}, + {file = "cytoolz-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f3a509e4ac8e711703c368476b9bbce921fcef6ebb87fa3501525f7000e44185"}, + {file = "cytoolz-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a7eecab6373e933dfbf4fdc0601d8fd7614f8de76793912a103b5fccf98170cd"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e55ed62087f6e3e30917b5f55350c3b6be6470b849c6566018419cd159d2cebc"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:43de33d99a4ccc07234cecd81f385456b55b0ea9c39c9eebf42f024c313728a5"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:139bed875828e1727018aa0982aa140e055cbafccb7fd89faf45cbb4f2a21514"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22c12671194b518aa8ce2f4422bd5064f25ab57f410ba0b78705d0a219f4a97a"}, + {file = "cytoolz-1.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79888f2f7dc25709cd5d37b032a8833741e6a3692c8823be181d542b5999128e"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:51628b4eb41fa25bd428f8f7b5b74fbb05f3ae65fbd265019a0dd1ded4fdf12a"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:1db9eb7179285403d2fb56ba1ff6ec35a44921b5e2fa5ca19d69f3f9f0285ea5"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:08ab7efae08e55812340bfd1b3f09f63848fe291675e2105eab1aa5327d3a16e"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e5fdc5264f884e7c0a1711a81dff112708a64b9c8561654ee578bfdccec6be09"}, + {file = "cytoolz-1.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:90d6a2e6ab891043ee655ec99d5e77455a9bee9e1131bdfcfb745edde81200dd"}, + {file = "cytoolz-1.0.1-cp38-cp38-win32.whl", hash = "sha256:08946e083faa5147751b34fbf78ab931f149ef758af5c1092932b459e18dcf5c"}, + {file = "cytoolz-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:a91b4e10a9c03796c0dc93e47ebe25bb41ecc6fafc3cf5197c603cf767a3d44d"}, + {file = "cytoolz-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:980c323e626ba298b77ae62871b2de7c50b9d7219e2ddf706f52dd34b8be7349"}, + {file = "cytoolz-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:45f6fa1b512bc2a0f2de5123db932df06c7f69d12874fe06d67772b2828e2c8b"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f93f42d9100c415155ad1f71b0de362541afd4ac95e3153467c4c79972521b6b"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a76d20dec9c090cdf4746255bbf06a762e8cc29b5c9c1d138c380bbdb3122ade"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:239039585487c69aa50c5b78f6a422016297e9dea39755761202fb9f0530fe87"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28307640ca2ab57b9fbf0a834b9bf563958cd9e038378c3a559f45f13c3c541"}, + {file = "cytoolz-1.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:454880477bb901cee3a60f6324ec48c95d45acc7fecbaa9d49a5af737ded0595"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:902115d1b1f360fd81e44def30ac309b8641661150fcbdde18ead446982ada6a"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e68e6b38473a3a79cee431baa22be31cac39f7df1bf23eaa737eaff42e213883"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:32fba3f63fcb76095b0a22f4bdcc22bc62a2bd2d28d58bf02fd21754c155a3ec"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0724ba4cf41eb40b6cf75250820ab069e44bdf4183ff78857aaf4f0061551075"}, + {file = "cytoolz-1.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c42420e0686f887040d5230420ed44f0e960ccbfa29a0d65a3acd9ca52459209"}, + {file = "cytoolz-1.0.1-cp39-cp39-win32.whl", hash = "sha256:4ba8b16358ea56b1fe8e637ec421e36580866f2e787910bac1cf0a6997424a34"}, + {file = "cytoolz-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:92d27f84bf44586853d9562bfa3610ecec000149d030f793b4cb614fd9da1813"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:83d19d55738ad9c60763b94f3f6d3c6e4de979aeb8d76841c1401081e0e58d96"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f112a71fad6ea824578e6393765ce5c054603afe1471a5c753ff6c67fd872d10"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a515df8f8aa6e1eaaf397761a6e4aff2eef73b5f920aedf271416d5471ae5ee"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92c398e7b7023460bea2edffe5fcd0a76029580f06c3f6938ac3d198b47156f3"}, + {file = "cytoolz-1.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3237e56211e03b13df47435b2369f5df281e02b04ad80a948ebd199b7bc10a47"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba0d1da50aab1909b165f615ba1125c8b01fcc30d606c42a61c42ea0269b5e2c"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25b6e8dec29aa5a390092d193abd673e027d2c0b50774ae816a31454286c45c7"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36cd6989ebb2f18fe9af8f13e3c61064b9f741a40d83dc5afeb0322338ad25f2"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a47394f8ab7fca3201f40de61fdeea20a2baffb101485ae14901ea89c3f6c95d"}, + {file = "cytoolz-1.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d00ac423542af944302e034e618fb055a0c4e87ba704cd6a79eacfa6ac83a3c9"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a5ca923d1fa632f7a4fb33c0766c6fba7f87141a055c305c3e47e256fb99c413"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:058bf996bcae9aad3acaeeb937d42e0c77c081081e67e24e9578a6a353cb7fb2"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69e2a1f41a3dad94a17aef4a5cc003323359b9f0a9d63d4cc867cb5690a2551d"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67daeeeadb012ec2b59d63cb29c4f2a2023b0c4957c3342d354b8bb44b209e9a"}, + {file = "cytoolz-1.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:54d3d36bbf0d4344d1afa22c58725d1668e30ff9de3a8f56b03db1a6da0acb11"}, + {file = "cytoolz-1.0.1.tar.gz", hash = "sha256:89cc3161b89e1bb3ed7636f74ed2e55984fd35516904fc878cae216e42b2c7d6"}, ] [package.dependencies] @@ -3239,13 +3251,13 @@ type = ["importlib-metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.12 [[package]] name = "six" -version = "1.16.0" +version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] [[package]] @@ -3294,13 +3306,43 @@ files = [ [[package]] name = "tomli" -version = "2.1.0" +version = "2.2.1" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, - {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, + {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]] @@ -3443,13 +3485,13 @@ files = [ [[package]] name = "virtualenv" -version = "20.27.1" +version = "20.28.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.27.1-py3-none-any.whl", hash = "sha256:f11f1b8a29525562925f745563bfd48b189450f61fb34c4f9cc79dd5aa32a1f4"}, - {file = "virtualenv-20.27.1.tar.gz", hash = "sha256:142c6be10212543b32c6c45d3d3893dff89112cc588b7d0879ae5a1ec03a47ba"}, + {file = "virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0"}, + {file = "virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa"}, ] [package.dependencies]