diff --git a/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py b/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py index 410e46fd9bb..4addbd5c7e8 100644 --- a/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py +++ b/abr-testing/abr_testing/protocols/liquid_setups/7_HDQ DNA Bacteria Extraction Liquid Setup.py @@ -35,15 +35,23 @@ def run(protocol: protocol_api.ProtocolContext) -> None: res1 = protocol.load_labware("nest_12_reservoir_15ml", "D2", "reagent reservoir 1") # Label Reservoirs well1 = res1["A1"].top() + well2 = res1["A2"].top() well3 = res1["A3"].top() well4 = res1["A4"].top() + well5 = res1["A5"].top() + well6 = res1["A6"].top() well7 = res1["A7"].top() + well8 = res1["A8"].top() + well9 = res1["A9"].top() well10 = res1["A10"].top() - + well11 = res1["A11"].top() + well12 = res1["A12"].top() # Volumes wash = 600 - al_and_pk = 468 - beads_and_binding = 552 + binding = 320 + beads = 230 + pk = 230 + lysis = 230 # Sample Plate p1000.transfer( @@ -65,9 +73,41 @@ def run(protocol: protocol_api.ProtocolContext) -> None: ) # Res 1 p1000.transfer( - volume=[beads_and_binding, al_and_pk, wash, wash, wash], + volume=[ + binding, + beads, + binding, + beads, + lysis, + pk, + wash, + wash, + wash, + wash, + wash, + wash, + wash, + wash, + wash, + ], source=source_reservoir["A1"].bottom(z=0.5), - dest=[well1, well3, well4, well7, well10], + dest=[ + well1, + well1, + well2, + well2, + well3, + well3, + well4, + well5, + well6, + well7, + well8, + well9, + well10, + well11, + well12, + ], blowout=True, blowout_location="source well", trash=False, diff --git a/api/src/opentrons/protocol_engine/commands/command.py b/api/src/opentrons/protocol_engine/commands/command.py index 1fefcbf7315..fe47c9dbbcc 100644 --- a/api/src/opentrons/protocol_engine/commands/command.py +++ b/api/src/opentrons/protocol_engine/commands/command.py @@ -6,8 +6,9 @@ import dataclasses from abc import ABC, abstractmethod from datetime import datetime -from enum import Enum +import enum from typing import ( + cast, TYPE_CHECKING, Generic, Optional, @@ -15,6 +16,11 @@ List, Type, Union, + Callable, + Awaitable, + Literal, + Final, + TypeAlias, ) from pydantic import BaseModel, Field @@ -41,7 +47,7 @@ _ErrorT_co = TypeVar("_ErrorT_co", bound=ErrorOccurrence, covariant=True) -class CommandStatus(str, Enum): +class CommandStatus(str, enum.Enum): """Command execution status.""" QUEUED = "queued" @@ -50,7 +56,7 @@ class CommandStatus(str, Enum): FAILED = "failed" -class CommandIntent(str, Enum): +class CommandIntent(str, enum.Enum): """Run intent for a given command. Props: @@ -242,6 +248,240 @@ class BaseCommand( ] +class IsErrorValue(Exception): + """Panic exception if a Maybe contains an Error.""" + + pass + + +class _NothingEnum(enum.Enum): + _NOTHING = enum.auto() + + +NOTHING: Final = _NothingEnum._NOTHING +NothingT: TypeAlias = Literal[_NothingEnum._NOTHING] + + +class _UnknownEnum(enum.Enum): + _UNKNOWN = enum.auto() + + +UNKNOWN: Final = _UnknownEnum._UNKNOWN +UnknownT: TypeAlias = Literal[_UnknownEnum._UNKNOWN] + +_ResultT_co_general = TypeVar("_ResultT_co_general", covariant=True) +_ErrorT_co_general = TypeVar("_ErrorT_co_general", covariant=True) + + +_SecondResultT_co_general = TypeVar("_SecondResultT_co_general", covariant=True) +_SecondErrorT_co_general = TypeVar("_SecondErrorT_co_general", covariant=True) + + +@dataclasses.dataclass +class Maybe(Generic[_ResultT_co_general, _ErrorT_co_general]): + """Represents an possibly completed, possibly errored result. + + By using this class's chaining methods like and_then or or_else, you can build + functions that preserve previous defined errors and augment them or transform them + and transform the results. + + Build objects of this type using from_result or from_error on fully type-qualified + aliases. For instance, + + MyFunctionReturn = Maybe[SuccessData[SomeSuccessModel], DefinedErrorData[SomeErrorKind]] + + def my_function(args...) -> MyFunctionReturn: + try: + do_thing(args...) + except SomeException as e: + return MyFunctionReturn.from_error(ErrorOccurrence.from_error(e)) + else: + return MyFunctionReturn.from_result(SuccessData(SomeSuccessModel(args...))) + + Then, in the calling function, you can react to the results and unwrap to a union: + + OuterMaybe = Maybe[SuccessData[SomeOtherModel], DefinedErrorData[SomeErrors]] + OuterReturn = Union[SuccessData[SomeOtherModel], DefinedErrorData[SomeErrors]] + + def my_calling_function(args...) -> OuterReturn: + def handle_result(result: SuccessData[SomeSuccessModel]) -> OuterMaybe: + return OuterMaybe.from_result(result=some_result_transformer(result)) + return do_thing.and_then(handle_result).unwrap() + """ + + _contents: tuple[_ResultT_co_general, NothingT] | tuple[ + NothingT, _ErrorT_co_general + ] + + _CtorErrorT = TypeVar("_CtorErrorT") + _CtorResultT = TypeVar("_CtorResultT") + + @classmethod + def from_result( + cls: Type[Maybe[_CtorResultT, _CtorErrorT]], result: _CtorResultT + ) -> Maybe[_CtorResultT, _CtorErrorT]: + """Build a Maybe from a valid result.""" + return cls(_contents=(result, NOTHING)) + + @classmethod + def from_error( + cls: Type[Maybe[_CtorResultT, _CtorErrorT]], error: _CtorErrorT + ) -> Maybe[_CtorResultT, _CtorErrorT]: + """Build a Maybe from a known error.""" + return cls(_contents=(NOTHING, error)) + + def result_or_panic(self) -> _ResultT_co_general: + """Unwrap to a result or throw if the Maybe is an error.""" + contents = self._contents + if contents[1] is NOTHING: + # https://github.com/python/mypy/issues/12364 + return cast(_ResultT_co_general, contents[0]) + else: + raise IsErrorValue() + + def unwrap(self) -> _ResultT_co_general | _ErrorT_co_general: + """Unwrap to a union, which is useful for command returns.""" + # https://github.com/python/mypy/issues/12364 + if self._contents[1] is NOTHING: + return cast(_ResultT_co_general, self._contents[0]) + else: + return self._contents[1] + + # note: casts in these methods are because of https://github.com/python/mypy/issues/11730 + def and_then( + self, + functor: Callable[ + [_ResultT_co_general], + Maybe[_SecondResultT_co_general, _SecondErrorT_co_general], + ], + ) -> Maybe[ + _SecondResultT_co_general, _ErrorT_co_general | _SecondErrorT_co_general + ]: + """Conditionally execute functor if the Maybe contains a result. + + Functor should take the result type and return a new Maybe. Since this function returns + a Maybe, it can be chained. The result type will have only the Result type of the Maybe + returned by the functor, but the error type is the union of the error type in the Maybe + returned by the functor and the error type in this Maybe, since the functor may not have + actually been called. + """ + match self._contents: + case (result, _NothingEnum._NOTHING): + return cast( + Maybe[ + _SecondResultT_co_general, + _ErrorT_co_general | _SecondErrorT_co_general, + ], + functor(cast(_ResultT_co_general, result)), + ) + case _: + return cast( + Maybe[ + _SecondResultT_co_general, + _ErrorT_co_general | _SecondErrorT_co_general, + ], + self, + ) + + def or_else( + self, + functor: Callable[ + [_ErrorT_co_general], + Maybe[_SecondResultT_co_general, _SecondErrorT_co_general], + ], + ) -> Maybe[ + _SecondResultT_co_general | _ResultT_co_general, _SecondErrorT_co_general + ]: + """Conditionally execute functor if the Maybe contains an error. + + The functor should take the error type and return a new Maybe. Since this function returns + a Maybe, it can be chained. The result type will have only the Error type of the Maybe + returned by the functor, but the result type is the union of the Result of the Maybe returned + by the functor and the Result of this Maybe, since the functor may not have been called. + """ + match self._contents: + case (_NothingEnum._NOTHING, error): + return cast( + Maybe[ + _ResultT_co_general | _SecondResultT_co_general, + _SecondErrorT_co_general, + ], + functor(cast(_ErrorT_co_general, error)), + ) + case _: + return cast( + Maybe[ + _ResultT_co_general | _SecondResultT_co_general, + _SecondErrorT_co_general, + ], + self, + ) + + async def and_then_async( + self, + functor: Callable[ + [_ResultT_co_general], + Awaitable[Maybe[_SecondResultT_co_general, _SecondErrorT_co_general]], + ], + ) -> Awaitable[ + Maybe[_SecondResultT_co_general, _ErrorT_co_general | _SecondErrorT_co_general] + ]: + """As and_then, but for an async functor.""" + match self._contents: + case (result, _NothingEnum._NOTHING): + return cast( + Awaitable[ + Maybe[ + _SecondResultT_co_general, + _ErrorT_co_general | _SecondErrorT_co_general, + ] + ], + await functor(cast(_ResultT_co_general, result)), + ) + case _: + return cast( + Awaitable[ + Maybe[ + _SecondResultT_co_general, + _ErrorT_co_general | _SecondErrorT_co_general, + ] + ], + self, + ) + + async def or_else_async( + self, + functor: Callable[ + [_ErrorT_co_general], + Awaitable[Maybe[_SecondResultT_co_general, _SecondErrorT_co_general]], + ], + ) -> Awaitable[ + Maybe[_SecondResultT_co_general | _ResultT_co_general, _SecondErrorT_co_general] + ]: + """As or_else, but for an async functor.""" + match self._contents: + case (_NothingEnum._NOTHING, error): + return cast( + Awaitable[ + Maybe[ + _ResultT_co_general | _SecondResultT_co_general, + _SecondErrorT_co_general, + ] + ], + await functor(cast(_ErrorT_co_general, error)), + ) + case _: + return cast( + Awaitable[ + Maybe[ + _ResultT_co_general | _SecondResultT_co_general, + _SecondErrorT_co_general, + ] + ], + self, + ) + + _ExecuteReturnT_co = TypeVar( "_ExecuteReturnT_co", bound=Union[ diff --git a/api/src/opentrons/protocol_engine/commands/pipetting_common.py b/api/src/opentrons/protocol_engine/commands/pipetting_common.py index 2dafb4c81b2..6e0064211fa 100644 --- a/api/src/opentrons/protocol_engine/commands/pipetting_common.py +++ b/api/src/opentrons/protocol_engine/commands/pipetting_common.py @@ -1,12 +1,20 @@ """Common pipetting command base models.""" +from __future__ import annotations from opentrons_shared_data.errors import ErrorCodes from pydantic import BaseModel, Field -from typing import Literal, Optional, Tuple, TypedDict +from typing import Literal, Optional, Tuple, TypedDict, TYPE_CHECKING from opentrons.protocol_engine.errors.error_occurrence import ErrorOccurrence +from opentrons_shared_data.errors.exceptions import PipetteOverpressureError +from .command import Maybe, DefinedErrorData, SuccessData +from opentrons.protocol_engine.state.update_types import StateUpdate from ..types import WellLocation, LiquidHandlingWellLocation, DeckPoint +if TYPE_CHECKING: + from ..execution.pipetting import PipettingHandler + from ..resources import ModelUtils + class PipetteIdMixin(BaseModel): """Mixin for command requests that take a pipette ID.""" @@ -201,3 +209,44 @@ class TipPhysicallyAttachedError(ErrorOccurrence): errorCode: str = ErrorCodes.TIP_DROP_FAILED.value.code detail: str = ErrorCodes.TIP_DROP_FAILED.value.detail + + +PrepareForAspirateReturn = Maybe[ + SuccessData[BaseModel], DefinedErrorData[OverpressureError] +] + + +async def prepare_for_aspirate( + pipette_id: str, + pipetting: PipettingHandler, + model_utils: ModelUtils, + location_if_error: ErrorLocationInfo, +) -> PrepareForAspirateReturn: + """Execute pipetting.prepare_for_aspirate, handle errors, and marshal success.""" + state_update = StateUpdate() + try: + await pipetting.prepare_for_aspirate(pipette_id) + except PipetteOverpressureError as e: + state_update.set_fluid_unknown(pipette_id=pipette_id) + return PrepareForAspirateReturn.from_error( + DefinedErrorData( + public=OverpressureError( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + wrappedErrors=[ + ErrorOccurrence.from_failed( + id=model_utils.generate_id(), + createdAt=model_utils.get_timestamp(), + error=e, + ) + ], + errorInfo=location_if_error, + ), + state_update=state_update, + ) + ) + else: + state_update.set_fluid_empty(pipette_id=pipette_id) + return PrepareForAspirateReturn.from_result( + SuccessData(public=BaseModel(), state_update=state_update) + ) diff --git a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py index f5525b3c90e..38f3a60516a 100644 --- a/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py +++ b/api/src/opentrons/protocol_engine/commands/prepare_to_aspirate.py @@ -1,24 +1,20 @@ """Prepare to aspirate command request, result, and implementation models.""" from __future__ import annotations -from opentrons_shared_data.errors.exceptions import PipetteOverpressureError from pydantic import BaseModel from typing import TYPE_CHECKING, Optional, Type, Union from typing_extensions import Literal -from .pipetting_common import ( - OverpressureError, - PipetteIdMixin, -) +from .pipetting_common import OverpressureError, PipetteIdMixin, prepare_for_aspirate from .command import ( AbstractCommandImpl, BaseCommand, BaseCommandCreate, DefinedErrorData, SuccessData, + Maybe, ) from ..errors.error_occurrence import ErrorOccurrence -from ..state import update_types if TYPE_CHECKING: from ..execution import PipettingHandler, GantryMover @@ -46,6 +42,11 @@ class PrepareToAspirateResult(BaseModel): ] +_ExecuteMaybe = Maybe[ + SuccessData[PrepareToAspirateResult], DefinedErrorData[OverpressureError] +] + + class PrepareToAspirateImplementation( AbstractCommandImpl[PrepareToAspirateParams, _ExecuteReturn] ): @@ -62,44 +63,29 @@ def __init__( self._model_utils = model_utils self._gantry_mover = gantry_mover + def _transform_result(self, result: SuccessData[BaseModel]) -> _ExecuteMaybe: + return _ExecuteMaybe.from_result( + SuccessData( + public=PrepareToAspirateResult(), state_update=result.state_update + ) + ) + async def execute(self, params: PrepareToAspirateParams) -> _ExecuteReturn: """Prepare the pipette to aspirate.""" current_position = await self._gantry_mover.get_position(params.pipetteId) - state_update = update_types.StateUpdate() - try: - await self._pipetting_handler.prepare_for_aspirate( - pipette_id=params.pipetteId, - ) - except PipetteOverpressureError as e: - state_update.set_fluid_unknown(pipette_id=params.pipetteId) - return DefinedErrorData( - public=OverpressureError( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - wrappedErrors=[ - ErrorOccurrence.from_failed( - id=self._model_utils.generate_id(), - createdAt=self._model_utils.get_timestamp(), - error=e, - ) - ], - errorInfo=( - { - "retryLocation": ( - current_position.x, - current_position.y, - current_position.z, - ) - } - ), - ), - state_update=state_update, - ) - else: - state_update.set_fluid_empty(pipette_id=params.pipetteId) - return SuccessData( - public=PrepareToAspirateResult(), state_update=state_update - ) + prepare_result = await prepare_for_aspirate( + pipette_id=params.pipetteId, + pipetting=self._pipetting_handler, + model_utils=self._model_utils, + location_if_error={ + "retryLocation": ( + current_position.x, + current_position.y, + current_position.z, + ) + }, + ) + return prepare_result.and_then(self._transform_result).unwrap() class PrepareToAspirate( diff --git a/api/tests/opentrons/protocol_api_integration/conftest.py b/api/tests/opentrons/protocol_api_integration/conftest.py new file mode 100644 index 00000000000..fa98ccbb039 --- /dev/null +++ b/api/tests/opentrons/protocol_api_integration/conftest.py @@ -0,0 +1,28 @@ +"""Fixtures for protocol api integration tests.""" + +import pytest +from _pytest.fixtures import SubRequest +from typing import Generator + +from opentrons import simulate, protocol_api +from opentrons.protocol_api.core.engine import ENGINE_CORE_API_VERSION + + +@pytest.fixture +def simulated_protocol_context( + request: SubRequest, +) -> Generator[protocol_api.ProtocolContext, None, None]: + """Return a protocol context with requested version and robot.""" + version, robot_type = request.param + context = simulate.get_protocol_api(version=version, robot_type=robot_type) + try: + yield context + finally: + if context.api_version >= ENGINE_CORE_API_VERSION: + # TODO(jbl, 2024-11-14) this is a hack of a hack to close the hardware and the PE thread when a test is + # complete. At some point this should be replaced with a more holistic way of safely cleaning up these + # threads so they don't leak and cause tests to fail when `get_protocol_api` is called too many times. + simulate._LIVE_PROTOCOL_ENGINE_CONTEXTS.close() + else: + # If this is a non-PE context we need to clean up the hardware thread manually + context._hw_manager.hardware.clean_up() diff --git a/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py b/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py index 6621a790801..1a6e19f85be 100644 --- a/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py +++ b/api/tests/opentrons/protocol_api_integration/test_liquid_classes.py @@ -3,22 +3,30 @@ from decoy import Decoy from opentrons_shared_data.robot.types import RobotTypeEnum -from opentrons import simulate +from opentrons.protocol_api import ProtocolContext from opentrons.config import feature_flags as ff @pytest.mark.ot2_only +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "OT-2")], indirect=True +) def test_liquid_class_creation_and_property_fetching( - decoy: Decoy, mock_feature_flags: None + decoy: Decoy, + mock_feature_flags: None, + simulated_protocol_context: ProtocolContext, ) -> None: """It should create the liquid class and provide access to its properties.""" decoy.when(ff.allow_liquid_classes(RobotTypeEnum.OT2)).then_return(True) - protocol_context = simulate.get_protocol_api(version="2.20", robot_type="OT-2") - pipette_left = protocol_context.load_instrument("p20_single_gen2", mount="left") - pipette_right = protocol_context.load_instrument("p300_multi", mount="right") - tiprack = protocol_context.load_labware("opentrons_96_tiprack_20ul", "1") + pipette_left = simulated_protocol_context.load_instrument( + "p20_single_gen2", mount="left" + ) + pipette_right = simulated_protocol_context.load_instrument( + "p300_multi", mount="right" + ) + tiprack = simulated_protocol_context.load_labware("opentrons_96_tiprack_20ul", "1") - glycerol_50 = protocol_context.define_liquid_class("fixture_glycerol50") + glycerol_50 = simulated_protocol_context.define_liquid_class("fixture_glycerol50") assert glycerol_50.name == "fixture_glycerol50" assert glycerol_50.display_name == "Glycerol 50%" @@ -50,11 +58,13 @@ def test_liquid_class_creation_and_property_fetching( glycerol_50.display_name = "bar" # type: ignore with pytest.raises(ValueError, match="Liquid class definition not found"): - protocol_context.define_liquid_class("non-existent-liquid") + simulated_protocol_context.define_liquid_class("non-existent-liquid") -def test_liquid_class_feature_flag() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "OT-2")], indirect=True +) +def test_liquid_class_feature_flag(simulated_protocol_context: ProtocolContext) -> None: """It should raise a not implemented error without the allowLiquidClass flag set.""" - protocol_context = simulate.get_protocol_api(version="2.20", robot_type="OT-2") with pytest.raises(NotImplementedError): - protocol_context.define_liquid_class("fixture_glycerol50") + simulated_protocol_context.define_liquid_class("fixture_glycerol50") diff --git a/api/tests/opentrons/protocol_api_integration/test_modules.py b/api/tests/opentrons/protocol_api_integration/test_modules.py index e8a26112d88..72ee8ed8c52 100644 --- a/api/tests/opentrons/protocol_api_integration/test_modules.py +++ b/api/tests/opentrons/protocol_api_integration/test_modules.py @@ -3,13 +3,17 @@ import typing import pytest -from opentrons import simulate, protocol_api +from opentrons import protocol_api -def test_absorbance_reader_labware_load_conflict() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.21", "Flex")], indirect=True +) +def test_absorbance_reader_labware_load_conflict( + simulated_protocol_context: protocol_api.ProtocolContext, +) -> None: """It should prevent loading a labware onto a closed absorbance reader.""" - protocol = simulate.get_protocol_api(version="2.21", robot_type="Flex") - module = protocol.load_module("absorbanceReaderV1", "A3") + module = simulated_protocol_context.load_module("absorbanceReaderV1", "A3") # The lid should be treated as initially closed. with pytest.raises(Exception): @@ -19,7 +23,7 @@ def test_absorbance_reader_labware_load_conflict() -> None: # Should not raise after opening the lid. labware_1 = module.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") - protocol.move_labware(labware_1, protocol_api.OFF_DECK) + simulated_protocol_context.move_labware(labware_1, protocol_api.OFF_DECK) # Should raise after closing the lid again. module.close_lid() # type: ignore[union-attr] @@ -27,34 +31,44 @@ def test_absorbance_reader_labware_load_conflict() -> None: module.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt") -def test_absorbance_reader_labware_move_conflict() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.21", "Flex")], indirect=True +) +def test_absorbance_reader_labware_move_conflict( + simulated_protocol_context: protocol_api.ProtocolContext, +) -> None: """It should prevent moving a labware onto a closed absorbance reader.""" - protocol = simulate.get_protocol_api(version="2.21", robot_type="Flex") - module = protocol.load_module("absorbanceReaderV1", "A3") - labware = protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", "A1") + module = simulated_protocol_context.load_module("absorbanceReaderV1", "A3") + labware = simulated_protocol_context.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", "A1" + ) with pytest.raises(Exception): # The lid should be treated as initially closed. - protocol.move_labware(labware, module, use_gripper=True) + simulated_protocol_context.move_labware(labware, module, use_gripper=True) module.open_lid() # type: ignore[union-attr] # Should not raise after opening the lid. - protocol.move_labware(labware, module, use_gripper=True) + simulated_protocol_context.move_labware(labware, module, use_gripper=True) - protocol.move_labware(labware, "A1", use_gripper=True) + simulated_protocol_context.move_labware(labware, "A1", use_gripper=True) # Should raise after closing the lid again. module.close_lid() # type: ignore[union-attr] with pytest.raises(Exception): - protocol.move_labware(labware, module, use_gripper=True) + simulated_protocol_context.move_labware(labware, module, use_gripper=True) -def test_absorbance_reader_read_preconditions() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.21", "Flex")], indirect=True +) +def test_absorbance_reader_read_preconditions( + simulated_protocol_context: protocol_api.ProtocolContext, +) -> None: """Test the preconditions for triggering an absorbance reader read.""" - protocol = simulate.get_protocol_api(version="2.21", robot_type="Flex") module = typing.cast( protocol_api.AbsorbanceReaderContext, - protocol.load_module("absorbanceReaderV1", "A3"), + simulated_protocol_context.load_module("absorbanceReaderV1", "A3"), ) with pytest.raises(Exception, match="initialize"): diff --git a/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py b/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py index cad2bffddf9..2b7fc11ca91 100644 --- a/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py +++ b/api/tests/opentrons/protocol_api_integration/test_pipette_movement_deck_conflicts.py @@ -2,54 +2,59 @@ import pytest -from opentrons import simulate -from opentrons.protocol_api import COLUMN, ALL, SINGLE, ROW +from opentrons.protocol_api import COLUMN, ALL, SINGLE, ROW, ProtocolContext from opentrons.protocol_api.core.engine.pipette_movement_conflict import ( PartialTipMovementNotAllowedError, ) @pytest.mark.ot3_only -def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.16", "Flex")], indirect=True +) +def test_deck_conflicts_for_96_ch_a12_column_configuration( + simulated_protocol_context: ProtocolContext, +) -> None: """It should raise errors for the expected deck conflicts.""" - protocol_context = simulate.get_protocol_api(version="2.16", robot_type="Flex") - trash_labware = protocol_context.load_labware( + trash_labware = simulated_protocol_context.load_labware( "opentrons_1_trash_3200ml_fixed", "A3" ) - badly_placed_tiprack = protocol_context.load_labware( + badly_placed_tiprack = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C2" ) - well_placed_tiprack = protocol_context.load_labware( + well_placed_tiprack = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C1" ) - tiprack_on_adapter = protocol_context.load_labware( + tiprack_on_adapter = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C3", adapter="opentrons_flex_96_tiprack_adapter", ) - thermocycler = protocol_context.load_module("thermocyclerModuleV2") - tc_adjacent_plate = protocol_context.load_labware( + thermocycler = simulated_protocol_context.load_module("thermocyclerModuleV2") + tc_adjacent_plate = simulated_protocol_context.load_labware( "opentrons_96_wellplate_200ul_pcr_full_skirt", "A2" ) accessible_plate = thermocycler.load_labware( "opentrons_96_wellplate_200ul_pcr_full_skirt" ) - instrument = protocol_context.load_instrument("flex_96channel_1000", mount="left") + instrument = simulated_protocol_context.load_instrument( + "flex_96channel_1000", mount="left" + ) instrument.trash_container = trash_labware # ############ SHORT LABWARE ################ # These labware should be to the west of tall labware to avoid any partial tip deck conflicts - badly_placed_labware = protocol_context.load_labware( + badly_placed_labware = simulated_protocol_context.load_labware( "nest_96_wellplate_200ul_flat", "D2" ) - well_placed_labware = protocol_context.load_labware( + well_placed_labware = simulated_protocol_context.load_labware( "nest_96_wellplate_200ul_flat", "D3" ) # ############ TALL LABWARE ############## - protocol_context.load_labware( + simulated_protocol_context.load_labware( "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "D1" ) @@ -104,24 +109,30 @@ def test_deck_conflicts_for_96_ch_a12_column_configuration() -> None: @pytest.mark.ot3_only -def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "Flex")], indirect=True +) +def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration( + simulated_protocol_context: ProtocolContext, +) -> None: """Shouldn't raise errors for "almost collision"s.""" - protocol_context = simulate.get_protocol_api(version="2.20", robot_type="Flex") - res12 = protocol_context.load_labware("nest_12_reservoir_15ml", "C3") + res12 = simulated_protocol_context.load_labware("nest_12_reservoir_15ml", "C3") # Mag block and tiprack adapter are very close to the destination reservoir labware - protocol_context.load_module("magneticBlockV1", "D2") - protocol_context.load_labware( + simulated_protocol_context.load_module("magneticBlockV1", "D2") + simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_200ul", "B3", adapter="opentrons_flex_96_tiprack_adapter", ) - tiprack_8 = protocol_context.load_labware("opentrons_flex_96_tiprack_200ul", "B2") - hs = protocol_context.load_module("heaterShakerModuleV1", "C1") + tiprack_8 = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_200ul", "B2" + ) + hs = simulated_protocol_context.load_module("heaterShakerModuleV1", "C1") hs_adapter = hs.load_adapter("opentrons_96_deep_well_adapter") deepwell = hs_adapter.load_labware("nest_96_wellplate_2ml_deep") - protocol_context.load_trash_bin("A3") - p1000_96 = protocol_context.load_instrument("flex_96channel_1000") + simulated_protocol_context.load_trash_bin("A3") + p1000_96 = simulated_protocol_context.load_instrument("flex_96channel_1000") p1000_96.configure_nozzle_layout(style=SINGLE, start="A12", tip_racks=[tiprack_8]) hs.close_labware_latch() # type: ignore[union-attr] @@ -135,16 +146,28 @@ def test_close_shave_deck_conflicts_for_96_ch_a12_column_configuration() -> None @pytest.mark.ot3_only -def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.16", "Flex")], indirect=True +) +def test_deck_conflicts_for_96_ch_a1_column_configuration( + simulated_protocol_context: ProtocolContext, +) -> None: """It should raise errors for expected deck conflicts.""" - protocol = simulate.get_protocol_api(version="2.16", robot_type="Flex") - instrument = protocol.load_instrument("flex_96channel_1000", mount="left") - trash_labware = protocol.load_labware("opentrons_1_trash_3200ml_fixed", "A3") + instrument = simulated_protocol_context.load_instrument( + "flex_96channel_1000", mount="left" + ) + trash_labware = simulated_protocol_context.load_labware( + "opentrons_1_trash_3200ml_fixed", "A3" + ) instrument.trash_container = trash_labware - badly_placed_tiprack = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C2") - well_placed_tiprack = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "A1") - tiprack_on_adapter = protocol.load_labware( + badly_placed_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "C2" + ) + well_placed_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "A1" + ) + tiprack_on_adapter = simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "C3", adapter="opentrons_flex_96_tiprack_adapter", @@ -152,11 +175,15 @@ def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: # ############ SHORT LABWARE ################ # These labware should be to the east of tall labware to avoid any partial tip deck conflicts - badly_placed_plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "B1") - well_placed_plate = protocol.load_labware("nest_96_wellplate_200ul_flat", "B3") + badly_placed_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "B1" + ) + well_placed_plate = simulated_protocol_context.load_labware( + "nest_96_wellplate_200ul_flat", "B3" + ) # ############ TALL LABWARE ############### - my_tuberack = protocol.load_labware( + my_tuberack = simulated_protocol_context.load_labware( "opentrons_10_tuberack_falcon_4x50ml_6x15ml_conical", "B2" ) @@ -208,7 +235,7 @@ def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: instrument.drop_tip() instrument.trash_container = None # type: ignore - protocol.load_trash_bin("C1") + simulated_protocol_context.load_trash_bin("C1") # This doesn't raise an error because it now treats the trash bin as an addressable area # and the bounds check doesn't yet check moves to addressable areas. @@ -229,28 +256,38 @@ def test_deck_conflicts_for_96_ch_a1_column_configuration() -> None: @pytest.mark.ot3_only -def test_deck_conflicts_for_96_ch_and_reservoirs() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.20", "Flex")], indirect=True +) +def test_deck_conflicts_for_96_ch_and_reservoirs( + simulated_protocol_context: ProtocolContext, +) -> None: """It should raise errors for expected deck conflicts when moving to reservoirs. This test checks that the critical point of the pipette is taken into account, specifically when it differs from the primary nozzle. """ - protocol = simulate.get_protocol_api(version="2.20", robot_type="Flex") - instrument = protocol.load_instrument("flex_96channel_1000", mount="left") + instrument = simulated_protocol_context.load_instrument( + "flex_96channel_1000", mount="left" + ) # trash_labware = protocol.load_labware("opentrons_1_trash_3200ml_fixed", "A3") # instrument.trash_container = trash_labware - protocol.load_trash_bin("A3") - right_tiprack = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "C3") - front_tiprack = protocol.load_labware("opentrons_flex_96_tiprack_50ul", "D2") + simulated_protocol_context.load_trash_bin("A3") + right_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "C3" + ) + front_tiprack = simulated_protocol_context.load_labware( + "opentrons_flex_96_tiprack_50ul", "D2" + ) # Tall deck item in B3 - protocol.load_labware( + simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "B3", adapter="opentrons_flex_96_tiprack_adapter", ) # Tall deck item in B1 - protocol.load_labware( + simulated_protocol_context.load_labware( "opentrons_flex_96_tiprack_50ul", "B1", adapter="opentrons_flex_96_tiprack_adapter", @@ -258,8 +295,12 @@ def test_deck_conflicts_for_96_ch_and_reservoirs() -> None: # ############ RESERVOIRS ################ # These labware should be to the east of tall labware to avoid any partial tip deck conflicts - reservoir_1_well = protocol.load_labware("nest_1_reservoir_195ml", "C2") - reservoir_12_well = protocol.load_labware("nest_12_reservoir_15ml", "B2") + reservoir_1_well = simulated_protocol_context.load_labware( + "nest_1_reservoir_195ml", "C2" + ) + reservoir_12_well = simulated_protocol_context.load_labware( + "nest_12_reservoir_15ml", "B2" + ) # ########### Use COLUMN A1 Config ############# instrument.configure_nozzle_layout(style=COLUMN, start="A1") diff --git a/api/tests/opentrons/protocol_api_integration/test_trashes.py b/api/tests/opentrons/protocol_api_integration/test_trashes.py index 18dfa62170d..1166ba01c70 100644 --- a/api/tests/opentrons/protocol_api_integration/test_trashes.py +++ b/api/tests/opentrons/protocol_api_integration/test_trashes.py @@ -1,46 +1,42 @@ """Tests for the APIs around waste chutes and trash bins.""" -from opentrons import protocol_api, simulate +from opentrons import protocol_api from opentrons.protocols.api_support.types import APIVersion from opentrons.protocols.api_support.util import UnsupportedAPIError import contextlib from typing import ContextManager, Optional, Type -from typing_extensions import Literal import re import pytest @pytest.mark.parametrize( - ("version", "robot_type", "expected_trash_class"), + ("simulated_protocol_context", "expected_trash_class"), [ - ("2.13", "OT-2", protocol_api.Labware), - ("2.14", "OT-2", protocol_api.Labware), - ("2.15", "OT-2", protocol_api.Labware), + (("2.13", "OT-2"), protocol_api.Labware), + (("2.14", "OT-2"), protocol_api.Labware), + (("2.15", "OT-2"), protocol_api.Labware), pytest.param( - "2.15", - "Flex", + ("2.15", "Flex"), protocol_api.Labware, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), pytest.param( - "2.16", - "OT-2", + ("2.16", "OT-2"), protocol_api.TrashBin, ), pytest.param( - "2.16", - "Flex", + ("2.16", "Flex"), None, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), ], + indirect=["simulated_protocol_context"], ) def test_fixed_trash_presence( - robot_type: Literal["OT-2", "Flex"], - version: str, + simulated_protocol_context: protocol_api.ProtocolContext, expected_trash_class: Optional[Type[object]], ) -> None: """Test the presence of the fixed trash. @@ -49,9 +45,10 @@ def test_fixed_trash_presence( For those that do, ProtocolContext.fixed_trash and InstrumentContext.trash_container should point to it. The type of the object depends on the API version. """ - protocol = simulate.get_protocol_api(version=version, robot_type=robot_type) - instrument = protocol.load_instrument( - "p300_single_gen2" if robot_type == "OT-2" else "flex_1channel_50", + instrument = simulated_protocol_context.load_instrument( + "p300_single_gen2" + if simulated_protocol_context._core.robot_type == "OT-2 Standard" + else "flex_1channel_50", mount="left", ) @@ -59,46 +56,53 @@ def test_fixed_trash_presence( with pytest.raises( UnsupportedAPIError, match=re.escape( - "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16." + " You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." ), ): - protocol.fixed_trash + simulated_protocol_context.fixed_trash with pytest.raises(Exception, match="No trash container has been defined"): instrument.trash_container else: - assert isinstance(protocol.fixed_trash, expected_trash_class) - assert instrument.trash_container is protocol.fixed_trash + assert isinstance(simulated_protocol_context.fixed_trash, expected_trash_class) + assert instrument.trash_container is simulated_protocol_context.fixed_trash @pytest.mark.ot3_only # Simulating a Flex protocol requires a Flex hardware API. -def test_trash_search() -> None: +@pytest.mark.parametrize( + "simulated_protocol_context", [("2.16", "Flex")], indirect=True +) +def test_trash_search(simulated_protocol_context: protocol_api.ProtocolContext) -> None: """Test the automatic trash search for protocols without a fixed trash.""" - protocol = simulate.get_protocol_api(version="2.16", robot_type="Flex") - instrument = protocol.load_instrument("flex_1channel_50", mount="left") + instrument = simulated_protocol_context.load_instrument( + "flex_1channel_50", mount="left" + ) # By default, there should be no trash. with pytest.raises( UnsupportedAPIError, match=re.escape( - "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16." + " You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." ), ): - protocol.fixed_trash + simulated_protocol_context.fixed_trash with pytest.raises(Exception, match="No trash container has been defined"): instrument.trash_container - loaded_first = protocol.load_trash_bin("A1") - loaded_second = protocol.load_trash_bin("B1") + loaded_first = simulated_protocol_context.load_trash_bin("A1") + loaded_second = simulated_protocol_context.load_trash_bin("B1") # After loading some trashes, there should still be no protocol.fixed_trash... with pytest.raises( UnsupportedAPIError, match=re.escape( - "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16. You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." + "Error 4002 API_REMOVED (UnsupportedAPIError): Fixed Trash is not available after API version 2.16." + " You are currently using API version 2.16. Fixed trash is no longer supported on Flex protocols." ), ): - protocol.fixed_trash + simulated_protocol_context.fixed_trash # ...but instrument.trash_container should automatically update to point to # the first trash that we loaded. assert instrument.trash_container is loaded_first @@ -109,40 +113,36 @@ def test_trash_search() -> None: @pytest.mark.parametrize( - ("version", "robot_type", "expect_load_to_succeed"), + ("simulated_protocol_context", "expect_load_to_succeed"), [ pytest.param( - "2.13", - "OT-2", + ("2.13", "OT-2"), False, # This xfail (the system does let you load a labware onto slot 12, and does not raise) # is surprising to me. It may be be a bug in old PAPI versions. marks=pytest.mark.xfail(strict=True, raises=pytest.fail.Exception), ), - ("2.14", "OT-2", False), - ("2.15", "OT-2", False), + (("2.14", "OT-2"), False), + (("2.15", "OT-2"), False), pytest.param( - "2.15", - "Flex", + ("2.15", "Flex"), False, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), pytest.param( - "2.16", - "OT-2", + ("2.16", "OT-2"), False, ), pytest.param( - "2.16", - "Flex", + ("2.16", "Flex"), True, marks=pytest.mark.ot3_only, # Simulating a Flex protocol requires a Flex hardware API. ), ], + indirect=["simulated_protocol_context"], ) def test_fixed_trash_load_conflicts( - robot_type: Literal["Flex", "OT-2"], - version: str, + simulated_protocol_context: protocol_api.ProtocolContext, expect_load_to_succeed: bool, ) -> None: """Test loading something onto the location historically used for the fixed trash. @@ -150,14 +150,12 @@ def test_fixed_trash_load_conflicts( In configurations where there is a fixed trash, this should be disallowed. In configurations without a fixed trash, this should be allowed. """ - protocol = simulate.get_protocol_api(version=version, robot_type=robot_type) - if expect_load_to_succeed: expected_error: ContextManager[object] = contextlib.nullcontext() else: # If we're expecting an error, it'll be a LocationIsOccupied for 2.15 and below, otherwise # it will fail with an IncompatibleAddressableAreaError, since slot 12 will not be in the deck config - if APIVersion.from_string(version) < APIVersion(2, 16): + if simulated_protocol_context.api_version < APIVersion(2, 16): error_name = "LocationIsOccupiedError" else: error_name = "IncompatibleAddressableAreaError" @@ -169,4 +167,6 @@ def test_fixed_trash_load_conflicts( ) with expected_error: - protocol.load_labware("opentrons_96_wellplate_200ul_pcr_full_skirt", 12) + simulated_protocol_context.load_labware( + "opentrons_96_wellplate_200ul_pcr_full_skirt", 12 + ) diff --git a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py index 2de35e38332..f9eded1ffa0 100644 --- a/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py +++ b/api/tests/opentrons/protocol_engine/commands/test_prepare_to_aspirate.py @@ -34,14 +34,19 @@ def subject( async def test_prepare_to_aspirate_implementation( - decoy: Decoy, subject: PrepareToAspirateImplementation, pipetting: PipettingHandler + decoy: Decoy, + gantry_mover: GantryMover, + subject: PrepareToAspirateImplementation, + pipetting: PipettingHandler, ) -> None: """A PrepareToAspirate command should have an executing implementation.""" data = PrepareToAspirateParams(pipetteId="some id") + position = Point(x=1, y=2, z=3) decoy.when(await pipetting.prepare_for_aspirate(pipette_id="some id")).then_return( None ) + decoy.when(await gantry_mover.get_position("some id")).then_return(position) result = await subject.execute(data) assert result == SuccessData( diff --git a/components/src/atoms/Checkbox/index.tsx b/components/src/atoms/Checkbox/index.tsx index 02fa36da6d4..8ace61cb0bf 100644 --- a/components/src/atoms/Checkbox/index.tsx +++ b/components/src/atoms/Checkbox/index.tsx @@ -13,7 +13,6 @@ import { } from '../../styles' import { RESPONSIVENESS, SPACING } from '../../ui-style-constants' import { StyledText } from '../StyledText' -import { truncateString } from '../../utils' export interface CheckboxProps { /** checkbox is checked if value is true */ @@ -41,7 +40,6 @@ export function Checkbox(props: CheckboxProps): JSX.Element { width = FLEX_MAX_CONTENT, type = 'round', } = props - const truncatedLabel = truncateString(labelText, 25) const CHECKBOX_STYLE = css` width: ${width}; @@ -89,7 +87,7 @@ export function Checkbox(props: CheckboxProps): JSX.Element { css={CHECKBOX_STYLE} > - {truncatedLabel} + {labelText} diff --git a/components/src/molecules/DropdownMenu/index.tsx b/components/src/molecules/DropdownMenu/index.tsx index f6b7fa689c0..8b2d50e38d6 100644 --- a/components/src/molecules/DropdownMenu/index.tsx +++ b/components/src/molecules/DropdownMenu/index.tsx @@ -292,7 +292,7 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { {filterOptions.map((option, index) => ( { diff --git a/opentrons-ai-client/src/OpentronsAIRoutes.tsx b/opentrons-ai-client/src/OpentronsAIRoutes.tsx index 32d09f351cf..1b435ac4138 100644 --- a/opentrons-ai-client/src/OpentronsAIRoutes.tsx +++ b/opentrons-ai-client/src/OpentronsAIRoutes.tsx @@ -1,6 +1,6 @@ import { Route, Navigate, Routes } from 'react-router-dom' import { Landing } from './pages/Landing' -import { UpdateProtocol } from './organisms/UpdateProtocol' +import { UpdateProtocol } from './pages/UpdateProtocol' import type { RouteProps } from './resources/types' import { Chat } from './pages/Chat' diff --git a/opentrons-ai-client/src/assets/localization/en/create_protocol.json b/opentrons-ai-client/src/assets/localization/en/create_protocol.json index ebf38133718..a91099593f4 100644 --- a/opentrons-ai-client/src/assets/localization/en/create_protocol.json +++ b/opentrons-ai-client/src/assets/localization/en/create_protocol.json @@ -17,7 +17,7 @@ "opentrons_ot2_label": "Opentrons OT-2", "opentrons_ot2": "Opentrons OT-2", "instruments_pipettes_title": "What pipettes would you like to use?", - "two_pipettes_label": "Two pipettes", + "two_pipettes_label": "1-Channel or 8-Channel pipettes", "right_pipette_label": "Right mount", "left_pipette_label": "Left mount", "choose_pipette_placeholder": "Choose pipette", diff --git a/opentrons-ai-client/src/assets/localization/en/protocol_generator.json b/opentrons-ai-client/src/assets/localization/en/protocol_generator.json index 94b1afae702..a4d89e97303 100644 --- a/opentrons-ai-client/src/assets/localization/en/protocol_generator.json +++ b/opentrons-ai-client/src/assets/localization/en/protocol_generator.json @@ -30,10 +30,10 @@ "login": "Login", "logout": "Logout", "make_sure_your_prompt": "Write a prompt in a natural language for OpentronsAI to generate a protocol using the Opentrons Python Protocol API v2. The better the prompt, the better the quality of the protocol produced by OpentronsAI.", - "modify_intro": "Modify the following Python code using the Opentrons Python Protocol API v2. Ensure that the new labware and pipettes are compatible with the Flex robot. Ensure that you perform the correct Type of Update use the Details of Changes.\n\n", + "modify_intro": "Modify the following Python code using the Opentrons Python Protocol API v2. Ensure that the new labware and pipettes are compatible with the Flex robot.\n\n", "modify_python_code": "Original Python Code:\n", "modify_type_of_update": "Type of update:\n- ", - "modify_details_of_change": "Details of Changes:\n- ", + "modify_details_of_change": "Detail of changes:\n- ", "modules_and_adapters": "Modules and adapters: Specify the modules and labware adapters required by your protocol.", "notes": "A few important things to note:", "opentrons": "Opentrons", diff --git a/opentrons-ai-client/src/atoms/SendButton/index.tsx b/opentrons-ai-client/src/atoms/SendButton/index.tsx index ed4128e56ca..eba4eeeae58 100644 --- a/opentrons-ai-client/src/atoms/SendButton/index.tsx +++ b/opentrons-ai-client/src/atoms/SendButton/index.tsx @@ -67,14 +67,17 @@ export function SendButton({ if (isLoading) { const interval = setInterval(() => { setProgressIndex(prevIndex => { - const newIndex = (prevIndex + 1) % progressTexts.length - setButtonText(progressTexts[newIndex]) + let newIndex = prevIndex + 1 + if (newIndex > progressTexts.length - 1) { + newIndex = progressTexts.length - 1 + } return newIndex }) - }, 5000) + }, 10000) return () => { setProgressIndex(0) + setButtonText(progressTexts[0]) clearInterval(interval) } } diff --git a/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx b/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx index 7836d18f90f..7226aa6a1a9 100644 --- a/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx +++ b/opentrons-ai-client/src/molecules/ChatDisplay/__tests__/ChatDisplay.test.tsx @@ -1,12 +1,22 @@ import type * as React from 'react' -import { screen } from '@testing-library/react' -import { describe, it, beforeEach } from 'vitest' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { describe, it, beforeEach, afterEach, vi, expect } from 'vitest' import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' import { ChatDisplay } from '../index' import { useForm, FormProvider } from 'react-hook-form' +const mockUseTrackEvent = vi.fn() + +vi.mock('../../../resources/hooks/useTrackEvent', () => ({ + useTrackEvent: () => mockUseTrackEvent, +})) + +vi.mock('../../../hooks/useTrackEvent', () => ({ + useTrackEvent: () => mockUseTrackEvent, +})) + const RenderChatDisplay = (props: React.ComponentProps) => { const methods = useForm({ defaultValues: {}, @@ -38,6 +48,11 @@ describe('ChatDisplay', () => { chatId: 'mockId', } }) + + afterEach(() => { + vi.clearAllMocks() + }) + it('should display response from the backend and label', () => { render(props) screen.getByText('OpentronsAI') @@ -62,4 +77,58 @@ describe('ChatDisplay', () => { // const display = screen.getByTextId('ChatDisplay_from_user') // expect(display).toHaveStyle(`background-color: ${COLORS.blue}`) }) + + it('should call trackEvent when regenerate button is clicked', () => { + render(props) + // eslint-disable-next-line testing-library/no-node-access, @typescript-eslint/non-nullable-type-assertion-style + const regeneratePath = document.querySelector( + '[aria-roledescription="reload"]' + ) as Element + fireEvent.click(regeneratePath) + + expect(mockUseTrackEvent).toHaveBeenCalledWith({ + name: 'regenerate-protocol', + properties: {}, + }) + }) + + it('should call trackEvent when download button is clicked', () => { + URL.createObjectURL = vi.fn() + window.URL.revokeObjectURL = vi.fn() + HTMLAnchorElement.prototype.click = vi.fn() + + render(props) + // eslint-disable-next-line testing-library/no-node-access, @typescript-eslint/non-nullable-type-assertion-style + const downloadPath = document.querySelector( + '[aria-roledescription="download"]' + ) as Element + fireEvent.click(downloadPath) + + expect(mockUseTrackEvent).toHaveBeenCalledWith({ + name: 'download-protocol', + properties: {}, + }) + }) + + it('should call trackEvent when copy button is clicked', async () => { + Object.defineProperty(navigator, 'clipboard', { + value: { + writeText: async () => {}, + }, + }) + + render(props) + // eslint-disable-next-line testing-library/no-node-access, @typescript-eslint/non-nullable-type-assertion-style + const copyPath = document.querySelector( + '[aria-roledescription="content-copy"]' + ) as Element + fireEvent.click(copyPath) + + await waitFor(() => { + expect(mockUseTrackEvent).toHaveBeenCalledWith({ + name: 'copy-protocol', + properties: {}, + }) + }) + }) }) diff --git a/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx b/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx index 22dbee37f1a..7d01d282903 100644 --- a/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx +++ b/opentrons-ai-client/src/molecules/ChatDisplay/index.tsx @@ -26,10 +26,14 @@ import { useAtom } from 'jotai' import { chatDataAtom, feedbackModalAtom, + regenerateProtocolAtom, scrollToBottomAtom, + createProtocolChatAtom, + updateProtocolChatAtom, } from '../../resources/atoms' import { delay } from 'lodash' import { useFormContext } from 'react-hook-form' +import { useTrackEvent } from '../../resources/hooks/useTrackEvent' interface ChatDisplayProps { chat: ChatData @@ -55,7 +59,11 @@ const StyledIcon = styled(Icon)` export function ChatDisplay({ chat, chatId }: ChatDisplayProps): JSX.Element { const { t } = useTranslation('protocol_generator') + const trackEvent = useTrackEvent() const [isCopied, setIsCopied] = useState(false) + const [, setRegenerateProtocol] = useAtom(regenerateProtocolAtom) + const [createProtocolChat] = useAtom(createProtocolChatAtom) + const [updateProtocolChat] = useAtom(updateProtocolChatAtom) const [, setShowFeedbackModal] = useAtom(feedbackModalAtom) const { setValue } = useFormContext() const [chatdata] = useAtom(chatDataAtom) @@ -64,11 +72,36 @@ export function ChatDisplay({ chat, chatId }: ChatDisplayProps): JSX.Element { const isUser = role === 'user' const setInputFieldToCorrespondingRequest = (): void => { - const prompt = chatdata.find( - chat => chat.role === 'user' && chat.requestId === requestId - )?.reply + let prompt = '' + if ( + requestId.includes('NewProtocol') || + requestId.includes('UpdateProtocol') + ) { + setRegenerateProtocol({ + isCreateOrUpdateProtocol: true, + regenerate: true, + }) + if (createProtocolChat.prompt !== '') { + prompt = createProtocolChat.prompt + } else { + prompt = updateProtocolChat.prompt + } + } else { + setRegenerateProtocol({ + isCreateOrUpdateProtocol: false, + regenerate: true, + }) + prompt = + chatdata.find( + chat => chat.role === 'user' && chat.requestId === requestId + )?.reply ?? '' + } setScrollToBottom(!scrollToBottom) setValue('userPrompt', prompt) + trackEvent({ + name: 'regenerate-protocol', + properties: {}, + }) } const handleFileDownload = (): void => { @@ -85,6 +118,11 @@ export function ChatDisplay({ chat, chatId }: ChatDisplayProps): JSX.Element { a.download = 'OpentronsAI.py' a.click() window.URL.revokeObjectURL(url) + + trackEvent({ + name: 'download-protocol', + properties: {}, + }) } const handleClickCopy = async (): Promise => { @@ -92,6 +130,10 @@ export function ChatDisplay({ chat, chatId }: ChatDisplayProps): JSX.Element { const code = lastCodeBlock?.textContent ?? '' await navigator.clipboard.writeText(code) setIsCopied(true) + trackEvent({ + name: 'copy-protocol', + properties: {}, + }) } useEffect(() => { diff --git a/opentrons-ai-client/src/molecules/ChatFooter/index.tsx b/opentrons-ai-client/src/molecules/ChatFooter/index.tsx index fef7596f6f4..817b97c3e0f 100644 --- a/opentrons-ai-client/src/molecules/ChatFooter/index.tsx +++ b/opentrons-ai-client/src/molecules/ChatFooter/index.tsx @@ -29,7 +29,7 @@ export function ChatFooter(): JSX.Element { const DISCLAIMER_TEXT_STYLE = css` color: ${COLORS.grey55}; - font-size: ${TYPOGRAPHY.fontSize20}; + font-size: ${TYPOGRAPHY.fontSizeH3}; line-height: ${TYPOGRAPHY.lineHeight24}; text-align: ${TYPOGRAPHY.textAlignCenter}; ` diff --git a/opentrons-ai-client/src/molecules/ControlledAddTextAreaFields/index.tsx b/opentrons-ai-client/src/molecules/ControlledAddTextAreaFields/index.tsx index bfa248be1a8..c7599068b3c 100644 --- a/opentrons-ai-client/src/molecules/ControlledAddTextAreaFields/index.tsx +++ b/opentrons-ai-client/src/molecules/ControlledAddTextAreaFields/index.tsx @@ -63,7 +63,13 @@ export function ControlledAddTextAreaFields({ { - field.onChange(values.filter((_, i) => i !== index)) + const newValues = values + .filter((_, i) => i !== index) + .map( + (value, i) => + `${t(name)} ${i + 1}: ${value.split(': ')[1]}` + ) + field.onChange(newValues) }} css={css` justify-content: flex-end; diff --git a/opentrons-ai-client/src/molecules/ControlledLabwareListItems/index.tsx b/opentrons-ai-client/src/molecules/ControlledLabwareListItems/index.tsx index f79f89ca858..507aa0b9392 100644 --- a/opentrons-ai-client/src/molecules/ControlledLabwareListItems/index.tsx +++ b/opentrons-ai-client/src/molecules/ControlledLabwareListItems/index.tsx @@ -14,7 +14,7 @@ import { getLabwareDisplayName } from '@opentrons/shared-data' import { LabwareDiagram } from '../../molecules/LabwareDiagram' import type { DisplayLabware } from '../../organisms/LabwareLiquidsSection' import { LABWARES_FIELD_NAME } from '../../organisms/LabwareLiquidsSection' -import { getAllDefinitions } from '../../resources/utils' +import { getOnlyLatestDefs } from '../../resources/utils' export function ControlledLabwareListItems(): JSX.Element | null { const { t } = useTranslation('create_protocol') @@ -22,7 +22,7 @@ export function ControlledLabwareListItems(): JSX.Element | null { const labwares: DisplayLabware[] = watch(LABWARES_FIELD_NAME) ?? [] - const defs = getAllDefinitions() + const defs = getOnlyLatestDefs() return ( - + {t('exit_confirmation_body')} diff --git a/opentrons-ai-client/src/molecules/FeedbackModal/__tests__/FeedbackModal.test.tsx b/opentrons-ai-client/src/molecules/FeedbackModal/__tests__/FeedbackModal.test.tsx index 15d17938e93..2d881633822 100644 --- a/opentrons-ai-client/src/molecules/FeedbackModal/__tests__/FeedbackModal.test.tsx +++ b/opentrons-ai-client/src/molecules/FeedbackModal/__tests__/FeedbackModal.test.tsx @@ -1,10 +1,20 @@ import { FeedbackModal } from '..' import { renderWithProviders } from '../../../__testing-utils__' -import { screen } from '@testing-library/react' -import { describe, it, expect } from 'vitest' +import { fireEvent, screen, waitFor } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' import { i18n } from '../../../i18n' import { feedbackModalAtom } from '../../../resources/atoms' +const mockUseTrackEvent = vi.fn() + +vi.mock('../../../resources/hooks/useTrackEvent', () => ({ + useTrackEvent: () => mockUseTrackEvent, +})) + +vi.mock('../../../hooks/useTrackEvent', () => ({ + useTrackEvent: () => mockUseTrackEvent, +})) + const initialValues: Array<[any, any]> = [[feedbackModalAtom, true]] const render = (): ReturnType => { @@ -33,4 +43,26 @@ describe('FeedbackModal', () => { // check if the feedbackModalAtom is set to false expect(feedbackModalAtom.read).toBe(false) }) + + it('should track event when feedback is sent', async () => { + render() + const feedbackInput = screen.getByRole('textbox') + fireEvent.change(feedbackInput, { + target: { value: 'This is a test feedback' }, + }) + const sendFeedbackButton = screen.getByRole('button', { + name: 'Send feedback', + }) + + fireEvent.click(sendFeedbackButton) + + await waitFor(() => { + expect(mockUseTrackEvent).toHaveBeenCalledWith({ + name: 'feedback-sent', + properties: { + feedback: 'This is a test feedback', + }, + }) + }) + }) }) diff --git a/opentrons-ai-client/src/molecules/FeedbackModal/index.tsx b/opentrons-ai-client/src/molecules/FeedbackModal/index.tsx index fbb006bdbd4..017048910b8 100644 --- a/opentrons-ai-client/src/molecules/FeedbackModal/index.tsx +++ b/opentrons-ai-client/src/molecules/FeedbackModal/index.tsx @@ -19,9 +19,11 @@ import { LOCAL_FEEDBACK_END_POINT, } from '../../resources/constants' import { useApiCall } from '../../resources/hooks' +import { useTrackEvent } from '../../resources/hooks/useTrackEvent' export function FeedbackModal(): JSX.Element { const { t } = useTranslation('protocol_generator') + const trackEvent = useTrackEvent() const [feedbackValue, setFeedbackValue] = useState('') const [, setShowFeedbackModal] = useAtom(feedbackModalAtom) @@ -58,6 +60,12 @@ export function FeedbackModal(): JSX.Element { }, } await callApi(config as AxiosRequestConfig) + trackEvent({ + name: 'feedback-sent', + properties: { + feedback: feedbackValue, + }, + }) setShowFeedbackModal(false) } catch (err: any) { console.error(`error: ${err.message}`) diff --git a/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx b/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx index f6f588aeab7..3f704a39dda 100644 --- a/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx +++ b/opentrons-ai-client/src/molecules/InputPrompt/__tests__/InputPrompt.test.tsx @@ -1,11 +1,21 @@ import type * as React from 'react' -import { describe, it, expect } from 'vitest' +import { describe, it, expect, vi } from 'vitest' import { FormProvider, useForm } from 'react-hook-form' -import { fireEvent, screen } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' import { InputPrompt } from '../index' +const mockUseTrackEvent = vi.fn() + +vi.mock('../../../resources/hooks/useTrackEvent', () => ({ + useTrackEvent: () => mockUseTrackEvent, +})) + +vi.mock('../../../hooks/useTrackEvent', () => ({ + useTrackEvent: () => mockUseTrackEvent, +})) + const WrappingForm = (wrappedComponent: { children: React.ReactNode }): JSX.Element => { @@ -44,4 +54,21 @@ describe('InputPrompt', () => { }) // ToDo (kk:04/19/2024) add more test cases + + it('should track event when send button is clicked', async () => { + render() + const textbox = screen.getByRole('textbox') + fireEvent.change(textbox, { target: { value: ['test'] } }) + const sendButton = screen.getByRole('button') + fireEvent.click(sendButton) + + await waitFor(() => { + expect(mockUseTrackEvent).toHaveBeenCalledWith({ + name: 'chat-submitted', + properties: { + chat: 'test', + }, + }) + }) + }) }) diff --git a/opentrons-ai-client/src/molecules/InputPrompt/index.tsx b/opentrons-ai-client/src/molecules/InputPrompt/index.tsx index 56114535733..e90ba453ab9 100644 --- a/opentrons-ai-client/src/molecules/InputPrompt/index.tsx +++ b/opentrons-ai-client/src/molecules/InputPrompt/index.tsx @@ -20,6 +20,7 @@ import { chatDataAtom, chatHistoryAtom, createProtocolChatAtom, + regenerateProtocolAtom, tokenAtom, updateProtocolChatAtom, } from '../../resources/atoms' @@ -38,11 +39,17 @@ import { } from '../../resources/constants' import type { AxiosRequestConfig } from 'axios' -import type { ChatData } from '../../resources/types' +import type { + ChatData, + CreatePrompt, + UpdatePrompt, +} from '../../resources/types' +import { useTrackEvent } from '../../resources/hooks/useTrackEvent' export function InputPrompt(): JSX.Element { const { t } = useTranslation('protocol_generator') const { register, watch, reset, setValue } = useFormContext() + const trackEvent = useTrackEvent() const [updateProtocol] = useAtom(updateProtocolChatAtom) const [createProtocol] = useAtom(createProtocolChatAtom) @@ -50,6 +57,9 @@ export function InputPrompt(): JSX.Element { const [sendAutoFilledPrompt, setSendAutoFilledPrompt] = useState( false ) + const [regenerateProtocol, setRegenerateProtocol] = useAtom( + regenerateProtocolAtom + ) const [, setChatData] = useAtom(chatDataAtom) const [chatHistory, setChatHistory] = useAtom(chatHistoryAtom) @@ -78,13 +88,24 @@ export function InputPrompt(): JSX.Element { } }, [watchUserPrompt]) + useEffect(() => { + if (regenerateProtocol.regenerate) { + handleClick(regenerateProtocol.isCreateOrUpdateProtocol, true) + setRegenerateProtocol({ + isCreateOrUpdateProtocol: false, + regenerate: false, + }) + } + }, [regenerateProtocol]) + const handleClick = async ( - isUpdateOrCreateRequest: boolean = false + isUpdateOrCreateRequest: boolean = false, + isRegenerateRequest: boolean = false ): Promise => { - setRequestId(uuidv4() + getPreFixText(isUpdateOrCreateRequest)) - + const newRequestId = uuidv4() + getPreFixText(isUpdateOrCreateRequest) + setRequestId(newRequestId) const userInput: ChatData = { - requestId, + requestId: newRequestId, role: 'user', reply: watchUserPrompt, } @@ -106,7 +127,7 @@ export function InputPrompt(): JSX.Element { method: 'POST', headers, data: isUpdateOrCreateRequest - ? getUpdateOrCreatePrompt() + ? getUpdateOrCreatePrompt(isRegenerateRequest) : { message: watchUserPrompt, history: chatHistory, @@ -119,6 +140,12 @@ export function InputPrompt(): JSX.Element { { role: 'user', content: watchUserPrompt }, ]) await callApi(config as AxiosRequestConfig) + trackEvent({ + name: 'chat-submitted', + properties: { + chat: watchUserPrompt, + }, + }) setSubmitted(true) } catch (err: any) { console.error(`error: ${err.message}`) @@ -126,7 +153,11 @@ export function InputPrompt(): JSX.Element { } } - const getUpdateOrCreatePrompt = (): any => { + const getUpdateOrCreatePrompt = ( + isRegenerateRequest: boolean + ): CreatePrompt | UpdatePrompt => { + createProtocol.regenerate = isRegenerateRequest + updateProtocol.regenerate = isRegenerateRequest return isNewProtocol ? createProtocol : updateProtocol } @@ -159,6 +190,13 @@ export function InputPrompt(): JSX.Element { { role: 'assistant', content: reply }, ]) setChatData(chatData => [...chatData, assistantResponse]) + trackEvent({ + name: 'generated-protocol', + properties: { + createOrUpdate: isNewProtocol ? 'create' : 'update', + protocol: reply, + }, + }) setSubmitted(false) } }, [data, isLoading, submitted]) diff --git a/opentrons-ai-client/src/molecules/ModuleListItemGroup/index.tsx b/opentrons-ai-client/src/molecules/ModuleListItemGroup/index.tsx index 279c4d2060b..0c570600bd4 100644 --- a/opentrons-ai-client/src/molecules/ModuleListItemGroup/index.tsx +++ b/opentrons-ai-client/src/molecules/ModuleListItemGroup/index.tsx @@ -22,7 +22,7 @@ import { Controller, useFormContext } from 'react-hook-form' import { ModuleDiagram } from '../ModelDiagram' import { MODULES_FIELD_NAME } from '../../organisms/ModulesSection' import type { DisplayModules } from '../../organisms/ModulesSection' -import { getAllDefinitions } from '../../resources/utils' +import { getOnlyLatestDefs } from '../../resources/utils' import { useTranslation } from 'react-i18next' import { useMemo } from 'react' @@ -70,7 +70,7 @@ export function ModuleListItemGroup(): JSX.Element | null { const modulesWatch: DisplayModules[] = watch(MODULES_FIELD_NAME) ?? [] const allDefinitionsValues = useMemo( - () => Object.values(getAllDefinitions()), + () => Object.values(getOnlyLatestDefs()), [] ) diff --git a/opentrons-ai-client/src/organisms/ApplicationSection/__tests__/ApplicationSection.test.tsx b/opentrons-ai-client/src/organisms/ApplicationSection/__tests__/ApplicationSection.test.tsx index 50e01fcb66f..477c871b7c5 100644 --- a/opentrons-ai-client/src/organisms/ApplicationSection/__tests__/ApplicationSection.test.tsx +++ b/opentrons-ai-client/src/organisms/ApplicationSection/__tests__/ApplicationSection.test.tsx @@ -13,6 +13,8 @@ const TestFormProviderComponent = () => { return ( + +

{`form is ${methods.formState.isValid ? 'valid' : 'invalid'}`}

) } @@ -33,7 +35,6 @@ describe('ApplicationSection', () => { expect( screen.getByText('Describe what you are trying to do') ).toBeInTheDocument() - expect(screen.getByText('Confirm')).toBeInTheDocument() }) it('should not render other application dropdown if Other option is not selected', () => { @@ -54,10 +55,10 @@ describe('ApplicationSection', () => { expect(screen.getByText('Other application')).toBeInTheDocument() }) - it('should enable confirm button when all fields are filled', async () => { + it('should update the form state to valid when all fields are filled', async () => { render() - expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() const applicationDropdown = screen.getByText('Select an option') fireEvent.click(applicationDropdown) @@ -69,14 +70,13 @@ describe('ApplicationSection', () => { fireEvent.change(describeInput, { target: { value: 'Test description' } }) await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled() + expect(screen.getByText('form is valid')).toBeInTheDocument() }) }) - it('should disable confirm button when all fields are not filled', () => { + it('should update the form state to invalid when not all fields are filled', () => { render() - const confirmButton = screen.getByRole('button') - expect(confirmButton).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() }) }) diff --git a/opentrons-ai-client/src/organisms/ApplicationSection/index.tsx b/opentrons-ai-client/src/organisms/ApplicationSection/index.tsx index 5e3cc523f68..54819e99a3a 100644 --- a/opentrons-ai-client/src/organisms/ApplicationSection/index.tsx +++ b/opentrons-ai-client/src/organisms/ApplicationSection/index.tsx @@ -1,19 +1,8 @@ -import { - DIRECTION_COLUMN, - DISPLAY_FLEX, - Flex, - JUSTIFY_FLEX_END, - LargeButton, - SPACING, -} from '@opentrons/components' +import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' import { ControlledDropdownMenu } from '../../atoms/ControlledDropdownMenu' import { ControlledInputField } from '../../atoms/ControlledInputField' -import { useAtom } from 'jotai' -import { createProtocolAtom } from '../../resources/atoms' -import { APPLICATION_STEP } from '../ProtocolSectionsContainer' export const BASIC_ALIQUOTING = 'basic_aliquoting' export const PCR = 'pcr' @@ -25,11 +14,7 @@ export const APPLICATION_DESCRIBE = 'application.description' export function ApplicationSection(): JSX.Element | null { const { t } = useTranslation('create_protocol') - const { - watch, - formState: { isValid }, - } = useFormContext() - const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom) + const { watch } = useFormContext() const options = [ { name: t(BASIC_ALIQUOTING), value: BASIC_ALIQUOTING }, @@ -39,16 +24,6 @@ export function ApplicationSection(): JSX.Element | null { const isOtherSelected = watch(APPLICATION_SCIENTIFIC_APPLICATION) === OTHER - function handleConfirmButtonClick(): void { - const step = - currentStep > APPLICATION_STEP ? currentStep : APPLICATION_STEP + 1 - - setCreateProtocolAtom({ - currentStep: step, - focusStep: step, - }) - } - return ( - - - - ) } - -const ButtonContainer = styled.div` - display: ${DISPLAY_FLEX}; - justify-content: ${JUSTIFY_FLEX_END}; -` diff --git a/opentrons-ai-client/src/organisms/InstrumentsSection/__tests__/InstrumentsSection.test.tsx b/opentrons-ai-client/src/organisms/InstrumentsSection/__tests__/InstrumentsSection.test.tsx index 11e6bceaf45..0712ede4757 100644 --- a/opentrons-ai-client/src/organisms/InstrumentsSection/__tests__/InstrumentsSection.test.tsx +++ b/opentrons-ai-client/src/organisms/InstrumentsSection/__tests__/InstrumentsSection.test.tsx @@ -13,6 +13,8 @@ const TestFormProviderComponent = () => { return ( + +

{`form is ${methods.formState.isValid ? 'valid' : 'invalid'}`}

) } @@ -24,7 +26,7 @@ const render = (): ReturnType => { } describe('ApplicationSection', () => { - it('should render robot, pipette, flex gripper radios, mounts dropdowns, and confirm button', async () => { + it('should render robot, pipette, flex gripper radios and mounts dropdowns', async () => { render() expect( @@ -40,7 +42,6 @@ describe('ApplicationSection', () => { expect( screen.getByText('Do you want to use the Flex Gripper?') ).toBeInTheDocument() - expect(screen.getByText('Confirm')).toBeInTheDocument() }) it('should not render left and right mount dropdowns if 96-Channel 1000µL pipette radio is selected', () => { @@ -80,7 +81,7 @@ describe('ApplicationSection', () => { render() await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() }) const leftMount = screen.getAllByText('Choose pipette')[0] @@ -96,14 +97,14 @@ describe('ApplicationSection', () => { }) expect(screen.getByText('None')).toBeInTheDocument() - expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled() + expect(screen.getByText('form is valid')).toBeInTheDocument() }) it('should not be able to select two pipettes with none value', async () => { render() await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() }) const leftMount = screen.getAllByText('Choose pipette')[0] @@ -115,15 +116,15 @@ describe('ApplicationSection', () => { fireEvent.click(screen.getAllByText('None')[1]) await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() }) }) - it('should enable confirm button when all fields are filled', async () => { + it('should update the form state to valid when all fields are filled', async () => { render() await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() }) const leftMount = screen.getAllByText('Choose pipette')[0] @@ -135,16 +136,7 @@ describe('ApplicationSection', () => { fireEvent.click(screen.getByText('Flex 8-Channel 50 μL')) await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled() - }) - }) - - it('should disable confirm button when all fields are not filled', async () => { - render() - - const confirmButton = screen.getByRole('button') - await waitFor(() => { - expect(confirmButton).not.toBeEnabled() + expect(screen.getByText('form is valid')).toBeInTheDocument() }) }) }) diff --git a/opentrons-ai-client/src/organisms/InstrumentsSection/index.tsx b/opentrons-ai-client/src/organisms/InstrumentsSection/index.tsx index c56276e1dfa..6f815b45a5d 100644 --- a/opentrons-ai-client/src/organisms/InstrumentsSection/index.tsx +++ b/opentrons-ai-client/src/organisms/InstrumentsSection/index.tsx @@ -1,19 +1,13 @@ import { COLORS, DIRECTION_COLUMN, - DISPLAY_FLEX, Flex, - JUSTIFY_FLEX_END, - LargeButton, SPACING, StyledText, } from '@opentrons/components' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { useAtom } from 'jotai' -import { createProtocolAtom } from '../../resources/atoms' -import { INSTRUMENTS_STEP } from '../ProtocolSectionsContainer' import { ControlledDropdownMenu } from '../../atoms/ControlledDropdownMenu' import { ControlledRadioButtonGroup } from '../../molecules/ControlledRadioButtonGroup' import { useMemo } from 'react' @@ -21,7 +15,6 @@ import { getAllPipetteNames, getPipetteSpecsV2, OT2_PIPETTES, - OT2_ROBOT_TYPE, OT3_PIPETTES, } from '@opentrons/shared-data' @@ -40,11 +33,7 @@ export const NO_PIPETTES = 'none' export function InstrumentsSection(): JSX.Element | null { const { t } = useTranslation('create_protocol') - const { - formState: { isValid }, - watch, - } = useFormContext() - const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom) + const { watch } = useFormContext() const robotType = watch(ROBOT_FIELD_NAME) const isOtherPipettesSelected = watch(PIPETTES_FIELD_NAME) === TWO_PIPETTES const isOpentronsOT2Selected = robotType === OPENTRONS_OT2 @@ -91,7 +80,7 @@ export function InstrumentsSection(): JSX.Element | null { const pipetteOptions = useMemo(() => { const allPipetteOptions = getAllPipetteNames('maxVolume', 'channels') .filter(name => - (robotType === OT2_ROBOT_TYPE ? OT2_PIPETTES : OT3_PIPETTES).includes( + (robotType === OPENTRONS_OT2 ? OT2_PIPETTES : OT3_PIPETTES).includes( name ) ) @@ -103,16 +92,6 @@ export function InstrumentsSection(): JSX.Element | null { return [{ name: t('none'), value: NO_PIPETTES }, ...allPipetteOptions] }, [robotType]) - function handleConfirmButtonClick(): void { - const step = - currentStep > INSTRUMENTS_STEP ? currentStep : INSTRUMENTS_STEP + 1 - - setCreateProtocolAtom({ - currentStep: step, - focusStep: step, - }) - } - return ( )} - - - - ) } -const ButtonContainer = styled.div` - display: ${DISPLAY_FLEX}; - justify-content: ${JUSTIFY_FLEX_END}; -` - const PipettesDropdown = styled.div<{ isOpentronsOT2Selected?: boolean }>` display: flex; flex-direction: column; diff --git a/opentrons-ai-client/src/organisms/LabwareLiquidsSection/__tests__/LabwareLiquidsSection.test.tsx b/opentrons-ai-client/src/organisms/LabwareLiquidsSection/__tests__/LabwareLiquidsSection.test.tsx index ce5f40e8e52..9e4d76391f0 100644 --- a/opentrons-ai-client/src/organisms/LabwareLiquidsSection/__tests__/LabwareLiquidsSection.test.tsx +++ b/opentrons-ai-client/src/organisms/LabwareLiquidsSection/__tests__/LabwareLiquidsSection.test.tsx @@ -1,4 +1,4 @@ -import { fireEvent, screen } from '@testing-library/react' +import { fireEvent, screen, waitFor } from '@testing-library/react' import { describe, it, expect } from 'vitest' import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' @@ -15,6 +15,8 @@ const TestFormProviderComponent = () => { return ( + +

{`form is ${methods.formState.isValid ? 'valid' : 'invalid'}`}

) } @@ -31,7 +33,6 @@ describe('LabwareLiquidsSection', () => { expect(screen.getByText('Add Opentrons labware')).toBeInTheDocument() expect(screen.getByText('No labware added yet')).toBeInTheDocument() - expect(screen.getByText('Confirm')).toBeInTheDocument() }) it('should not display the no labware added message if labwares have been added', async () => { @@ -51,20 +52,27 @@ describe('LabwareLiquidsSection', () => { expect(screen.queryByText('No labware added yet')).not.toBeInTheDocument() }) - // it('should enable the confirm button when labwares have been added', async () => { - // render() + it('should update form state to valid when labwares and liquids have been added', async () => { + render() - // expect(screen.getByText('Confirm')).toBeDisabled() + await waitFor(() => { + expect(screen.getByText('form is invalid')).toBeInTheDocument() + }) + const addButton = screen.getByText('Add Opentrons labware') + fireEvent.click(addButton) - // const addButton = screen.getByText('Add Opentrons labware') - // fireEvent.click(addButton) + fireEvent.click(screen.getByText('Tip rack')) + fireEvent.click( + await screen.findByText('Opentrons Flex 96 Tip Rack 1000 µL') + ) + fireEvent.click(screen.getByText('Save')) - // fireEvent.click(screen.getByText('Tip rack')) - // fireEvent.click( - // await screen.findByText('Opentrons Flex 96 Tip Rack 1000 µL') - // ) - // fireEvent.click(screen.getByText('Save')) + fireEvent.change(screen.getByRole('textbox'), { + target: { value: 'test liquid' }, + }) - // expect(screen.getByText('Confirm')).toBeEnabled() - // }) + await waitFor(() => { + expect(screen.getByText('form is valid')).toBeInTheDocument() + }) + }) }) diff --git a/opentrons-ai-client/src/organisms/LabwareLiquidsSection/index.tsx b/opentrons-ai-client/src/organisms/LabwareLiquidsSection/index.tsx index 627b3a5ea76..24d50e32f21 100644 --- a/opentrons-ai-client/src/organisms/LabwareLiquidsSection/index.tsx +++ b/opentrons-ai-client/src/organisms/LabwareLiquidsSection/index.tsx @@ -1,21 +1,14 @@ import { COLORS, DIRECTION_COLUMN, - DISPLAY_FLEX, EmptySelectorButton, Flex, InfoScreen, - JUSTIFY_FLEX_END, - LargeButton, SPACING, StyledText, } from '@opentrons/components' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' -import { useAtom } from 'jotai' -import { createProtocolAtom } from '../../resources/atoms' -import { LABWARE_LIQUIDS_STEP } from '../ProtocolSectionsContainer' import { useState } from 'react' import { LabwareModal } from '../LabwareModal' import { ControlledLabwareListItems } from '../../molecules/ControlledLabwareListItems' @@ -31,29 +24,12 @@ export const LIQUIDS_FIELD_NAME = 'liquids' export function LabwareLiquidsSection(): JSX.Element | null { const { t } = useTranslation('create_protocol') - const { - formState: { isValid }, - setValue, - watch, - } = useFormContext() - const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom) + const { setValue, watch } = useFormContext() const [displayLabwareModal, setDisplayLabwareModal] = useState(false) const labwares: DisplayLabware[] = watch(LABWARES_FIELD_NAME) ?? [] const liquids: string[] = watch(LIQUIDS_FIELD_NAME) ?? [] - function handleConfirmButtonClick(): void { - const step = - currentStep > LABWARE_LIQUIDS_STEP - ? currentStep - : LABWARE_LIQUIDS_STEP + 1 - - setCreateProtocolAtom({ - currentStep: step, - focusStep: step, - }) - } - return ( - - - - ) } - -const ButtonContainer = styled.div` - display: ${DISPLAY_FLEX}; - justify-content: ${JUSTIFY_FLEX_END}; -` diff --git a/opentrons-ai-client/src/organisms/LabwareModal/index.tsx b/opentrons-ai-client/src/organisms/LabwareModal/index.tsx index baf83087877..0fcb6b17de1 100644 --- a/opentrons-ai-client/src/organisms/LabwareModal/index.tsx +++ b/opentrons-ai-client/src/organisms/LabwareModal/index.tsx @@ -19,7 +19,7 @@ import { createPortal } from 'react-dom' import { reduce } from 'lodash' import { ListButtonCheckbox } from '../../atoms/ListButtonCheckbox/ListButtonCheckbox' import { LABWARES_FIELD_NAME } from '../LabwareLiquidsSection' -import { getAllDefinitions } from '../../resources/utils' +import { getOnlyLatestDefs } from '../../resources/utils' import type { DisplayLabware } from '../LabwareLiquidsSection' import type { LabwareDefByDefURI, @@ -56,7 +56,7 @@ export function LabwareModal({ const searchFilter = (termToCheck: string): boolean => termToCheck.toLowerCase().includes(searchTerm.toLowerCase()) - const defs = getAllDefinitions() + const defs = getOnlyLatestDefs() const labwareByCategory: Record< string, @@ -229,10 +229,18 @@ export function LabwareModal({ setValue( LABWARES_FIELD_NAME, [ - ...selectedLabwares.map(labwareURI => ({ - labwareURI, - count: 1, - })), + ...selectedLabwares.map(labwareURI => { + const existingLabware = labwares.find( + lw => lw.labwareURI === labwareURI + ) + return { + labwareURI, + count: + existingLabware != null + ? existingLabware.count + : 1, + } + }), ], { shouldValidate: true } ) diff --git a/opentrons-ai-client/src/organisms/ModulesSection/__tests__/ModulesSection.test.tsx b/opentrons-ai-client/src/organisms/ModulesSection/__tests__/ModulesSection.test.tsx index 16c7046f152..652df2a1b95 100644 --- a/opentrons-ai-client/src/organisms/ModulesSection/__tests__/ModulesSection.test.tsx +++ b/opentrons-ai-client/src/organisms/ModulesSection/__tests__/ModulesSection.test.tsx @@ -15,6 +15,8 @@ const TestFormProviderComponent = () => { return ( + +

{`form is ${methods.formState.isValid ? 'valid' : 'invalid'}`}

) } @@ -26,12 +28,11 @@ const render = (): ReturnType => { } describe('ModulesSection', () => { - it('should render modules buttons, no modules added yet, and confirm button', async () => { + it('should render modules buttons and no modules added yet', async () => { render() - expect(screen.getAllByRole('button').length).toBe(5) + expect(screen.getAllByRole('button').length).toBe(4) expect(screen.getByText('No modules added yet')).toBeInTheDocument() - expect(screen.getByText('Confirm')).toBeInTheDocument() }) it('should render a list item with the selected module if user clicks the module button', () => { @@ -71,20 +72,11 @@ describe('ModulesSection', () => { expect(screen.getAllByText('Heater-Shaker Module GEN1').length).toBe(1) }) - it('should disable confirm button when all fields are not filled', async () => { - render() - - const confirmButton = screen.getByRole('button', { name: 'Confirm' }) - await waitFor(() => { - expect(confirmButton).not.toBeEnabled() - }) - }) - - it('should render with Confirm button enabled, modules are not required', async () => { + it('should render with form state valid, modules are not required', async () => { render() await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled() + expect(screen.getByText('form is valid')).toBeInTheDocument() }) }) }) diff --git a/opentrons-ai-client/src/organisms/ModulesSection/index.tsx b/opentrons-ai-client/src/organisms/ModulesSection/index.tsx index 85f068bc226..2531d9aab37 100644 --- a/opentrons-ai-client/src/organisms/ModulesSection/index.tsx +++ b/opentrons-ai-client/src/organisms/ModulesSection/index.tsx @@ -1,18 +1,11 @@ import { DIRECTION_COLUMN, - DISPLAY_FLEX, Flex, InfoScreen, - JUSTIFY_FLEX_END, - LargeButton, SPACING, } from '@opentrons/components' import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' -import styled from 'styled-components' -import { useAtom } from 'jotai' -import { createProtocolAtom } from '../../resources/atoms' -import { MODULES_STEP } from '../ProtocolSectionsContainer' import { ControlledEmptySelectorButtonGroup } from '../../molecules/ControlledEmptySelectorButtonGroup' import { ModuleListItemGroup } from '../../molecules/ModuleListItemGroup' import type { ModuleType, ModuleModel } from '@opentrons/shared-data' @@ -31,11 +24,7 @@ export const MODULES_FIELD_NAME = 'modules' export function ModulesSection(): JSX.Element | null { const { t } = useTranslation('create_protocol') - const { - formState: { isValid }, - watch, - } = useFormContext() - const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom) + const { watch } = useFormContext() const modules: DisplayModules[] = [ { @@ -60,15 +49,6 @@ export function ModulesSection(): JSX.Element | null { }, ] - function handleConfirmButtonClick(): void { - const step = currentStep > MODULES_STEP ? currentStep : MODULES_STEP + 1 - - setCreateProtocolAtom({ - currentStep: step, - focusStep: step, - }) - } - const modulesWatch: DisplayModules[] = watch(MODULES_FIELD_NAME) ?? [] return ( @@ -84,19 +64,6 @@ export function ModulesSection(): JSX.Element | null { )} - - - -
) } - -const ButtonContainer = styled.div` - display: ${DISPLAY_FLEX}; - justify-content: ${JUSTIFY_FLEX_END}; -` diff --git a/opentrons-ai-client/src/organisms/ProtocolSectionsContainer/index.tsx b/opentrons-ai-client/src/organisms/ProtocolSectionsContainer/index.tsx index 7240a83ae4f..3946d6a5cf5 100644 --- a/opentrons-ai-client/src/organisms/ProtocolSectionsContainer/index.tsx +++ b/opentrons-ai-client/src/organisms/ProtocolSectionsContainer/index.tsx @@ -1,15 +1,23 @@ -import { DIRECTION_COLUMN, Flex, SPACING } from '@opentrons/components' +import { + DIRECTION_COLUMN, + DISPLAY_FLEX, + Flex, + JUSTIFY_FLEX_END, + LargeButton, + SPACING, +} from '@opentrons/components' import { useTranslation } from 'react-i18next' import { Accordion } from '../../molecules/Accordion' import styled from 'styled-components' import { ApplicationSection } from '../../organisms/ApplicationSection' import { createProtocolAtom } from '../../resources/atoms' import { useAtom } from 'jotai' -import { useFormContext } from 'react-hook-form' import { InstrumentsSection } from '../InstrumentsSection' import { ModulesSection } from '../ModulesSection' import { LabwareLiquidsSection } from '../LabwareLiquidsSection' import { StepsSection } from '../StepsSection' +import { useFormContext } from 'react-hook-form' +import { COLUMN } from '@opentrons/shared-data' export const APPLICATION_STEP = 0 export const INSTRUMENTS_STEP = 1 @@ -22,62 +30,83 @@ export function ProtocolSectionsContainer(): JSX.Element | null { const { formState: { isValid }, } = useFormContext() - const [{ currentStep, focusStep }, setCreateProtocolAtom] = useAtom( + const [{ currentSection, focusSection }, setCreateProtocolAtom] = useAtom( createProtocolAtom ) function handleSectionClick(stepNumber: number): void { - currentStep >= stepNumber && + currentSection >= stepNumber && isValid && setCreateProtocolAtom({ - currentStep, - focusStep: stepNumber, + currentSection, + focusSection: stepNumber, }) } function displayCheckmark(stepNumber: number): boolean { - return currentStep > stepNumber && focusStep !== stepNumber + return currentSection > stepNumber && focusSection !== stepNumber + } + + function handleConfirmButtonClick(): void { + const step = + currentSection > focusSection ? currentSection : focusSection + 1 + + setCreateProtocolAtom({ + currentSection: step, + focusSection: step, + }) } return ( {[ { - stepNumber: APPLICATION_STEP, + sectionNumber: APPLICATION_STEP, title: 'application_title', Component: ApplicationSection, }, { - stepNumber: INSTRUMENTS_STEP, + sectionNumber: INSTRUMENTS_STEP, title: 'instruments_title', Component: InstrumentsSection, }, { - stepNumber: MODULES_STEP, + sectionNumber: MODULES_STEP, title: 'modules_title', Component: ModulesSection, }, { - stepNumber: LABWARE_LIQUIDS_STEP, + sectionNumber: LABWARE_LIQUIDS_STEP, title: 'labware_liquids_title', Component: LabwareLiquidsSection, }, { - stepNumber: STEPS_STEP, + sectionNumber: STEPS_STEP, title: 'steps_title', Component: StepsSection, }, - ].map(({ stepNumber, title, Component }) => ( + ].map(({ sectionNumber, title, Component }) => ( { - handleSectionClick(stepNumber) + handleSectionClick(sectionNumber) }} - isCompleted={displayCheckmark(stepNumber)} + isCompleted={displayCheckmark(sectionNumber)} > - {focusStep === stepNumber && } + {focusSection === sectionNumber && ( + + + + + + + )} ))} @@ -89,3 +118,8 @@ const ProtocolSections = styled(Flex)` width: 100%; gap: ${SPACING.spacing16}; ` + +const ButtonContainer = styled.div` + display: ${DISPLAY_FLEX}; + justify-content: ${JUSTIFY_FLEX_END}; +` diff --git a/opentrons-ai-client/src/organisms/StepsSection/__tests__/StepsSection.test.tsx b/opentrons-ai-client/src/organisms/StepsSection/__tests__/StepsSection.test.tsx index 8ef93100bc0..06fc8b30741 100644 --- a/opentrons-ai-client/src/organisms/StepsSection/__tests__/StepsSection.test.tsx +++ b/opentrons-ai-client/src/organisms/StepsSection/__tests__/StepsSection.test.tsx @@ -23,6 +23,8 @@ const TestFormProviderComponent = () => { ) : (

{steps}

)} + +

{`form is ${methods.formState.isValid ? 'valid' : 'invalid'}`}

) } @@ -113,17 +115,17 @@ describe('StepsSection', () => { expect(screen.getAllByText('description test')[1]).toBeInTheDocument() }) - it('should enable the confirm button when steps have been added', async () => { + it('should update form state to valid when steps have been added', async () => { render() - expect(screen.getByRole('button', { name: 'Confirm' })).toBeDisabled() + expect(screen.getByText('form is invalid')).toBeInTheDocument() fireEvent.change(screen.getByRole('textbox'), { target: { value: 'description test' }, }) await waitFor(() => { - expect(screen.getByRole('button', { name: 'Confirm' })).toBeEnabled() + expect(screen.getByText('form is valid')).toBeInTheDocument() }) }) }) diff --git a/opentrons-ai-client/src/organisms/StepsSection/index.tsx b/opentrons-ai-client/src/organisms/StepsSection/index.tsx index b44d0985039..f2fe7dd8024 100644 --- a/opentrons-ai-client/src/organisms/StepsSection/index.tsx +++ b/opentrons-ai-client/src/organisms/StepsSection/index.tsx @@ -1,11 +1,8 @@ import { COLORS, DIRECTION_COLUMN, - DISPLAY_FLEX, EmptySelectorButton, Flex, - JUSTIFY_FLEX_END, - LargeButton, SPACING, StyledText, Tabs, @@ -13,9 +10,6 @@ import { import { useFormContext } from 'react-hook-form' import { useTranslation } from 'react-i18next' import styled from 'styled-components' -import { useAtom } from 'jotai' -import { createProtocolAtom } from '../../resources/atoms' -import { STEPS_STEP } from '../ProtocolSectionsContainer' import { useState } from 'react' import { COLUMN } from '@opentrons/shared-data' import { ControlledAddTextAreaFields } from '../../molecules/ControlledAddTextAreaFields' @@ -25,25 +19,11 @@ export const STEPS_FIELD_NAME = 'steps' export function StepsSection(): JSX.Element | null { const { t } = useTranslation('create_protocol') - const { - formState: { isValid }, - setValue, - watch, - } = useFormContext() - const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom) + const { setValue, watch } = useFormContext() const [isIndividualStep, setIsIndividualStep] = useState(true) const steps = watch(STEPS_FIELD_NAME) ?? [] - function handleConfirmButtonClick(): void { - const step = currentStep > STEPS_STEP ? currentStep : STEPS_STEP + 1 - - setCreateProtocolAtom({ - currentStep: step, - focusStep: step, - }) - } - return ( )} - - - -
) } -const ButtonContainer = styled.div` - display: ${DISPLAY_FLEX}; - justify-content: ${JUSTIFY_FLEX_END}; -` - const ExampleOrderedList = styled.ol` margin-left: ${SPACING.spacing20}; font-size: 14px; diff --git a/opentrons-ai-client/src/pages/CreateProtocol/__tests__/CreateProtocol.test.tsx b/opentrons-ai-client/src/pages/CreateProtocol/__tests__/CreateProtocol.test.tsx index 4951299f2c6..919e5f735e8 100644 --- a/opentrons-ai-client/src/pages/CreateProtocol/__tests__/CreateProtocol.test.tsx +++ b/opentrons-ai-client/src/pages/CreateProtocol/__tests__/CreateProtocol.test.tsx @@ -260,7 +260,7 @@ describe('CreateProtocol', () => { expect(mockNavigate).toHaveBeenCalledWith('/chat') expect(mockUseTrackEvent).toHaveBeenCalledWith({ name: 'submit-prompt', - properties: { prompt: expect.any(String) }, + properties: { isCreateOrUpdate: 'create', prompt: expect.any(String) }, }) }) }) diff --git a/opentrons-ai-client/src/pages/CreateProtocol/index.tsx b/opentrons-ai-client/src/pages/CreateProtocol/index.tsx index 0012a429107..525c79154b9 100644 --- a/opentrons-ai-client/src/pages/CreateProtocol/index.tsx +++ b/opentrons-ai-client/src/pages/CreateProtocol/index.tsx @@ -9,6 +9,8 @@ import { useEffect, useRef, useState } from 'react' import { PromptPreview } from '../../molecules/PromptPreview' import { useForm, FormProvider } from 'react-hook-form' import { + chatDataAtom, + chatHistoryAtom, createProtocolAtom, createProtocolChatAtom, headerWithMeterAtom, @@ -29,7 +31,7 @@ import { ResizeBar } from '../../atoms/ResizeBar' export interface CreateProtocolFormData { application: { scientificApplication: string - otherApplication?: string + otherApplication: string description: string } instruments: { @@ -50,9 +52,13 @@ const TOTAL_STEPS = 5 export function CreateProtocol(): JSX.Element | null { const { t } = useTranslation('create_protocol') const [, setHeaderWithMeterAtom] = useAtom(headerWithMeterAtom) - const [{ currentStep }, setCreateProtocolAtom] = useAtom(createProtocolAtom) + const [{ currentSection }, setCreateProtocolAtom] = useAtom( + createProtocolAtom + ) const [, setCreateProtocolChatAtom] = useAtom(createProtocolChatAtom) const [, setUpdateProtocolChatAtom] = useAtom(updateProtocolChatAtom) + const [, setChatHistoryAtom] = useAtom(chatHistoryAtom) + const [, setChatData] = useAtom(chatDataAtom) const navigate = useNavigate() const trackEvent = useTrackEvent() const [leftWidth, setLeftWidth] = useState(50) @@ -77,8 +83,23 @@ export function CreateProtocol(): JSX.Element | null { }, }) - // Reset the update protocol chat atom when navigating to the create protocol page + // Reset the chat data atom and protocol atoms when navigating to the update protocol page useEffect(() => { + setCreateProtocolChatAtom({ + prompt: '', + regenerate: false, + scientific_application_type: '', + description: '', + robots: 'opentrons_flex', + mounts: [], + flexGripper: false, + modules: [], + labware: [], + liquids: [], + steps: [], + fake: false, + fake_id: 0, + }) setUpdateProtocolChatAtom({ prompt: '', protocol_text: '', @@ -88,6 +109,8 @@ export function CreateProtocol(): JSX.Element | null { fake: false, fake_id: 0, }) + setChatHistoryAtom([]) + setChatData([]) }, []) useEffect(() => { @@ -95,7 +118,7 @@ export function CreateProtocol(): JSX.Element | null { displayHeaderWithMeter: true, progress: calculateProgress(), }) - }, [currentStep]) + }, [currentSection]) useEffect(() => { return () => { @@ -106,8 +129,8 @@ export function CreateProtocol(): JSX.Element | null { methods.reset() setCreateProtocolAtom({ - currentStep: 0, - focusStep: 0, + currentSection: 0, + focusSection: 0, }) } }, []) @@ -138,7 +161,7 @@ export function CreateProtocol(): JSX.Element | null { }, [isResizing]) function calculateProgress(): number { - return currentStep > 0 ? currentStep / TOTAL_STEPS : 0 + return currentSection > 0 ? currentSection / TOTAL_STEPS : 0 } function handleMouseDown( @@ -184,6 +207,7 @@ export function CreateProtocol(): JSX.Element | null { trackEvent({ name: 'submit-prompt', properties: { + isCreateOrUpdate: 'create', prompt: chatPromptData, }, }) @@ -209,7 +233,7 @@ export function CreateProtocol(): JSX.Element | null {
diff --git a/opentrons-ai-client/src/organisms/UpdateProtocol/__tests__/UpdateProtocol.test.tsx b/opentrons-ai-client/src/pages/UpdateProtocol/__tests__/UpdateProtocol.test.tsx similarity index 73% rename from opentrons-ai-client/src/organisms/UpdateProtocol/__tests__/UpdateProtocol.test.tsx rename to opentrons-ai-client/src/pages/UpdateProtocol/__tests__/UpdateProtocol.test.tsx index 04c3ad3b167..f69d4b88bf8 100644 --- a/opentrons-ai-client/src/organisms/UpdateProtocol/__tests__/UpdateProtocol.test.tsx +++ b/opentrons-ai-client/src/pages/UpdateProtocol/__tests__/UpdateProtocol.test.tsx @@ -122,4 +122,53 @@ describe('Update Protocol', () => { }) expect(mockNavigate).toHaveBeenCalledWith('/chat') }) + + it('should call trackEvent when submit prompt button is clicked', async () => { + render() + + // upload file + const blobParts: BlobPart[] = [ + 'x = 1\n', + 'x = 2\n', + 'x = 3\n', + 'x = 4\n', + 'print("x is 1.")\n', + ] + const file = new File(blobParts, 'test-file.py', { type: 'text/python' }) + fireEvent.drop(screen.getByTestId('file_drop_zone'), { + dataTransfer: { + files: [file], + }, + }) + + // input description + const describeInput = screen.getByRole('textbox') + fireEvent.change(describeInput, { target: { value: 'Test description' } }) + + expect(screen.getByDisplayValue('Test description')).toBeInTheDocument() + + // select update type + const applicationDropdown = screen.getByText('Select an option') + fireEvent.click(applicationDropdown) + + const basicOtherOption = screen.getByText('Other') + fireEvent.click(basicOtherOption) + + const submitPromptButton = screen.getByText('Submit prompt') + await waitFor(() => { + expect(submitPromptButton).toBeEnabled() + }) + + fireEvent.click(submitPromptButton) + + await waitFor(() => { + expect(mockUseTrackEvent).toHaveBeenCalledWith({ + name: 'submit-prompt', + properties: { + isCreateOrUpdate: 'update', + prompt: expect.any(String), + }, + }) + }) + }) }) diff --git a/opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx b/opentrons-ai-client/src/pages/UpdateProtocol/index.tsx similarity index 91% rename from opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx rename to opentrons-ai-client/src/pages/UpdateProtocol/index.tsx index 9b47ce4c251..6d12c3f700a 100644 --- a/opentrons-ai-client/src/organisms/UpdateProtocol/index.tsx +++ b/opentrons-ai-client/src/pages/UpdateProtocol/index.tsx @@ -4,7 +4,6 @@ import { DIRECTION_COLUMN, DIRECTION_ROW, Flex, - InputField, JUSTIFY_CENTER, JUSTIFY_END, LargeButton, @@ -21,13 +20,16 @@ import { Trans, useTranslation } from 'react-i18next' import { FileUpload } from '../../molecules/FileUpload' import { useNavigate } from 'react-router-dom' import { + chatHistoryAtom, createProtocolChatAtom, headerWithMeterAtom, updateProtocolChatAtom, + chatDataAtom, } from '../../resources/atoms' import { CSSTransition } from 'react-transition-group' import { useAtom } from 'jotai' import { useTrackEvent } from '../../resources/hooks/useTrackEvent' +import { TextAreaField } from '../../atoms/TextAreaField' interface UpdateOptionsDropdown extends DropdownOption { value: UpdateOptions @@ -105,16 +107,19 @@ export function UpdateProtocol(): JSX.Element { const [headerState, setHeaderWithMeterAtom] = useAtom(headerWithMeterAtom) const [updateType, setUpdateType] = useState(null) const [detailsValue, setDetailsValue] = useState('') - const [, setUpdatePromptAtom] = useAtom(updateProtocolChatAtom) + const [, setUpdateProtocolChatAtom] = useAtom(updateProtocolChatAtom) const [, setCreateProtocolChatAtom] = useAtom(createProtocolChatAtom) + const [, setChatHistoryAtom] = useAtom(chatHistoryAtom) + const [, setChatData] = useAtom(chatDataAtom) const [fileValue, setFile] = useState(null) const [pythonText, setPythonTextValue] = useState('') const [errorText, setErrorText] = useState(null) - // Reset the create protocol chat atom when navigating to the update protocol page + // Reset the chat data atom and protocol atoms when navigating to the update protocol page useEffect(() => { setCreateProtocolChatAtom({ prompt: '', + regenerate: false, scientific_application_type: '', description: '', robots: 'opentrons_flex', @@ -127,6 +132,17 @@ export function UpdateProtocol(): JSX.Element { fake: false, fake_id: 0, }) + setUpdateProtocolChatAtom({ + prompt: '', + protocol_text: '', + regenerate: false, + update_type: 'adapt_python_protocol', + update_details: '', + fake: false, + fake_id: 0, + }) + setChatHistoryAtom([]) + setChatData([]) }, []) useEffect(() => { @@ -156,7 +172,7 @@ export function UpdateProtocol(): JSX.Element { setHeaderWithMeterAtom, ]) - const handleInputChange = (event: ChangeEvent): void => { + const handleInputChange = (event: ChangeEvent): void => { setDetailsValue(event.target.value) } @@ -171,7 +187,6 @@ export function UpdateProtocol(): JSX.Element { if (typeof text === 'string' && text !== '') { setErrorText(null) - console.log('File read successfully:\n', text) setPythonTextValue(text) } else { setErrorText(t('file_length_error')) @@ -196,7 +211,7 @@ export function UpdateProtocol(): JSX.Element { console.log(chatPrompt) - setUpdatePromptAtom({ + setUpdateProtocolChatAtom({ prompt: chatPrompt, protocol_text: pythonText, regenerate: false, @@ -209,6 +224,7 @@ export function UpdateProtocol(): JSX.Element { trackEvent({ name: 'submit-prompt', properties: { + isCreateOrUpdate: 'update', prompt: chatPrompt, }, }) @@ -310,10 +326,10 @@ export function UpdateProtocol(): JSX.Element { /> {t('provide_details_of_changes')} - ([]) /** CreateProtocolChatAtom is for the prefilled userprompt when navigating to the chat page from Create New protocol page */ export const createProtocolChatAtom = atom({ prompt: '', + regenerate: false, scientific_application_type: '', description: '', robots: 'opentrons_flex', @@ -40,6 +41,15 @@ export const updateProtocolChatAtom = atom({ fake_id: 0, }) +/** Regenerate protocol atom */ +export const regenerateProtocolAtom = atom<{ + isCreateOrUpdateProtocol: boolean + regenerate: boolean +}>({ + isCreateOrUpdateProtocol: false, + regenerate: false, +}) + /** Scroll to bottom of chat atom */ export const scrollToBottomAtom = atom(false) @@ -59,9 +69,9 @@ export const headerWithMeterAtom = atom({ progress: 0, }) -export const createProtocolAtom = atom({ - currentStep: 0, - focusStep: 0, +export const createProtocolAtom = atom({ + currentSection: 0, + focusSection: 0, }) export const displayExitConfirmModalAtom = atom(false) diff --git a/opentrons-ai-client/src/resources/types.ts b/opentrons-ai-client/src/resources/types.ts index 7a84eba1054..7e16e1a8642 100644 --- a/opentrons-ai-client/src/resources/types.ts +++ b/opentrons-ai-client/src/resources/types.ts @@ -15,6 +15,7 @@ export interface ChatData { export interface CreatePrompt { /** the prompt that is generated by the create protocol page */ prompt: string + regenerate: boolean scientific_application_type: string description: string robots: 'opentrons_flex' | 'opentrons_ot2' | string @@ -84,9 +85,9 @@ export interface HeaderWithMeterAtomProps { progress: number } -export interface createProtocolAtomProps { - currentStep: number - focusStep: number +export interface CreateProtocolAtomProps { + currentSection: number + focusSection: number } export interface PromptData { diff --git a/opentrons-ai-client/src/resources/utils/createProtocolUtils.tsx b/opentrons-ai-client/src/resources/utils/createProtocolUtils.tsx index 1c5899a7d0e..a2ccffd988b 100644 --- a/opentrons-ai-client/src/resources/utils/createProtocolUtils.tsx +++ b/opentrons-ai-client/src/resources/utils/createProtocolUtils.tsx @@ -13,7 +13,7 @@ import { } from '../../organisms/InstrumentsSection' import type { UseFormWatch } from 'react-hook-form' import type { CreateProtocolFormData } from '../../pages/CreateProtocol' -import { getAllDefinitions } from './labware' +import { getOnlyLatestDefs } from './labware' import type { CreatePrompt } from '../types' export function generatePromptPreviewApplicationItems( @@ -92,10 +92,14 @@ export function generatePromptPreviewLabwareLiquidsItems( const { labwares, liquids } = watch() const items: string[] = [] - const defs = getAllDefinitions() + const defs = getOnlyLatestDefs() labwares?.forEach(labware => { - items.push(getLabwareDisplayName(defs[labware.labwareURI]) as string) + items.push( + `${labware.count} x ${ + getLabwareDisplayName(defs[labware.labwareURI]) as string + }` + ) }) liquids?.forEach(liquid => { @@ -155,7 +159,7 @@ export function generateChatPrompt( args_0: CreatePrompt | ((prev: CreatePrompt) => CreatePrompt) ) => void ): string { - const defs = getAllDefinitions() + const defs = getOnlyLatestDefs() const robotType = t(values.instruments.robot) const scientificApplication = t(values.application.scientificApplication) @@ -226,7 +230,11 @@ export function generateChatPrompt( setCreateProtocolChatAtom({ prompt, - scientific_application_type: values.application.scientificApplication, + regenerate: false, + scientific_application_type: + values.application.scientificApplication === OTHER + ? values.application.otherApplication + : values.application.scientificApplication, description, robots: values.instruments.robot, mounts, diff --git a/opentrons-ai-client/src/resources/utils/labware.ts b/opentrons-ai-client/src/resources/utils/labware.ts index b0844c57a70..f7bf0ddf427 100644 --- a/opentrons-ai-client/src/resources/utils/labware.ts +++ b/opentrons-ai-client/src/resources/utils/labware.ts @@ -2,16 +2,52 @@ import { LABWAREV2_DO_NOT_LIST, RETIRED_LABWARE, getAllDefinitions as _getAllDefinitions, + getLabwareDefURI, +} from '@opentrons/shared-data' +import { groupBy } from 'lodash' +import type { + LabwareDefByDefURI, + LabwareDefinition2, } from '@opentrons/shared-data' -import type { LabwareDefByDefURI } from '@opentrons/shared-data' let _definitions: LabwareDefByDefURI | null = null + +const BLOCK_LIST = [...RETIRED_LABWARE, ...LABWAREV2_DO_NOT_LIST] + export function getAllDefinitions(): LabwareDefByDefURI { if (_definitions == null) { - _definitions = _getAllDefinitions([ - ...RETIRED_LABWARE, - ...LABWAREV2_DO_NOT_LIST, - ]) + _definitions = _getAllDefinitions(BLOCK_LIST) } return _definitions } + +// filter out all but the latest version of each labware +// NOTE: this is similar to labware-library's getOnlyLatestDefs, but this one +// has the {labwareDefURI: def} shape, instead of an array of labware defs +let _latestDefs: LabwareDefByDefURI | null = null +export function getOnlyLatestDefs(): LabwareDefByDefURI { + if (!_latestDefs) { + const allDefs = getAllDefinitions() + const allURIs = Object.keys(allDefs) + const labwareDefGroups: Record = groupBy( + allURIs.map((uri: string) => allDefs[uri]), + d => `${d.namespace}/${d.parameters.loadName}` + ) + _latestDefs = Object.keys(labwareDefGroups).reduce( + (acc, groupKey: string) => { + const group = labwareDefGroups[groupKey] + const allVersions = group.map(d => d.version) + const highestVersionNum = Math.max(...allVersions) + const resultIdx = group.findIndex(d => d.version === highestVersionNum) + const latestDefInGroup = group[resultIdx] + return { + ...acc, + [getLabwareDefURI(latestDefInGroup)]: latestDefInGroup, + } + }, + {} + ) + } + + return _latestDefs +} diff --git a/opentrons-ai-server/Makefile b/opentrons-ai-server/Makefile index ecc643d9cd0..2bc7170e931 100644 --- a/opentrons-ai-server/Makefile +++ b/opentrons-ai-server/Makefile @@ -75,6 +75,12 @@ deploy: gen-requirements @echo "Deploying to environment: $(ENV)" python -m pipenv run python deploy.py --env $(ENV) $(if $(TAG),--tag $(TAG),) +.PHONY: dry-deploy +dry-deploy: gen-requirements + @echo "Dry run deploying to environment: $(ENV)" + @echo "Data is retrieved from AWS but no changes are made" + python -m pipenv run python deploy.py --dry --env $(ENV) $(if $(TAG),--tag $(TAG),) + .PHONY: prompted-deploy prompted-deploy: gen-requirements python -m pipenv run python deploy.py @@ -132,4 +138,9 @@ run-shell: .PHONY: shell shell: - docker exec -it $(CONTAINER_NAME) /bin/bash] + docker exec -it $(CONTAINER_NAME) /bin/bash + +.PHONY: test-googlesheet +test-googlesheet: + @echo "Loading environment variables from .env and running test-googlesheet" + pipenv run python -m api.integration.google_sheets diff --git a/opentrons-ai-server/Pipfile b/opentrons-ai-server/Pipfile index 34b0b8d32dd..4586798349a 100644 --- a/opentrons-ai-server/Pipfile +++ b/opentrons-ai-server/Pipfile @@ -17,6 +17,9 @@ beautifulsoup4 = "==4.12.3" markdownify = "==0.13.1" structlog = "==24.4.0" asgi-correlation-id = "==4.3.3" +gspread = "==6.1.4" +google-auth = "==2.36.0" +google-auth-oauthlib = "==1.2.1" [dev-packages] docker = "==7.1.0" diff --git a/opentrons-ai-server/Pipfile.lock b/opentrons-ai-server/Pipfile.lock index 55811db04cf..a4b9ba0dca5 100644 --- a/opentrons-ai-server/Pipfile.lock +++ b/opentrons-ai-server/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "20b9e324d809f68cb0465d5e3d98467ceb5860f583fddc347ade1e5ad6a3b6ab" + "sha256": "56aef120fbddf42f146e054b7d59ee0f59be75aa6e43f332f86b7ba8fa2499e0" }, "pipfile-spec": 6, "requires": { @@ -26,100 +26,85 @@ }, "aiohttp": { "hashes": [ - "sha256:02d1d6610588bcd743fae827bd6f2e47e0d09b346f230824b4c6fb85c6065f9c", - "sha256:03690541e4cc866eef79626cfa1ef4dd729c5c1408600c8cb9e12e1137eed6ab", - "sha256:0bc059ecbce835630e635879f5f480a742e130d9821fbe3d2f76610a6698ee25", - "sha256:0c21c82df33b264216abffff9f8370f303dab65d8eee3767efbbd2734363f677", - "sha256:1298b854fd31d0567cbb916091be9d3278168064fca88e70b8468875ef9ff7e7", - "sha256:1321658f12b6caffafdc35cfba6c882cb014af86bef4e78c125e7e794dfb927b", - "sha256:143b0026a9dab07a05ad2dd9e46aa859bffdd6348ddc5967b42161168c24f857", - "sha256:16e6a51d8bc96b77f04a6764b4ad03eeef43baa32014fce71e882bd71302c7e4", - "sha256:172ad884bb61ad31ed7beed8be776eb17e7fb423f1c1be836d5cb357a096bf12", - "sha256:17c272cfe7b07a5bb0c6ad3f234e0c336fb53f3bf17840f66bd77b5815ab3d16", - "sha256:1a0ee6c0d590c917f1b9629371fce5f3d3f22c317aa96fbdcce3260754d7ea21", - "sha256:2746d8994ebca1bdc55a1e998feff4e94222da709623bb18f6e5cfec8ec01baf", - "sha256:2914caa46054f3b5ff910468d686742ff8cff54b8a67319d75f5d5945fd0a13d", - "sha256:2bbf94d4a0447705b7775417ca8bb8086cc5482023a6e17cdc8f96d0b1b5aba6", - "sha256:2bd9f3eac515c16c4360a6a00c38119333901b8590fe93c3257a9b536026594d", - "sha256:2c33fa6e10bb7ed262e3ff03cc69d52869514f16558db0626a7c5c61dde3c29f", - "sha256:2d37f4718002863b82c6f391c8efd4d3a817da37030a29e2682a94d2716209de", - "sha256:3668d0c2a4d23fb136a753eba42caa2c0abbd3d9c5c87ee150a716a16c6deec1", - "sha256:36d4fba838be5f083f5490ddd281813b44d69685db910907636bc5dca6322316", - "sha256:40ff5b7660f903dc587ed36ef08a88d46840182d9d4b5694e7607877ced698a1", - "sha256:42775de0ca04f90c10c5c46291535ec08e9bcc4756f1b48f02a0657febe89b10", - "sha256:482c85cf3d429844396d939b22bc2a03849cb9ad33344689ad1c85697bcba33a", - "sha256:4e6cb75f8ddd9c2132d00bc03c9716add57f4beff1263463724f6398b813e7eb", - "sha256:4edc3fd701e2b9a0d605a7b23d3de4ad23137d23fc0dbab726aa71d92f11aaaf", - "sha256:4fd16b30567c5b8e167923be6e027eeae0f20cf2b8a26b98a25115f28ad48ee0", - "sha256:5002a02c17fcfd796d20bac719981d2fca9c006aac0797eb8f430a58e9d12431", - "sha256:51d0a4901b27272ae54e42067bc4b9a90e619a690b4dc43ea5950eb3070afc32", - "sha256:558b3d223fd631ad134d89adea876e7fdb4c93c849ef195049c063ada82b7d08", - "sha256:5c070430fda1a550a1c3a4c2d7281d3b8cfc0c6715f616e40e3332201a253067", - "sha256:5f392ef50e22c31fa49b5a46af7f983fa3f118f3eccb8522063bee8bfa6755f8", - "sha256:60555211a006d26e1a389222e3fab8cd379f28e0fbf7472ee55b16c6c529e3a6", - "sha256:608cecd8d58d285bfd52dbca5b6251ca8d6ea567022c8a0eaae03c2589cd9af9", - "sha256:60ad5b8a7452c0f5645c73d4dad7490afd6119d453d302cd5b72b678a85d6044", - "sha256:63649309da83277f06a15bbdc2a54fbe75efb92caa2c25bb57ca37762789c746", - "sha256:6ebdc3b3714afe1b134b3bbeb5f745eed3ecbcff92ab25d80e4ef299e83a5465", - "sha256:6f3c6648aa123bcd73d6f26607d59967b607b0da8ffcc27d418a4b59f4c98c7c", - "sha256:7003f33f5f7da1eb02f0446b0f8d2ccf57d253ca6c2e7a5732d25889da82b517", - "sha256:776e9f3c9b377fcf097c4a04b241b15691e6662d850168642ff976780609303c", - "sha256:85711eec2d875cd88c7eb40e734c4ca6d9ae477d6f26bd2b5bb4f7f60e41b156", - "sha256:87d1e4185c5d7187684d41ebb50c9aeaaaa06ca1875f4c57593071b0409d2444", - "sha256:8a3f063b41cc06e8d0b3fcbbfc9c05b7420f41287e0cd4f75ce0a1f3d80729e6", - "sha256:8b3fb28a9ac8f2558760d8e637dbf27aef1e8b7f1d221e8669a1074d1a266bb2", - "sha256:8bd9125dd0cc8ebd84bff2be64b10fdba7dc6fd7be431b5eaf67723557de3a31", - "sha256:8be1a65487bdfc285bd5e9baf3208c2132ca92a9b4020e9f27df1b16fab998a9", - "sha256:8cc0d13b4e3b1362d424ce3f4e8c79e1f7247a00d792823ffd640878abf28e56", - "sha256:8d9d10d10ec27c0d46ddaecc3c5598c4db9ce4e6398ca872cdde0525765caa2f", - "sha256:8debb45545ad95b58cc16c3c1cc19ad82cffcb106db12b437885dbee265f0ab5", - "sha256:91aa966858593f64c8a65cdefa3d6dc8fe3c2768b159da84c1ddbbb2c01ab4ef", - "sha256:9331dd34145ff105177855017920dde140b447049cd62bb589de320fd6ddd582", - "sha256:99f9678bf0e2b1b695e8028fedac24ab6770937932eda695815d5a6618c37e04", - "sha256:9fdf5c839bf95fc67be5794c780419edb0dbef776edcfc6c2e5e2ffd5ee755fa", - "sha256:a14e4b672c257a6b94fe934ee62666bacbc8e45b7876f9dd9502d0f0fe69db16", - "sha256:a19caae0d670771ea7854ca30df76f676eb47e0fd9b2ee4392d44708f272122d", - "sha256:a35ed3d03910785f7d9d6f5381f0c24002b2b888b298e6f941b2fc94c5055fcd", - "sha256:a61df62966ce6507aafab24e124e0c3a1cfbe23c59732987fc0fd0d71daa0b88", - "sha256:a6e00c8a92e7663ed2be6fcc08a2997ff06ce73c8080cd0df10cc0321a3168d7", - "sha256:ac3196952c673822ebed8871cf8802e17254fff2a2ed4835d9c045d9b88c5ec7", - "sha256:ac74e794e3aee92ae8f571bfeaa103a141e409863a100ab63a253b1c53b707eb", - "sha256:ad3675c126f2a95bde637d162f8231cff6bc0bc9fbe31bd78075f9ff7921e322", - "sha256:aeebd3061f6f1747c011e1d0b0b5f04f9f54ad1a2ca183e687e7277bef2e0da2", - "sha256:ba1a599255ad6a41022e261e31bc2f6f9355a419575b391f9655c4d9e5df5ff5", - "sha256:bbdb8def5268f3f9cd753a265756f49228a20ed14a480d151df727808b4531dd", - "sha256:c2555e4949c8d8782f18ef20e9d39730d2656e218a6f1a21a4c4c0b56546a02e", - "sha256:c2695c61cf53a5d4345a43d689f37fc0f6d3a2dc520660aec27ec0f06288d1f9", - "sha256:c2b627d3c8982691b06d89d31093cee158c30629fdfebe705a91814d49b554f8", - "sha256:c46131c6112b534b178d4e002abe450a0a29840b61413ac25243f1291613806a", - "sha256:c54dc329cd44f7f7883a9f4baaefe686e8b9662e2c6c184ea15cceee587d8d69", - "sha256:c7d7cafc11d70fdd8801abfc2ff276744ae4cb39d8060b6b542c7e44e5f2cfc2", - "sha256:cb0b2d5d51f96b6cc19e6ab46a7b684be23240426ae951dcdac9639ab111b45e", - "sha256:d15a29424e96fad56dc2f3abed10a89c50c099f97d2416520c7a543e8fddf066", - "sha256:d1f5c9169e26db6a61276008582d945405b8316aae2bb198220466e68114a0f5", - "sha256:d271f770b52e32236d945911b2082f9318e90ff835d45224fa9e28374303f729", - "sha256:d646fdd74c25bbdd4a055414f0fe32896c400f38ffbdfc78c68e62812a9e0257", - "sha256:d6e395c3d1f773cf0651cd3559e25182eb0c03a2777b53b4575d8adc1149c6e9", - "sha256:d7c071235a47d407b0e93aa6262b49422dbe48d7d8566e1158fecc91043dd948", - "sha256:d97273a52d7f89a75b11ec386f786d3da7723d7efae3034b4dda79f6f093edc1", - "sha256:dcf354661f54e6a49193d0b5653a1b011ba856e0b7a76bda2c33e4c6892f34ea", - "sha256:e3e7fabedb3fe06933f47f1538df7b3a8d78e13d7167195f51ca47ee12690373", - "sha256:e525b69ee8a92c146ae5b4da9ecd15e518df4d40003b01b454ad694a27f498b5", - "sha256:e709d6ac598c5416f879bb1bae3fd751366120ac3fa235a01de763537385d036", - "sha256:e83dfefb4f7d285c2d6a07a22268344a97d61579b3e0dce482a5be0251d672ab", - "sha256:e86260b76786c28acf0b5fe31c8dca4c2add95098c709b11e8c35b424ebd4f5b", - "sha256:e883b61b75ca6efc2541fcd52a5c8ccfe288b24d97e20ac08fdf343b8ac672ea", - "sha256:f0a44bb40b6aaa4fb9a5c1ee07880570ecda2065433a96ccff409c9c20c1624a", - "sha256:f82ace0ec57c94aaf5b0e118d4366cff5889097412c75aa14b4fd5fc0c44ee3e", - "sha256:f9ca09414003c0e96a735daa1f071f7d7ed06962ef4fa29ceb6c80d06696d900", - "sha256:fa430b871220dc62572cef9c69b41e0d70fcb9d486a4a207a5de4c1f25d82593", - "sha256:fc262c3df78c8ff6020c782d9ce02e4bcffe4900ad71c0ecdad59943cba54442", - "sha256:fcd546782d03181b0b1d20b43d612429a90a68779659ba8045114b867971ab71", - "sha256:fd4ceeae2fb8cabdd1b71c82bfdd39662473d3433ec95b962200e9e752fb70d0", - "sha256:fec5fac7aea6c060f317f07494961236434928e6f4374e170ef50b3001e14581" - ], - "markers": "python_version >= '3.8'", - "version": "==3.10.9" + "sha256:024409c1b1d6076d0ed933dcebd7e4fc6f3320a227bfa0c1b6b93a8b5a146f04", + "sha256:04b24497b3baf15035730de5f207ade88a67d4483a5f16ced7ece348933a5b47", + "sha256:08474e71772a516ba2e2167b4707af8361d2c452b3d8a5364c984f4867869499", + "sha256:0e7a0762cc29cd3acd01a4d2b547b3af7956ad230ebb80b529a8e4f3e4740fe8", + "sha256:104deb7873681273c5daa13c41924693df394043a118dae90387d35bc5531788", + "sha256:104ea21994b1403e4c1b398866f1187c1694fa291314ad7216ec1d8ec6b49f38", + "sha256:113bf06b029143e94a47c4f36e11a8b7e396e9d1f1fc8cea58e6b7e370cfed38", + "sha256:12071dd2cc95ba81e0f2737bebcb98b2a8656015e87772e84e8fb9e635b5da6e", + "sha256:170fb2324826bb9f08055a8291f42192ae5ee2f25b2966c8f0f4537c61d73a7b", + "sha256:21b4545e8d96870da9652930c5198366605ff8f982757030e2148cf341e5746b", + "sha256:229ae13959a5f499d90ffbb4b9eac2255d8599315027d6f7c22fa9803a94d5b1", + "sha256:2ec5efbc872b00ddd85e3904059d274f284cff314e13f48776050ca2c58f451d", + "sha256:31b91ff3a1fcb206a1fa76e0de1f08c9ffb1dc0deb7296fa2618adfe380fc676", + "sha256:329f5059e0bf6983dceebac8e6ed20e75eaff6163b3414f4a4cb59e0d7037672", + "sha256:37f8cf3c43f292d9bb3e6760476c2b55b9663a581fad682a586a410c43a7683e", + "sha256:3e1ed8d152cccceffb1ee7a2ac227c16372e453fb11b3aeaa56783049b85d3f6", + "sha256:3ed360d6672a9423aad39902a4e9fe305464d20ed7931dbdba30a4625782d875", + "sha256:40dc9446cff326672fcbf93efdb8ef7e949824de1097624efe4f61ac7f0d2c43", + "sha256:4d218d3eca40196384ad3b481309c56fd60e664128885d1734da0a8aa530d433", + "sha256:4e4e155968040e32c124a89852a1a5426d0e920a35f4331e1b3949037bfe93a3", + "sha256:4f698aa61879df64425191d41213dfd99efdc1627e6398e6d7aa5c312fac9702", + "sha256:508cfcc99534b1282595357592d8367b44392b21f6eb5d4dc021f8d0d809e94d", + "sha256:577c7429f8869fa30186fc2c9eee64d75a30b51b61f26aac9725866ae5985cfd", + "sha256:57e17c6d71f2dc857a8a1d09be1be7802e35d90fb4ba4b06cf1aab6414a57894", + "sha256:5ecc2fb1a0a9d48cf773add34196cddf7e488e48e9596e090849751bf43098f4", + "sha256:600b1d9f86a130131915e2f2127664311b33902c486b21a747d626f5144b4471", + "sha256:62502b8ffee8c6a4b5c6bf99d1de277d42bf51b2fb713975d9b63b560150b7ac", + "sha256:62a2f5268b672087c45b33479ba1bb1d5a48c6d76c133cfce3a4f77410c200d1", + "sha256:6362f50a6f0e5482c4330d2151cb682779230683da0e155c15ec9fc58cb50b6a", + "sha256:6533dd06df3d17d1756829b68b365b1583929b54082db8f65083a4184bf68322", + "sha256:6c5a6958f4366496004cf503d847093d464814543f157ef3b738bbf604232415", + "sha256:72cd984f7f14e8c01b3e38f18f39ea85dba84e52ea05e37116ba5e2a72eef396", + "sha256:76d6ee8bb132f8ee0fcb0e205b4708ddb6fba524eb515ee168113063d825131b", + "sha256:7867d0808614f04e78e0a8d5a2c1f8ac6bc626a0c0e2f62be48be6b749e2f8b2", + "sha256:7d664e5f937c08adb7908ea9f391fbf2928a9b09cb412ac0aba602bde9e499e4", + "sha256:85ae6f182be72c3531915e90625cc65afce4df8a0fc4988bd52d8a5d5faaeb68", + "sha256:89a96a0696dc67d548f69cb518c581a7a33cc1f26ab42229dea1709217c9d926", + "sha256:8b323b5d3aef7dd811424c269322eec58a977c0c8152e650159e47210d900504", + "sha256:8c47a0ba6c2b3d3e5715f8338d657badd21f778c6be16701922c65521c5ecfc9", + "sha256:8fef105113d56e817cb9bcc609667ee461321413a7b972b03f5b4939f40f307c", + "sha256:900ff74d78eb580ae4aa5883242893b123a0c442a46570902500f08d6a7e6696", + "sha256:9095580806d9ed07c0c29b23364a0b1fb78258ef9f4bddf7e55bac0e475d4edf", + "sha256:91d3991fad8b65e5dbc13cd95669ea689fe0a96ff63e4e64ac24ed724e4f8103", + "sha256:9231d610754724273a6ac05a1f177979490bfa6f84d49646df3928af2e88cfd5", + "sha256:97056d3422594e0787733ac4c45bef58722d452f4dc6615fee42f59fe51707dd", + "sha256:a896059b6937d1a22d8ee8377cdcd097bd26cd8c653b8f972051488b9baadee9", + "sha256:aabc4e92cb153636d6be54e84dad1b252ddb9aebe077942b6dcffe5e468d476a", + "sha256:ad14cdc0fba4df31c0f6e06c21928c5b924725cbf60d0ccc5f6e7132636250e9", + "sha256:ae36ae52b0c22fb69fb8b744eff82a20db512a29eafc6e3a4ab43b17215b219d", + "sha256:b3e4fb7f5354d39490d8209aefdf5830b208d01c7293a2164e404312c3d8bc55", + "sha256:b40c304ab01e89ad0aeeecf91bbaa6ae3b00e27b796c9e8d50b71a4a7e885cc8", + "sha256:b7349205bb163318dcc102329d30be59a647a3d24c82c3d91ed35b7e7301ea7e", + "sha256:b8b95a63a8e8b5f0464bd8b1b0d59d2bec98a59b6aacc71e9be23df6989b3dfb", + "sha256:bb2e82e515e268b965424ecabebd91834a41b36260b6ef5db015ee12ddb28ef3", + "sha256:c0315978b2a4569e03fb59100f6a7e7d23f718a4521491f5c13d946d37549f3d", + "sha256:c1828e10c3a49e2b234b87600ecb68a92b8a8dcf8b99bca9447f16c4baaa1630", + "sha256:c1c49bc393d854d4421ebc174a0a41f9261f50d3694d8ca277146cbbcfd24ee7", + "sha256:c415b9601ff50709d6050c8a9281733a9b042b9e589265ac40305b875cf9c463", + "sha256:c54c635d1f52490cde7ef3a423645167a8284e452a35405d5c7dc1242a8e75c9", + "sha256:c5e6a1f8b0268ffa1c84d7c3558724956002ba8361176e76406233e704bbcffb", + "sha256:c98a596ac20e8980cc6f34c0c92a113e98eb08f3997c150064d26d2aeb043e5a", + "sha256:cd0834e4260eab78671b81d34f110fbaac449563e48d419cec0030d9a8e58693", + "sha256:cdad66685fcf2ad14ce522cf849d4a025f4fd206d6cfc3f403d9873e4c243b03", + "sha256:d1ea006426edf7e1299c52a58b0443158012f7a56fed3515164b60bfcb1503a9", + "sha256:d33b4490026968bdc7f0729b9d87a3a6b1e09043557d2fc1c605c6072deb2f11", + "sha256:d5cae4cd271e20b7ab757e966cc919186b9f02535418ab36c471a5377ef4deaa", + "sha256:dd505a1121ad5b666191840b7bd1d8cb917df2647deeca6f3474331b72452362", + "sha256:e1668ef2f3a7ec9881f4b6a917e5f97c87a343fa6b0d5fc826b7b0297ddd0887", + "sha256:e7bcfcede95531589295f56e924702cef7f9685c9e4e5407592e04ded6a65bf3", + "sha256:ebf610c37df4f09c71c9bbf8309b4b459107e6fe889ac0d7e16f6e4ebd975f86", + "sha256:f3bf5c132eb48002bcc3825702d241d35b4e9585009e65e9dcf9c4635d0b7424", + "sha256:f40380c96dd407dfa84eb2d264e68aa47717b53bdbe210a59cc3c35a4635f195", + "sha256:f57a0de48dda792629e7952d34a0c7b81ea336bb9b721391c7c58145b237fe55", + "sha256:f6b925c7775ab857bdc1e52e1f5abcae7d18751c09b751aeb641a5276d9b990e", + "sha256:f8f0d79b923070f25674e4ea8f3d61c9d89d24d9598d50ff32c5b9b23c79a25b", + "sha256:feca9fafa4385aea6759c171cd25ea82f7375312fca04178dae35331be45e538" + ], + "markers": "python_version >= '3.9'", + "version": "==3.11.0" }, "aiosignal": { "hashes": [ @@ -139,11 +124,11 @@ }, "anyio": { "hashes": [ - "sha256:137b4559cbb034c477165047febb6ff83f390fc3b20bf181c1fc0a728cb8beeb", - "sha256:c7d2e9d63e31599eeb636c8c5c03a7e108d73b345f064f1c19fdc87b79036a9a" + "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", + "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d" ], "markers": "python_version >= '3.9'", - "version": "==4.6.0" + "version": "==4.6.2.post1" }, "asgi-correlation-id": { "hashes": [ @@ -173,11 +158,19 @@ }, "bytecode": { "hashes": [ - "sha256:0a1dc340cac823cff605609b8b214f7f9bf80418c6b9e0fc8c6db1793c27137d", - "sha256:7263239a8d3f70fc7c303862b20cd2c6788052e37ce0a26e67309d280e985984" + "sha256:06676a3c3bccc9d3dc73ee625650ea57df2bc117358826f4f290f0e1faa42292", + "sha256:76080b7c0eb9e7e17f961d61fd06e933aa47f3b753770a3249537439d8203a25" ], "markers": "python_version >= '3.12'", - "version": "==0.15.1" + "version": "==0.16.0" + }, + "cachetools": { + "hashes": [ + "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", + "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a" + ], + "markers": "python_version >= '3.7'", + "version": "==5.5.0" }, "cattrs": { "hashes": [ @@ -270,99 +263,114 @@ }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "click": { "hashes": [ @@ -374,35 +382,35 @@ }, "cryptography": { "hashes": [ - "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", - "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", - "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", - "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", - "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", - "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", - "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", - "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", - "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", - "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", - "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", - "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", - "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2", - "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", - "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", - "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365", - "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96", - "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", - "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", - "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", - "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", - "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", - "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", - "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172", - "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", - "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", - "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" - ], - "version": "==43.0.1" + "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", + "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", + "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", + "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", + "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", + "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", + "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", + "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", + "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", + "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", + "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", + "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", + "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", + "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", + "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", + "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", + "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", + "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", + "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", + "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", + "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", + "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", + "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", + "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", + "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", + "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", + "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + ], + "version": "==43.0.3" }, "dataclasses-json": { "hashes": [ @@ -528,11 +536,11 @@ }, "envier": { "hashes": [ - "sha256:4e7e398cb09a8dd360508ef7e12511a152355426d2544b8487a34dad27cc20ad", - "sha256:65099cf3aa9b3b3b4b92db2f7d29e2910672e085b76f7e587d2167561a834add" + "sha256:3309a01bb3d8850c9e7a31a5166d5a836846db2faecb79b9cb32654dd50ca9f9", + "sha256:73609040a76be48bbcb97074d9969666484aa0de706183a6e9ef773156a8a6a9" ], "markers": "python_version >= '3.7'", - "version": "==0.5.2" + "version": "==0.6.1" }, "fastapi": { "hashes": [ @@ -553,94 +561,127 @@ }, "frozenlist": { "hashes": [ - "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7", - "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98", - "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad", - "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5", - "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae", - "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e", - "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a", - "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701", - "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d", - "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6", - "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6", - "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106", - "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75", - "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868", - "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a", - "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0", - "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1", - "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826", - "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec", - "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6", - "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950", - "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19", - "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0", - "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8", - "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a", - "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09", - "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86", - "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c", - "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5", - "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b", - "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b", - "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d", - "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0", - "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea", - "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776", - "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a", - "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897", - "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7", - "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09", - "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9", - "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe", - "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd", - "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742", - "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09", - "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0", - "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932", - "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1", - "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a", - "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49", - "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d", - "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7", - "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480", - "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89", - "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e", - "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b", - "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82", - "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb", - "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068", - "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8", - "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b", - "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb", - "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2", - "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11", - "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b", - "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc", - "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0", - "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497", - "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17", - "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0", - "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2", - "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439", - "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5", - "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac", - "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825", - "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887", - "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced", - "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74" - ], - "markers": "python_version >= '3.8'", - "version": "==1.4.1" + "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", + "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf", + "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6", + "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a", + "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d", + "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f", + "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", + "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", + "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", + "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", + "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec", + "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2", + "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c", + "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336", + "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4", + "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d", + "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b", + "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c", + "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10", + "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08", + "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942", + "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", + "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f", + "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10", + "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5", + "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", + "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", + "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", + "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d", + "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923", + "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", + "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", + "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17", + "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0", + "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", + "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", + "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c", + "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a", + "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0", + "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", + "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab", + "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", + "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3", + "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", + "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", + "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604", + "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", + "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5", + "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", + "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", + "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", + "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", + "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d", + "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", + "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3", + "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", + "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", + "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9", + "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf", + "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76", + "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba", + "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171", + "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb", + "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", + "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", + "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972", + "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d", + "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869", + "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9", + "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411", + "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723", + "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2", + "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b", + "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99", + "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e", + "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", + "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3", + "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb", + "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", + "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", + "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca", + "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45", + "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", + "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f", + "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5", + "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307", + "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e", + "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2", + "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778", + "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a", + "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30", + "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a" + ], + "markers": "python_version >= '3.8'", + "version": "==1.5.0" }, "fsspec": { "hashes": [ - "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8", - "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b" + "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", + "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493" ], "markers": "python_version >= '3.8'", - "version": "==2024.9.0" + "version": "==2024.10.0" + }, + "google-auth": { + "hashes": [ + "sha256:51a15d47028b66fd36e5c64a82d2d57480075bccc7da37cde257fc94177a61fb", + "sha256:545e9618f2df0bcbb7dcbc45a546485b1212624716975a1ea5ae8149ce769ab1" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==2.36.0" + }, + "google-auth-oauthlib": { + "hashes": [ + "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f", + "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263" + ], + "index": "pypi", + "markers": "python_version >= '3.6'", + "version": "==1.2.1" }, "greenlet": { "hashes": [ @@ -721,6 +762,15 @@ "markers": "python_version < '3.13' and platform_machine == 'aarch64' or (platform_machine == 'ppc64le' or (platform_machine == 'x86_64' or (platform_machine == 'amd64' or (platform_machine == 'AMD64' or (platform_machine == 'win32' or platform_machine == 'WIN32')))))", "version": "==3.1.1" }, + "gspread": { + "hashes": [ + "sha256:b8eec27de7cadb338bb1b9f14a9be168372dee8965c0da32121816b5050ac1de", + "sha256:c34781c426031a243ad154952b16f21ac56a5af90687885fbee3d1fba5280dcd" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==6.1.4" + }, "h11": { "hashes": [ "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", @@ -739,44 +789,51 @@ }, "httptools": { "hashes": [ - "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563", - "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142", - "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d", - "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b", - "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4", - "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb", - "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658", - "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084", - "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2", - "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97", - "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837", - "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3", - "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58", - "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da", - "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d", - "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90", - "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0", - "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1", - "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2", - "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e", - "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0", - "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf", - "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc", - "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3", - "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503", - "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a", - "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3", - "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949", - "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84", - "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb", - "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a", - "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f", - "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e", - "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81", - "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185", - "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3" - ], - "version": "==0.6.1" + "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", + "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd", + "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", + "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", + "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", + "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", + "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", + "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da", + "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0", + "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", + "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", + "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff", + "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", + "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", + "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", + "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4", + "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", + "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9", + "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", + "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", + "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003", + "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959", + "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc", + "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076", + "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490", + "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", + "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6", + "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c", + "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50", + "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547", + "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba", + "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440", + "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", + "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab", + "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", + "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1", + "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", + "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f", + "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", + "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", + "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", + "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", + "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43" + ], + "version": "==0.6.4" }, "httpx": { "hashes": [ @@ -797,11 +854,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", - "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5" + "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", + "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7" ], "markers": "python_version >= '3.8'", - "version": "==8.4.0" + "version": "==8.5.0" }, "jinja2": { "hashes": [ @@ -878,11 +935,11 @@ }, "llama-index-legacy": { "hashes": [ - "sha256:04221320d84d96ba9ee3e21e5055bd8527cbd769e8f1c60cf0368ed907e012a2", - "sha256:f6969f1085efb0abebd6367e46f3512020f3f6b9c086f458a519830dd61e8206" + "sha256:4b817d7c343fb5f7f00c4410eff519f320013b8d5f24c4fedcf270c471f92038", + "sha256:f8a9764e7e134a52bfef5e53d2d62561bfc01fc09874c51cc001df6f5302ae30" ], "markers": "python_version < '4.0' and python_full_version >= '3.8.1'", - "version": "==0.9.48.post3" + "version": "==0.9.48.post4" }, "llama-index-llms-openai": { "hashes": [ @@ -958,78 +1015,78 @@ }, "markupsafe": { "hashes": [ - "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396", - "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38", - "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a", - "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8", - "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b", - "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad", - "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a", - "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a", - "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da", - "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6", - "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8", - "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344", - "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a", - "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8", - "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5", - "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7", - "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170", - "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132", - "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9", - "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd", - "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9", - "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346", - "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc", - "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589", - "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5", - "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915", - "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295", - "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453", - "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea", - "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b", - "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d", - "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b", - "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4", - "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b", - "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7", - "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf", - "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f", - "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91", - "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd", - "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50", - "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b", - "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583", - "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a", - "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984", - "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c", - "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c", - "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25", - "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa", - "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4", - "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3", - "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97", - "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1", - "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd", - "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772", - "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a", - "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729", - "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca", - "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6", - "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635", - "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b", - "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f" + "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", + "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", + "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", + "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", + "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", + "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", + "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", + "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", + "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", + "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", + "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", + "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", + "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", + "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", + "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", + "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", + "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", + "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", + "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", + "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", + "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", + "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", + "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", + "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", + "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", + "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", + "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", + "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", + "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", + "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", + "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", + "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", + "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", + "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", + "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", + "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", + "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", + "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", + "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", + "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", + "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", + "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", + "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", + "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", + "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", + "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", + "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", + "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", + "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", + "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", + "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", + "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", + "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", + "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", + "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", + "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", + "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", + "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", + "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", + "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", + "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], "markers": "python_version >= '3.9'", - "version": "==3.0.1" + "version": "==3.0.2" }, "marshmallow": { "hashes": [ - "sha256:4972f529104a220bb8637d595aa4c9762afbe7f7a77d82dc58c1615d70c5823e", - "sha256:71a2dce49ef901c3f97ed296ae5051135fd3febd2bf43afe0ae9a82143a494d9" + "sha256:3a8dfda6edd8dcdbf216c0ede1d1e78d230a6dc9c5a088f58c4083b974a0d468", + "sha256:fece2eb2c941180ea1b7fcbd4a83c51bfdd50093fdd3ad2585ee5e1df2508491" ], - "markers": "python_version >= '3.8'", - "version": "==3.22.0" + "markers": "python_version >= '3.9'", + "version": "==3.23.1" }, "mdurl": { "hashes": [ @@ -1155,11 +1212,11 @@ }, "networkx": { "hashes": [ - "sha256:0c127d8b2f4865f59ae9cb8aafcd60b5c70f3241ebd66f7defad7c4ab90126c9", - "sha256:28575580c6ebdaf4505b22c6256a2b9de86b316dc63ba9e93abde3d78dfdbcf2" + "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", + "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f" ], "markers": "python_version >= '3.10'", - "version": "==3.3" + "version": "==3.4.2" }, "nltk": { "hashes": [ @@ -1211,6 +1268,14 @@ "markers": "python_version >= '3.9'", "version": "==1.26.4" }, + "oauthlib": { + "hashes": [ + "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", + "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" + ], + "markers": "python_version >= '3.6'", + "version": "==3.2.2" + }, "openai": { "hashes": [ "sha256:aa2f381f476f5fa4df8728a34a3e454c321caa064b7b68ab6e9daa1ed082dbf9", @@ -1222,82 +1287,83 @@ }, "opentelemetry-api": { "hashes": [ - "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7", - "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342" + "sha256:6fa7295a12c707f5aebef82da3d9ec5afe6992f3e42bfe7bec0339a44b3518e7", + "sha256:bfe86c95576cf19a914497f439fd79c9553a38de0adbdc26f7cfc46b0c00b16c" ], "markers": "python_version >= '3.8'", - "version": "==1.27.0" + "version": "==1.28.1" }, "orjson": { "hashes": [ - "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23", - "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9", - "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5", - "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad", - "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98", - "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412", - "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1", - "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864", - "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6", - "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91", - "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac", - "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c", - "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1", - "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f", - "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250", - "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09", - "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0", - "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225", - "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354", - "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f", - "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e", - "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469", - "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c", - "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12", - "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3", - "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3", - "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149", - "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb", - "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2", - "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2", - "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f", - "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0", - "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a", - "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58", - "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe", - "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09", - "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e", - "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2", - "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c", - "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313", - "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6", - "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93", - "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7", - "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866", - "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c", - "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b", - "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5", - "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175", - "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9", - "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0", - "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff", - "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20", - "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5", - "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960", - "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024", - "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd", - "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84" - ], - "markers": "python_version >= '3.8'", - "version": "==3.10.7" + "sha256:03246774131701de8e7059b2e382597da43144a9a7400f178b2a32feafc54bd5", + "sha256:0efabbf839388a1dab5b72b5d3baedbd6039ac83f3b55736eb9934ea5494d258", + "sha256:10f416b2a017c8bd17f325fb9dee1fb5cdd7a54e814284896b7c3f2763faa017", + "sha256:1444f9cb7c14055d595de1036f74ecd6ce15f04a715e73f33bb6326c9cef01b6", + "sha256:1789d9db7968d805f3d94aae2c25d04014aae3a2fa65b1443117cd462c6da647", + "sha256:19b3763e8bbf8ad797df6b6b5e0fc7c843ec2e2fc0621398534e0c6400098f87", + "sha256:1a1222ffcee8a09476bbdd5d4f6f33d06d0d6642df2a3d78b7a195ca880d669b", + "sha256:1be83a13312e5e58d633580c5eb8d0495ae61f180da2722f20562974188af205", + "sha256:1f39728c7f7d766f1f5a769ce4d54b5aaa4c3f92d5b84817053cc9995b977acc", + "sha256:360a4e2c0943da7c21505e47cf6bd725588962ff1d739b99b14e2f7f3545ba51", + "sha256:461311b693d3d0a060439aa669c74f3603264d4e7a08faa68c47ae5a863f352d", + "sha256:496e2cb45de21c369079ef2d662670a4892c81573bcc143c4205cae98282ba97", + "sha256:4bfb30c891b530f3f80e801e3ad82ef150b964e5c38e1fb8482441c69c35c61c", + "sha256:4d83f87582d223e54efb2242a79547611ba4ebae3af8bae1e80fa9a0af83bb7f", + "sha256:4eed32f33a0ea6ef36ccc1d37f8d17f28a1d6e8eefae5928f76aff8f1df85e67", + "sha256:51f3382415747e0dbda9dade6f1e1a01a9d37f630d8c9049a8ed0e385b7a90c0", + "sha256:52ca832f17d86a78cbab86cdc25f8c13756ebe182b6fc1a97d534051c18a08de", + "sha256:52e5834d7d6e58a36846e059d00559cb9ed20410664f3ad156cd2cc239a11230", + "sha256:5576b1e5a53a5ba8f8df81872bb0878a112b3ebb1d392155f00f54dd86c83ff6", + "sha256:63fc9d5fe1d4e8868f6aae547a7b8ba0a2e592929245fff61d633f4caccdcdd6", + "sha256:655a493bac606655db9a47fe94d3d84fc7f3ad766d894197c94ccf0c5408e7d3", + "sha256:65cd3e3bb4fbb4eddc3c1e8dce10dc0b73e808fcb875f9fab40c81903dd9323e", + "sha256:677f23e32491520eebb19c99bb34675daf5410c449c13416f7f0d93e2cf5f981", + "sha256:6dade64687f2bd7c090281652fe18f1151292d567a9302b34c2dbb92a3872f1f", + "sha256:6f67c570602300c4befbda12d153113b8974a3340fdcf3d6de095ede86c06d92", + "sha256:705f03cee0cb797256d54de6695ef219e5bc8c8120b6654dd460848d57a9af3d", + "sha256:77b0fed6f209d76c1c39f032a70df2d7acf24b1812ca3e6078fd04e8972685a3", + "sha256:7dfa8db55c9792d53c5952900c6a919cfa377b4f4534c7a786484a6a4a350c19", + "sha256:80c00d4acded0c51c98754fe8218cb49cb854f0f7eb39ea4641b7f71732d2cb7", + "sha256:80df27dd8697242b904f4ea54820e2d98d3f51f91e97e358fc13359721233e4b", + "sha256:82f07c550a6ccd2b9290849b22316a609023ed851a87ea888c0456485a7d196a", + "sha256:86b9dd983857970c29e4c71bb3e95ff085c07d3e83e7c46ebe959bac07ebd80b", + "sha256:8b5759063a6c940a69c728ea70d7c33583991c6982915a839c8da5f957e0103a", + "sha256:96ed1de70fcb15d5fed529a656df29f768187628727ee2788344e8a51e1c1350", + "sha256:9fd0ad1c129bc9beb1154c2655f177620b5beaf9a11e0d10bac63ef3fce96950", + "sha256:a11225d7b30468dcb099498296ffac36b4673a8398ca30fdaec1e6c20df6aa55", + "sha256:a2fc947e5350fdce548bfc94f434e8760d5cafa97fb9c495d2fef6757aa02ec0", + "sha256:a3f29634260708c200c4fe148e42b4aae97d7b9fee417fbdd74f8cfc265f15b0", + "sha256:afacfd1ab81f46dedd7f6001b6d4e8de23396e4884cd3c3436bd05defb1a6446", + "sha256:b592597fe551d518f42c5a2eb07422eb475aa8cfdc8c51e6da7054b836b26782", + "sha256:b7fcfc6f7ca046383fb954ba528587e0f9336828b568282b27579c49f8e16aad", + "sha256:b9546b278c9fb5d45380f4809e11b4dd9844ca7aaf1134024503e134ed226161", + "sha256:bc274ac261cc69260913b2d1610760e55d3c0801bb3457ba7b9004420b6b4270", + "sha256:bd9a187742d3ead9df2e49240234d728c67c356516cf4db018833a86f20ec18c", + "sha256:c46294faa4e4d0eb73ab68f1a794d2cbf7bab33b1dda2ac2959ffb7c61591899", + "sha256:c95f2ecafe709b4e5c733b5e2768ac569bed308623c85806c395d9cca00e08af", + "sha256:cb4d0bea56bba596723d73f074c420aec3b2e5d7d30698bc56e6048066bd560c", + "sha256:cdec57fe3b4bdebcc08a946db3365630332dbe575125ff3d80a3272ebd0ddafe", + "sha256:d496c74fc2b61341e3cefda7eec21b7854c5f672ee350bc55d9a4997a8a95204", + "sha256:d4a62c49c506d4d73f59514986cadebb7e8d186ad510c518f439176cf8d5359d", + "sha256:df8c677df2f9f385fcc85ab859704045fa88d4668bc9991a527c86e710392bec", + "sha256:dfbb2d460a855c9744bbc8e36f9c3a997c4b27d842f3d5559ed54326e6911f9b", + "sha256:e2f3b7c5803138e67028dde33450e054c87e0703afbe730c105f1fcd873496d5", + "sha256:e35b6d730de6384d5b2dab5fd23f0d76fae8bbc8c353c2f78210aa5fa4beb3ef", + "sha256:f1eec3421a558ff7a9b010a6c7effcfa0ade65327a71bb9b02a1c3b77a247284", + "sha256:f35a1b9f50a219f470e0e497ca30b285c9f34948d3c8160d5ad3a755d9299433", + "sha256:f4c57ea78a753812f528178aa2f1c57da633754c91d2124cb28991dab4c79a54", + "sha256:f91d9eb554310472bd09f5347950b24442600594c2edc1421403d7610a0998fd" + ], + "markers": "python_version >= '3.8'", + "version": "==3.10.11" }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==24.2" }, "pandas": { "hashes": [ @@ -1349,89 +1415,84 @@ }, "pillow": { "hashes": [ - "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", - "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", - "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", - "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", - "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", - "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", - "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", - "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", - "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", - "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", - "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", - "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", - "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", - "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", - "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", - "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", - "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", - "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", - "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", - "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", - "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", - "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", - "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", - "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", - "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", - "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", - "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", - "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", - "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", - "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", - "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", - "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", - "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", - "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", - "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", - "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", - "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", - "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", - "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", - "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", - "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", - "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", - "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", - "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", - "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", - "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", - "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", - "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", - "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", - "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", - "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", - "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", - "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", - "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", - "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", - "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", - "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", - "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", - "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", - "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", - "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", - "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", - "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", - "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", - "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", - "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", - "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", - "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", - "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", - "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", - "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", - "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", - "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", - "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", - "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", - "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", - "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", - "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", - "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", - "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" - ], - "markers": "python_version >= '3.8'", - "version": "==10.4.0" + "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7", + "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5", + "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903", + "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2", + "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38", + "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2", + "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9", + "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f", + "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc", + "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8", + "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d", + "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2", + "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316", + "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a", + "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25", + "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd", + "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba", + "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc", + "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273", + "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa", + "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a", + "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b", + "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a", + "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae", + "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291", + "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97", + "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06", + "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904", + "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b", + "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b", + "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8", + "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527", + "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947", + "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb", + "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003", + "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5", + "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f", + "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739", + "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944", + "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830", + "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f", + "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3", + "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4", + "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84", + "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7", + "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6", + "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6", + "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9", + "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de", + "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4", + "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47", + "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd", + "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50", + "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c", + "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086", + "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba", + "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306", + "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699", + "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e", + "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488", + "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa", + "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2", + "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3", + "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9", + "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923", + "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2", + "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790", + "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734", + "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916", + "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1", + "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f", + "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798", + "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb", + "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2", + "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9" + ], + "markers": "python_version >= '3.9'", + "version": "==11.0.0" }, "propcache": { "hashes": [ @@ -1539,20 +1600,36 @@ }, "protobuf": { "hashes": [ - "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132", - "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f", - "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece", - "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0", - "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f", - "sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0", - "sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276", - "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7", - "sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3", - "sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36", - "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d" + "sha256:0c4eec6f987338617072592b97943fdbe30d019c56126493111cf24344c1cc24", + "sha256:135658402f71bbd49500322c0f736145731b16fc79dc8f367ab544a17eab4535", + "sha256:27b246b3723692bf1068d5734ddaf2fccc2cdd6e0c9b47fe099244d80200593b", + "sha256:3e6101d095dfd119513cde7259aa703d16c6bbdfae2554dfe5cfdbe94e32d548", + "sha256:3fa2de6b8b29d12c61911505d893afe7320ce7ccba4df913e2971461fa36d584", + "sha256:64badbc49180a5e401f373f9ce7ab1d18b63f7dd4a9cdc43c92b9f0b481cef7b", + "sha256:70585a70fc2dd4818c51287ceef5bdba6387f88a578c86d47bb34669b5552c36", + "sha256:712319fbdddb46f21abb66cd33cb9e491a5763b2febd8f228251add221981135", + "sha256:91fba8f445723fcf400fdbe9ca796b19d3b1242cd873907979b9ed71e4afe868", + "sha256:a3f6857551e53ce35e60b403b8a27b0295f7d6eb63d10484f12bc6879c715687", + "sha256:cee1757663fa32a1ee673434fcf3bf24dd54763c79690201208bafec62f19eed" + ], + "markers": "python_version >= '3.8'", + "version": "==5.28.3" + }, + "pyasn1": { + "hashes": [ + "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", + "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034" + ], + "markers": "python_version >= '3.8'", + "version": "==0.6.1" + }, + "pyasn1-modules": { + "hashes": [ + "sha256:49bfa96b45a292b711e986f222502c1c9a5e1f4e568fc30e2574a6c7d07838fd", + "sha256:c28e2dbf9c06ad61c71a075c7e0f9fd0f1b0bb2d2ad4377f240d33ac2ab60a7c" ], "markers": "python_version >= '3.8'", - "version": "==5.28.2" + "version": "==0.4.1" }, "pycparser": { "hashes": [ @@ -1711,11 +1788,11 @@ }, "python-multipart": { "hashes": [ - "sha256:045e1f98d719c1ce085ed7f7e1ef9d8ccc8c02ba02b5566d5f7521410ced58cb", - "sha256:43dcf96cf65888a9cd3423544dd0d75ac10f7aa0c3c28a175bbcd00c9ce1aebf" + "sha256:15dc4f487e0a9476cc1201261188ee0940165cffc94429b6fc565c4d3045cb5d", + "sha256:41330d831cae6e2f22902704ead2826ea038d0419530eadff3ea80175aec5538" ], "markers": "python_version >= '3.8'", - "version": "==0.0.12" + "version": "==0.0.17" }, "pytz": { "hashes": [ @@ -1785,103 +1862,103 @@ }, "regex": { "hashes": [ - "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623", - "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199", - "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664", - "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f", - "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca", - "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066", - "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca", - "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39", - "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d", - "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6", - "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35", - "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408", - "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5", - "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a", - "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9", - "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92", - "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766", - "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168", - "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca", - "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508", - "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df", - "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf", - "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b", - "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4", - "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268", - "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6", - "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c", - "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62", - "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231", - "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36", - "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba", - "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4", - "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e", - "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822", - "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4", - "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d", - "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71", - "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50", - "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d", - "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad", - "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8", - "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8", - "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8", - "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd", - "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16", - "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664", - "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a", - "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f", - "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd", - "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a", - "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9", - "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199", - "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d", - "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963", - "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009", - "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a", - "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679", - "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96", - "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42", - "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8", - "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e", - "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7", - "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8", - "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802", - "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366", - "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137", - "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784", - "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29", - "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3", - "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771", - "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60", - "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a", - "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4", - "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0", - "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84", - "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd", - "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1", - "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776", - "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142", - "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89", - "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c", - "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8", - "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35", - "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a", - "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86", - "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9", - "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64", - "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554", - "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85", - "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb", - "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0", - "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8", - "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb", - "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919" - ], - "markers": "python_version >= '3.8'", - "version": "==2024.9.11" + "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c", + "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", + "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d", + "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", + "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67", + "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773", + "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", + "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef", + "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", + "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", + "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", + "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", + "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", + "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", + "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e", + "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", + "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", + "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", + "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e", + "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", + "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", + "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", + "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0", + "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", + "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b", + "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", + "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd", + "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57", + "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", + "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", + "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f", + "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b", + "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519", + "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", + "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", + "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", + "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b", + "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839", + "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", + "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf", + "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", + "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0", + "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f", + "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95", + "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4", + "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", + "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13", + "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", + "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", + "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008", + "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9", + "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc", + "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48", + "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", + "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", + "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", + "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf", + "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b", + "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd", + "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", + "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", + "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", + "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", + "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", + "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3", + "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983", + "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e", + "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", + "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", + "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", + "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467", + "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", + "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001", + "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", + "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", + "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", + "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf", + "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6", + "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e", + "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde", + "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62", + "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df", + "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", + "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5", + "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86", + "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2", + "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2", + "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", + "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c", + "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f", + "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", + "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2", + "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", + "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91" + ], + "markers": "python_version >= '3.8'", + "version": "==2024.11.6" }, "requests": { "hashes": [ @@ -1891,21 +1968,37 @@ "markers": "python_version >= '3.8'", "version": "==2.32.3" }, + "requests-oauthlib": { + "hashes": [ + "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", + "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9" + ], + "markers": "python_version >= '3.4'", + "version": "==2.0.0" + }, "rich": { "hashes": [ - "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", - "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1" + "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", + "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" ], "markers": "python_full_version >= '3.8.0'", - "version": "==13.9.2" + "version": "==13.9.4" + }, + "rsa": { + "hashes": [ + "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", + "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" + ], + "markers": "python_version >= '3.6' and python_version < '4'", + "version": "==4.9" }, "setuptools": { "hashes": [ - "sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2", - "sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538" + "sha256:5c4ccb41111392671f02bb5f8436dfc5a9a7185e80500531b133f5775c4163ef", + "sha256:87cb777c3b96d638ca02031192d40390e0ad97737e27b6b4fa831bea86f2f829" ], "markers": "python_version >= '3.12'", - "version": "==75.1.0" + "version": "==75.5.0" }, "shellingham": { "hashes": [ @@ -1944,58 +2037,66 @@ "asyncio" ], "hashes": [ - "sha256:016b2e665f778f13d3c438651dd4de244214b527a275e0acf1d44c05bc6026a9", - "sha256:032d979ce77a6c2432653322ba4cbeabf5a6837f704d16fa38b5a05d8e21fa00", - "sha256:0375a141e1c0878103eb3d719eb6d5aa444b490c96f3fedab8471c7f6ffe70ee", - "sha256:042622a5306c23b972192283f4e22372da3b8ddf5f7aac1cc5d9c9b222ab3ff6", - "sha256:05c3f58cf91683102f2f0265c0db3bd3892e9eedabe059720492dbaa4f922da1", - "sha256:0630774b0977804fba4b6bbea6852ab56c14965a2b0c7fc7282c5f7d90a1ae72", - "sha256:0f9f3f9a3763b9c4deb8c5d09c4cc52ffe49f9876af41cc1b2ad0138878453cf", - "sha256:1b56961e2d31389aaadf4906d453859f35302b4eb818d34a26fab72596076bb8", - "sha256:22b83aed390e3099584b839b93f80a0f4a95ee7f48270c97c90acd40ee646f0b", - "sha256:25b0f63e7fcc2a6290cb5f7f5b4fc4047843504983a28856ce9b35d8f7de03cc", - "sha256:2a275a806f73e849e1c309ac11108ea1a14cd7058577aba962cd7190e27c9e3c", - "sha256:2ab3f0336c0387662ce6221ad30ab3a5e6499aab01b9790879b6578fd9b8faa1", - "sha256:2e795c2f7d7249b75bb5f479b432a51b59041580d20599d4e112b5f2046437a3", - "sha256:3655af10ebcc0f1e4e06c5900bb33e080d6a1fa4228f502121f28a3b1753cde5", - "sha256:4668bd8faf7e5b71c0319407b608f278f279668f358857dbfd10ef1954ac9f90", - "sha256:4c31943b61ed8fdd63dfd12ccc919f2bf95eefca133767db6fbbd15da62078ec", - "sha256:4fdcd72a789c1c31ed242fd8c1bcd9ea186a98ee8e5408a50e610edfef980d71", - "sha256:627dee0c280eea91aed87b20a1f849e9ae2fe719d52cbf847c0e0ea34464b3f7", - "sha256:67219632be22f14750f0d1c70e62f204ba69d28f62fd6432ba05ab295853de9b", - "sha256:6921ee01caf375363be5e9ae70d08ce7ca9d7e0e8983183080211a062d299468", - "sha256:69683e02e8a9de37f17985905a5eca18ad651bf592314b4d3d799029797d0eb3", - "sha256:6a93c5a0dfe8d34951e8a6f499a9479ffb9258123551fa007fc708ae2ac2bc5e", - "sha256:732e026240cdd1c1b2e3ac515c7a23820430ed94292ce33806a95869c46bd139", - "sha256:7befc148de64b6060937231cbff8d01ccf0bfd75aa26383ffdf8d82b12ec04ff", - "sha256:890da8cd1941fa3dab28c5bac3b9da8502e7e366f895b3b8e500896f12f94d11", - "sha256:89b64cd8898a3a6f642db4eb7b26d1b28a497d4022eccd7717ca066823e9fb01", - "sha256:8a6219108a15fc6d24de499d0d515c7235c617b2540d97116b663dade1a54d62", - "sha256:8cdf1a0dbe5ced887a9b127da4ffd7354e9c1a3b9bb330dce84df6b70ccb3a8d", - "sha256:8d625eddf7efeba2abfd9c014a22c0f6b3796e0ffb48f5d5ab106568ef01ff5a", - "sha256:93a71c8601e823236ac0e5d087e4f397874a421017b3318fd92c0b14acf2b6db", - "sha256:9509c4123491d0e63fb5e16199e09f8e262066e58903e84615c301dde8fa2e87", - "sha256:a29762cd3d116585278ffb2e5b8cc311fb095ea278b96feef28d0b423154858e", - "sha256:a62dd5d7cc8626a3634208df458c5fe4f21200d96a74d122c83bc2015b333bc1", - "sha256:ada603db10bb865bbe591939de854faf2c60f43c9b763e90f653224138f910d9", - "sha256:aee110e4ef3c528f3abbc3c2018c121e708938adeeff9006428dd7c8555e9b3f", - "sha256:b76d63495b0508ab9fc23f8152bac63205d2a704cd009a2b0722f4c8e0cba8e0", - "sha256:c0d8326269dbf944b9201911b0d9f3dc524d64779a07518199a58384c3d37a44", - "sha256:c41411e192f8d3ea39ea70e0fae48762cd11a2244e03751a98bd3c0ca9a4e936", - "sha256:c68fe3fcde03920c46697585620135b4ecfdfc1ed23e75cc2c2ae9f8502c10b8", - "sha256:cb8bea573863762bbf45d1e13f87c2d2fd32cee2dbd50d050f83f87429c9e1ea", - "sha256:cc32b2990fc34380ec2f6195f33a76b6cdaa9eecf09f0c9404b74fc120aef36f", - "sha256:ccae5de2a0140d8be6838c331604f91d6fafd0735dbdcee1ac78fc8fbaba76b4", - "sha256:d299797d75cd747e7797b1b41817111406b8b10a4f88b6e8fe5b5e59598b43b0", - "sha256:e04b622bb8a88f10e439084486f2f6349bf4d50605ac3e445869c7ea5cf0fa8c", - "sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f", - "sha256:e21f66748ab725ade40fa7af8ec8b5019c68ab00b929f6643e1b1af461eddb60", - "sha256:eb60b026d8ad0c97917cb81d3662d0b39b8ff1335e3fabb24984c6acd0c900a2", - "sha256:f021d334f2ca692523aaf7bbf7592ceff70c8594fad853416a81d66b35e3abf9", - "sha256:f552023710d4b93d8fb29a91fadf97de89c5926c6bd758897875435f2a939f33" + "sha256:03e08af7a5f9386a43919eda9de33ffda16b44eb11f3b313e6822243770e9763", + "sha256:0572f4bd6f94752167adfd7c1bed84f4b240ee6203a95e05d1e208d488d0d436", + "sha256:07b441f7d03b9a66299ce7ccf3ef2900abc81c0db434f42a5694a37bd73870f2", + "sha256:1bc330d9d29c7f06f003ab10e1eaced295e87940405afe1b110f2eb93a233588", + "sha256:1e0d612a17581b6616ff03c8e3d5eff7452f34655c901f75d62bd86449d9750e", + "sha256:23623166bfefe1487d81b698c423f8678e80df8b54614c2bf4b4cfcd7c711959", + "sha256:2519f3a5d0517fc159afab1015e54bb81b4406c278749779be57a569d8d1bb0d", + "sha256:28120ef39c92c2dd60f2721af9328479516844c6b550b077ca450c7d7dc68575", + "sha256:37350015056a553e442ff672c2d20e6f4b6d0b2495691fa239d8aa18bb3bc908", + "sha256:39769a115f730d683b0eb7b694db9789267bcd027326cccc3125e862eb03bfd8", + "sha256:3c01117dd36800f2ecaa238c65365b7b16497adc1522bf84906e5710ee9ba0e8", + "sha256:3d6718667da04294d7df1670d70eeddd414f313738d20a6f1d1f379e3139a545", + "sha256:3dbb986bad3ed5ceaf090200eba750b5245150bd97d3e67343a3cfed06feecf7", + "sha256:4557e1f11c5f653ebfdd924f3f9d5ebfc718283b0b9beebaa5dd6b77ec290971", + "sha256:46331b00096a6db1fdc052d55b101dbbfc99155a548e20a0e4a8e5e4d1362855", + "sha256:4a121d62ebe7d26fec9155f83f8be5189ef1405f5973ea4874a26fab9f1e262c", + "sha256:4f5e9cd989b45b73bd359f693b935364f7e1f79486e29015813c338450aa5a71", + "sha256:50aae840ebbd6cdd41af1c14590e5741665e5272d2fee999306673a1bb1fdb4d", + "sha256:59b1ee96617135f6e1d6f275bbe988f419c5178016f3d41d3c0abb0c819f75bb", + "sha256:59b8f3adb3971929a3e660337f5dacc5942c2cdb760afcabb2614ffbda9f9f72", + "sha256:66bffbad8d6271bb1cc2f9a4ea4f86f80fe5e2e3e501a5ae2a3dc6a76e604e6f", + "sha256:69f93723edbca7342624d09f6704e7126b152eaed3cdbb634cb657a54332a3c5", + "sha256:6a440293d802d3011028e14e4226da1434b373cbaf4a4bbb63f845761a708346", + "sha256:72c28b84b174ce8af8504ca28ae9347d317f9dba3999e5981a3cd441f3712e24", + "sha256:79d2e78abc26d871875b419e1fd3c0bca31a1cb0043277d0d850014599626c2e", + "sha256:7f2767680b6d2398aea7082e45a774b2b0767b5c8d8ffb9c8b683088ea9b29c5", + "sha256:8318f4776c85abc3f40ab185e388bee7a6ea99e7fa3a30686580b209eaa35c08", + "sha256:8958b10490125124463095bbdadda5aa22ec799f91958e410438ad6c97a7b793", + "sha256:8c78ac40bde930c60e0f78b3cd184c580f89456dd87fc08f9e3ee3ce8765ce88", + "sha256:90812a8933df713fdf748b355527e3af257a11e415b613dd794512461eb8a686", + "sha256:9bc633f4ee4b4c46e7adcb3a9b5ec083bf1d9a97c1d3854b92749d935de40b9b", + "sha256:9e46ed38affdfc95d2c958de328d037d87801cfcbea6d421000859e9789e61c2", + "sha256:9fe53b404f24789b5ea9003fc25b9a3988feddebd7e7b369c8fac27ad6f52f28", + "sha256:a4e46a888b54be23d03a89be510f24a7652fe6ff660787b96cd0e57a4ebcb46d", + "sha256:a86bfab2ef46d63300c0f06936bd6e6c0105faa11d509083ba8f2f9d237fb5b5", + "sha256:ac9dfa18ff2a67b09b372d5db8743c27966abf0e5344c555d86cc7199f7ad83a", + "sha256:af148a33ff0349f53512a049c6406923e4e02bf2f26c5fb285f143faf4f0e46a", + "sha256:b11d0cfdd2b095e7b0686cf5fabeb9c67fae5b06d265d8180715b8cfa86522e3", + "sha256:b2985c0b06e989c043f1dc09d4fe89e1616aadd35392aea2844f0458a989eacf", + "sha256:b544ad1935a8541d177cb402948b94e871067656b3a0b9e91dbec136b06a2ff5", + "sha256:b5cc79df7f4bc3d11e4b542596c03826063092611e481fcf1c9dfee3c94355ef", + "sha256:b817d41d692bf286abc181f8af476c4fbef3fd05e798777492618378448ee689", + "sha256:b81ee3d84803fd42d0b154cb6892ae57ea6b7c55d8359a02379965706c7efe6c", + "sha256:be9812b766cad94a25bc63bec11f88c4ad3629a0cec1cd5d4ba48dc23860486b", + "sha256:c245b1fbade9c35e5bd3b64270ab49ce990369018289ecfde3f9c318411aaa07", + "sha256:c3f3631693003d8e585d4200730616b78fafd5a01ef8b698f6967da5c605b3fa", + "sha256:c4ae3005ed83f5967f961fd091f2f8c5329161f69ce8480aa8168b2d7fe37f06", + "sha256:c54a1e53a0c308a8e8a7dffb59097bff7facda27c70c286f005327f21b2bd6b1", + "sha256:d0ddd9db6e59c44875211bc4c7953a9f6638b937b0a88ae6d09eb46cced54eff", + "sha256:dc022184d3e5cacc9579e41805a681187650e170eb2fd70e28b86192a479dcaa", + "sha256:e32092c47011d113dc01ab3e1d3ce9f006a47223b18422c5c0d150af13a00687", + "sha256:f7b64e6ec3f02c35647be6b4851008b26cff592a95ecb13b6788a54ef80bbdd4", + "sha256:f942a799516184c855e1a32fbc7b29d7e571b52612647866d4ec1c3242578fcb", + "sha256:f9511d8dd4a6e9271d07d150fb2f81874a3c8c95e11ff9af3a2dfc35fe42ee44", + "sha256:fd3a55deef00f689ce931d4d1b23fa9f04c880a48ee97af488fd215cf24e2a6c", + "sha256:fddbe92b4760c6f5d48162aef14824add991aeda8ddadb3c31d56eb15ca69f8e", + "sha256:fdf3386a801ea5aba17c6410dd1dc8d39cf454ca2565541b5ac42a84e1e28f53" ], "markers": "python_version >= '3.7'", - "version": "==2.0.35" + "version": "==2.0.36" }, "starlette": { "hashes": [ @@ -2068,19 +2169,19 @@ }, "tqdm": { "hashes": [ - "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", - "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad" + "sha256:0cd8af9d56911acab92182e88d763100d4788bdf421d251616040cc4d44863be", + "sha256:fe5a6f95e6fe0b9755e9469b77b9c3cf850048224ecaa8293d7d2d31f97d869a" ], "markers": "python_version >= '3.7'", - "version": "==4.66.5" + "version": "==4.67.0" }, "typer": { "hashes": [ - "sha256:62fe4e471711b147e3365034133904df3e235698399bc4de2b36c8579298d52b", - "sha256:f592f089bedcc8ec1b974125d64851029c3b1af145f04aca64d69410f0c9b722" + "sha256:d85fe0b777b2517cc99c8055ed735452f2659cd45e451507c76f48ce5c1d00e2", + "sha256:f1c7198347939361eec90139ffa0fd8b3df3a2259d5852a0f7400e476d95985c" ], "markers": "python_version >= '3.7'", - "version": "==0.12.5" + "version": "==0.13.0" }, "typing-extensions": { "hashes": [ @@ -2202,47 +2303,53 @@ "standard" ], "hashes": [ - "sha256:13bc21373d103859f68fe739608e2eb054a816dea79189bc3ca08ea89a275906", - "sha256:cac7be4dd4d891c363cd942160a7b02e69150dcbc7a36be04d5f4af4b17c8ced" + "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82", + "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e" ], "markers": "python_version >= '3.8'", - "version": "==0.31.0" + "version": "==0.32.0" }, "uvloop": { "hashes": [ - "sha256:265a99a2ff41a0fd56c19c3838b29bf54d1d177964c300dad388b27e84fd7847", - "sha256:2beee18efd33fa6fdb0976e18475a4042cd31c7433c866e8a09ab604c7c22ff2", - "sha256:35968fc697b0527a06e134999eef859b4034b37aebca537daeb598b9d45a137b", - "sha256:36c530d8fa03bfa7085af54a48f2ca16ab74df3ec7108a46ba82fd8b411a2315", - "sha256:3a609780e942d43a275a617c0839d85f95c334bad29c4c0918252085113285b5", - "sha256:4603ca714a754fc8d9b197e325db25b2ea045385e8a3ad05d3463de725fdf469", - "sha256:4b75f2950ddb6feed85336412b9a0c310a2edbcf4cf931aa5cfe29034829676d", - "sha256:4f44af67bf39af25db4c1ac27e82e9665717f9c26af2369c404be865c8818dcf", - "sha256:6462c95f48e2d8d4c993a2950cd3d31ab061864d1c226bbf0ee2f1a8f36674b9", - "sha256:649c33034979273fa71aa25d0fe120ad1777c551d8c4cd2c0c9851d88fcb13ab", - "sha256:746242cd703dc2b37f9d8b9f173749c15e9a918ddb021575a0205ec29a38d31e", - "sha256:77fbc69c287596880ecec2d4c7a62346bef08b6209749bf6ce8c22bbaca0239e", - "sha256:80dc1b139516be2077b3e57ce1cb65bfed09149e1d175e0478e7a987863b68f0", - "sha256:82edbfd3df39fb3d108fc079ebc461330f7c2e33dbd002d146bf7c445ba6e756", - "sha256:89e8d33bb88d7263f74dc57d69f0063e06b5a5ce50bb9a6b32f5fcbe655f9e73", - "sha256:94707205efbe809dfa3a0d09c08bef1352f5d3d6612a506f10a319933757c006", - "sha256:95720bae002ac357202e0d866128eb1ac82545bcf0b549b9abe91b5178d9b541", - "sha256:9b04d96188d365151d1af41fa2d23257b674e7ead68cfd61c725a422764062ae", - "sha256:9d0fba61846f294bce41eb44d60d58136090ea2b5b99efd21cbdf4e21927c56a", - "sha256:9ebafa0b96c62881d5cafa02d9da2e44c23f9f0cd829f3a32a6aff771449c996", - "sha256:a0fac7be202596c7126146660725157d4813aa29a4cc990fe51346f75ff8fde7", - "sha256:aea15c78e0d9ad6555ed201344ae36db5c63d428818b4b2a42842b3870127c00", - "sha256:b10c2956efcecb981bf9cfb8184d27d5d64b9033f917115a960b83f11bfa0d6b", - "sha256:b16696f10e59d7580979b420eedf6650010a4a9c3bd8113f24a103dfdb770b10", - "sha256:d8c36fdf3e02cec92aed2d44f63565ad1522a499c654f07935c8f9d04db69e95", - "sha256:e237f9c1e8a00e7d9ddaa288e535dc337a39bcbf679f290aee9d26df9e72bce9", - "sha256:e50289c101495e0d1bb0bfcb4a60adde56e32f4449a67216a1ab2750aa84f037", - "sha256:e7d61fe8e8d9335fac1bf8d5d82820b4808dd7a43020c149b63a1ada953d48a6", - "sha256:e97152983442b499d7a71e44f29baa75b3b02e65d9c44ba53b10338e98dedb66", - "sha256:f0e94b221295b5e69de57a1bd4aeb0b3a29f61be6e1b478bb8a69a73377db7ba", - "sha256:fee6044b64c965c425b65a4e17719953b96e065c5b7e09b599ff332bb2744bdf" - ], - "version": "==0.20.0" + "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", + "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f", + "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc", + "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414", + "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", + "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d", + "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd", + "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff", + "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", + "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", + "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", + "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a", + "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", + "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2", + "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0", + "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", + "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c", + "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", + "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", + "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb", + "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75", + "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", + "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", + "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", + "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", + "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", + "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206", + "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", + "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", + "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b", + "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", + "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79", + "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f", + "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe", + "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26", + "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", + "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2" + ], + "version": "==0.21.0" }, "watchfiles": { "hashes": [ @@ -2334,94 +2441,77 @@ }, "websockets": { "hashes": [ - "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a", - "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54", - "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23", - "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7", - "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135", - "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700", - "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf", - "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5", - "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e", - "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c", - "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02", - "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a", - "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418", - "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f", - "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3", - "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68", - "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978", - "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20", - "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295", - "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b", - "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6", - "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb", - "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a", - "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa", - "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0", - "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a", - "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238", - "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c", - "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084", - "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19", - "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d", - "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7", - "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9", - "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79", - "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96", - "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6", - "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe", - "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842", - "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa", - "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3", - "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d", - "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51", - "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7", - "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09", - "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096", - "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9", - "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b", - "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5", - "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678", - "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea", - "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d", - "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49", - "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc", - "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5", - "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027", - "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0", - "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878", - "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c", - "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa", - "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f", - "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6", - "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2", - "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf", - "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708", - "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6", - "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f", - "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd", - "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2", - "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d", - "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7", - "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f", - "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5", - "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6", - "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557", - "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14", - "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7", - "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd", - "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c", - "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17", - "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23", - "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db", - "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6", - "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d", - "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9", - "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee", - "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6" - ], - "version": "==13.1" + "sha256:00fe5da3f037041da1ee0cf8e308374e236883f9842c7c465aa65098b1c9af59", + "sha256:01bb2d4f0a6d04538d3c5dfd27c0643269656c28045a53439cbf1c004f90897a", + "sha256:034feb9f4286476f273b9a245fb15f02c34d9586a5bc936aff108c3ba1b21beb", + "sha256:04a97aca96ca2acedf0d1f332c861c5a4486fdcba7bcef35873820f940c4231e", + "sha256:0d4290d559d68288da9f444089fd82490c8d2744309113fc26e2da6e48b65da6", + "sha256:1288369a6a84e81b90da5dbed48610cd7e5d60af62df9851ed1d1d23a9069f10", + "sha256:14839f54786987ccd9d03ed7f334baec0f02272e7ec4f6e9d427ff584aeea8b4", + "sha256:1d045cbe1358d76b24d5e20e7b1878efe578d9897a25c24e6006eef788c0fdf0", + "sha256:1f874ba705deea77bcf64a9da42c1f5fc2466d8f14daf410bc7d4ceae0a9fcb0", + "sha256:205f672a6c2c671a86d33f6d47c9b35781a998728d2c7c2a3e1cf3333fcb62b7", + "sha256:2177ee3901075167f01c5e335a6685e71b162a54a89a56001f1c3e9e3d2ad250", + "sha256:219c8187b3ceeadbf2afcf0f25a4918d02da7b944d703b97d12fb01510869078", + "sha256:25225cc79cfebc95ba1d24cd3ab86aaa35bcd315d12fa4358939bd55e9bd74a5", + "sha256:3630b670d5057cd9e08b9c4dab6493670e8e762a24c2c94ef312783870736ab9", + "sha256:368a05465f49c5949e27afd6fbe0a77ce53082185bbb2ac096a3a8afaf4de52e", + "sha256:36ebd71db3b89e1f7b1a5deaa341a654852c3518ea7a8ddfdf69cc66acc2db1b", + "sha256:39450e6215f7d9f6f7bc2a6da21d79374729f5d052333da4d5825af8a97e6735", + "sha256:398b10c77d471c0aab20a845e7a60076b6390bfdaac7a6d2edb0d2c59d75e8d8", + "sha256:3c3deac3748ec73ef24fc7be0b68220d14d47d6647d2f85b2771cb35ea847aa1", + "sha256:3f14a96a0034a27f9d47fd9788913924c89612225878f8078bb9d55f859272b0", + "sha256:3fc753451d471cff90b8f467a1fc0ae64031cf2d81b7b34e1811b7e2691bc4bc", + "sha256:414ffe86f4d6f434a8c3b7913655a1a5383b617f9bf38720e7c0799fac3ab1c6", + "sha256:449d77d636f8d9c17952628cc7e3b8faf6e92a17ec581ec0c0256300717e1512", + "sha256:4b6caec8576e760f2c7dd878ba817653144d5f369200b6ddf9771d64385b84d4", + "sha256:4d4fc827a20abe6d544a119896f6b78ee13fe81cbfef416f3f2ddf09a03f0e2e", + "sha256:5a42d3ecbb2db5080fc578314439b1d79eef71d323dc661aa616fb492436af5d", + "sha256:5b918d288958dc3fa1c5a0b9aa3256cb2b2b84c54407f4813c45d52267600cd3", + "sha256:5ef440054124728cc49b01c33469de06755e5a7a4e83ef61934ad95fc327fbb0", + "sha256:660c308dabd2b380807ab64b62985eaccf923a78ebc572bd485375b9ca2b7dc7", + "sha256:6a6c9bcf7cdc0fd41cc7b7944447982e8acfd9f0d560ea6d6845428ed0562058", + "sha256:6d24fc337fc055c9e83414c94e1ee0dee902a486d19d2a7f0929e49d7d604b09", + "sha256:7048eb4415d46368ef29d32133134c513f507fff7d953c18c91104738a68c3b3", + "sha256:77569d19a13015e840b81550922056acabc25e3f52782625bc6843cfa034e1da", + "sha256:8149a0f5a72ca36720981418eeffeb5c2729ea55fa179091c81a0910a114a5d2", + "sha256:836bef7ae338a072e9d1863502026f01b14027250a4545672673057997d5c05a", + "sha256:8621a07991add373c3c5c2cf89e1d277e49dc82ed72c75e3afc74bd0acc446f0", + "sha256:87e31011b5c14a33b29f17eb48932e63e1dcd3fa31d72209848652310d3d1f0d", + "sha256:88cf9163ef674b5be5736a584c999e98daf3aabac6e536e43286eb74c126b9c7", + "sha256:8fda642151d5affdee8a430bd85496f2e2517be3a2b9d2484d633d5712b15c56", + "sha256:90b5d9dfbb6d07a84ed3e696012610b6da074d97453bd01e0e30744b472c8179", + "sha256:90f4c7a069c733d95c308380aae314f2cb45bd8a904fb03eb36d1a4983a4993f", + "sha256:9481a6de29105d73cf4515f2bef8eb71e17ac184c19d0b9918a3701c6c9c4f23", + "sha256:9607b9a442392e690a57909c362811184ea429585a71061cd5d3c2b98065c199", + "sha256:9777564c0a72a1d457f0848977a1cbe15cfa75fa2f67ce267441e465717dcf1a", + "sha256:a032855dc7db987dff813583d04f4950d14326665d7e714d584560b140ae6b8b", + "sha256:a0adf84bc2e7c86e8a202537b4fd50e6f7f0e4a6b6bf64d7ccb96c4cd3330b29", + "sha256:a35f704be14768cea9790d921c2c1cc4fc52700410b1c10948511039be824aac", + "sha256:a3dfff83ca578cada2d19e665e9c8368e1598d4e787422a460ec70e531dbdd58", + "sha256:a4c805c6034206143fbabd2d259ec5e757f8b29d0a2f0bf3d2fe5d1f60147a4a", + "sha256:a655bde548ca98f55b43711b0ceefd2a88a71af6350b0c168aa77562104f3f45", + "sha256:ad2ab2547761d79926effe63de21479dfaf29834c50f98c4bf5b5480b5838434", + "sha256:b1f3628a0510bd58968c0f60447e7a692933589b791a6b572fcef374053ca280", + "sha256:b7e7ea2f782408c32d86b87a0d2c1fd8871b0399dd762364c731d86c86069a78", + "sha256:bc6ccf7d54c02ae47a48ddf9414c54d48af9c01076a2e1023e3b486b6e72c707", + "sha256:bea45f19b7ca000380fbd4e02552be86343080120d074b87f25593ce1700ad58", + "sha256:cc1fc87428c1d18b643479caa7b15db7d544652e5bf610513d4a3478dbe823d0", + "sha256:cd7c11968bc3860d5c78577f0dbc535257ccec41750675d58d8dc66aa47fe52c", + "sha256:ceada5be22fa5a5a4cdeec74e761c2ee7db287208f54c718f2df4b7e200b8d4a", + "sha256:cf5201a04550136ef870aa60ad3d29d2a59e452a7f96b94193bee6d73b8ad9a9", + "sha256:d9fd19ecc3a4d5ae82ddbfb30962cf6d874ff943e56e0c81f5169be2fda62979", + "sha256:ddaa4a390af911da6f680be8be4ff5aaf31c4c834c1a9147bc21cbcbca2d4370", + "sha256:df174ece723b228d3e8734a6f2a6febbd413ddec39b3dc592f5a4aa0aff28098", + "sha256:e0744623852f1497d825a49a99bfbec9bea4f3f946df6eb9d8a2f0c37a2fec2e", + "sha256:e5dc25a9dbd1a7f61eca4b7cb04e74ae4b963d658f9e4f9aad9cd00b688692c8", + "sha256:e7591d6f440af7f73c4bd9404f3772bfee064e639d2b6cc8c94076e71b2471c1", + "sha256:eb6d38971c800ff02e4a6afd791bbe3b923a9a57ca9aeab7314c21c84bf9ff05", + "sha256:ed907449fe5e021933e46a3e65d651f641975a768d0649fee59f10c2985529ed", + "sha256:f6cf0ad281c979306a6a34242b371e90e891bce504509fb6bb5246bbbf31e7b6", + "sha256:f95ba34d71e2fa0c5d225bde3b3bdb152e957150100e75c86bc7f3964c450d89" + ], + "version": "==14.1" }, "wrapt": { "hashes": [ @@ -2501,117 +2591,107 @@ }, "xmltodict": { "hashes": [ - "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56", - "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852" + "sha256:201e7c28bb210e374999d1dde6382923ab0ed1a8a5faeece48ab525b7810a553", + "sha256:20cc7d723ed729276e808f26fb6b3599f786cbc37e06c65e192ba77c40f20aac" ], - "markers": "python_version >= '3.4'", - "version": "==0.13.0" + "markers": "python_version >= '3.6'", + "version": "==0.14.2" }, "yarl": { "hashes": [ - "sha256:047b258e00b99091b6f90355521f026238c63bd76dcf996d93527bb13320eefd", - "sha256:06ff23462398333c78b6f4f8d3d70410d657a471c2c5bbe6086133be43fc8f1a", - "sha256:07f9eaf57719d6721ab15805d85f4b01a5b509a0868d7320134371bcb652152d", - "sha256:0aa92e3e30a04f9462a25077db689c4ac5ea9ab6cc68a2e563881b987d42f16d", - "sha256:0cf21f46a15d445417de8fc89f2568852cf57fe8ca1ab3d19ddb24d45c0383ae", - "sha256:0fd7b941dd1b00b5f0acb97455fea2c4b7aac2dd31ea43fb9d155e9bc7b78664", - "sha256:147e36331f6f63e08a14640acf12369e041e0751bb70d9362df68c2d9dcf0c87", - "sha256:16a682a127930f3fc4e42583becca6049e1d7214bcad23520c590edd741d2114", - "sha256:176110bff341b6730f64a1eb3a7070e12b373cf1c910a9337e7c3240497db76f", - "sha256:19268b4fec1d7760134f2de46ef2608c2920134fb1fa61e451f679e41356dc55", - "sha256:1b16f6c75cffc2dc0616ea295abb0e1967601bd1fb1e0af6a1de1c6c887f3439", - "sha256:1bfc25aa6a7c99cf86564210f79a0b7d4484159c67e01232b116e445b3036547", - "sha256:1ca3894e9e9f72da93544f64988d9c052254a338a9f855165f37f51edb6591de", - "sha256:1dda53508df0de87b6e6b0a52d6718ff6c62a5aca8f5552748404963df639269", - "sha256:217a782020b875538eebf3948fac3a7f9bbbd0fd9bf8538f7c2ad7489e80f4e8", - "sha256:2192f718db4a8509f63dd6d950f143279211fa7e6a2c612edc17d85bf043d36e", - "sha256:29a84a46ec3ebae7a1c024c055612b11e9363a8a23238b3e905552d77a2bc51b", - "sha256:3007a5b75cb50140708420fe688c393e71139324df599434633019314ceb8b59", - "sha256:30600ba5db60f7c0820ef38a2568bb7379e1418ecc947a0f76fd8b2ff4257a97", - "sha256:337912bcdcf193ade64b9aae5a4017a0a1950caf8ca140362e361543c6773f21", - "sha256:37001e5d4621cef710c8dc1429ca04e189e572f128ab12312eab4e04cf007132", - "sha256:3d569f877ed9a708e4c71a2d13d2940cb0791da309f70bd970ac1a5c088a0a92", - "sha256:4009def9be3a7e5175db20aa2d7307ecd00bbf50f7f0f989300710eee1d0b0b9", - "sha256:46a9772a1efa93f9cd170ad33101c1817c77e0e9914d4fe33e2da299d7cf0f9b", - "sha256:47eede5d11d669ab3759b63afb70d28d5328c14744b8edba3323e27dc52d298d", - "sha256:498b3c55087b9d762636bca9b45f60d37e51d24341786dc01b81253f9552a607", - "sha256:4e0d45ebf975634468682c8bec021618b3ad52c37619e5c938f8f831fa1ac5c0", - "sha256:4f24f08b6c9b9818fd80612c97857d28f9779f0d1211653ece9844fc7b414df2", - "sha256:55c144d363ad4626ca744556c049c94e2b95096041ac87098bb363dcc8635e8d", - "sha256:582cedde49603f139be572252a318b30dc41039bc0b8165f070f279e5d12187f", - "sha256:587c3cc59bc148a9b1c07a019346eda2549bc9f468acd2f9824d185749acf0a6", - "sha256:5cd5dad8366e0168e0fd23d10705a603790484a6dbb9eb272b33673b8f2cce72", - "sha256:5d02d700705d67e09e1f57681f758f0b9d4412eeb70b2eb8d96ca6200b486db3", - "sha256:625f207b1799e95e7c823f42f473c1e9dbfb6192bd56bba8695656d92be4535f", - "sha256:659603d26d40dd4463200df9bfbc339fbfaed3fe32e5c432fe1dc2b5d4aa94b4", - "sha256:689a99a42ee4583fcb0d3a67a0204664aa1539684aed72bdafcbd505197a91c4", - "sha256:68ac1a09392ed6e3fd14be880d39b951d7b981fd135416db7d18a6208c536561", - "sha256:6a615cad11ec3428020fb3c5a88d85ce1b5c69fd66e9fcb91a7daa5e855325dd", - "sha256:73bedd2be05f48af19f0f2e9e1353921ce0c83f4a1c9e8556ecdcf1f1eae4892", - "sha256:742aef0a99844faaac200564ea6f5e08facb285d37ea18bd1a5acf2771f3255a", - "sha256:75ff4c819757f9bdb35de049a509814d6ce851fe26f06eb95a392a5640052482", - "sha256:781e2495e408a81e4eaeedeb41ba32b63b1980dddf8b60dbbeff6036bcd35049", - "sha256:7a9f917966d27f7ce30039fe8d900f913c5304134096554fd9bea0774bcda6d1", - "sha256:7e2637d75e92763d1322cb5041573279ec43a80c0f7fbbd2d64f5aee98447b17", - "sha256:8089d4634d8fa2b1806ce44fefa4979b1ab2c12c0bc7ef3dfa45c8a374811348", - "sha256:816d24f584edefcc5ca63428f0b38fee00b39fe64e3c5e558f895a18983efe96", - "sha256:8385ab36bf812e9d37cf7613999a87715f27ef67a53f0687d28c44b819df7cb0", - "sha256:85cb3e40eaa98489f1e2e8b29f5ad02ee1ee40d6ce6b88d50cf0f205de1d9d2c", - "sha256:8648180b34faaea4aa5b5ca7e871d9eb1277033fa439693855cf0ea9195f85f1", - "sha256:8892fa575ac9b1b25fae7b221bc4792a273877b9b56a99ee2d8d03eeb3dbb1d2", - "sha256:88c7d9d58aab0724b979ab5617330acb1c7030b79379c8138c1c8c94e121d1b3", - "sha256:8a2f8fb7f944bcdfecd4e8d855f84c703804a594da5123dd206f75036e536d4d", - "sha256:8f4e475f29a9122f908d0f1f706e1f2fc3656536ffd21014ff8a6f2e1b14d1d8", - "sha256:8f50eb3837012a937a2b649ec872b66ba9541ad9d6f103ddcafb8231cfcafd22", - "sha256:91d875f75fabf76b3018c5f196bf3d308ed2b49ddcb46c1576d6b075754a1393", - "sha256:94b2bb9bcfd5be9d27004ea4398fb640373dd0c1a9e219084f42c08f77a720ab", - "sha256:9557c9322aaa33174d285b0c1961fb32499d65ad1866155b7845edc876c3c835", - "sha256:95e16e9eaa2d7f5d87421b8fe694dd71606aa61d74b824c8d17fc85cc51983d1", - "sha256:96952f642ac69075e44c7d0284528938fdff39422a1d90d3e45ce40b72e5e2d9", - "sha256:985623575e5c4ea763056ffe0e2d63836f771a8c294b3de06d09480538316b13", - "sha256:99ff3744f5fe48288be6bc402533b38e89749623a43208e1d57091fc96b783b9", - "sha256:9abe80ae2c9d37c17599557b712e6515f4100a80efb2cda15f5f070306477cd2", - "sha256:a152751af7ef7b5d5fa6d215756e508dd05eb07d0cf2ba51f3e740076aa74373", - "sha256:a2e4725a08cb2b4794db09e350c86dee18202bb8286527210e13a1514dc9a59a", - "sha256:a56fbe3d7f3bce1d060ea18d2413a2ca9ca814eea7cedc4d247b5f338d54844e", - "sha256:ab3abc0b78a5dfaa4795a6afbe7b282b6aa88d81cf8c1bb5e394993d7cae3457", - "sha256:b03384eed107dbeb5f625a99dc3a7de8be04fc8480c9ad42fccbc73434170b20", - "sha256:b0547ab1e9345dc468cac8368d88ea4c5bd473ebc1d8d755347d7401982b5dd8", - "sha256:b4c1ecba93e7826dc71ddba75fb7740cdb52e7bd0be9f03136b83f54e6a1f511", - "sha256:b693c63e7e64b524f54aa4888403c680342d1ad0d97be1707c531584d6aeeb4f", - "sha256:b6d0147574ce2e7b812c989e50fa72bbc5338045411a836bd066ce5fc8ac0bce", - "sha256:b9cfef3f14f75bf6aba73a76caf61f9d00865912a04a4393c468a7ce0981b519", - "sha256:b9f805e37ed16cc212fdc538a608422d7517e7faf539bedea4fe69425bc55d76", - "sha256:bab03192091681d54e8225c53f270b0517637915d9297028409a2a5114ff4634", - "sha256:bc24f968b82455f336b79bf37dbb243b7d76cd40897489888d663d4e028f5069", - "sha256:c14b504a74e58e2deb0378b3eca10f3d076635c100f45b113c18c770b4a47a50", - "sha256:c2089a9afef887664115f7fa6d3c0edd6454adaca5488dba836ca91f60401075", - "sha256:c8ed4034f0765f8861620c1f2f2364d2e58520ea288497084dae880424fc0d9f", - "sha256:cd2660c01367eb3ef081b8fa0a5da7fe767f9427aa82023a961a5f28f0d4af6c", - "sha256:d8361c7d04e6a264481f0b802e395f647cd3f8bbe27acfa7c12049efea675bd1", - "sha256:d9baec588f015d0ee564057aa7574313c53a530662ffad930b7886becc85abdf", - "sha256:dbd9ff43a04f8ffe8a959a944c2dca10d22f5f99fc6a459f49c3ebfb409309d9", - "sha256:e3f8bfc1db82589ef965ed234b87de30d140db8b6dc50ada9e33951ccd8ec07a", - "sha256:e6a2c5c5bb2556dfbfffffc2bcfb9c235fd2b566d5006dfb2a37afc7e3278a07", - "sha256:e749af6c912a7bb441d105c50c1a3da720474e8acb91c89350080dd600228f0e", - "sha256:e85d86527baebb41a214cc3b45c17177177d900a2ad5783dbe6f291642d4906f", - "sha256:ee2c68e4f2dd1b1c15b849ba1c96fac105fca6ffdb7c1e8be51da6fabbdeafb9", - "sha256:f3ab950f8814f3b7b5e3eebc117986f817ec933676f68f0a6c5b2137dd7c9c69", - "sha256:f4f4547944d4f5cfcdc03f3f097d6f05bbbc915eaaf80a2ee120d0e756de377d", - "sha256:f72a0d746d38cb299b79ce3d4d60ba0892c84bbc905d0d49c13df5bace1b65f8", - "sha256:fc2c80bc87fba076e6cbb926216c27fba274dae7100a7b9a0983b53132dd99f2", - "sha256:fe4d2536c827f508348d7b40c08767e8c7071614250927233bf0c92170451c0a" - ], - "markers": "python_version >= '3.8'", - "version": "==1.14.0" + "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac", + "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47", + "sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91", + "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5", + "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df", + "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3", + "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463", + "sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b", + "sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5", + "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74", + "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3", + "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3", + "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4", + "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0", + "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299", + "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2", + "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac", + "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61", + "sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931", + "sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21", + "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3", + "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7", + "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96", + "sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f", + "sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243", + "sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857", + "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f", + "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca", + "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488", + "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da", + "sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948", + "sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5", + "sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934", + "sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473", + "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7", + "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685", + "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e", + "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147", + "sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71", + "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67", + "sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04", + "sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822", + "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11", + "sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6", + "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0", + "sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec", + "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda", + "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556", + "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4", + "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c", + "sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f", + "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8", + "sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba", + "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258", + "sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95", + "sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383", + "sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e", + "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938", + "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374", + "sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55", + "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139", + "sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17", + "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217", + "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d", + "sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d", + "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe", + "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199", + "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d", + "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8", + "sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c", + "sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29", + "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172", + "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860", + "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7", + "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170", + "sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138", + "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06", + "sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004", + "sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159", + "sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da", + "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988", + "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75" + ], + "markers": "python_version >= '3.9'", + "version": "==1.17.1" }, "zipp": { "hashes": [ - "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", - "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29" + "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", + "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931" ], - "markers": "python_version >= '3.8'", - "version": "==3.20.2" + "markers": "python_version >= '3.9'", + "version": "==3.21.0" } }, "develop": { @@ -2672,11 +2752,11 @@ }, "botocore-stubs": { "hashes": [ - "sha256:b1aebecdfa4f4fc02b0a68a5e438877034b195168809a7202ee32b42245d3ece", - "sha256:d79a408dfc503a1a0389d10cd29ad22a01450d0d53902ea216815e2ba98913ba" + "sha256:1456af3358be1a0e49dd8428bfb81863406659d9fad871362bf18a098eeac90a", + "sha256:dd83003963ca957a6e4835d192d7f163fb55312ce3d3f798f625ac9438616e4f" ], "markers": "python_version >= '3.8'", - "version": "==1.35.35" + "version": "==1.35.59" }, "certifi": { "hashes": [ @@ -2761,99 +2841,114 @@ }, "charset-normalizer": { "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" + "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621", + "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", + "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", + "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912", + "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c", + "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b", + "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d", + "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d", + "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95", + "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e", + "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", + "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", + "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab", + "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be", + "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", + "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907", + "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0", + "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2", + "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62", + "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62", + "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", + "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc", + "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", + "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca", + "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455", + "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858", + "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", + "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594", + "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", + "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", + "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", + "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea", + "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6", + "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", + "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749", + "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", + "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd", + "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99", + "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242", + "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee", + "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129", + "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", + "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51", + "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee", + "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8", + "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", + "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613", + "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742", + "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe", + "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3", + "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5", + "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", + "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7", + "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", + "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c", + "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea", + "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417", + "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", + "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", + "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca", + "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa", + "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99", + "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149", + "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41", + "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574", + "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0", + "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f", + "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", + "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654", + "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3", + "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19", + "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", + "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578", + "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", + "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", + "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51", + "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", + "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236", + "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a", + "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c", + "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade", + "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944", + "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc", + "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6", + "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6", + "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27", + "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6", + "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2", + "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12", + "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf", + "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", + "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7", + "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", + "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", + "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b", + "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", + "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", + "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4", + "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", + "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365", + "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a", + "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748", + "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b", + "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", + "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482" ], "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" + "version": "==3.4.0" }, "click": { "hashes": [ @@ -2865,35 +2960,35 @@ }, "cryptography": { "hashes": [ - "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494", - "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806", - "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d", - "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062", - "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2", - "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4", - "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1", - "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85", - "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84", - "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042", - "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d", - "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962", - "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2", - "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa", - "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d", - "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365", - "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96", - "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47", - "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d", - "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d", - "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c", - "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb", - "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277", - "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172", - "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034", - "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a", - "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289" - ], - "version": "==43.0.1" + "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", + "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", + "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa", + "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83", + "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff", + "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", + "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", + "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664", + "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08", + "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", + "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", + "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", + "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", + "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", + "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", + "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", + "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3", + "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", + "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", + "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", + "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c", + "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", + "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", + "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", + "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7", + "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", + "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7" + ], + "version": "==43.0.3" }, "docker": { "hashes": [ @@ -2988,11 +3083,11 @@ }, "packaging": { "hashes": [ - "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", - "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124" + "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", + "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f" ], "markers": "python_version >= '3.8'", - "version": "==24.1" + "version": "==24.2" }, "pathspec": { "hashes": [ @@ -3061,11 +3156,11 @@ }, "rich": { "hashes": [ - "sha256:51a2c62057461aaf7152b4d611168f93a9fc73068f8ded2790f29fe2b5366d0c", - "sha256:8c82a3d3f8dcfe9e734771313e606b39d8247bb6b826e196f4914b333b743cf1" + "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", + "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90" ], "markers": "python_full_version >= '3.8.0'", - "version": "==13.9.2" + "version": "==13.9.4" }, "ruff": { "hashes": [ @@ -3093,11 +3188,11 @@ }, "s3transfer": { "hashes": [ - "sha256:0711534e9356d3cc692fdde846b4a1e4b0cb6519971860796e6bc4c7aea00ef6", - "sha256:eca1c20de70a39daee580aef4986996620f365c4e0fda6a86100231d62f1bf69" + "sha256:263ed587a5803c6c708d3ce44dc4dfedaab4c1a32e8329bab818933d79ddcf5d", + "sha256:4f50ed74ab84d474ce614475e0b8d5047ff080810aac5d01ea25231cfc944b0c" ], "markers": "python_version >= '3.8'", - "version": "==0.10.2" + "version": "==0.10.3" }, "six": { "hashes": [ @@ -3109,20 +3204,20 @@ }, "types-awscrt": { "hashes": [ - "sha256:67a660c90bad360c339f6a79310cc17094d12472042c7ca5a41450aaf5fc9a54", - "sha256:b2c196bbd3226bab42d80fae13c34548de9ddc195f5a366d79c15d18e5897aa9" + "sha256:3fd1edeac923d1956c0e907c973fb83bda465beae7f054716b371b293f9b5fdc", + "sha256:517d9d06f19cf58d778ca90ad01e52e0489466bf70dcf78c7f47f74fdf151a60" ], "markers": "python_version >= '3.8'", - "version": "==0.22.0" + "version": "==0.23.0" }, "types-beautifulsoup4": { "hashes": [ - "sha256:32f5ac48514b488f15241afdd7d2f73f0baf3c54e874e23b66708503dd288489", - "sha256:8d023b86530922070417a1d4c4d91678ab0ff2439b3b2b2cffa3b628b49ebab1" + "sha256:158370d08d0cd448bd11b132a50ff5279237a5d4b5837beba074de152a513059", + "sha256:c95e66ce15a4f5f0835f7fbc5cd886321ae8294f977c495424eaf4225307fd30" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==4.12.0.20240907" + "version": "==4.12.0.20241020" }, "types-docker": { "hashes": [ @@ -3135,28 +3230,28 @@ }, "types-html5lib": { "hashes": [ - "sha256:575c4fd84ba8eeeaa8520c7e4c7042b7791f5ec3e9c0a5d5c418124c42d9e7e4", - "sha256:8060dc98baf63d6796a765bbbc809fff9f7a383f6e3a9add526f814c086545ef" + "sha256:3f1e064d9ed2c289001ae6392c84c93833abb0816165c6ff0abfc304a779f403", + "sha256:98042555ff78d9e3a51c77c918b1041acbb7eb6c405408d8a9e150ff5beccafa" ], "markers": "python_version >= '3.8'", - "version": "==1.1.11.20240806" + "version": "==1.1.11.20241018" }, "types-requests": { "hashes": [ - "sha256:2850e178db3919d9bf809e434eef65ba49d0e7e33ac92d588f4a5e295fffd405", - "sha256:59c2f673eb55f32a99b2894faf6020e1a9f4a402ad0f192bfee0b64469054310" + "sha256:0d9cad2f27515d0e3e3da7134a1b6f28fb97129d86b867f24d9c726452634d95", + "sha256:4195d62d6d3e043a4eaaf08ff8a62184584d2e8684e9d2aa178c7915a7da3747" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==2.32.0.20240914" + "version": "==2.32.0.20241016" }, "types-s3transfer": { "hashes": [ - "sha256:60167a3bfb5c536ec6cdb5818f7f9a28edca9dc3e0b5ff85ae374526fc5e576e", - "sha256:7a3fec8cd632e2b5efb665a355ef93c2a87fdd5a45b74a949f95a9e628a86356" + "sha256:d34c5a82f531af95bb550927136ff5b737a1ed3087f90a59d545591dfde5b4cc", + "sha256:f761b2876ac4c208e6c6b75cdf5f6939009768be9950c545b11b0225e7703ee7" ], "markers": "python_version >= '3.8'", - "version": "==0.10.2" + "version": "==0.10.3" }, "typing-extensions": { "hashes": [ diff --git a/opentrons-ai-server/README.md b/opentrons-ai-server/README.md index b072429c41c..041328a5c99 100644 --- a/opentrons-ai-server/README.md +++ b/opentrons-ai-server/README.md @@ -60,6 +60,15 @@ In the deployed environments the FastAPI server is run in a docker container. To Now the API is running at View the API docs in a browser at +##### Docker shell + +1. make clean +1. make build +1. make run-shell +1. make shell + +Now you are in the docker container and can inspect the environment and such. + #### Direct API Interaction and Authentication > There is only 1 endpoint with the potential to call the OpenAI API. This is the `/api/chat/completion` endpoint. This endpoint requires authentication and the steps are outlined below. In the POST request body setting `"fake": true` will short circuit the handling of the call. The OpenAI API will not be hit. Instead, a hard coded response is returned. We plan to extend this capability to allow for live local testing of the UI without calling the OpenAI API. @@ -117,3 +126,48 @@ The live-test target will run tests against any environment. The default is loca 1. alter the `Pipfile` to the new pinned version 1. run `make setup` to update the `Pipfile.lock` + +## Google Sheets Integration + +1. Create a Google Cloud Platform project +1. Enable the Google Sheets and Drive API +1. Go to APIs & Services > Library and enable the Google Sheets API. +1. Go to APIs & Services > Credentials and create a Service Account. This account will be used by your application to access the Google Sheets API. +1. After creating the Service Account, click on it in the Credentials section, go to the Keys tab, and create a JSON key. This will download a JSON file with credentials for your Service Account. +1. Open the JSON file and store its content securely. You’ll set this JSON content as an environment variable. +1. Configure Access to the Google Sheet +1. Open the Google Sheet you want to access. +1. Click Share and add the Service Account email (found in the JSON file under "client_email") as a collaborator, typically with Editor access. This allows the Service Account to interact with the sheet. + +### Test that the credentials work with a direct call to the Integration + +```shell +make test-googlesheet +``` + +## Add Secrets or Environment Variables + +1. Define the new secret or environment variable in the `api/settings.py` file. +1. Add the new secret or environment variable to your local `.env` file. +1. Test locally. +1. Log into the AWS console and navigate to the Secrets Manager. +1. Environment variables are added into the json secret named ENV_VARIABLES_SECRET_NAME in deploy.py for a given environment. +1. Environment variables MUST be named the same as the property in the Settings class. +1. Secret names MUST be the same as the property in the Settings class but with \_ replaced with - and prefixed with the environment name-. +1. The deploy script will load the environment variables from the secret and set them in the container definition. +1. The deploy script will map the secrets from Settings and match them to the container secrets. +1. If any secrets are missing, the deploy script with retrieve the secret ARN and set the secret in the container definition. + +## AWS Deployment + +Locally test the deployment script like so: + +```shell +AWS_PROFILE=robotics_ai_staging make dry-deploy ENV=staging +``` + +Locally deploy to the staging environment like so: + +```shell +AWS_PROFILE=robotics_ai_staging make deploy ENV=staging +``` diff --git a/opentrons-ai-server/api/handler/fast.py b/opentrons-ai-server/api/handler/fast.py index 9534906adbe..9182f827a9a 100644 --- a/opentrons-ai-server/api/handler/fast.py +++ b/opentrons-ai-server/api/handler/fast.py @@ -1,14 +1,14 @@ import asyncio import os import time -from typing import Any, Awaitable, Callable, List, Literal, Union +from typing import Annotated, Any, Awaitable, Callable, List, Literal, Union import structlog from asgi_correlation_id import CorrelationIdMiddleware from asgi_correlation_id.context import correlation_id from ddtrace import tracer from ddtrace.contrib.asgi.middleware import TraceMiddleware -from fastapi import FastAPI, HTTPException, Query, Request, Response, Security, status +from fastapi import BackgroundTasks, FastAPI, HTTPException, Query, Request, Response, Security, status from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html @@ -21,13 +21,17 @@ from api.domain.openai_predict import OpenAIPredict from api.handler.custom_logging import setup_logging from api.integration.auth import VerifyToken +from api.integration.google_sheets import GoogleSheetsClient from api.models.chat_request import ChatRequest from api.models.chat_response import ChatResponse from api.models.create_protocol import CreateProtocol from api.models.empty_request_error import EmptyRequestError +from api.models.error_response import ErrorResponse +from api.models.feedback_request import FeedbackRequest from api.models.feedback_response import FeedbackResponse from api.models.internal_server_error import InternalServerError from api.models.update_protocol import UpdateProtocol +from api.models.user import User from api.settings import Settings settings: Settings = Settings() @@ -38,6 +42,7 @@ auth: VerifyToken = VerifyToken() openai: OpenAIPredict = OpenAIPredict(settings) +google_sheets_client = GoogleSheetsClient(settings) # Initialize FastAPI app with metadata @@ -147,10 +152,6 @@ class Status(BaseModel): version: str -class ErrorResponse(BaseModel): - message: str - - class HealthResponse(BaseModel): status: Status @@ -175,7 +176,7 @@ class CorsHeadersResponse(BaseModel): description="Generate a chat response based on the provided prompt.", ) async def create_chat_completion( - body: ChatRequest, auth_result: Any = Security(auth.verify) # noqa: B008 + body: ChatRequest, user: Annotated[User, Security(auth.verify)] ) -> Union[ChatResponse, ErrorResponse]: # noqa: B008 """ Generate a chat completion response using OpenAI. @@ -183,7 +184,7 @@ async def create_chat_completion( - **request**: The HTTP request containing the chat message. - **returns**: A chat response or an error message. """ - logger.info("POST /api/chat/completion", extra={"body": body.model_dump(), "auth_result": auth_result}) + logger.info("POST /api/chat/completion", extra={"body": body.model_dump(), "user": user}) try: if not body.message or body.message == "": raise HTTPException( @@ -198,9 +199,9 @@ async def create_chat_completion( response: Union[str, None] = openai.predict(prompt=body.message, chat_completion_message_params=body.history) if response is None or response == "": - return ChatResponse(reply="No response was generated", fake=body.fake) + return ChatResponse(reply="No response was generated", fake=bool(body.fake)) - return ChatResponse(reply=response, fake=body.fake) + return ChatResponse(reply=response, fake=bool(body.fake)) except Exception as e: logger.exception("Error processing chat completion") @@ -217,15 +218,15 @@ async def create_chat_completion( description="Generate a chat response based on the provided prompt that will update an existing protocol with the required changes.", ) async def update_protocol( - body: UpdateProtocol, auth_result: Any = Security(auth.verify) # noqa: B008 + body: UpdateProtocol, user: Annotated[User, Security(auth.verify)] ) -> Union[ChatResponse, ErrorResponse]: # noqa: B008 """ - Generate an updated protocolusing OpenAI. + Generate an updated protocol using OpenAI. - **request**: The HTTP request containing the existing protocol and other relevant parameters. - **returns**: A chat response or an error message. """ - logger.info("POST /api/chat/updateProtocol", extra={"body": body.model_dump(), "auth_result": auth_result}) + logger.info("POST /api/chat/updateProtocol", extra={"body": body.model_dump(), "user": user}) try: if not body.protocol_text or body.protocol_text == "": raise HTTPException( @@ -233,14 +234,14 @@ async def update_protocol( ) if body.fake: - return ChatResponse(reply="Fake response", fake=body.fake) + return ChatResponse(reply="Fake response", fake=bool(body.fake)) response: Union[str, None] = openai.predict(prompt=body.prompt, chat_completion_message_params=None) if response is None or response == "": - return ChatResponse(reply="No response was generated", fake=body.fake) + return ChatResponse(reply="No response was generated", fake=bool(body.fake)) - return ChatResponse(reply=response, fake=body.fake) + return ChatResponse(reply=response, fake=bool(body.fake)) except Exception as e: logger.exception("Error processing protocol update") @@ -257,15 +258,15 @@ async def update_protocol( description="Generate a chat response based on the provided prompt that will create a new protocol with the required changes.", ) async def create_protocol( - body: CreateProtocol, auth_result: Any = Security(auth.verify) # noqa: B008 + body: CreateProtocol, user: Annotated[User, Security(auth.verify)] ) -> Union[ChatResponse, ErrorResponse]: # noqa: B008 """ - Generate an updated protocolusing OpenAI. + Generate an updated protocol using OpenAI. - **request**: The HTTP request containing the chat message. - **returns**: A chat response or an error message. """ - logger.info("POST /api/chat/createProtocol", extra={"body": body.model_dump(), "auth_result": auth_result}) + logger.info("POST /api/chat/createProtocol", extra={"body": body.model_dump(), "user": user}) try: if not body.prompt or body.prompt == "": @@ -279,9 +280,9 @@ async def create_protocol( response: Union[str, None] = openai.predict(prompt=str(body.model_dump()), chat_completion_message_params=None) if response is None or response == "": - return ChatResponse(reply="No response was generated", fake=body.fake) + return ChatResponse(reply="No response was generated", fake=bool(body.fake)) - return ChatResponse(reply=response, fake=body.fake) + return ChatResponse(reply=response, fake=bool(body.fake)) except Exception as e: logger.exception("Error processing protocol creation") @@ -339,23 +340,19 @@ async def redoc_html() -> HTMLResponse: summary="Feedback", description="Send feedback to the team.", ) -async def feedback(request: Request, auth_result: Any = Security(auth.verify)) -> FeedbackResponse: # noqa: B008 - """ - Send feedback to the team. - - - **request**: The HTTP request containing the feedback message. - - **returns**: A feedback response or an error message. - """ +async def feedback( + body: FeedbackRequest, user: Annotated[User, Security(auth.verify)], background_tasks: BackgroundTasks +) -> FeedbackResponse: logger.info("POST /api/feedback") try: - body = await request.json() - if "feedbackText" not in body.keys() or body["feedbackText"] == "": - logger.info("Feedback empty") - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=EmptyRequestError(message="Request body is empty")) - logger.info(f"Feedback received: {body}") - feedbackText = body["feedbackText"] - # todo: Store feedback text in a database - return FeedbackResponse(reply=f"Feedback Received: {feedbackText}", fake=False) + if body.fake: + return FeedbackResponse(reply="Fake response", fake=bool(body.fake)) + feedback_text = body.feedbackText + logger.info("Feedback received", user_id=user.sub, feedback=feedback_text) + background_tasks.add_task(google_sheets_client.append_feedback_to_sheet, user_id=str(user.sub), feedback=feedback_text) + return FeedbackResponse( + reply=f"Feedback Received and sanitized: {google_sheets_client.sanitize_for_google_sheets(feedback_text)}", fake=False + ) except Exception as e: logger.exception("Error processing feedback") diff --git a/opentrons-ai-server/api/integration/auth.py b/opentrons-ai-server/api/integration/auth.py index 12e8b2a4a9e..addc0abafb8 100644 --- a/opentrons-ai-server/api/integration/auth.py +++ b/opentrons-ai-server/api/integration/auth.py @@ -1,10 +1,9 @@ -from typing import Any, Optional - import jwt import structlog from fastapi import HTTPException, Security, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer, SecurityScopes +from api.models.user import User from api.settings import Settings settings: Settings = Settings() @@ -28,8 +27,8 @@ def __init__(self) -> None: self.jwks_client = jwt.PyJWKClient(jwks_url) async def verify( - self, security_scopes: SecurityScopes, credentials: Optional[HTTPAuthorizationCredentials] = Security(HTTPBearer()) # noqa: B008 - ) -> Any: + self, security_scopes: SecurityScopes, credentials: HTTPAuthorizationCredentials = Security(HTTPBearer()) # noqa: B008 + ) -> User: if credentials is None: raise UnauthenticatedException() @@ -50,8 +49,9 @@ async def verify( audience=self.config.auth0_api_audience, issuer=self.config.auth0_issuer, ) - logger.info("Decoded token", extra={"token": payload}) - return payload + user = User(**payload) + logger.info("User object", extra={"user": user}) + return user except jwt.ExpiredSignatureError: logger.error("Expired Signature", extra={"credentials": credentials}, exc_info=True) # Handle token expiration, e.g., refresh token, re-authenticate, etc. diff --git a/opentrons-ai-server/api/integration/google_sheets.py b/opentrons-ai-server/api/integration/google_sheets.py new file mode 100644 index 00000000000..e86d4103097 --- /dev/null +++ b/opentrons-ai-server/api/integration/google_sheets.py @@ -0,0 +1,75 @@ +import json +import random +import re + +import gspread +import structlog +from google.oauth2.service_account import Credentials +from gspread import SpreadsheetNotFound # type: ignore +from gspread.client import Client as GspreadClient + +from api.settings import Settings + + +class GoogleSheetsClient: + SCOPES = ["https://www.googleapis.com/auth/spreadsheets"] + + def __init__(self, settings: Settings) -> None: + self.settings = settings + self.logger = structlog.stdlib.get_logger(settings.logger_name) + self.client: GspreadClient = self._initialize_client() + + def _initialize_client(self) -> GspreadClient: + """Initialize the gspread client with Service Account credentials loaded from the environment.""" + creds: Credentials = self._get_credentials() + return gspread.authorize(creds) # type: ignore + + def _get_credentials(self) -> Credentials: + """Load Service Account credentials from an environment variable.""" + google_credentials_json = self.settings.google_credentials_json.get_secret_value() + if not google_credentials_json: + raise EnvironmentError("Missing GOOGLE_SHEETS_CREDENTIALS environment variable.") + + creds_info = json.loads(google_credentials_json) + creds: Credentials = Credentials.from_service_account_info(info=creds_info, scopes=self.SCOPES) # type: ignore + return creds + + @staticmethod + def sanitize_for_google_sheets(input_text: str) -> str: + """Sanitize input to remove JavaScript and HTML tags, and prevent formulas.""" + script_pattern = re.compile(r'(javascript:[^"]*|.*?|on\w+=".*?"|on\w+=\'.*?\')', re.IGNORECASE) + sanitized_text = re.sub(script_pattern, "", input_text) + sanitized_text = re.sub(r"(<.*?>|<.*?>)", "", sanitized_text) + sanitized_text = re.sub(r"^\s*=\s*", "", sanitized_text) + return sanitized_text.strip() + + def append_feedback_to_sheet(self, user_id: str, feedback: str) -> None: + """Append a row of feedback to the Google Sheet.""" + try: + sheet_id = self.settings.google_sheet_id + worksheet_name = self.settings.google_sheet_worksheet + spreadsheet = self.client.open_by_key(sheet_id) + worksheet = spreadsheet.worksheet(worksheet_name) + + feedback = self.sanitize_for_google_sheets(feedback) + + worksheet.append_row([user_id, feedback]) + self.logger.info("Feedback successfully appended to Google Sheet.") + except SpreadsheetNotFound: + self.logger.error("Spreadsheet not found or not accessible.") + except Exception: + self.logger.error("Error appending feedback to Google Sheet.", exc_info=True) + + +# Example usage +def main() -> None: + """Run an example appending feedback to Google Sheets.""" + settings = Settings() + google_sheets_client = GoogleSheetsClient(settings) + user_id = str(random.randint(100000, 999999)) + feedback = f"This is a test feedback for user {user_id}." + google_sheets_client.append_feedback_to_sheet(user_id, feedback) + + +if __name__ == "__main__": + main() diff --git a/opentrons-ai-server/api/models/create_protocol.py b/opentrons-ai-server/api/models/create_protocol.py index 5b011284848..f94d87d2bf8 100644 --- a/opentrons-ai-server/api/models/create_protocol.py +++ b/opentrons-ai-server/api/models/create_protocol.py @@ -5,6 +5,7 @@ class CreateProtocol(BaseModel): prompt: str = Field(..., description="Prompt") + regenerate: bool = Field(..., description="Flag to indicate if regeneration is needed") scientific_application_type: str = Field(..., description="Scientific application type") description: str = Field(..., description="Description of the protocol") robots: Literal["opentrons_flex", "opentrons_ot2"] = Field(..., description="List of required robots") diff --git a/opentrons-ai-server/api/models/error_response.py b/opentrons-ai-server/api/models/error_response.py new file mode 100644 index 00000000000..ba52d60547e --- /dev/null +++ b/opentrons-ai-server/api/models/error_response.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class ErrorResponse(BaseModel): + message: str diff --git a/opentrons-ai-server/api/models/feedback_request.py b/opentrons-ai-server/api/models/feedback_request.py new file mode 100644 index 00000000000..89a098b0f92 --- /dev/null +++ b/opentrons-ai-server/api/models/feedback_request.py @@ -0,0 +1,15 @@ +from typing import Optional + +from pydantic import BaseModel, Field, field_validator + + +class FeedbackRequest(BaseModel): + feedbackText: str = Field(..., description="The feedback message content") + fake: Optional[bool] = Field(False, description="Indicates if this is a fake feedback entry") + + # Validation to ensure feedback_text is populated and not empty + @field_validator("feedbackText") + def feedback_text_must_not_be_empty(cls, value: str) -> str: + if not value or value.strip() == "": + raise ValueError("feedback_text must be populated and not empty") + return value diff --git a/opentrons-ai-server/api/models/user.py b/opentrons-ai-server/api/models/user.py new file mode 100644 index 00000000000..d1d79c2b6d1 --- /dev/null +++ b/opentrons-ai-server/api/models/user.py @@ -0,0 +1,16 @@ +from typing import List, Optional, Union + +from pydantic import BaseModel, Field + + +class User(BaseModel): + aud: Union[str, List[str]] = Field(..., description="Audience URL(s)") + azp: str = Field(..., description="Authorized party ID") + exp: int = Field(..., description="Expiration timestamp") + iat: int = Field(..., description="Issued-at timestamp") + iss: str = Field(..., description="Issuer URL") + scope: Optional[str] = Field(None, description="Space-separated scopes") + sub: str = Field(..., description="Subject identifier for the token") + + class Config: + extra = "allow" # Allows additional fields not specified in the model diff --git a/opentrons-ai-server/api/settings.py b/opentrons-ai-server/api/settings.py index c59a25c33de..9557b51614b 100644 --- a/opentrons-ai-server/api/settings.py +++ b/opentrons-ai-server/api/settings.py @@ -34,11 +34,15 @@ class Settings(BaseSettings): dd_trace_enabled: str = "false" cpu: str = "1028" memory: str = "2048" + google_sheet_id: str = "harcoded_default_from_settings" + google_sheet_worksheet: str = "Sheet1" # Secrets # These come from environment variables in the local and deployed execution environments openai_api_key: SecretStr = SecretStr("default_openai_api_key") huggingface_api_key: SecretStr = SecretStr("default_huggingface_api_key") + google_credentials_json: SecretStr = SecretStr("default_google_credentials_json") + datadog_api_key: SecretStr = SecretStr("default_datadog_api_key") @property def json_logging(self) -> bool: diff --git a/opentrons-ai-server/deploy.py b/opentrons-ai-server/deploy.py index 61cbc64b9a1..813dc3ccca1 100644 --- a/opentrons-ai-server/deploy.py +++ b/opentrons-ai-server/deploy.py @@ -3,7 +3,7 @@ import datetime import subprocess from dataclasses import dataclass -from typing import Dict, List +from typing import Any, Dict, List import boto3 import docker @@ -12,7 +12,7 @@ from rich import print from rich.prompt import Prompt -ENVIRONMENTS = ["crt", "dev", "sandbox", "staging", "prod"] +ENVIRONMENTS = ["staging", "prod"] def get_aws_account_id() -> str: @@ -28,6 +28,7 @@ def get_aws_region() -> str: @dataclass(frozen=True) class BaseDeploymentConfig: + ENV: str IMAGE_NAME: str # local image name ECR_URL: str ECR_REPOSITORY: str @@ -40,42 +41,9 @@ class BaseDeploymentConfig: DEPLOYMENT_POLL_INTERVAL_S: int = 20 -@dataclass(frozen=True) -class CrtDeploymentConfig(BaseDeploymentConfig): - ECR_REPOSITORY: str = "crt-ecr-repo" - ECR_URL: str = f"{get_aws_account_id()}.dkr.ecr.{get_aws_region()}.amazonaws.com" - IMAGE_NAME: str = "crt-ai-server" - CLUSTER_NAME: str = "crt-ai-cluster" - SERVICE_NAME: str = "crt-ai-service" - CONTAINER_NAME: str = "crt-ai-api" - ENV_VARIABLES_SECRET_NAME: str = "crt-environment-variables" - - -@dataclass(frozen=True) -class SandboxDeploymentConfig(BaseDeploymentConfig): - ECR_REPOSITORY: str = "sandbox-ecr-repo" - ECR_URL: str = f"{get_aws_account_id()}.dkr.ecr.{get_aws_region()}.amazonaws.com" - IMAGE_NAME: str = "sandbox-ai-server" - CLUSTER_NAME: str = "sandbox-ai-cluster" - SERVICE_NAME: str = "sandbox-ai-service" - CONTAINER_NAME: str = "sandbox-ai-api" - ENV_VARIABLES_SECRET_NAME: str = "sandbox-environment-variables" - - -@dataclass(frozen=True) -class DevDeploymentConfig(BaseDeploymentConfig): - ECR_REPOSITORY: str = "dev-ecr-repo" - ECR_URL: str = f"{get_aws_account_id()}.dkr.ecr.{get_aws_region()}.amazonaws.com" - FUNCTION_NAME: str = "dev-api-function" - IMAGE_NAME: str = "dev-ai-server" - CLUSTER_NAME: str = "dev-ai-cluster" - SERVICE_NAME: str = "dev-ai-service" - CONTAINER_NAME: str = "dev-ai-api" - ENV_VARIABLES_SECRET_NAME: str = "dev-environment-variables" - - @dataclass(frozen=True) class StagingDeploymentConfig(BaseDeploymentConfig): + ENV: str = "staging" ECR_REPOSITORY: str = "staging-ecr-repo" ECR_URL: str = f"{get_aws_account_id()}.dkr.ecr.{get_aws_region()}.amazonaws.com" IMAGE_NAME: str = "staging-ai-server" @@ -87,6 +55,7 @@ class StagingDeploymentConfig(BaseDeploymentConfig): @dataclass(frozen=True) class ProdDeploymentConfig(BaseDeploymentConfig): + ENV: str = "prod" ECR_REPOSITORY: str = "prod-ecr-repo" ECR_URL: str = f"{get_aws_account_id()}.dkr.ecr.{get_aws_region()}.amazonaws.com" IMAGE_NAME: str = "prod-ai-server" @@ -156,7 +125,43 @@ def update_environment_variables(self, environment_variables: List[Dict[str, str return updated_environment_variables - def update_ecs_task(self) -> None: + def get_secret_arn(self, secret_name: str) -> str: + response = self.secret_manager_client.describe_secret(SecretId=secret_name) + return str(response["ARN"]) + + def update_secrets_in_container_definition(self, container_definition: dict[str, Any]) -> None: + expected_secrets = {field.upper() for field, field_type in self.env_variables.__annotations__.items() if field_type == SecretStr} + print(f"Expected secrets: {expected_secrets}") + + task_secrets = {secret["name"].upper() for secret in container_definition.get("secrets", [])} + print(f"Existing secrets: {task_secrets}") + + if not task_secrets: + raise ValueError("No secrets found in the api container definition ...") + + unexpected_secrets = [secret.upper() for secret in task_secrets if secret not in expected_secrets] + if unexpected_secrets: + raise ValueError(f"Secrets found in the api container definition that are NOT in Settings: {', '.join(unexpected_secrets)}") + + missing_secrets = [secret.upper() for secret in expected_secrets if secret.upper() not in task_secrets] + + if missing_secrets: + print(f"Missing secrets: {missing_secrets}") + for secret in missing_secrets: + print(f"Adding missing secret: {secret}") + # secret name is the same as the property name + # of the secret in the Settings class + # but with _ replaced with - + secret_name = f"{self.config.ENV}-{secret.lower().replace("_", "-")}" + value_from = self.get_secret_arn(secret_name) + # name is the all caps version of the secret name + # valueFrom is the ARN of the secret + new_secret = {"name": secret, "valueFrom": value_from} + container_definition["secrets"].append(new_secret) + else: + print("No secrets need to be added.") + + def update_ecs_task(self, dry: bool) -> None: print(f"Updating ECS task with new image: {self.full_image_name}") response = self.ecs_client.describe_services(cluster=self.config.CLUSTER_NAME, services=[self.config.SERVICE_NAME]) task_definition_arn = response["services"][0]["taskDefinition"] @@ -164,6 +169,9 @@ def update_ecs_task(self) -> None: task_definition = self.ecs_client.describe_task_definition(taskDefinition=task_definition_arn)["taskDefinition"] container_definitions = task_definition["containerDefinitions"] for container_definition in container_definitions: + # ENV--datadog-agent container has one secret and 2 environment variables + # ENV--log-router container has no secrets or environment variables + # These are managed in the infra repo, NOT here if container_definition["name"] == self.config.CONTAINER_NAME: container_definition["image"] = self.full_image_name environment_variables = container_definition.get("environment", []) @@ -171,13 +179,15 @@ def update_ecs_task(self) -> None: for key, value in self.env_variables.model_dump().items(): if not isinstance(value, SecretStr): # Secrets are not set here - # They are set in the secrets key of ECS task definition + # They are set in the secrets key of the containerDefinition environment_variables = self.update_environment_variables(environment_variables, key, value) # Overwrite the DD_VERSION environment variable # with the current deployment tag # this is what we are using for version currently environment_variables = self.update_environment_variables(environment_variables, "DD_VERSION", self.config.TAG) container_definition["environment"] = environment_variables + # Update the secrets in the container definition + self.update_secrets_in_container_definition(container_definition) print("Updated container definition:") print(container_definition) break @@ -195,6 +205,11 @@ def update_ecs_task(self) -> None: } print("New task definition:") print(new_task_definition) + + if dry: + print("Dry run, not updating the ECS task.") + return + register_response = self.ecs_client.register_task_definition(**new_task_definition) new_task_definition_arn = register_response["taskDefinition"]["taskDefinitionArn"] @@ -204,14 +219,21 @@ def update_ecs_task(self) -> None: taskDefinition=new_task_definition_arn, forceNewDeployment=True, ) + print(f"Deployment to {self.config.ENV} started.") + print("The API container definition was updated.") + print("A new Task definition was defined and registered.") + print("Then we told the ECS service to deploy the new definition.") + print("Monitor the deployment in the ECS console.") def main() -> None: parser = argparse.ArgumentParser(description="Manage ECS Fargate deployment.") parser.add_argument("--env", type=str, help=f"Deployment environment {ENVIRONMENTS}") parser.add_argument("--tag", type=str, help="The tag and therefore version of the container to use") + # action="store_true" sets args.dry to True only if --dry is provided on the command line + parser.add_argument("--dry", action="store_true", help="Dry run, do not make any changes") args = parser.parse_args() - # Determine if the script was called with command-line arguments + if args.env: if args.env.lower() not in ENVIRONMENTS: print(f"[red]Invalid environment specified: {args.env}[/red]") @@ -221,7 +243,6 @@ def main() -> None: tag = args.tag else: if args.env: - # Passing --env alone generates a tag and does not prompt! tag = str(int(datetime.datetime.now().timestamp())) else: # Interactive prompts if env not set @@ -237,24 +258,17 @@ def main() -> None: config = ProdDeploymentConfig(TAG=tag) elif env == "staging": config = StagingDeploymentConfig(TAG=tag) - elif env == "crt": - config = CrtDeploymentConfig(TAG=tag) - elif env == "dev": - config = DevDeploymentConfig(TAG=tag) - elif env == "sandbox": - config = SandboxDeploymentConfig(TAG=tag) else: print(f"[red]Invalid environment specified: {env}[/red]") exit(1) aws = Deploy(config) aws.build_docker_image() - aws.push_docker_image_to_ecr() - aws.update_ecs_task() - print(f"Deployment to {env} started.") - print(f"A new image was built and pushed to ECR with tag: {tag}") - print("A new Task definition was defined and registered.") - print("Then we told the ECS service to deploy the new definition.") - print("Monitor the deployment in the ECS console.") + if args.dry: + print("Dry run, not pushing image to ECR.") + else: + aws.push_docker_image_to_ecr() + print(f"A new image was built and pushed to ECR with tag: {tag}") + aws.update_ecs_task(dry=args.dry) if __name__ == "__main__": diff --git a/opentrons-ai-server/tests/helpers/client.py b/opentrons-ai-server/tests/helpers/client.py index 7c0d2383ffd..bf5a7febb3c 100644 --- a/opentrons-ai-server/tests/helpers/client.py +++ b/opentrons-ai-server/tests/helpers/client.py @@ -3,6 +3,7 @@ from typing import Any, Callable, Optional, TypeVar from api.models.chat_request import ChatRequest, FakeKeys +from api.models.feedback_request import FeedbackRequest from httpx import Client as HttpxClient from httpx import Response, Timeout from rich.console import Console, Group @@ -68,10 +69,13 @@ def get_chat_completion(self, message: str, fake: bool = True, fake_key: Optiona headers = self.standard_headers if not bad_auth else self.invalid_auth_headers return self.httpx.post("/chat/completion", headers=headers, json=request.model_dump()) - def get_feedback(self, message: str, fake: bool = True) -> Response: + def post_feedback(self, message: str, fake: bool = True, bad_auth: bool = False) -> Response: """Call the /chat/feedback endpoint and return the response.""" - request = f'{"feedbackText": "{message}"}' - return self.httpx.post("/chat/feedback", headers=self.standard_headers, json=request) + request: dict[str, Any] = {"message": message, "fake": fake} + if message != "": + request = FeedbackRequest(feedbackText=message, fake=fake).model_dump() + headers = self.standard_headers if not bad_auth else self.invalid_auth_headers + return self.httpx.post("/chat/feedback", headers=headers, json=request) def get_bad_endpoint(self, bad_auth: bool = False) -> Response: """Call nonexistent endpoint and return the response.""" @@ -113,6 +117,11 @@ def main() -> None: response = client.get_health() print_response(response) + console.print(Rule("Submit feedback", style="bold")) + feedback_message = Prompt.ask("Enter feedback message") + response = client.post_feedback(feedback_message, fake=False) + print_response(response) + console.print(Rule("Getting chat completion with fake=True and good auth (won't call OpenAI)", style="bold")) response = client.get_chat_completion("How do I load a pipette?") print_response(response) diff --git a/opentrons-ai-server/tests/test_google_sheets_sanatize.py b/opentrons-ai-server/tests/test_google_sheets_sanatize.py new file mode 100644 index 00000000000..b7b8e3778f0 --- /dev/null +++ b/opentrons-ai-server/tests/test_google_sheets_sanatize.py @@ -0,0 +1,23 @@ +import pytest +from api.integration.google_sheets import GoogleSheetsClient + + +@pytest.mark.unit +@pytest.mark.parametrize( + "input_text, expected_output", + [ + ('Click here!', "Click here!"), + ('javascript:alert("Malicious code")', '"Malicious code")'), + ("Important message", "Important message"), + ("onload=\"alert('Attack')\" Hello!", "Hello!"), + ('=IMPORTRANGE("https://example.com/sheet", "Sheet1!A1")', 'IMPORTRANGE("https://example.com/sheet", "Sheet1!A1")'), + ("Hello, world!", "Hello, world!"), + ("link", "link"), + ("=SUM(A1:A10)", "SUM(A1:A10)"), + ('', ""), + ('<script>alert("test")</script>', 'alert("test")'), + ], +) +def test_sanitize_for_google_sheets(input_text: str, expected_output: str) -> None: + sanitized_text = GoogleSheetsClient.sanitize_for_google_sheets(input_text) + assert sanitized_text == expected_output, f"Expected '{expected_output}' but got '{sanitized_text}'" diff --git a/opentrons-ai-server/tests/test_live.py b/opentrons-ai-server/tests/test_live.py index ce22f4ff405..797d21fe7b6 100644 --- a/opentrons-ai-server/tests/test_live.py +++ b/opentrons-ai-server/tests/test_live.py @@ -1,5 +1,6 @@ import pytest from api.models.chat_response import ChatResponse +from api.models.error_response import ErrorResponse from api.models.feedback_response import FeedbackResponse from tests.helpers.client import Client @@ -28,13 +29,39 @@ def test_get_chat_completion_bad_auth(client: Client) -> None: @pytest.mark.live -def test_get_feedback_good_auth(client: Client) -> None: +def test_post_feedback_good_auth(client: Client) -> None: """Test the feedback endpoint with good authentication.""" - response = client.get_feedback("How do I load tipracks for my 8 channel pipette on an OT2?", fake=True) + response = client.post_feedback("Would be nice if it were faster", fake=False) assert response.status_code == 200, "Feedback with good auth should return HTTP 200" + assert response.json()["reply"] == "Feedback Received and sanitized: Would be nice if it were faster", "Response should contain input" FeedbackResponse.model_validate(response.json()) +@pytest.mark.live +def test_post_empty_feedback_good_auth(client: Client) -> None: + """Test the feedback endpoint with good authentication.""" + response = client.post_feedback("", fake=False) + assert response.status_code == 422, "Feedback with feebackText = '' should return HTTP 422" + ErrorResponse.model_validate(response.json()) + + +@pytest.mark.live +def test_post_feedback_good_auth_fake(client: Client) -> None: + """Test the feedback endpoint with good authentication.""" + response = client.post_feedback("More LLM", fake=True) + assert response.status_code == 200, "Fake response" + assert response.json()["fake"] is True, "Fake indicator should be True" + assert response.json()["reply"] == "Fake response", "Response should be 'Fake response'" + FeedbackResponse.model_validate(response.json()) + + +@pytest.mark.live +def test_post_feedback_bad_auth(client: Client) -> None: + """Test the feedback endpoint with bad authentication.""" + response = client.post_feedback("How do I load tipracks for my 8 channel pipette on an OT2?", fake=False, bad_auth=True) + assert response.status_code == 401, "Feedback with bad auth should return HTTP 401" + + @pytest.mark.live def test_get_bad_endpoint_with_good_auth(client: Client) -> None: """Test a nonexistent endpoint with good authentication.""" diff --git a/protocol-designer/src/organisms/AssignLiquidsModal/LiquidToolbox.tsx b/protocol-designer/src/organisms/AssignLiquidsModal/LiquidToolbox.tsx index 9e9442cf726..7bca54d3f67 100644 --- a/protocol-designer/src/organisms/AssignLiquidsModal/LiquidToolbox.tsx +++ b/protocol-designer/src/organisms/AssignLiquidsModal/LiquidToolbox.tsx @@ -53,7 +53,7 @@ interface LiquidToolboxProps { } export function LiquidToolbox(props: LiquidToolboxProps): JSX.Element { const { onClose } = props - const { t } = useTranslation(['liquids', 'shared', 'form']) + const { t } = useTranslation(['liquids', 'form', 'shared']) const dispatch = useDispatch() const [showDefineLiquidModal, setDefineLiquidModal] = useState(false) const liquids = useSelector(labwareIngredSelectors.allIngredientNamesIds) diff --git a/protocol-designer/src/organisms/WellOrderModal/index.tsx b/protocol-designer/src/organisms/WellOrderModal/index.tsx index 91a7406104e..cb0d7d64ee4 100644 --- a/protocol-designer/src/organisms/WellOrderModal/index.tsx +++ b/protocol-designer/src/organisms/WellOrderModal/index.tsx @@ -92,8 +92,7 @@ export function WellOrderModal(props: WellOrderModalProps): JSX.Element | null { } const handleReset = (): void => { - setWellOrder({ firstValue: DEFAULT_FIRST, secondValue: DEFAULT_SECOND }) - applyChanges() + updateValues(DEFAULT_FIRST, DEFAULT_SECOND) closeModal() } @@ -144,6 +143,13 @@ export function WellOrderModal(props: WellOrderModalProps): JSX.Element | null { if (!isOpen) return null + let secondaryOptions = WELL_ORDER_VALUES + if (VERTICAL_VALUES.includes(wellOrder.firstValue)) { + secondaryOptions = HORIZONTAL_VALUES + } else if (HORIZONTAL_VALUES.includes(wellOrder.firstValue)) { + secondaryOptions = VERTICAL_VALUES + } + return createPortal( ({ + filterOptions={secondaryOptions.map(value => ({ value, name: t(`step_edit_form.field.well_order.option.${value}`), disabled: isSecondOptionDisabled(value), diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx index 33aa24787fb..fc811b2665a 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/SelectPipettes.tsx @@ -182,7 +182,6 @@ export function SelectPipettes(props: WizardTileProps): JSX.Element | null { {page === 'add' ? ( diff --git a/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx b/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx index 140878c9994..b5d69253435 100644 --- a/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx +++ b/protocol-designer/src/pages/CreateNewProtocolWizard/WizardBody.tsx @@ -2,8 +2,8 @@ import type * as React from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' import { - ALIGN_END, ALIGN_CENTER, + ALIGN_END, BORDERS, Btn, COLORS, @@ -11,10 +11,11 @@ import { Flex, JUSTIFY_SPACE_BETWEEN, LargeButton, + OVERFLOW_SCROLL, SPACING, StyledText, - TYPOGRAPHY, Tooltip, + TYPOGRAPHY, useHoverTooltip, } from '@opentrons/components' import temporaryImg from '../../assets/images/placeholder_image_delete.png' @@ -56,13 +57,19 @@ export function WizardBody(props: WizardBodyProps): JSX.Element { > - + - {labwareLoadedOnModule != null ? ( + {labwareLoadedOnModule != null && + !isLabwareOccludedByThermocyclerLid ? ( <> option.value === selectedValue) ?? options[0] + } onClick={value => { updateValue(value) setSelectedValue(value) diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx index 778159b6d31..c198359ab52 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/ConnectedStepInfo.tsx @@ -1,4 +1,5 @@ import { useDispatch, useSelector } from 'react-redux' +import type { Dispatch, SetStateAction } from 'react' import { useTranslation } from 'react-i18next' import { useConditionalConfirm } from '@opentrons/components' import * as timelineWarningSelectors from '../../../../top-selectors/timelineWarnings' @@ -33,7 +34,6 @@ import { nonePressed, } from './utils' -import type * as React from 'react' import type { ThunkDispatch } from 'redux-thunk' import type { HoverOnStepAction, @@ -47,10 +47,18 @@ export interface ConnectedStepInfoProps { stepId: StepIdType stepNumber: number dragHovered?: boolean + openedOverflowMenuId?: string | null + setOpenedOverflowMenuId?: Dispatch> } export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element { - const { stepId, stepNumber, dragHovered = false } = props + const { + stepId, + stepNumber, + dragHovered = false, + openedOverflowMenuId, + setOpenedOverflowMenuId, + } = props const { t } = useTranslation('application') const dispatch = useDispatch>() const stepIds = useSelector(getOrderedStepIds) @@ -203,6 +211,8 @@ export function ConnectedStepInfo(props: ConnectedStepInfoProps): JSX.Element { /> )} void findStepIndex: (stepId: StepIdType) => number orderedStepIds: string[] + openedOverflowMenuId?: string | null + setOpenedOverflowMenuId?: Dispatch> } interface DropType { @@ -30,7 +33,15 @@ interface DropType { } function DragDropStep(props: DragDropStepProps): JSX.Element { - const { stepId, moveStep, findStepIndex, orderedStepIds, stepNumber } = props + const { + stepId, + moveStep, + findStepIndex, + orderedStepIds, + stepNumber, + openedOverflowMenuId, + setOpenedOverflowMenuId, + } = props const stepRef = useRef(null) const [{ isDragging }, drag] = useDrag( @@ -73,6 +84,8 @@ function DragDropStep(props: DragDropStepProps): JSX.Element { data-handler-id={handlerId} > (null) const findStepIndex = (stepId: StepIdType): number => orderedStepIds.findIndex(id => stepId === id) @@ -123,6 +139,8 @@ export function DraggableSteps(props: DraggableStepsProps): JSX.Element | null { moveStep={moveStep} findStepIndex={findStepIndex} orderedStepIds={orderedStepIds} + openedOverflowMenuId={openedOverflowMenuId} + setOpenedOverflowMenuId={setOpenedOverflowMenuId} /> ))} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx index ce5860b2cbf..4ed55987f08 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/StepContainer.tsx @@ -35,6 +35,11 @@ import { LINE_CLAMP_TEXT_STYLE } from '../../../../atoms' import { StepOverflowMenu } from './StepOverflowMenu' import { capitalizeFirstLetterAfterNumber } from './utils' +import type { + SetStateAction, + Dispatch, + MouseEvent as ReactMouseEvent, +} from 'react' import type { ThunkDispatch } from 'redux-thunk' import type { IconName } from '@opentrons/components' import type { StepIdType } from '../../../../form-types' @@ -42,16 +47,18 @@ import type { BaseState } from '../../../../types' const STARTING_DECK_STATE = 'Starting deck state' const FINAL_DECK_STATE = 'Final deck state' - +const PX_HEIGHT_TO_TOP_OF_CONTAINER = 32 export interface StepContainerProps { title: string iconName: IconName + openedOverflowMenuId?: string | null + setOpenedOverflowMenuId?: Dispatch> stepId?: string iconColor?: string - onClick?: (event: React.MouseEvent) => void - onDoubleClick?: (event: React.MouseEvent) => void - onMouseEnter?: (event: React.MouseEvent) => void - onMouseLeave?: (event: React.MouseEvent) => void + onClick?: (event: ReactMouseEvent) => void + onDoubleClick?: (event: ReactMouseEvent) => void + onMouseEnter?: (event: ReactMouseEvent) => void + onMouseLeave?: (event: ReactMouseEvent) => void selected?: boolean hovered?: boolean hasError?: boolean @@ -74,10 +81,11 @@ export function StepContainer(props: StepContainerProps): JSX.Element { hasError = false, isStepAfterError = false, dragHovered = false, + setOpenedOverflowMenuId, + openedOverflowMenuId, } = props const [top, setTop] = useState(0) const menuRootRef = useRef(null) - const [stepOverflowMenu, setStepOverflowMenu] = useState(false) const isStartingOrEndingState = title === STARTING_DECK_STATE || title === FINAL_DECK_STATE const dispatch = useDispatch>() @@ -104,22 +112,21 @@ export function StepContainer(props: StepContainerProps): JSX.Element { menuRootRef.current?.contains(event.target) ) - if (wasOutside && stepOverflowMenu) { - setStepOverflowMenu(false) + if (wasOutside) { + setOpenedOverflowMenuId?.(null) } } - const handleOverflowClick = (event: React.MouseEvent): void => { - const { clientY } = event - + const handleOverflowClick = (event: ReactMouseEvent): void => { + const buttonRect = event.currentTarget.getBoundingClientRect() const screenHeight = window.innerHeight - const rootHeight = menuRootRef.current - ? menuRootRef.current.offsetHeight - : 0 + const rootHeight = menuRootRef.current?.offsetHeight || 0 + + const spaceBelow = screenHeight - buttonRect.bottom const top = - screenHeight - clientY > rootHeight - ? clientY + 5 - : clientY - rootHeight - 5 + spaceBelow > rootHeight + ? buttonRect.bottom - PX_HEIGHT_TO_TOP_OF_CONTAINER + : buttonRect.top - rootHeight + PX_HEIGHT_TO_TOP_OF_CONTAINER setTop(top) } @@ -135,7 +142,7 @@ export function StepContainer(props: StepContainerProps): JSX.Element { if (stepId != null) { dispatch(populateForm(stepId)) } - setStepOverflowMenu(false) + setOpenedOverflowMenuId?.(null) } const onDeleteClickAction = (): void => { @@ -168,7 +175,6 @@ export function StepContainer(props: StepContainerProps): JSX.Element { ) } } - const { confirm: confirmDelete, showConfirmation: showDeleteConfirmation, @@ -242,10 +248,15 @@ export function StepContainer(props: StepContainerProps): JSX.Element { { + onClick={(e: ReactMouseEvent) => { e.preventDefault() e.stopPropagation() - setStepOverflowMenu(prev => !prev) + if (openedOverflowMenuId === stepId) { + setOpenedOverflowMenuId?.(null) + } else { + setOpenedOverflowMenuId?.(stepId ?? null) + } + handleOverflowClick(e) }} /> @@ -262,10 +273,12 @@ export function StepContainer(props: StepContainerProps): JSX.Element { /> ) : null} - {stepOverflowMenu && stepId != null + {stepId != null && + openedOverflowMenuId === stepId && + setOpenedOverflowMenuId != null ? createPortal( top: number - setStepOverflowMenu: React.Dispatch> + setOpenedOverflowMenuId: React.Dispatch> handleEdit: () => void confirmDelete: () => void confirmMultiDelete: () => void @@ -44,7 +44,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { stepId, menuRootRef, top, - setStepOverflowMenu, + setOpenedOverflowMenuId, handleEdit, confirmDelete, confirmMultiDelete, @@ -91,7 +91,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { ref={menuRootRef} zIndex={12} top={top} - left="19.5rem" + left="18.75rem" position={POSITION_ABSOLUTE} whiteSpace={NO_WRAP} borderRadius={BORDERS.borderRadius8} @@ -109,7 +109,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { disabled={batchEditFormHasUnstagedChanges} onClick={() => { duplicateMultipleSteps() - setStepOverflowMenu(false) + setOpenedOverflowMenuId(null) }} > {t('duplicate_steps')} @@ -118,7 +118,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { { confirmMultiDelete() - setStepOverflowMenu(false) + setOpenedOverflowMenuId(null) }} > {t('delete_steps')} @@ -133,7 +133,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { { - setStepOverflowMenu(false) + setOpenedOverflowMenuId(null) dispatch(hoverOnStep(stepId)) dispatch(toggleViewSubstep(stepId)) dispatch(analyticsEvent(selectViewDetailsEvent)) @@ -146,7 +146,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { disabled={singleEditFormHasUnsavedChanges} onClick={() => { duplicateStep(stepId) - setStepOverflowMenu(false) + setOpenedOverflowMenuId(null) }} > {t('duplicate')} @@ -155,7 +155,7 @@ export function StepOverflowMenu(props: StepOverflowMenuProps): JSX.Element { { confirmDelete() - setStepOverflowMenu(false) + setOpenedOverflowMenuId(null) }} > {t('delete')} diff --git a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx index 502ccf68f06..d283468dc33 100644 --- a/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx +++ b/protocol-designer/src/pages/Designer/ProtocolSteps/Timeline/__tests__/StepOverflowMenu.test.tsx @@ -58,7 +58,7 @@ describe('StepOverflowMenu', () => { stepId: moveLiquidStepId, top: 0, menuRootRef: { current: null }, - setStepOverflowMenu: vi.fn(), + setOpenedOverflowMenuId: vi.fn(), multiSelectItemIds: [], handleEdit: vi.fn(), confirmDelete: mockConfirm, diff --git a/protocol-designer/src/pages/ProtocolOverview/ProtocolMetadata.tsx b/protocol-designer/src/pages/ProtocolOverview/ProtocolMetadata.tsx index b29cdc1fbc6..d750edaaad5 100644 --- a/protocol-designer/src/pages/ProtocolOverview/ProtocolMetadata.tsx +++ b/protocol-designer/src/pages/ProtocolOverview/ProtocolMetadata.tsx @@ -12,7 +12,7 @@ import { TYPOGRAPHY, } from '@opentrons/components' -import { BUTTON_LINK_STYLE } from '../../atoms' +import { BUTTON_LINK_STYLE, LINE_CLAMP_TEXT_STYLE } from '../../atoms' const REQUIRED_APP_VERSION = '8.2.0' @@ -74,7 +74,10 @@ export function ProtocolMetadata({ } content={ - + {value ?? t('na')} } diff --git a/robot-server/robot_server/persistence/tables/schema_7.py b/robot-server/robot_server/persistence/tables/schema_7.py index 1690298007f..9f0c2879533 100644 --- a/robot-server/robot_server/persistence/tables/schema_7.py +++ b/robot-server/robot_server/persistence/tables/schema_7.py @@ -207,7 +207,14 @@ class DataFileSourceSQLEnum(enum.Enum): sqlalchemy.Column("index_in_run", sqlalchemy.Integer, nullable=False), sqlalchemy.Column("command_id", sqlalchemy.String, nullable=False), sqlalchemy.Column("command", sqlalchemy.String, nullable=False), - sqlalchemy.Column("command_intent", sqlalchemy.String, nullable=False, index=True), + sqlalchemy.Column( + "command_intent", + sqlalchemy.String, + # nullable=True to match the underlying SQL, which is nullable because of a bug + # in the migration that introduced this column. This is not intended to ever be + # null in practice. + nullable=True, + ), sqlalchemy.Index( "ix_run_run_id_command_id", # An arbitrary name for the index. "run_id", @@ -251,10 +258,16 @@ class DataFileSourceSQLEnum(enum.Enum): DataFileSourceSQLEnum, values_callable=lambda obj: [e.value for e in obj], validate_strings=True, - create_constraint=True, + # create_constraint=False to match the underlying SQL, which omits + # the constraint because of a bug in the migration that introduced this + # column. This is not intended to ever have values other than those in + # DataFileSourceSQLEnum. + create_constraint=False, ), - index=True, - nullable=False, + # nullable=True to match the underlying SQL, which is nullable because of a bug + # in the migration that introduced this column. This is not intended to ever be + # null in practice. + nullable=True, ), ) diff --git a/robot-server/tests/persistence/test_tables.py b/robot-server/tests/persistence/test_tables.py index 9c069157144..642d2506e93 100644 --- a/robot-server/tests/persistence/test_tables.py +++ b/robot-server/tests/persistence/test_tables.py @@ -109,7 +109,7 @@ index_in_run INTEGER NOT NULL, command_id VARCHAR NOT NULL, command VARCHAR NOT NULL, - command_intent VARCHAR NOT NULL, + command_intent VARCHAR, PRIMARY KEY (row_id), FOREIGN KEY(run_id) REFERENCES run (id) ) @@ -121,23 +121,16 @@ CREATE UNIQUE INDEX ix_run_run_id_index_in_run ON run_command (run_id, index_in_run) """, """ - CREATE INDEX ix_data_files_source ON data_files (source) - """, - """ CREATE INDEX ix_protocol_protocol_kind ON protocol (protocol_kind) """, """ - CREATE INDEX ix_run_command_command_intent ON run_command (command_intent) - """, - """ CREATE TABLE data_files ( id VARCHAR NOT NULL, name VARCHAR NOT NULL, file_hash VARCHAR NOT NULL, created_at DATETIME NOT NULL, - source VARCHAR(9) NOT NULL, - PRIMARY KEY (id), - CONSTRAINT datafilesourcesqlenum CHECK (source IN ('uploaded', 'generated')) + source VARCHAR(9), + PRIMARY KEY (id) ) """, """ @@ -542,7 +535,7 @@ def _normalize_statement(statement: str) -> str: - """Fix up the formatting of a SQL statement for easier comparison.""" + """Fix up the internal formatting of a single SQL statement for easier comparison.""" lines = statement.splitlines() # Remove whitespace at the beginning and end of each line. @@ -551,7 +544,10 @@ def _normalize_statement(statement: str) -> str: # Filter out blank lines. lines = [line for line in lines if line != ""] - return "\n".join(lines) + # Normalize line breaks to spaces. When we ask SQLite for its schema, it appears + # inconsistent in whether it uses spaces or line breaks to separate tokens. + # That may have to do with whether `ALTER TABLE` has been used on the table. + return " ".join(lines) @pytest.mark.parametrize( @@ -598,18 +594,6 @@ def record_statement( assert set(normalized_actual) == set(normalized_expected) -# FIXME(mm, 2024-11-12): https://opentrons.atlassian.net/browse/EXEC-827 -# -# There are at least these mismatches: -# -# - `ix_data_files_source` is present in metadata, but not emitted by the migration path -# - `ix_run_command_command_intent` is present in metadata, but not emitted by the migration path -# - `data_files.source` is nullable as emitted by the migration path, but not as declared in metadata -# - `command.command_intent` is nullable as emitted by the migration path, but not as declared in metadata -# - constraint `datafilesourcesqlenum` is present in metadata, but not not emitted by the migration path -# -# Remove this xfail mark when the mismatches are resolved. -@pytest.mark.xfail(strict=True) def test_migrated_db_matches_db_created_from_metadata(tmp_path: Path) -> None: """Test that the output of migration matches `metadata.create_all()`.