diff --git a/.github/workflows/cd.yaml b/.github/workflows/cd.yaml index cd0a8697..3530431b 100644 --- a/.github/workflows/cd.yaml +++ b/.github/workflows/cd.yaml @@ -4,6 +4,9 @@ on: push: branches: - main + paths-ignore: + - "docs/**" + - ".github/**" workflow_dispatch: inputs: production_release: diff --git a/.github/workflows/check-python.yaml b/.github/workflows/check-python.yaml index e70bde62..ab38fe66 100644 --- a/.github/workflows/check-python.yaml +++ b/.github/workflows/check-python.yaml @@ -48,8 +48,7 @@ jobs: - name: Check docstrings are up to date run: poetry run poe docstrings-check - # TODO: Restore before prod release of v3 - # - name: Check docs are up to date - # run: | - # poetry run poe docs-md-only - # git diff --exit-code ':!docs/markdown/autoapi/index.md' ':!docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md' docs + - name: Check docs are up to date + run: | + poetry run poe docs-md-only + git diff --exit-code ':!docs/markdown/autoapi/index.md' ':!docs/markdown/autoapi/algokit_utils/applications/app_factory/index.md' docs diff --git a/docs/html/.buildinfo b/docs/html/.buildinfo index e97a8474..ffc6fde6 100644 --- a/docs/html/.buildinfo +++ b/docs/html/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 -# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 8b79d003fcc06bc866328aa37206c6ac +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: a0ddb94c9519cbe4048eaa2307361694 tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/html/_sources/apidocs/algokit_utils/algokit_utils.md.txt b/docs/html/_sources/apidocs/algokit_utils/algokit_utils.md.txt deleted file mode 100644 index be0e90b8..00000000 --- a/docs/html/_sources/apidocs/algokit_utils/algokit_utils.md.txt +++ /dev/null @@ -1,1841 +0,0 @@ ---- -orphan: true ---- - -# {py:mod}`algokit_utils` - -```{py:module} algokit_utils -``` - -```{autodoc2-docstring} algokit_utils -:parser: myst -:allowtitles: -``` - -## Data - -````{py:data} AppSpecStateDict -:canonical: algokit_utils.application_specification.AppSpecStateDict -:type: typing.TypeAlias -:value: > - None - -```{autodoc2-docstring} algokit_utils.application_specification.AppSpecStateDict -:parser: myst -``` - -```` - - -````{py:data} DELETABLE_TEMPLATE_NAME -:canonical: algokit_utils.deploy.DELETABLE_TEMPLATE_NAME -:value: > - None - -```{autodoc2-docstring} algokit_utils.deploy.DELETABLE_TEMPLATE_NAME -:parser: myst -``` - -```` - - -````{py:data} DefaultArgumentType -:canonical: algokit_utils.application_specification.DefaultArgumentType -:type: typing.TypeAlias -:value: > - None - -```{autodoc2-docstring} algokit_utils.application_specification.DefaultArgumentType -:parser: myst -``` - -```` - - -````{py:data} MethodConfigDict -:canonical: algokit_utils.application_specification.MethodConfigDict -:type: typing.TypeAlias -:value: > - None - -```{autodoc2-docstring} algokit_utils.application_specification.MethodConfigDict -:parser: myst -``` - -```` - - -````{py:data} NOTE_PREFIX -:canonical: algokit_utils.deploy.NOTE_PREFIX -:value: > - 'ALGOKIT_DEPLOYER:j' - -```{autodoc2-docstring} algokit_utils.deploy.NOTE_PREFIX -:parser: myst -``` - -```` - - -````{py:data} OnCompleteActionName -:canonical: algokit_utils.application_specification.OnCompleteActionName -:type: typing.TypeAlias -:value: > - None - -```{autodoc2-docstring} algokit_utils.application_specification.OnCompleteActionName -:parser: myst -``` - -```` - - -````{py:data} TemplateValueDict -:canonical: algokit_utils.deploy.TemplateValueDict -:type: typing.TypeAlias -:value: > - None - -```{autodoc2-docstring} algokit_utils.deploy.TemplateValueDict -:parser: myst -``` - -```` - - -````{py:data} TemplateValueMapping -:canonical: algokit_utils.deploy.TemplateValueMapping -:type: typing.TypeAlias -:value: > - None - -```{autodoc2-docstring} algokit_utils.deploy.TemplateValueMapping -:parser: myst -``` - -```` - - -````{py:data} UPDATABLE_TEMPLATE_NAME -:canonical: algokit_utils.deploy.UPDATABLE_TEMPLATE_NAME -:value: > - None - -```{autodoc2-docstring} algokit_utils.deploy.UPDATABLE_TEMPLATE_NAME -:parser: myst -``` - -```` - - -## Classes - -````{py:class} ABICallArgs -:canonical: algokit_utils.deploy.ABICallArgs - -Bases: {py:obj}`algokit_utils.deploy.DeployCallArgs`, {py:obj}`algokit_utils.deploy.ABICall` - -```{autodoc2-docstring} algokit_utils.deploy.ABICallArgs -:parser: myst -``` - -```` - - -````{py:class} ABICallArgsDict() -:canonical: algokit_utils.deploy.ABICallArgsDict - -Bases: {py:obj}`algokit_utils.deploy.DeployCallArgsDict`, {py:obj}`typing.TypedDict` - -```{autodoc2-docstring} algokit_utils.deploy.ABICallArgsDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.ABICallArgsDict.__init__ -:parser: myst -``` - -```` - - -````{py:class} ABICreateCallArgs -:canonical: algokit_utils.deploy.ABICreateCallArgs - -Bases: {py:obj}`algokit_utils.deploy.DeployCreateCallArgs`, {py:obj}`algokit_utils.deploy.ABICall` - -```{autodoc2-docstring} algokit_utils.deploy.ABICreateCallArgs -:parser: myst -``` - -```` - - -````{py:class} ABICreateCallArgsDict() -:canonical: algokit_utils.deploy.ABICreateCallArgsDict - -Bases: {py:obj}`algokit_utils.deploy.DeployCreateCallArgsDict`, {py:obj}`typing.TypedDict` - -```{autodoc2-docstring} algokit_utils.deploy.ABICreateCallArgsDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.ABICreateCallArgsDict.__init__ -:parser: myst -``` - -```` - - -`````{py:class} ABITransactionResponse -:canonical: algokit_utils.models.ABITransactionResponse - -Bases: {py:obj}`algokit_utils.models.TransactionResponse`, {py:obj}`typing.Generic`\[{py:obj}`algokit_utils.models.ReturnType`\] - -```{autodoc2-docstring} algokit_utils.models.ABITransactionResponse -:parser: myst -``` - -````{py:attribute} decode_error -:canonical: algokit_utils.models.ABITransactionResponse.decode_error -:type: Exception | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.ABITransactionResponse.decode_error -:parser: myst -``` - -```` - -````{py:attribute} method -:canonical: algokit_utils.models.ABITransactionResponse.method -:type: algosdk.abi.Method -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.ABITransactionResponse.method -:parser: myst -``` - -```` - -````{py:attribute} raw_value -:canonical: algokit_utils.models.ABITransactionResponse.raw_value -:type: bytes -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.ABITransactionResponse.raw_value -:parser: myst -``` - -```` - -````{py:attribute} return_value -:canonical: algokit_utils.models.ABITransactionResponse.return_value -:type: algokit_utils.models.ReturnType -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.ABITransactionResponse.return_value -:parser: myst -``` - -```` - -````{py:attribute} tx_info -:canonical: algokit_utils.models.ABITransactionResponse.tx_info -:type: dict -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.ABITransactionResponse.tx_info -:parser: myst -``` - -```` - -````` - - -`````{py:class} Account -:canonical: algokit_utils.models.Account - -```{autodoc2-docstring} algokit_utils.models.Account -:parser: myst -``` - -````{py:attribute} address -:canonical: algokit_utils.models.Account.address -:type: str -:value: > - 'field(...)' - -```{autodoc2-docstring} algokit_utils.models.Account.address -:parser: myst -``` - -```` - -````{py:attribute} private_key -:canonical: algokit_utils.models.Account.private_key -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.Account.private_key -:parser: myst -``` - -```` - -````{py:property} public_key -:canonical: algokit_utils.models.Account.public_key -:type: bytes - -```{autodoc2-docstring} algokit_utils.models.Account.public_key -:parser: myst -``` - -```` - -````{py:property} signer -:canonical: algokit_utils.models.Account.signer -:type: algosdk.atomic_transaction_composer.AccountTransactionSigner - -```{autodoc2-docstring} algokit_utils.models.Account.signer -:parser: myst -``` - -```` - -````` - - -`````{py:class} AlgoClientConfig -:canonical: algokit_utils.network_clients.AlgoClientConfig - -```{autodoc2-docstring} algokit_utils.network_clients.AlgoClientConfig -:parser: myst -``` - -````{py:attribute} server -:canonical: algokit_utils.network_clients.AlgoClientConfig.server -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils.network_clients.AlgoClientConfig.server -:parser: myst -``` - -```` - -````{py:attribute} token -:canonical: algokit_utils.network_clients.AlgoClientConfig.token -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils.network_clients.AlgoClientConfig.token -:parser: myst -``` - -```` - -````` - - -````{py:class} AppDeployMetaData -:canonical: algokit_utils.deploy.AppDeployMetaData - -```{autodoc2-docstring} algokit_utils.deploy.AppDeployMetaData -:parser: myst -``` - -```` - - -````{py:class} AppLookup -:canonical: algokit_utils.deploy.AppLookup - -```{autodoc2-docstring} algokit_utils.deploy.AppLookup -:parser: myst -``` - -```` - - -````{py:class} AppMetaData -:canonical: algokit_utils.deploy.AppMetaData - -Bases: {py:obj}`algokit_utils.deploy.AppReference`, {py:obj}`algokit_utils.deploy.AppDeployMetaData` - -```{autodoc2-docstring} algokit_utils.deploy.AppMetaData -:parser: myst -``` - -```` - - -````{py:class} AppReference -:canonical: algokit_utils.deploy.AppReference - -```{autodoc2-docstring} algokit_utils.deploy.AppReference -:parser: myst -``` - -```` - - -`````{py:class} ApplicationClient(algod_client: algosdk.v2client.algod.AlgodClient, app_spec: algokit_utils.application_specification.ApplicationSpecification | pathlib.Path, *, app_id: int = 0, creator: str | algokit_utils.models.Account | None = None, indexer_client: IndexerClient | None = None, existing_deployments: algokit_utils.deploy.AppLookup | None = None, signer: algosdk.atomic_transaction_composer.TransactionSigner | algokit_utils.models.Account | None = None, sender: str | None = None, suggested_params: algosdk.transaction.SuggestedParams | None = None, template_values: algokit_utils.deploy.TemplateValueMapping | None = None, app_name: str | None = None) -:canonical: algokit_utils.application_client.ApplicationClient - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.__init__ -:parser: myst -``` - -````{py:method} add_method_call(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, abi_method: algokit_utils.models.ABIMethod | bool | None = None, *, abi_args: algokit_utils.models.ABIArgsDict | None = None, app_id: int | None = None, parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, on_complete: algosdk.transaction.OnComplete = transaction.OnComplete.NoOpOC, local_schema: algosdk.transaction.StateSchema | None = None, global_schema: algosdk.transaction.StateSchema | None = None, approval_program: bytes | None = None, clear_program: bytes | None = None, extra_pages: int | None = None, app_args: list[bytes] | None = None, call_config: algokit_utils.application_specification.CallConfig = au_spec.CallConfig.CALL) -> None -:canonical: algokit_utils.application_client.ApplicationClient.add_method_call - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.add_method_call -:parser: myst -``` - -```` - -````{py:method} call(call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.OnCompleteCallParameters | algokit_utils.models.OnCompleteCallParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> algokit_utils.models.TransactionResponse | algokit_utils.models.ABITransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.call - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.call -:parser: myst -``` - -```` - -````{py:method} clear_state(transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, app_args: list[bytes] | None = None) -> algokit_utils.models.TransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.clear_state - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.clear_state -:parser: myst -``` - -```` - -````{py:method} close_out(call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> algokit_utils.models.TransactionResponse | algokit_utils.models.ABITransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.close_out - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.close_out -:parser: myst -``` - -```` - -````{py:method} compose_call(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.OnCompleteCallParameters | algokit_utils.models.OnCompleteCallParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_call - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_call -:parser: myst -``` - -```` - -````{py:method} compose_clear_state(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, app_args: list[bytes] | None = None) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_clear_state - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_clear_state -:parser: myst -``` - -```` - -````{py:method} compose_close_out(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_close_out - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_close_out -:parser: myst -``` - -```` - -````{py:method} compose_create(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.CreateCallParameters | algokit_utils.models.CreateCallParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_create - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_create -:parser: myst -``` - -```` - -````{py:method} compose_delete(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_delete - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_delete -:parser: myst -``` - -```` - -````{py:method} compose_opt_in(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_opt_in - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_opt_in -:parser: myst -``` - -```` - -````{py:method} compose_update(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, /, call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> None -:canonical: algokit_utils.application_client.ApplicationClient.compose_update - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.compose_update -:parser: myst -``` - -```` - -````{py:method} create(call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.CreateCallParameters | algokit_utils.models.CreateCallParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> algokit_utils.models.TransactionResponse | algokit_utils.models.ABITransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.create - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.create -:parser: myst -``` - -```` - -````{py:method} delete(call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> algokit_utils.models.TransactionResponse | algokit_utils.models.ABITransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.delete - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.delete -:parser: myst -``` - -```` - -````{py:method} deploy(version: str | None = None, *, signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, sender: str | None = None, allow_update: bool | None = None, allow_delete: bool | None = None, on_update: algokit_utils.deploy.OnUpdate = au_deploy.OnUpdate.Fail, on_schema_break: algokit_utils.deploy.OnSchemaBreak = au_deploy.OnSchemaBreak.Fail, template_values: algokit_utils.deploy.TemplateValueMapping | None = None, create_args: algokit_utils.deploy.ABICreateCallArgs | algokit_utils.deploy.ABICreateCallArgsDict | algokit_utils.deploy.DeployCreateCallArgs | None = None, update_args: algokit_utils.deploy.ABICallArgs | algokit_utils.deploy.ABICallArgsDict | algokit_utils.deploy.DeployCallArgs | None = None, delete_args: algokit_utils.deploy.ABICallArgs | algokit_utils.deploy.ABICallArgsDict | algokit_utils.deploy.DeployCallArgs | None = None) -> algokit_utils.deploy.DeployResponse -:canonical: algokit_utils.application_client.ApplicationClient.deploy - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.deploy -:parser: myst -``` - -```` - -````{py:method} export_source_map() -> str | None -:canonical: algokit_utils.application_client.ApplicationClient.export_source_map - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.export_source_map -:parser: myst -``` - -```` - -````{py:method} get_global_state(*, raw: bool = False) -> dict[bytes | str, bytes | str | int] -:canonical: algokit_utils.application_client.ApplicationClient.get_global_state - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.get_global_state -:parser: myst -``` - -```` - -````{py:method} get_local_state(account: str | None = None, *, raw: bool = False) -> dict[bytes | str, bytes | str | int] -:canonical: algokit_utils.application_client.ApplicationClient.get_local_state - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.get_local_state -:parser: myst -``` - -```` - -````{py:method} get_signer_sender(signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, sender: str | None = None) -> tuple[algosdk.atomic_transaction_composer.TransactionSigner | None, str | None] -:canonical: algokit_utils.application_client.ApplicationClient.get_signer_sender - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.get_signer_sender -:parser: myst -``` - -```` - -````{py:method} import_source_map(source_map_json: str) -> None -:canonical: algokit_utils.application_client.ApplicationClient.import_source_map - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.import_source_map -:parser: myst -``` - -```` - -````{py:method} opt_in(call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> algokit_utils.models.TransactionResponse | algokit_utils.models.ABITransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.opt_in - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.opt_in -:parser: myst -``` - -```` - -````{py:method} prepare(signer: algosdk.atomic_transaction_composer.TransactionSigner | algokit_utils.models.Account | None = None, sender: str | None = None, app_id: int | None = None, template_values: algokit_utils.deploy.TemplateValueDict | None = None) -> algokit_utils.application_client.ApplicationClient -:canonical: algokit_utils.application_client.ApplicationClient.prepare - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.prepare -:parser: myst -``` - -```` - -````{py:method} resolve(to_resolve: algokit_utils.application_specification.DefaultArgumentDict) -> int | str | bytes -:canonical: algokit_utils.application_client.ApplicationClient.resolve - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.resolve -:parser: myst -``` - -```` - -````{py:method} resolve_signer_sender(signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, sender: str | None = None) -> tuple[algosdk.atomic_transaction_composer.TransactionSigner, str] -:canonical: algokit_utils.application_client.ApplicationClient.resolve_signer_sender - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.resolve_signer_sender -:parser: myst -``` - -```` - -````{py:method} update(call_abi_method: algokit_utils.models.ABIMethod | bool | None = None, transaction_parameters: algokit_utils.models.TransactionParameters | algokit_utils.models.TransactionParametersDict | None = None, **abi_kwargs: algokit_utils.models.ABIArgType) -> algokit_utils.models.TransactionResponse | algokit_utils.models.ABITransactionResponse -:canonical: algokit_utils.application_client.ApplicationClient.update - -```{autodoc2-docstring} algokit_utils.application_client.ApplicationClient.update -:parser: myst -``` - -```` - -````` - - -`````{py:class} ApplicationSpecification -:canonical: algokit_utils.application_specification.ApplicationSpecification - -```{autodoc2-docstring} algokit_utils.application_specification.ApplicationSpecification -:parser: myst -``` - -````{py:method} export(directory: pathlib.Path | str | None = None) -> None -:canonical: algokit_utils.application_specification.ApplicationSpecification.export - -```{autodoc2-docstring} algokit_utils.application_specification.ApplicationSpecification.export -:parser: myst -``` - -```` - -````` - - -`````{py:class} CallConfig() -:canonical: algokit_utils.application_specification.CallConfig - -Bases: {py:obj}`enum.IntFlag` - -```{autodoc2-docstring} algokit_utils.application_specification.CallConfig -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.application_specification.CallConfig.__init__ -:parser: myst -``` - -````{py:attribute} ALL -:canonical: algokit_utils.application_specification.CallConfig.ALL -:value: > - 3 - -```{autodoc2-docstring} algokit_utils.application_specification.CallConfig.ALL -:parser: myst -``` - -```` - -````{py:attribute} CALL -:canonical: algokit_utils.application_specification.CallConfig.CALL -:value: > - 1 - -```{autodoc2-docstring} algokit_utils.application_specification.CallConfig.CALL -:parser: myst -``` - -```` - -````{py:attribute} CREATE -:canonical: algokit_utils.application_specification.CallConfig.CREATE -:value: > - 2 - -```{autodoc2-docstring} algokit_utils.application_specification.CallConfig.CREATE -:parser: myst -``` - -```` - -````{py:attribute} NEVER -:canonical: algokit_utils.application_specification.CallConfig.NEVER -:value: > - 0 - -```{autodoc2-docstring} algokit_utils.application_specification.CallConfig.NEVER -:parser: myst -``` - -```` - -````` - - -````{py:class} CreateCallParameters -:canonical: algokit_utils.models.CreateCallParameters - -Bases: {py:obj}`algokit_utils.models.OnCompleteCallParameters` - -```{autodoc2-docstring} algokit_utils.models.CreateCallParameters -:parser: myst -``` - -```` - - -````{py:class} CreateCallParametersDict() -:canonical: algokit_utils.models.CreateCallParametersDict - -Bases: {py:obj}`typing.TypedDict`, {py:obj}`algokit_utils.models.OnCompleteCallParametersDict` - -```{autodoc2-docstring} algokit_utils.models.CreateCallParametersDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.models.CreateCallParametersDict.__init__ -:parser: myst -``` - -```` - - -````{py:class} CreateTransactionParameters -:canonical: algokit_utils.models.CreateTransactionParameters - -Bases: {py:obj}`algokit_utils.models.TransactionParameters` - -```{autodoc2-docstring} algokit_utils.models.CreateTransactionParameters -:parser: myst -``` - -```` - - -````{py:class} DefaultArgumentDict() -:canonical: algokit_utils.application_specification.DefaultArgumentDict - -Bases: {py:obj}`typing.TypedDict` - -```{autodoc2-docstring} algokit_utils.application_specification.DefaultArgumentDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.application_specification.DefaultArgumentDict.__init__ -:parser: myst -``` - -```` - - -````{py:class} DeployCallArgs -:canonical: algokit_utils.deploy.DeployCallArgs - -```{autodoc2-docstring} algokit_utils.deploy.DeployCallArgs -:parser: myst -``` - -```` - - -````{py:class} DeployCallArgsDict() -:canonical: algokit_utils.deploy.DeployCallArgsDict - -Bases: {py:obj}`typing.TypedDict` - -```{autodoc2-docstring} algokit_utils.deploy.DeployCallArgsDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.DeployCallArgsDict.__init__ -:parser: myst -``` - -```` - - -````{py:class} DeployCreateCallArgs -:canonical: algokit_utils.deploy.DeployCreateCallArgs - -Bases: {py:obj}`algokit_utils.deploy.DeployCallArgs` - -```{autodoc2-docstring} algokit_utils.deploy.DeployCreateCallArgs -:parser: myst -``` - -```` - - -````{py:class} DeployCreateCallArgsDict() -:canonical: algokit_utils.deploy.DeployCreateCallArgsDict - -Bases: {py:obj}`algokit_utils.deploy.DeployCallArgsDict`, {py:obj}`typing.TypedDict` - -```{autodoc2-docstring} algokit_utils.deploy.DeployCreateCallArgsDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.DeployCreateCallArgsDict.__init__ -:parser: myst -``` - -```` - - -````{py:class} DeployResponse -:canonical: algokit_utils.deploy.DeployResponse - -```{autodoc2-docstring} algokit_utils.deploy.DeployResponse -:parser: myst -``` - -```` - - -`````{py:class} EnsureBalanceParameters -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters -:parser: myst -``` - -````{py:attribute} account_to_fund -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.account_to_fund -:type: algokit_utils.models.Account | algosdk.atomic_transaction_composer.AccountTransactionSigner | str -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.account_to_fund -:parser: myst -``` - -```` - -````{py:attribute} fee_micro_algos -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.fee_micro_algos -:type: int | None -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.fee_micro_algos -:parser: myst -``` - -```` - -````{py:attribute} funding_source -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.funding_source -:type: algokit_utils.models.Account | algosdk.atomic_transaction_composer.AccountTransactionSigner | algokit_utils.dispenser_api.TestNetDispenserApiClient | None -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.funding_source -:parser: myst -``` - -```` - -````{py:attribute} max_fee_micro_algos -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.max_fee_micro_algos -:type: int | None -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.max_fee_micro_algos -:parser: myst -``` - -```` - -````{py:attribute} min_funding_increment_micro_algos -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.min_funding_increment_micro_algos -:type: int -:value: > - 0 - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.min_funding_increment_micro_algos -:parser: myst -``` - -```` - -````{py:attribute} min_spending_balance_micro_algos -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.min_spending_balance_micro_algos -:type: int -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.min_spending_balance_micro_algos -:parser: myst -``` - -```` - -````{py:attribute} note -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.note -:type: str | bytes | None -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.note -:parser: myst -``` - -```` - -````{py:attribute} suggested_params -:canonical: algokit_utils._ensure_funded.EnsureBalanceParameters.suggested_params -:type: algosdk.transaction.SuggestedParams | None -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureBalanceParameters.suggested_params -:parser: myst -``` - -```` - -````` - - -`````{py:class} EnsureFundedResponse -:canonical: algokit_utils._ensure_funded.EnsureFundedResponse - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureFundedResponse -:parser: myst -``` - -````{py:attribute} transaction_id -:canonical: algokit_utils._ensure_funded.EnsureFundedResponse.transaction_id -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils._ensure_funded.EnsureFundedResponse.transaction_id -:parser: myst -``` - -```` - -````` - - -````{py:class} MethodHints -:canonical: algokit_utils.application_specification.MethodHints - -```{autodoc2-docstring} algokit_utils.application_specification.MethodHints -:parser: myst -``` - -```` - - -````{py:class} OnCompleteCallParameters -:canonical: algokit_utils.models.OnCompleteCallParameters - -Bases: {py:obj}`algokit_utils.models.TransactionParameters` - -```{autodoc2-docstring} algokit_utils.models.OnCompleteCallParameters -:parser: myst -``` - -```` - - -````{py:class} OnCompleteCallParametersDict() -:canonical: algokit_utils.models.OnCompleteCallParametersDict - -Bases: {py:obj}`typing.TypedDict`, {py:obj}`algokit_utils.models.TransactionParametersDict` - -```{autodoc2-docstring} algokit_utils.models.OnCompleteCallParametersDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.models.OnCompleteCallParametersDict.__init__ -:parser: myst -``` - -```` - - -`````{py:class} OnSchemaBreak(*args, **kwds) -:canonical: algokit_utils.deploy.OnSchemaBreak - -Bases: {py:obj}`enum.Enum` - -```{autodoc2-docstring} algokit_utils.deploy.OnSchemaBreak -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.OnSchemaBreak.__init__ -:parser: myst -``` - -````{py:attribute} AppendApp -:canonical: algokit_utils.deploy.OnSchemaBreak.AppendApp -:value: > - 3 - -```{autodoc2-docstring} algokit_utils.deploy.OnSchemaBreak.AppendApp -:parser: myst -``` - -```` - -````{py:attribute} Fail -:canonical: algokit_utils.deploy.OnSchemaBreak.Fail -:value: > - 0 - -```{autodoc2-docstring} algokit_utils.deploy.OnSchemaBreak.Fail -:parser: myst -``` - -```` - -````{py:attribute} ReplaceApp -:canonical: algokit_utils.deploy.OnSchemaBreak.ReplaceApp -:value: > - 2 - -```{autodoc2-docstring} algokit_utils.deploy.OnSchemaBreak.ReplaceApp -:parser: myst -``` - -```` - -````` - - -`````{py:class} OnUpdate(*args, **kwds) -:canonical: algokit_utils.deploy.OnUpdate - -Bases: {py:obj}`enum.Enum` - -```{autodoc2-docstring} algokit_utils.deploy.OnUpdate -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.OnUpdate.__init__ -:parser: myst -``` - -````{py:attribute} AppendApp -:canonical: algokit_utils.deploy.OnUpdate.AppendApp -:value: > - 3 - -```{autodoc2-docstring} algokit_utils.deploy.OnUpdate.AppendApp -:parser: myst -``` - -```` - -````{py:attribute} Fail -:canonical: algokit_utils.deploy.OnUpdate.Fail -:value: > - 0 - -```{autodoc2-docstring} algokit_utils.deploy.OnUpdate.Fail -:parser: myst -``` - -```` - -````{py:attribute} ReplaceApp -:canonical: algokit_utils.deploy.OnUpdate.ReplaceApp -:value: > - 2 - -```{autodoc2-docstring} algokit_utils.deploy.OnUpdate.ReplaceApp -:parser: myst -``` - -```` - -````{py:attribute} UpdateApp -:canonical: algokit_utils.deploy.OnUpdate.UpdateApp -:value: > - 1 - -```{autodoc2-docstring} algokit_utils.deploy.OnUpdate.UpdateApp -:parser: myst -``` - -```` - -````` - - -`````{py:class} OperationPerformed(*args, **kwds) -:canonical: algokit_utils.deploy.OperationPerformed - -Bases: {py:obj}`enum.Enum` - -```{autodoc2-docstring} algokit_utils.deploy.OperationPerformed -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.deploy.OperationPerformed.__init__ -:parser: myst -``` - -````{py:attribute} Create -:canonical: algokit_utils.deploy.OperationPerformed.Create -:value: > - 1 - -```{autodoc2-docstring} algokit_utils.deploy.OperationPerformed.Create -:parser: myst -``` - -```` - -````{py:attribute} Nothing -:canonical: algokit_utils.deploy.OperationPerformed.Nothing -:value: > - 0 - -```{autodoc2-docstring} algokit_utils.deploy.OperationPerformed.Nothing -:parser: myst -``` - -```` - -````{py:attribute} Replace -:canonical: algokit_utils.deploy.OperationPerformed.Replace -:value: > - 3 - -```{autodoc2-docstring} algokit_utils.deploy.OperationPerformed.Replace -:parser: myst -``` - -```` - -````{py:attribute} Update -:canonical: algokit_utils.deploy.OperationPerformed.Update -:value: > - 2 - -```{autodoc2-docstring} algokit_utils.deploy.OperationPerformed.Update -:parser: myst -``` - -```` - -````` - - -````{py:class} Program(program: str, client: algosdk.v2client.algod.AlgodClient) -:canonical: algokit_utils.common.Program - -```{autodoc2-docstring} algokit_utils.common.Program -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.common.Program.__init__ -:parser: myst -``` - -```` - - -`````{py:class} TestNetDispenserApiClient(auth_token: str | None = None, request_timeout: int = DISPENSER_REQUEST_TIMEOUT) -:canonical: algokit_utils.dispenser_api.TestNetDispenserApiClient - -```{autodoc2-docstring} algokit_utils.dispenser_api.TestNetDispenserApiClient -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.dispenser_api.TestNetDispenserApiClient.__init__ -:parser: myst -``` - -````{py:method} fund(address: str, amount: int, asset_id: int) -> algokit_utils.dispenser_api.DispenserFundResponse -:canonical: algokit_utils.dispenser_api.TestNetDispenserApiClient.fund - -```{autodoc2-docstring} algokit_utils.dispenser_api.TestNetDispenserApiClient.fund -:parser: myst -``` - -```` - -````{py:method} get_limit(address: str) -> algokit_utils.dispenser_api.DispenserLimitResponse -:canonical: algokit_utils.dispenser_api.TestNetDispenserApiClient.get_limit - -```{autodoc2-docstring} algokit_utils.dispenser_api.TestNetDispenserApiClient.get_limit -:parser: myst -``` - -```` - -````{py:method} refund(refund_txn_id: str) -> None -:canonical: algokit_utils.dispenser_api.TestNetDispenserApiClient.refund - -```{autodoc2-docstring} algokit_utils.dispenser_api.TestNetDispenserApiClient.refund -:parser: myst -``` - -```` - -````` - - -`````{py:class} TransactionParameters -:canonical: algokit_utils.models.TransactionParameters - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters -:parser: myst -``` - -````{py:attribute} accounts -:canonical: algokit_utils.models.TransactionParameters.accounts -:type: list[str] | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.accounts -:parser: myst -``` - -```` - -````{py:attribute} boxes -:canonical: algokit_utils.models.TransactionParameters.boxes -:type: collections.abc.Sequence[tuple[int, bytes | bytearray | str | int]] | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.boxes -:parser: myst -``` - -```` - -````{py:attribute} foreign_apps -:canonical: algokit_utils.models.TransactionParameters.foreign_apps -:type: list[int] | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.foreign_apps -:parser: myst -``` - -```` - -````{py:attribute} foreign_assets -:canonical: algokit_utils.models.TransactionParameters.foreign_assets -:type: list[int] | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.foreign_assets -:parser: myst -``` - -```` - -````{py:attribute} lease -:canonical: algokit_utils.models.TransactionParameters.lease -:type: bytes | str | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.lease -:parser: myst -``` - -```` - -````{py:attribute} note -:canonical: algokit_utils.models.TransactionParameters.note -:type: bytes | str | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.note -:parser: myst -``` - -```` - -````{py:attribute} rekey_to -:canonical: algokit_utils.models.TransactionParameters.rekey_to -:type: str | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.rekey_to -:parser: myst -``` - -```` - -````{py:attribute} sender -:canonical: algokit_utils.models.TransactionParameters.sender -:type: str | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.sender -:parser: myst -``` - -```` - -````{py:attribute} signer -:canonical: algokit_utils.models.TransactionParameters.signer -:type: algosdk.atomic_transaction_composer.TransactionSigner | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.signer -:parser: myst -``` - -```` - -````{py:attribute} suggested_params -:canonical: algokit_utils.models.TransactionParameters.suggested_params -:type: algosdk.transaction.SuggestedParams | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParameters.suggested_params -:parser: myst -``` - -```` - -````` - - -`````{py:class} TransactionParametersDict() -:canonical: algokit_utils.models.TransactionParametersDict - -Bases: {py:obj}`typing.TypedDict` - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict -:parser: myst -``` - -```{rubric} Initialization -``` - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.__init__ -:parser: myst -``` - -````{py:attribute} accounts -:canonical: algokit_utils.models.TransactionParametersDict.accounts -:type: list[str] -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.accounts -:parser: myst -``` - -```` - -````{py:attribute} boxes -:canonical: algokit_utils.models.TransactionParametersDict.boxes -:type: collections.abc.Sequence[tuple[int, bytes | bytearray | str | int]] -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.boxes -:parser: myst -``` - -```` - -````{py:attribute} foreign_apps -:canonical: algokit_utils.models.TransactionParametersDict.foreign_apps -:type: list[int] -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.foreign_apps -:parser: myst -``` - -```` - -````{py:attribute} foreign_assets -:canonical: algokit_utils.models.TransactionParametersDict.foreign_assets -:type: list[int] -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.foreign_assets -:parser: myst -``` - -```` - -````{py:attribute} lease -:canonical: algokit_utils.models.TransactionParametersDict.lease -:type: bytes | str -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.lease -:parser: myst -``` - -```` - -````{py:attribute} note -:canonical: algokit_utils.models.TransactionParametersDict.note -:type: bytes | str -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.note -:parser: myst -``` - -```` - -````{py:attribute} rekey_to -:canonical: algokit_utils.models.TransactionParametersDict.rekey_to -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.rekey_to -:parser: myst -``` - -```` - -````{py:attribute} sender -:canonical: algokit_utils.models.TransactionParametersDict.sender -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.sender -:parser: myst -``` - -```` - -````{py:attribute} signer -:canonical: algokit_utils.models.TransactionParametersDict.signer -:type: algosdk.atomic_transaction_composer.TransactionSigner -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.signer -:parser: myst -``` - -```` - -````{py:attribute} suggested_params -:canonical: algokit_utils.models.TransactionParametersDict.suggested_params -:type: algosdk.transaction.SuggestedParams -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionParametersDict.suggested_params -:parser: myst -``` - -```` - -````` - - -`````{py:class} TransactionResponse -:canonical: algokit_utils.models.TransactionResponse - -```{autodoc2-docstring} algokit_utils.models.TransactionResponse -:parser: myst -``` - -````{py:attribute} confirmed_round -:canonical: algokit_utils.models.TransactionResponse.confirmed_round -:type: int | None -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionResponse.confirmed_round -:parser: myst -``` - -```` - -````{py:method} from_atr(result: algosdk.atomic_transaction_composer.AtomicTransactionResponse | algosdk.atomic_transaction_composer.SimulateAtomicTransactionResponse, transaction_index: int = -1) -> algokit_utils.models.TransactionResponse -:canonical: algokit_utils.models.TransactionResponse.from_atr -:staticmethod: - -```{autodoc2-docstring} algokit_utils.models.TransactionResponse.from_atr -:parser: myst -``` - -```` - -````{py:attribute} tx_id -:canonical: algokit_utils.models.TransactionResponse.tx_id -:type: str -:value: > - None - -```{autodoc2-docstring} algokit_utils.models.TransactionResponse.tx_id -:parser: myst -``` - -```` - -````` - - -````{py:class} TransferAssetParameters -:canonical: algokit_utils._transfer.TransferAssetParameters - -Bases: {py:obj}`algokit_utils._transfer.TransferParametersBase` - -```{autodoc2-docstring} algokit_utils._transfer.TransferAssetParameters -:parser: myst -``` - -```` - - -````{py:class} TransferParameters -:canonical: algokit_utils._transfer.TransferParameters - -Bases: {py:obj}`algokit_utils._transfer.TransferParametersBase` - -```{autodoc2-docstring} algokit_utils._transfer.TransferParameters -:parser: myst -``` - -```` - - -## Functions - -````{py:function} create_kmd_wallet_account(kmd_client: algosdk.kmd.KMDClient, name: str) -> algokit_utils.models.Account -:canonical: algokit_utils.account.create_kmd_wallet_account - -```{autodoc2-docstring} algokit_utils.account.create_kmd_wallet_account -:parser: myst -``` -```` - - -````{py:function} ensure_funded(client: algosdk.v2client.algod.AlgodClient, parameters: algokit_utils._ensure_funded.EnsureBalanceParameters) -> algokit_utils._ensure_funded.EnsureFundedResponse | None -:canonical: algokit_utils._ensure_funded.ensure_funded - -```{autodoc2-docstring} algokit_utils._ensure_funded.ensure_funded -:parser: myst -``` -```` - - -````{py:function} execute_atc_with_logic_error(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, algod_client: algosdk.v2client.algod.AlgodClient, approval_program: str, wait_rounds: int = 4, approval_source_map: algosdk.source_map.SourceMap | typing.Callable[[], algosdk.source_map.SourceMap | None] | None = None) -> algosdk.atomic_transaction_composer.AtomicTransactionResponse -:canonical: algokit_utils.application_client.execute_atc_with_logic_error - -```{autodoc2-docstring} algokit_utils.application_client.execute_atc_with_logic_error -:parser: myst -``` -```` - - -````{py:function} get_account(client: algosdk.v2client.algod.AlgodClient, name: str, fund_with_algos: float = 1000, kmd_client: KMDClient | None = None) -> algokit_utils.models.Account -:canonical: algokit_utils.account.get_account - -```{autodoc2-docstring} algokit_utils.account.get_account -:parser: myst -``` -```` - - -````{py:function} get_account_from_mnemonic(mnemonic: str) -> algokit_utils.models.Account -:canonical: algokit_utils.account.get_account_from_mnemonic - -```{autodoc2-docstring} algokit_utils.account.get_account_from_mnemonic -:parser: myst -``` -```` - - -````{py:function} get_algod_client(config: algokit_utils.network_clients.AlgoClientConfig | None = None) -> algosdk.v2client.algod.AlgodClient -:canonical: algokit_utils.network_clients.get_algod_client - -```{autodoc2-docstring} algokit_utils.network_clients.get_algod_client -:parser: myst -``` -```` - - -````{py:function} get_app_id_from_tx_id(algod_client: algosdk.v2client.algod.AlgodClient, tx_id: str) -> int -:canonical: algokit_utils.deploy.get_app_id_from_tx_id - -```{autodoc2-docstring} algokit_utils.deploy.get_app_id_from_tx_id -:parser: myst -``` -```` - - -````{py:function} get_creator_apps(indexer: algosdk.v2client.indexer.IndexerClient, creator_account: algokit_utils.models.Account | str) -> algokit_utils.deploy.AppLookup -:canonical: algokit_utils.deploy.get_creator_apps - -```{autodoc2-docstring} algokit_utils.deploy.get_creator_apps -:parser: myst -``` -```` - - -````{py:function} get_default_localnet_config(config: typing.Literal[algod, indexer, kmd]) -> algokit_utils.network_clients.AlgoClientConfig -:canonical: algokit_utils.network_clients.get_default_localnet_config - -```{autodoc2-docstring} algokit_utils.network_clients.get_default_localnet_config -:parser: myst -``` -```` - - -````{py:function} get_dispenser_account(client: algosdk.v2client.algod.AlgodClient) -> algokit_utils.models.Account -:canonical: algokit_utils.account.get_dispenser_account - -```{autodoc2-docstring} algokit_utils.account.get_dispenser_account -:parser: myst -``` -```` - - -````{py:function} get_indexer_client(config: algokit_utils.network_clients.AlgoClientConfig | None = None) -> algosdk.v2client.indexer.IndexerClient -:canonical: algokit_utils.network_clients.get_indexer_client - -```{autodoc2-docstring} algokit_utils.network_clients.get_indexer_client -:parser: myst -``` -```` - - -````{py:function} get_kmd_client_from_algod_client(client: algosdk.v2client.algod.AlgodClient) -> algosdk.kmd.KMDClient -:canonical: algokit_utils.network_clients.get_kmd_client_from_algod_client - -```{autodoc2-docstring} algokit_utils.network_clients.get_kmd_client_from_algod_client -:parser: myst -``` -```` - - -````{py:function} get_kmd_wallet_account(client: algosdk.v2client.algod.AlgodClient, kmd_client: algosdk.kmd.KMDClient, name: str, predicate: Callable[[dict[str, Any]], bool] | None = None) -> algokit_utils.models.Account | None -:canonical: algokit_utils.account.get_kmd_wallet_account - -```{autodoc2-docstring} algokit_utils.account.get_kmd_wallet_account -:parser: myst -``` -```` - - -````{py:function} get_localnet_default_account(client: algosdk.v2client.algod.AlgodClient) -> algokit_utils.models.Account -:canonical: algokit_utils.account.get_localnet_default_account - -```{autodoc2-docstring} algokit_utils.account.get_localnet_default_account -:parser: myst -``` -```` - - -````{py:function} get_next_version(current_version: str) -> str -:canonical: algokit_utils.application_client.get_next_version - -```{autodoc2-docstring} algokit_utils.application_client.get_next_version -:parser: myst -``` -```` - - -````{py:function} get_or_create_kmd_wallet_account(client: algosdk.v2client.algod.AlgodClient, name: str, fund_with_algos: float = 1000, kmd_client: KMDClient | None = None) -> algokit_utils.models.Account -:canonical: algokit_utils.account.get_or_create_kmd_wallet_account - -```{autodoc2-docstring} algokit_utils.account.get_or_create_kmd_wallet_account -:parser: myst -``` -```` - - -````{py:function} get_sender_from_signer(signer: algosdk.atomic_transaction_composer.TransactionSigner | None) -> str | None -:canonical: algokit_utils.application_client.get_sender_from_signer - -```{autodoc2-docstring} algokit_utils.application_client.get_sender_from_signer -:parser: myst -``` -```` - - -````{py:function} is_localnet(client: algosdk.v2client.algod.AlgodClient) -> bool -:canonical: algokit_utils.network_clients.is_localnet - -```{autodoc2-docstring} algokit_utils.network_clients.is_localnet -:parser: myst -``` -```` - - -````{py:function} is_mainnet(client: algosdk.v2client.algod.AlgodClient) -> bool -:canonical: algokit_utils.network_clients.is_mainnet - -```{autodoc2-docstring} algokit_utils.network_clients.is_mainnet -:parser: myst -``` -```` - - -````{py:function} is_testnet(client: algosdk.v2client.algod.AlgodClient) -> bool -:canonical: algokit_utils.network_clients.is_testnet - -```{autodoc2-docstring} algokit_utils.network_clients.is_testnet -:parser: myst -``` -```` - - -````{py:function} num_extra_program_pages(approval: bytes, clear: bytes) -> int -:canonical: algokit_utils.application_client.num_extra_program_pages - -```{autodoc2-docstring} algokit_utils.application_client.num_extra_program_pages -:parser: myst -``` -```` - - -````{py:function} opt_in(algod_client: algosdk.v2client.algod.AlgodClient, account: algokit_utils.models.Account, asset_ids: list[int]) -> dict[int, str] -:canonical: algokit_utils.asset.opt_in - -```{autodoc2-docstring} algokit_utils.asset.opt_in -:parser: myst -``` -```` - - -````{py:function} opt_out(algod_client: algosdk.v2client.algod.AlgodClient, account: algokit_utils.models.Account, asset_ids: list[int]) -> dict[int, str] -:canonical: algokit_utils.asset.opt_out - -```{autodoc2-docstring} algokit_utils.asset.opt_out -:parser: myst -``` -```` - - -````{py:function} persist_sourcemaps(*, sources: list[algokit_utils._debugging.PersistSourceMapInput], project_root: pathlib.Path, client: algosdk.v2client.algod.AlgodClient, with_sources: bool = True, persist_mappings: bool = False) -> None -:canonical: algokit_utils._debugging.persist_sourcemaps - -```{autodoc2-docstring} algokit_utils._debugging.persist_sourcemaps -:parser: myst -``` -```` - - -````{py:function} replace_template_variables(program: str, template_values: algokit_utils.deploy.TemplateValueMapping) -> str -:canonical: algokit_utils.deploy.replace_template_variables - -```{autodoc2-docstring} algokit_utils.deploy.replace_template_variables -:parser: myst -``` -```` - - -````{py:function} simulate_and_persist_response(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, project_root: pathlib.Path, algod_client: algosdk.v2client.algod.AlgodClient, buffer_size_mb: float = 256) -> algosdk.atomic_transaction_composer.SimulateAtomicTransactionResponse -:canonical: algokit_utils._debugging.simulate_and_persist_response - -```{autodoc2-docstring} algokit_utils._debugging.simulate_and_persist_response -:parser: myst -``` -```` - - -````{py:function} transfer(client: algosdk.v2client.algod.AlgodClient, parameters: algokit_utils._transfer.TransferParameters) -> algosdk.transaction.PaymentTxn -:canonical: algokit_utils._transfer.transfer - -```{autodoc2-docstring} algokit_utils._transfer.transfer -:parser: myst -``` -```` - - -````{py:function} transfer_asset(client: algosdk.v2client.algod.AlgodClient, parameters: algokit_utils._transfer.TransferAssetParameters) -> algosdk.transaction.AssetTransferTxn -:canonical: algokit_utils._transfer.transfer_asset - -```{autodoc2-docstring} algokit_utils._transfer.transfer_asset -:parser: myst -``` -```` - diff --git a/docs/html/_sources/autoapi/algokit_utils/accounts/account_manager/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/accounts/account_manager/index.rst.txt new file mode 100644 index 00000000..05c5db16 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/accounts/account_manager/index.rst.txt @@ -0,0 +1,743 @@ +algokit_utils.accounts.account_manager +====================================== + +.. py:module:: algokit_utils.accounts.account_manager + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.accounts.account_manager.EnsureFundedResult + algokit_utils.accounts.account_manager.EnsureFundedFromTestnetDispenserApiResult + algokit_utils.accounts.account_manager.AccountInformation + algokit_utils.accounts.account_manager.AccountManager + + +Module Contents +--------------- + +.. py:class:: EnsureFundedResult + + Bases: :py:obj:`algokit_utils.transactions.transaction_sender.SendSingleTransactionResult`, :py:obj:`_CommonEnsureFundedParams` + + + Result from performing an ensure funded call. + + +.. py:class:: EnsureFundedFromTestnetDispenserApiResult + + Bases: :py:obj:`_CommonEnsureFundedParams` + + + Result from performing an ensure funded call using TestNet dispenser API. + + +.. py:class:: AccountInformation + + Information about an Algorand account's current status, balance and other properties. + + See `https://developer.algorand.org/docs/rest-apis/algod/#account` for detailed field descriptions. + + + .. py:attribute:: address + :type: str + + The account's address + + + + .. py:attribute:: amount + :type: algokit_utils.models.amount.AlgoAmount + + The account's current balance + + + + .. py:attribute:: amount_without_pending_rewards + :type: algokit_utils.models.amount.AlgoAmount + + The account's balance without the pending rewards + + + + .. py:attribute:: min_balance + :type: algokit_utils.models.amount.AlgoAmount + + The account's minimum required balance + + + + .. py:attribute:: pending_rewards + :type: algokit_utils.models.amount.AlgoAmount + + The amount of pending rewards + + + + .. py:attribute:: rewards + :type: algokit_utils.models.amount.AlgoAmount + + The amount of rewards earned + + + + .. py:attribute:: round + :type: int + + The round for which this information is relevant + + + + .. py:attribute:: status + :type: str + + The account's status (e.g., 'Offline', 'Online') + + + + .. py:attribute:: total_apps_opted_in + :type: int | None + :value: None + + + Number of applications this account has opted into + + + + .. py:attribute:: total_assets_opted_in + :type: int | None + :value: None + + + Number of assets this account has opted into + + + + .. py:attribute:: total_box_bytes + :type: int | None + :value: None + + + Total number of box bytes used by this account + + + + .. py:attribute:: total_boxes + :type: int | None + :value: None + + + Total number of boxes used by this account + + + + .. py:attribute:: total_created_apps + :type: int | None + :value: None + + + Number of applications created by this account + + + + .. py:attribute:: total_created_assets + :type: int | None + :value: None + + + Number of assets created by this account + + + + .. py:attribute:: apps_local_state + :type: list[dict] | None + :value: None + + + Local state of applications this account has opted into + + + + .. py:attribute:: apps_total_extra_pages + :type: int | None + :value: None + + + Number of extra pages allocated to applications + + + + .. py:attribute:: apps_total_schema + :type: dict | None + :value: None + + + Total schema for all applications + + + + .. py:attribute:: assets + :type: list[dict] | None + :value: None + + + Assets held by this account + + + + .. py:attribute:: auth_addr + :type: str | None + :value: None + + + If rekeyed, the authorized address + + + + .. py:attribute:: closed_at_round + :type: int | None + :value: None + + + Round when this account was closed + + + + .. py:attribute:: created_apps + :type: list[dict] | None + :value: None + + + Applications created by this account + + + + .. py:attribute:: created_assets + :type: list[dict] | None + :value: None + + + Assets created by this account + + + + .. py:attribute:: created_at_round + :type: int | None + :value: None + + + Round when this account was created + + + + .. py:attribute:: deleted + :type: bool | None + :value: None + + + Whether this account is deleted + + + + .. py:attribute:: incentive_eligible + :type: bool | None + :value: None + + + Whether this account is eligible for incentives + + + + .. py:attribute:: last_heartbeat + :type: int | None + :value: None + + + Last heartbeat round for this account + + + + .. py:attribute:: last_proposed + :type: int | None + :value: None + + + Last round this account proposed a block + + + + .. py:attribute:: participation + :type: dict | None + :value: None + + + Participation information for this account + + + + .. py:attribute:: reward_base + :type: int | None + :value: None + + + Base reward for this account + + + + .. py:attribute:: sig_type + :type: str | None + :value: None + + + Signature type for this account + + + +.. py:class:: AccountManager(client_manager: algokit_utils.clients.client_manager.ClientManager) + + Creates and keeps track of signing accounts that can sign transactions for a sending address. + + This class provides functionality to create, track, and manage various types of accounts including + mnemonic-based, rekeyed, multisig, and logic signature accounts. + + :param client_manager: The ClientManager client to use for algod and kmd clients + + :example: + >>> account_manager = AccountManager(client_manager) + + + .. py:property:: kmd + :type: algokit_utils.accounts.kmd_account_manager.KmdAccountManager + + + KMD account manager that allows you to easily get and create accounts using KMD. + + :return KmdAccountManager: The 'KmdAccountManager' instance + :example: + >>> kmd_manager = account_manager.kmd + + + + .. py:method:: set_default_signer(signer: algosdk.atomic_transaction_composer.TransactionSigner | algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> typing_extensions.Self + + Sets the default signer to use if no other signer is specified. + + If this isn't set and a transaction needs signing for a given sender + then an error will be thrown from `get_signer` / `get_account`. + + :param signer: A `TransactionSigner` signer to use. + :returns: The `AccountManager` so method calls can be chained + + :example: + >>> signer_account = account_manager.random() + >>> account_manager.set_default_signer(signer_account) + + + + .. py:method:: set_signer(sender: str, signer: algosdk.atomic_transaction_composer.TransactionSigner) -> typing_extensions.Self + + Tracks the given `TransactionSigner` against the given sender address for later signing. + + :param sender: The sender address to use this signer for + :param signer: The `TransactionSigner` to sign transactions with for the given sender + :returns: The `AccountManager` instance for method chaining + + :example: + >>> account_manager.set_signer("SENDERADDRESS", transaction_signer) + + + + .. py:method:: set_signers(*, another_account_manager: AccountManager, overwrite_existing: bool = True) -> typing_extensions.Self + + Merges the given `AccountManager` into this one. + + :param another_account_manager: The `AccountManager` to merge into this one + :param overwrite_existing: Whether to overwrite existing signers in this manager + :returns: The `AccountManager` instance for method chaining + + :example: + >>> accountManager2.set_signers(accountManager1) + + + + .. py:method:: set_signer_from_account(account: algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> typing_extensions.Self + + Tracks the given account for later signing. + + Note: If you are generating accounts via the various methods on `AccountManager` + (like `random`, `from_mnemonic`, `logic_sig`, etc.) then they automatically get tracked. + + :param account: The account to register + :returns: The `AccountManager` instance for method chaining + + :example: + >>> account_manager = AccountManager(client_manager) + >>> account_manager.set_signer_from_account(SigningAccount(private_key=algosdk.account.generate_account()[0])) + >>> account_manager.set_signer_from_account(LogicSigAccount(AlgosdkLogicSigAccount(program, args))) + >>> account_manager.set_signer_from_account(MultiSigAccount(multisig_params, [account1, account2])) + + + + .. py:method:: get_signer(sender: str | algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> algosdk.atomic_transaction_composer.TransactionSigner + + Returns the `TransactionSigner` for the given sender address. + + If no signer has been registered for that address then the default signer is used if registered. + + :param sender: The sender address or account + :returns: The `TransactionSigner` + :raises ValueError: If no signer is found and no default signer is set + + :example: + >>> signer = account_manager.get_signer("SENDERADDRESS") + + + + .. py:method:: get_account(sender: str) -> algokit_utils.protocols.account.TransactionSignerAccountProtocol + + Returns the `TransactionSignerAccountProtocol` for the given sender address. + + :param sender: The sender address + :returns: The `TransactionSignerAccountProtocol` + :raises ValueError: If no account is found or if the account is not a regular account + + :example: + >>> sender = account_manager.random().address + >>> # ... + >>> # Returns the `TransactionSignerAccountProtocol` for `sender` that has previously been registered + >>> account = account_manager.get_account(sender) + + + + .. py:method:: get_information(sender: str | algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> AccountInformation + + Returns the given sender account's current status, balance and spendable amounts. + + See ``_ + for response data schema details. + + :param sender: The address or account compliant with `TransactionSignerAccountProtocol` protocol to look up + :returns: The account information + + :example: + >>> address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA" + >>> account_info = account_manager.get_information(address) + + + + .. py:method:: from_mnemonic(*, mnemonic: str, sender: str | None = None) -> algokit_utils.models.account.SigningAccount + + Tracks and returns an Algorand account with secret key loaded by taking the mnemonic secret. + + :param mnemonic: The mnemonic secret representing the private key of an account + :param sender: Optional address to use as the sender + :returns: The account + + .. warning:: + Be careful how the mnemonic is handled. Never commit it into source control and ideally load it + from the environment (ideally via a secret storage service) rather than the file system. + + :example: + >>> account = account_manager.from_mnemonic("mnemonic secret ...") + + + + .. py:method:: from_environment(name: str, fund_with: algokit_utils.models.amount.AlgoAmount | None = None) -> algokit_utils.models.account.SigningAccount + + Tracks and returns an Algorand account with private key loaded by convention from environment variables. + + This allows you to write code that will work seamlessly in production and local development (LocalNet) + without manual config locally (including when you reset the LocalNet). + + :param name: The name identifier of the account + :param fund_with: Optional amount to fund the account with when it gets created + (when targeting LocalNet) + :returns: The account + :raises ValueError: If environment variable {NAME}_MNEMONIC is missing when looking for account {NAME} + + .. note:: + Convention: + * **Non-LocalNet:** will load `{NAME}_MNEMONIC` as a mnemonic secret. + If `{NAME}_SENDER` is defined then it will use that for the sender address + (i.e. to support rekeyed accounts) + * **LocalNet:** will load the account from a KMD wallet called {NAME} and if that wallet doesn't exist + it will create it and fund the account for you + + :example: + >>> # If you have a mnemonic secret loaded into `MY_ACCOUNT_MNEMONIC` then you can call: + >>> account = account_manager.from_environment('MY_ACCOUNT') + >>> # If that code runs against LocalNet then a wallet called `MY_ACCOUNT` will automatically be created + >>> # with an account that is automatically funded with the specified amount from the LocalNet dispenser + + + + .. py:method:: from_kmd(name: str, predicate: collections.abc.Callable[[dict[str, Any]], bool] | None = None, sender: str | None = None) -> algokit_utils.models.account.SigningAccount + + Tracks and returns an Algorand account with private key loaded from the given KMD wallet. + + :param name: The name of the wallet to retrieve an account from + :param predicate: Optional filter to use to find the account + :param sender: Optional sender address to use this signer for (aka a rekeyed account) + :returns: The account + :raises ValueError: If unable to find KMD account with given name and predicate + + :example: + >>> # Get default funded account in a LocalNet: + >>> defaultDispenserAccount = account.from_kmd('unencrypted-default-wallet', + ... lambda a: a.status != 'Offline' and a.amount > 1_000_000_000 + ... ) + + + + .. py:method:: logicsig(program: bytes, args: list[bytes] | None = None) -> algokit_utils.models.account.LogicSigAccount + + Tracks and returns an account that represents a logic signature. + + :param program: The bytes that make up the compiled logic signature + :param args: Optional (binary) arguments to pass into the logic signature + :returns: A logic signature account wrapper + + :example: + >>> account = account.logicsig(program, [new Uint8Array(3, ...)]) + + + + .. py:method:: multisig(metadata: algokit_utils.models.account.MultisigMetadata, signing_accounts: list[algokit_utils.models.account.SigningAccount]) -> algokit_utils.models.account.MultiSigAccount + + Tracks and returns an account that supports partial or full multisig signing. + + :param metadata: The metadata for the multisig account + :param signing_accounts: The signers that are currently present + :returns: A multisig account wrapper + + :example: + >>> account = account_manager.multi_sig( + ... version=1, + ... threshold=1, + ... addrs=["ADDRESS1...", "ADDRESS2..."], + ... signing_accounts=[account1, account2] + ... ) + + + + .. py:method:: random() -> algokit_utils.models.account.SigningAccount + + Tracks and returns a new, random Algorand account. + + :returns: The account + + :example: + >>> account = account_manager.random() + + + + .. py:method:: localnet_dispenser() -> algokit_utils.models.account.SigningAccount + + Returns an Algorand account with private key loaded for the default LocalNet dispenser account. + + This account can be used to fund other accounts. + + :returns: The account + + :example: + >>> account = account_manager.localnet_dispenser() + + + + .. py:method:: dispenser_from_environment() -> algokit_utils.models.account.SigningAccount + + Returns an account (with private key loaded) that can act as a dispenser from environment variables. + + If environment variables are not present, returns the default LocalNet dispenser account. + + :returns: The account + + :example: + >>> account = account_manager.dispenser_from_environment() + + + + .. py:method:: rekeyed(*, sender: str, account: algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> algokit_utils.models.account.TransactionSignerAccount | algokit_utils.models.account.SigningAccount + + Tracks and returns an Algorand account that is a rekeyed version of the given account to a new sender. + + :param sender: The account or address to use as the sender + :param account: The account to use as the signer for this new rekeyed account + :returns: The rekeyed account + + :example: + >>> account = account.from_mnemonic("mnemonic secret ...") + >>> rekeyed_account = account_manager.rekeyed(account, "SENDERADDRESS...") + + + + .. py:method:: rekey_account(account: str, rekey_to: str | algokit_utils.protocols.account.TransactionSignerAccountProtocol, *, signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, note: bytes | None = None, lease: bytes | None = None, static_fee: algokit_utils.models.amount.AlgoAmount | None = None, extra_fee: algokit_utils.models.amount.AlgoAmount | None = None, max_fee: algokit_utils.models.amount.AlgoAmount | None = None, validity_window: int | None = None, first_valid_round: int | None = None, last_valid_round: int | None = None, suppress_log: bool | None = None) -> algokit_utils.transactions.transaction_composer.SendAtomicTransactionComposerResults + + Rekey an account to a new address. + + :param account: The account to rekey + :param rekey_to: The address or account to rekey to + :param signer: Optional transaction signer + :param note: Optional transaction note + :param lease: Optional transaction lease + :param static_fee: Optional static fee + :param extra_fee: Optional extra fee + :param max_fee: Optional max fee + :param validity_window: Optional validity window + :param first_valid_round: Optional first valid round + :param last_valid_round: Optional last valid round + :param suppress_log: Optional flag to suppress logging + :returns: The result of the transaction and the transaction that was sent + + .. warning:: + Please be careful with this function and be sure to read the + `official rekey guidance `_. + + :example: + >>> # Basic example (with string addresses): + >>> algorand.account.rekey_account("ACCOUNTADDRESS", "NEWADDRESS") + >>> # Basic example (with signer accounts): + >>> algorand.account.rekey_account(account1, newSignerAccount) + >>> # Advanced example: + >>> algorand.account.rekey_account( + ... account="ACCOUNTADDRESS", + ... rekey_to="NEWADDRESS", + ... lease='lease', + ... note='note', + ... first_valid_round=1000, + ... validity_window=10, + ... extra_fee=AlgoAmount.from_micro_algo(1000), + ... static_fee=AlgoAmount.from_micro_algo(1000), + ... max_fee=AlgoAmount.from_micro_algo(3000), + ... suppress_log=True, + ... ) + + + + .. py:method:: ensure_funded(account_to_fund: str | algokit_utils.models.account.SigningAccount, dispenser_account: str | algokit_utils.models.account.SigningAccount, min_spending_balance: algokit_utils.models.amount.AlgoAmount, min_funding_increment: algokit_utils.models.amount.AlgoAmount | None = None, send_params: algokit_utils.models.transaction.SendParams | None = None, signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, rekey_to: str | None = None, note: bytes | None = None, lease: bytes | None = None, static_fee: algokit_utils.models.amount.AlgoAmount | None = None, extra_fee: algokit_utils.models.amount.AlgoAmount | None = None, max_fee: algokit_utils.models.amount.AlgoAmount | None = None, validity_window: int | None = None, first_valid_round: int | None = None, last_valid_round: int | None = None) -> EnsureFundedResult | None + + Funds a given account using a dispenser account as a funding source. + + Ensures the given account has a certain amount of Algo free to spend (accounting for + Algo locked in minimum balance requirement). + + See ``_ for details. + + :param account_to_fund: The account to fund + :param dispenser_account: The account to use as a dispenser funding source + :param min_spending_balance: The minimum balance of Algo that the account + should have available to spend + :param min_funding_increment: Optional minimum funding increment + :param send_params: Parameters for the send operation, defaults to None + :param signer: Optional transaction signer + :param rekey_to: Optional rekey address + :param note: Optional transaction note + :param lease: Optional transaction lease + :param static_fee: Optional static fee + :param extra_fee: Optional extra fee + :param max_fee: Optional maximum fee + :param validity_window: Optional validity window + :param first_valid_round: Optional first valid round + :param last_valid_round: Optional last valid round + :returns: The result of executing the dispensing transaction and the `amountFunded` if funds were needed, + or None if no funds were needed + + :example: + >>> # Basic example: + >>> algorand.account.ensure_funded("ACCOUNTADDRESS", "DISPENSERADDRESS", AlgoAmount.from_algo(1)) + >>> # With configuration: + >>> algorand.account.ensure_funded( + ... "ACCOUNTADDRESS", + ... "DISPENSERADDRESS", + ... AlgoAmount.from_algo(1), + ... min_funding_increment=AlgoAmount.from_algo(2), + ... fee=AlgoAmount.from_micro_algo(1000), + ... suppress_log=True + ... ) + + + + .. py:method:: ensure_funded_from_environment(account_to_fund: str | algokit_utils.models.account.SigningAccount, min_spending_balance: algokit_utils.models.amount.AlgoAmount, *, min_funding_increment: algokit_utils.models.amount.AlgoAmount | None = None, send_params: algokit_utils.models.transaction.SendParams | None = None, signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, rekey_to: str | None = None, note: bytes | None = None, lease: bytes | None = None, static_fee: algokit_utils.models.amount.AlgoAmount | None = None, extra_fee: algokit_utils.models.amount.AlgoAmount | None = None, max_fee: algokit_utils.models.amount.AlgoAmount | None = None, validity_window: int | None = None, first_valid_round: int | None = None, last_valid_round: int | None = None) -> EnsureFundedResult | None + + Ensure an account is funded from a dispenser account configured in environment. + + Uses a dispenser account retrieved from the environment, per the `dispenser_from_environment` method, + as a funding source such that the given account has a certain amount of Algo free to spend + (accounting for Algo locked in minimum balance requirement). + + See ``_ for details. + + :param account_to_fund: The account to fund + :param min_spending_balance: The minimum balance of Algo that the account should have available to + spend + :param min_funding_increment: Optional minimum funding increment + :param send_params: Parameters for the send operation, defaults to None + :param signer: Optional transaction signer + :param rekey_to: Optional rekey address + :param note: Optional transaction note + :param lease: Optional transaction lease + :param static_fee: Optional static fee + :param extra_fee: Optional extra fee + :param max_fee: Optional maximum fee + :param validity_window: Optional validity window + :param first_valid_round: Optional first valid round + :param last_valid_round: Optional last valid round + :returns: The result of executing the dispensing transaction and the `amountFunded` if funds were needed, or + None if no funds were needed + + .. note:: + The dispenser account is retrieved from the account mnemonic stored in + process.env.DISPENSER_MNEMONIC and optionally process.env.DISPENSER_SENDER + if it's a rekeyed account, or against default LocalNet if no environment variables present. + + :example: + >>> # Basic example: + >>> algorand.account.ensure_funded_from_environment("ACCOUNTADDRESS", AlgoAmount.from_algo(1)) + >>> # With configuration: + >>> algorand.account.ensure_funded_from_environment( + ... "ACCOUNTADDRESS", + ... AlgoAmount.from_algo(1), + ... min_funding_increment=AlgoAmount.from_algo(2), + ... fee=AlgoAmount.from_micro_algo(1000), + ... suppress_log=True + ... ) + + + + .. py:method:: ensure_funded_from_testnet_dispenser_api(account_to_fund: str | algokit_utils.models.account.SigningAccount, dispenser_client: algokit_utils.clients.dispenser_api_client.TestNetDispenserApiClient, min_spending_balance: algokit_utils.models.amount.AlgoAmount, *, min_funding_increment: algokit_utils.models.amount.AlgoAmount | None = None) -> EnsureFundedFromTestnetDispenserApiResult | None + + Ensure an account is funded using the TestNet Dispenser API. + + Uses the TestNet Dispenser API as a funding source such that the account has a certain amount + of Algo free to spend (accounting for Algo locked in minimum balance requirement). + + See ``_ for details. + + :param account_to_fund: The account to fund + :param dispenser_client: The TestNet dispenser funding client + :param min_spending_balance: The minimum balance of Algo that the account should have + available to spend + :param min_funding_increment: Optional minimum funding increment + :returns: The result of executing the dispensing transaction and the `amountFunded` if funds were needed, or + None if no funds were needed + :raises ValueError: If attempting to fund on non-TestNet network + + :example: + >>> # Basic example: + >>> account_manager.ensure_funded_from_testnet_dispenser_api( + ... "ACCOUNTADDRESS", + ... algorand.client.get_testnet_dispenser_from_environment(), + ... AlgoAmount.from_algo(1) + ... ) + >>> # With configuration: + >>> account_manager.ensure_funded_from_testnet_dispenser_api( + ... "ACCOUNTADDRESS", + ... algorand.client.get_testnet_dispenser_from_environment(), + ... AlgoAmount.from_algo(1), + ... min_funding_increment=AlgoAmount.from_algo(2) + ... ) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/accounts/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/accounts/index.rst.txt new file mode 100644 index 00000000..679f280f --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/accounts/index.rst.txt @@ -0,0 +1,16 @@ +algokit_utils.accounts +====================== + +.. py:module:: algokit_utils.accounts + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/accounts/account_manager/index + /autoapi/algokit_utils/accounts/kmd_account_manager/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/accounts/kmd_account_manager/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/accounts/kmd_account_manager/index.rst.txt new file mode 100644 index 00000000..5a780301 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/accounts/kmd_account_manager/index.rst.txt @@ -0,0 +1,82 @@ +algokit_utils.accounts.kmd_account_manager +========================================== + +.. py:module:: algokit_utils.accounts.kmd_account_manager + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.accounts.kmd_account_manager.KmdAccount + algokit_utils.accounts.kmd_account_manager.KmdAccountManager + + +Module Contents +--------------- + +.. py:class:: KmdAccount(private_key: str, address: str | None = None) + + Bases: :py:obj:`algokit_utils.models.account.SigningAccount` + + + Account retrieved from KMD with signing capabilities, extending base Account. + + Provides an account implementation that can be used to sign transactions using keys stored in KMD. + + :param private_key: Base64 encoded private key + :param address: Optional address override for rekeyed accounts, defaults to None + + +.. py:class:: KmdAccountManager(client_manager: algokit_utils.clients.client_manager.ClientManager) + + Provides abstractions over KMD that makes it easier to get and manage accounts. + + + .. py:method:: kmd() -> algosdk.kmd.KMDClient + + Returns the KMD client, initializing it if needed. + + :raises Exception: If KMD client is not configured and not running against LocalNet + :return: The KMD client + + + + .. py:method:: get_wallet_account(wallet_name: str, predicate: collections.abc.Callable[[dict[str, Any]], bool] | None = None, sender: str | None = None) -> KmdAccount | None + + Returns an Algorand signing account with private key loaded from the given KMD wallet. + + Retrieves an account from a KMD wallet that matches the given predicate, or a random account + if no predicate is provided. + + :param wallet_name: The name of the wallet to retrieve an account from + :param predicate: Optional filter to use to find the account (otherwise gets a random account from the wallet) + :param sender: Optional sender address to use this signer for (aka a rekeyed account) + :return: The signing account or None if no matching wallet or account was found + + + + .. py:method:: get_or_create_wallet_account(name: str, fund_with: algokit_utils.models.amount.AlgoAmount | None = None) -> KmdAccount + + Gets or creates a funded account in a KMD wallet of the given name. + + Provides idempotent access to accounts from LocalNet without specifying the private key. + + :param name: The name of the wallet to retrieve / create + :param fund_with: The number of Algos to fund the account with when created + :return: An Algorand account with private key loaded + + + + .. py:method:: get_localnet_dispenser_account() -> KmdAccount + + Returns an Algorand account with private key loaded for the default LocalNet dispenser account. + + Retrieves the default funded account from LocalNet that can be used to fund other accounts. + + :raises Exception: If not running against LocalNet or dispenser account not found + :return: The default LocalNet dispenser account + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/algorand/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/algorand/index.rst.txt new file mode 100644 index 00000000..f88aba0d --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/algorand/index.rst.txt @@ -0,0 +1,291 @@ +algokit_utils.algorand +====================== + +.. py:module:: algokit_utils.algorand + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.algorand.AlgorandClient + + +Module Contents +--------------- + +.. py:class:: AlgorandClient(config: algokit_utils.models.network.AlgoClientConfigs | algokit_utils.clients.client_manager.AlgoSdkClients) + + A client that brokers easy access to Algorand functionality. + + + .. py:method:: set_default_validity_window(validity_window: int) -> typing_extensions.Self + + Sets the default validity window for transactions. + + :param validity_window: The number of rounds between the first and last valid rounds + :return: The `AlgorandClient` so method calls can be chained + :example: + >>> algorand = AlgorandClient.mainnet().set_default_validity_window(1000); + + + + .. py:method:: set_default_signer(signer: algosdk.atomic_transaction_composer.TransactionSigner | algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> typing_extensions.Self + + Sets the default signer to use if no other signer is specified. + + :param signer: The signer to use, either a `TransactionSigner` or a `TransactionSignerAccountProtocol` + :return: The `AlgorandClient` so method calls can be chained + :example: + >>> signer = SigningAccount(private_key=..., address=...) + >>> algorand = AlgorandClient.mainnet().set_default_signer(signer) + + + + .. py:method:: set_signer(sender: str, signer: algosdk.atomic_transaction_composer.TransactionSigner) -> typing_extensions.Self + + Tracks the given account for later signing. + + :param sender: The sender address to use this signer for + :param signer: The signer to sign transactions with for the given sender + :return: The `AlgorandClient` so method calls can be chained + :example: + >>> signer = SigningAccount(private_key=..., address=...) + >>> algorand = AlgorandClient.mainnet().set_signer(signer.addr, signer.signer) + + + + .. py:method:: set_signer_from_account(signer: algokit_utils.protocols.account.TransactionSignerAccountProtocol) -> typing_extensions.Self + + Sets the default signer to use if no other signer is specified. + + :param signer: The signer to use, either a `TransactionSigner` or a `TransactionSignerAccountProtocol` + :return: The `AlgorandClient` so method calls can be chained + :example: + >>> accountManager = AlgorandClient.mainnet() + >>> accountManager.set_signer_from_account(TransactionSignerAccount(address=..., signer=...)) + >>> accountManager.set_signer_from_account(algosdk.LogicSigAccount(program, args)) + >>> accountManager.set_signer_from_account(SigningAccount(private_key=..., address=...)) + >>> accountManager.set_signer_from_account(MultisigAccount(metadata, signing_accounts)) + >>> accountManager.set_signer_from_account(account) + + + + .. py:method:: set_suggested_params_cache(suggested_params: algosdk.transaction.SuggestedParams, until: float | None = None) -> typing_extensions.Self + + Sets a cache value to use for suggested params. + + :param suggested_params: The suggested params to use + :param until: A timestamp until which to cache, or if not specified then the timeout is used + :return: The `AlgorandClient` so method calls can be chained + :example: + >>> algorand = AlgorandClient.mainnet().set_suggested_params_cache(suggested_params, time.time() + 3.6e6) + + + + .. py:method:: set_suggested_params_cache_timeout(timeout: int) -> typing_extensions.Self + + Sets the timeout for caching suggested params. + + :param timeout: The timeout in milliseconds + :return: The `AlgorandClient` so method calls can be chained + :example: + >>> algorand = AlgorandClient.mainnet().set_suggested_params_cache_timeout(10_000) + + + + .. py:method:: get_suggested_params() -> algosdk.transaction.SuggestedParams + + Get suggested params for a transaction (either cached or from algod if the cache is stale or empty) + + :example: + >>> algorand = AlgorandClient.mainnet().get_suggested_params() + + + + .. py:method:: new_group() -> algokit_utils.transactions.transaction_composer.TransactionComposer + + Start a new `TransactionComposer` transaction group + + :example: + >>> composer = AlgorandClient.mainnet().new_group() + >>> result = await composer.add_transaction(payment).send() + + + + .. py:property:: client + :type: algokit_utils.clients.client_manager.ClientManager + + + Get clients, including algosdk clients and app clients. + + :example: + >>> clientManager = AlgorandClient.mainnet().client + + + + .. py:property:: account + :type: algokit_utils.accounts.account_manager.AccountManager + + + Get or create accounts that can sign transactions. + + :example: + >>> accountManager = AlgorandClient.mainnet().account + + + + .. py:property:: asset + :type: algokit_utils.assets.asset_manager.AssetManager + + + Get or create assets. + + :example: + >>> assetManager = AlgorandClient.mainnet().asset + + + + .. py:property:: app + :type: algokit_utils.applications.app_manager.AppManager + + + Get or create applications. + + :example: + >>> appManager = AlgorandClient.mainnet().app + + + + .. py:property:: app_deployer + :type: algokit_utils.applications.app_deployer.AppDeployer + + + Get or create applications. + + :example: + >>> appDeployer = AlgorandClient.mainnet().app_deployer + + + + .. py:property:: send + :type: algokit_utils.transactions.transaction_sender.AlgorandClientTransactionSender + + + Methods for sending a transaction and waiting for confirmation + + :example: + >>> result = await AlgorandClient.mainnet().send.payment( + >>> PaymentParams( + >>> sender="SENDERADDRESS", + >>> receiver="RECEIVERADDRESS", + >>> amount=AlgoAmount(algo-1) + >>> )) + + + + .. py:property:: create_transaction + :type: algokit_utils.transactions.transaction_creator.AlgorandClientTransactionCreator + + + Methods for building transactions + + :example: + >>> transaction = AlgorandClient.mainnet().create_transaction.payment( + >>> PaymentParams( + >>> sender="SENDERADDRESS", + >>> receiver="RECEIVERADDRESS", + >>> amount=AlgoAmount(algo=1) + >>> )) + + + + .. py:method:: default_localnet() -> AlgorandClient + :staticmethod: + + + Returns an `AlgorandClient` pointing at default LocalNet ports and API token. + + :return: The `AlgorandClient` + + :example: + >>> algorand = AlgorandClient.default_localnet() + + + + .. py:method:: testnet() -> AlgorandClient + :staticmethod: + + + Returns an `AlgorandClient` pointing at TestNet using AlgoNode. + + :return: The `AlgorandClient` + + :example: + >>> algorand = AlgorandClient.testnet() + + + + .. py:method:: mainnet() -> AlgorandClient + :staticmethod: + + + Returns an `AlgorandClient` pointing at MainNet using AlgoNode. + + :return: The `AlgorandClient` + + :example: + >>> algorand = AlgorandClient.mainnet() + + + + .. py:method:: from_clients(algod: algosdk.v2client.algod.AlgodClient, indexer: algosdk.v2client.indexer.IndexerClient | None = None, kmd: algosdk.kmd.KMDClient | None = None) -> AlgorandClient + :staticmethod: + + + Returns an `AlgorandClient` pointing to the given client(s). + + :param algod: The algod client to use + :param indexer: The indexer client to use + :param kmd: The kmd client to use + :return: The `AlgorandClient` + + :example: + >>> algorand = AlgorandClient.from_clients(algod, indexer, kmd) + + + + .. py:method:: from_environment() -> AlgorandClient + :staticmethod: + + + Returns an `AlgorandClient` loading the configuration from environment variables. + + Retrieve configurations from environment variables when defined or get defaults. + + Expects to be called from a Python environment. + + :return: The `AlgorandClient` + + :example: + >>> algorand = AlgorandClient.from_environment() + + + + .. py:method:: from_config(algod_config: algokit_utils.models.network.AlgoClientNetworkConfig, indexer_config: algokit_utils.models.network.AlgoClientNetworkConfig | None = None, kmd_config: algokit_utils.models.network.AlgoClientNetworkConfig | None = None) -> AlgorandClient + :staticmethod: + + + Returns an `AlgorandClient` from the given config. + + :param algod_config: The config to use for the algod client + :param indexer_config: The config to use for the indexer client + :param kmd_config: The config to use for the kmd client + :return: The `AlgorandClient` + + :example: + >>> algorand = AlgorandClient.from_config(algod_config, indexer_config, kmd_config) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/abi/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/abi/index.rst.txt new file mode 100644 index 00000000..92953f86 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/abi/index.rst.txt @@ -0,0 +1,222 @@ +algokit_utils.applications.abi +============================== + +.. py:module:: algokit_utils.applications.abi + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.applications.abi.ABIValue + algokit_utils.applications.abi.ABIStruct + algokit_utils.applications.abi.Arc56ReturnValueType + algokit_utils.applications.abi.ABIType + algokit_utils.applications.abi.ABIArgumentType + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.abi.ABIReturn + algokit_utils.applications.abi.BoxABIValue + + +Functions +--------- + +.. autoapisummary:: + + algokit_utils.applications.abi.get_arc56_value + algokit_utils.applications.abi.get_abi_encoded_value + algokit_utils.applications.abi.get_abi_decoded_value + algokit_utils.applications.abi.get_abi_tuple_from_abi_struct + algokit_utils.applications.abi.get_abi_tuple_type_from_abi_struct_definition + algokit_utils.applications.abi.get_abi_struct_from_abi_tuple + + +Module Contents +--------------- + +.. py:data:: ABIValue + :type: TypeAlias + :value: bool | int | str | bytes | bytearray | list['ABIValue'] | tuple['ABIValue'] | dict[str, 'ABIValue'] + + +.. py:data:: ABIStruct + :type: TypeAlias + :value: dict[str, list[dict[str, 'ABIValue']]] + + +.. py:data:: Arc56ReturnValueType + :type: TypeAlias + :value: ABIValue | ABIStruct | None + + +.. py:data:: ABIType + :type: TypeAlias + :value: algosdk.abi.ABIType + + +.. py:data:: ABIArgumentType + :type: TypeAlias + :value: algosdk.abi.ABIType | algosdk.abi.ABITransactionType | algosdk.abi.ABIReferenceType + + +.. py:class:: ABIReturn(result: algosdk.atomic_transaction_composer.ABIResult) + + Represents the return value from an ABI method call. + + Wraps the raw return value and decoded value along with any decode errors. + + + .. py:attribute:: raw_value + :type: bytes | None + :value: None + + + The raw return value from the method call + + + + .. py:attribute:: value + :type: ABIValue | None + :value: None + + + The decoded return value from the method call + + + + .. py:attribute:: method + :type: algosdk.abi.method.Method | None + :value: None + + + The ABI method definition + + + + .. py:attribute:: decode_error + :type: Exception | None + :value: None + + + The exception that occurred during decoding, if any + + + + .. py:attribute:: tx_info + :type: dict[str, Any] | None + :value: None + + + The transaction info for the method call from raw algosdk `ABIResult` + + + + .. py:property:: is_success + :type: bool + + + Returns True if the ABI call was successful (no decode error) + + :return: True if no decode error occurred, False otherwise + + + + .. py:method:: get_arc56_value(method: algokit_utils.applications.app_spec.arc56.Method | algosdk.abi.method.Method, structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> Arc56ReturnValueType + + Gets the ARC-56 formatted return value. + + :param method: The ABI method definition + :param structs: Dictionary of struct definitions + :return: The decoded return value in ARC-56 format + + + +.. py:function:: get_arc56_value(abi_return: ABIReturn, method: algokit_utils.applications.app_spec.arc56.Method | algosdk.abi.method.Method, structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> Arc56ReturnValueType + + Gets the ARC-56 formatted return value from an ABI return. + + :param abi_return: The ABI return value to decode + :param method: The ABI method definition + :param structs: Dictionary of struct definitions + :raises ValueError: If there was an error decoding the return value + :return: The decoded return value in ARC-56 format + + +.. py:function:: get_abi_encoded_value(value: Any, type_str: str, structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> bytes + + Encodes a value according to its ABI type. + + :param value: The value to encode + :param type_str: The ABI type string + :param structs: Dictionary of struct definitions + :raises ValueError: If the value cannot be encoded for the given type + :return: The ABI encoded bytes + + +.. py:function:: get_abi_decoded_value(value: bytes | int | str, type_str: str | ABIArgumentType, structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> ABIValue + + Decodes a value according to its ABI type. + + :param value: The value to decode + :param type_str: The ABI type string or type object + :param structs: Dictionary of struct definitions + :return: The decoded ABI value + + +.. py:function:: get_abi_tuple_from_abi_struct(struct_value: dict[str, Any], struct_fields: list[algokit_utils.applications.app_spec.arc56.StructField], structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> list[Any] + + Converts an ABI struct to a tuple representation. + + :param struct_value: The struct value as a dictionary + :param struct_fields: List of struct field definitions + :param structs: Dictionary of struct definitions + :raises ValueError: If a required field is missing from the struct + :return: The struct as a tuple + + +.. py:function:: get_abi_tuple_type_from_abi_struct_definition(struct_def: list[algokit_utils.applications.app_spec.arc56.StructField], structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> algosdk.abi.TupleType + + Creates a TupleType from a struct definition. + + :param struct_def: The struct field definitions + :param structs: Dictionary of struct definitions + :raises ValueError: If a field type is invalid + :return: The TupleType representing the struct + + +.. py:function:: get_abi_struct_from_abi_tuple(decoded_tuple: Any, struct_fields: list[algokit_utils.applications.app_spec.arc56.StructField], structs: dict[str, list[algokit_utils.applications.app_spec.arc56.StructField]]) -> dict[str, Any] + + Converts a decoded tuple to an ABI struct. + + :param decoded_tuple: The tuple to convert + :param struct_fields: List of struct field definitions + :param structs: Dictionary of struct definitions + :return: The tuple as a struct dictionary + + +.. py:class:: BoxABIValue + + Represents an ABI value stored in a box. + + + .. py:attribute:: name + :type: algokit_utils.models.state.BoxName + + The name of the box + + + + .. py:attribute:: value + :type: ABIValue + + The ABI value stored in the box + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_client/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_client/index.rst.txt new file mode 100644 index 00000000..94c33900 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_client/index.rst.txt @@ -0,0 +1,852 @@ +algokit_utils.applications.app_client +===================================== + +.. py:module:: algokit_utils.applications.app_client + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.applications.app_client.CreateOnComplete + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.app_client.AppClientCompilationResult + algokit_utils.applications.app_client.AppClientCompilationParams + algokit_utils.applications.app_client.CommonAppCallParams + algokit_utils.applications.app_client.AppClientCreateSchema + algokit_utils.applications.app_client.CommonAppCallCreateParams + algokit_utils.applications.app_client.FundAppAccountParams + algokit_utils.applications.app_client.AppClientBareCallParams + algokit_utils.applications.app_client.AppClientBareCallCreateParams + algokit_utils.applications.app_client.BaseAppClientMethodCallParams + algokit_utils.applications.app_client.AppClientMethodCallParams + algokit_utils.applications.app_client.AppClientMethodCallCreateParams + algokit_utils.applications.app_client.AppClientParams + algokit_utils.applications.app_client.AppClient + + +Functions +--------- + +.. autoapisummary:: + + algokit_utils.applications.app_client.get_constant_block_offset + + +Module Contents +--------------- + +.. py:function:: get_constant_block_offset(program: bytes) -> int + + Calculate the offset after constant blocks in TEAL program. + + Analyzes a compiled TEAL program to find the ending offset position after any bytecblock and intcblock operations. + + :param program: The compiled TEAL program as bytes + :return: The maximum offset position after any constant block operations + + +.. py:data:: CreateOnComplete + +.. py:class:: AppClientCompilationResult + + Result of compiling an application's TEAL code. + + Contains the compiled approval and clear state programs along with optional compilation artifacts. + + + .. py:attribute:: approval_program + :type: bytes + + The compiled approval program bytes + + + + .. py:attribute:: clear_state_program + :type: bytes + + The compiled clear state program bytes + + + + .. py:attribute:: compiled_approval + :type: algokit_utils.models.application.CompiledTeal | None + :value: None + + + Optional compilation artifacts for approval program + + + + .. py:attribute:: compiled_clear + :type: algokit_utils.models.application.CompiledTeal | None + :value: None + + + Optional compilation artifacts for clear state program + + + +.. py:class:: AppClientCompilationParams + + Bases: :py:obj:`TypedDict` + + + Parameters for compiling an application's TEAL code. + + :ivar deploy_time_params: Optional template parameters to use during compilation + :ivar updatable: Optional flag indicating if app should be updatable + :ivar deletable: Optional flag indicating if app should be deletable + + + .. py:attribute:: deploy_time_params + :type: algokit_utils.models.state.TealTemplateParams | None + + + .. py:attribute:: updatable + :type: bool | None + + + .. py:attribute:: deletable + :type: bool | None + + +.. py:class:: CommonAppCallParams + + Common configuration for app call transaction parameters + + + .. py:attribute:: account_references + :type: list[str] | None + :value: None + + + List of account addresses to reference + + + + .. py:attribute:: app_references + :type: list[int] | None + :value: None + + + List of app IDs to reference + + + + .. py:attribute:: asset_references + :type: list[int] | None + :value: None + + + List of asset IDs to reference + + + + .. py:attribute:: box_references + :type: list[algokit_utils.models.state.BoxReference | algokit_utils.models.state.BoxIdentifier] | None + :value: None + + + List of box references to include + + + + .. py:attribute:: extra_fee + :type: algokit_utils.models.amount.AlgoAmount | None + :value: None + + + Additional fee to add to transaction + + + + .. py:attribute:: lease + :type: bytes | None + :value: None + + + Transaction lease value + + + + .. py:attribute:: max_fee + :type: algokit_utils.models.amount.AlgoAmount | None + :value: None + + + Maximum fee allowed for transaction + + + + .. py:attribute:: note + :type: bytes | None + :value: None + + + Custom note for the transaction + + + + .. py:attribute:: rekey_to + :type: str | None + :value: None + + + Address to rekey account to + + + + .. py:attribute:: sender + :type: str | None + :value: None + + + Sender address override + + + + .. py:attribute:: signer + :type: algosdk.atomic_transaction_composer.TransactionSigner | None + :value: None + + + Custom transaction signer + + + + .. py:attribute:: static_fee + :type: algokit_utils.models.amount.AlgoAmount | None + :value: None + + + Fixed fee for transaction + + + + .. py:attribute:: validity_window + :type: int | None + :value: None + + + Number of rounds valid + + + + .. py:attribute:: first_valid_round + :type: int | None + :value: None + + + First valid round number + + + + .. py:attribute:: last_valid_round + :type: int | None + :value: None + + + Last valid round number + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete | None + :value: None + + + Optional on complete action + + + +.. py:class:: AppClientCreateSchema + + Schema for application creation. + + + .. py:attribute:: extra_program_pages + :type: int | None + :value: None + + + Optional number of extra program pages + + + + .. py:attribute:: schema + :type: algokit_utils.transactions.transaction_composer.AppCreateSchema | None + :value: None + + + Optional application creation schema + + + +.. py:class:: CommonAppCallCreateParams + + Bases: :py:obj:`AppClientCreateSchema`, :py:obj:`CommonAppCallParams` + + + Common configuration for app create call transaction parameters. + + + .. py:attribute:: on_complete + :type: CreateOnComplete | None + :value: None + + + Optional on complete action + + + +.. py:class:: FundAppAccountParams + + Bases: :py:obj:`CommonAppCallParams` + + + Parameters for funding an application's account. + + + .. py:attribute:: amount + :type: algokit_utils.models.amount.AlgoAmount + + Amount to fund + + + + .. py:attribute:: close_remainder_to + :type: str | None + :value: None + + + Optional address to close remainder to + + + +.. py:class:: AppClientBareCallParams + + Bases: :py:obj:`CommonAppCallParams` + + + Parameters for bare application calls. + + + .. py:attribute:: args + :type: list[bytes] | None + :value: None + + + Optional arguments + + + +.. py:class:: AppClientBareCallCreateParams + + Bases: :py:obj:`CommonAppCallCreateParams` + + + Parameters for creating application with bare call. + + + .. py:attribute:: on_complete + :type: CreateOnComplete | None + :value: None + + + Optional on complete action + + + +.. py:class:: BaseAppClientMethodCallParams + + Bases: :py:obj:`Generic`\ [\ :py:obj:`ArgsT`\ , :py:obj:`MethodT`\ ], :py:obj:`CommonAppCallParams` + + + Base parameters for application method calls. + + + .. py:attribute:: method + :type: MethodT + + Method to call + + + + .. py:attribute:: args + :type: ArgsT | None + :value: None + + + Arguments to pass to the application method call + + + +.. py:class:: AppClientMethodCallParams + + Bases: :py:obj:`BaseAppClientMethodCallParams`\ [\ :py:obj:`collections.abc.Sequence`\ [\ :py:obj:`algokit_utils.applications.abi.ABIValue | algokit_utils.applications.abi.ABIStruct | algokit_utils.transactions.transaction_composer.AppMethodCallTransactionArgument | None`\ ]\ , :py:obj:`str`\ ] + + + Parameters for application method calls. + + +.. py:class:: AppClientMethodCallCreateParams + + Bases: :py:obj:`AppClientCreateSchema`, :py:obj:`AppClientMethodCallParams` + + + Parameters for creating application with method call + + + .. py:attribute:: on_complete + :type: CreateOnComplete | None + :value: None + + + Optional on complete action + + + +.. py:class:: AppClientParams + + Full parameters for creating an app client + + + .. py:attribute:: app_spec + :type: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils.applications.app_spec.arc32.Arc32Contract | str + + The application specification + + + + .. py:attribute:: algorand + :type: algokit_utils.algorand.AlgorandClient + + The Algorand client + + + + .. py:attribute:: app_id + :type: int + + The application ID + + + + .. py:attribute:: app_name + :type: str | None + :value: None + + + The application name + + + + .. py:attribute:: default_sender + :type: str | None + :value: None + + + The default sender address + + + + .. py:attribute:: default_signer + :type: algosdk.atomic_transaction_composer.TransactionSigner | None + :value: None + + + The default transaction signer + + + + .. py:attribute:: approval_source_map + :type: algosdk.source_map.SourceMap | None + :value: None + + + The approval source map + + + + .. py:attribute:: clear_source_map + :type: algosdk.source_map.SourceMap | None + :value: None + + + The clear source map + + + +.. py:class:: AppClient(params: AppClientParams) + + A client for interacting with an Algorand smart contract application. + + Provides a high-level interface for interacting with Algorand smart contracts, including + methods for calling application methods, managing state, and handling transactions. + + :param params: Parameters for creating the app client + + :example: + >>> params = AppClientParams( + ... app_spec=Arc56Contract.from_json(app_spec_json), + ... algorand=algorand, + ... app_id=1234567890, + ... app_name="My App", + ... default_sender="SENDERADDRESS", + ... default_signer=TransactionSigner( + ... account="SIGNERACCOUNT", + ... private_key="SIGNERPRIVATEKEY", + ... ), + ... approval_source_map=SourceMap( + ... source="APPROVALSOURCE", + ... ), + ... clear_source_map=SourceMap( + ... source="CLEARSOURCE", + ... ), + ... ) + >>> client = AppClient(params) + + + .. py:property:: algorand + :type: algokit_utils.algorand.AlgorandClient + + + Get the Algorand client instance. + + :return: The Algorand client used by this app client + + + + .. py:property:: app_id + :type: int + + + Get the application ID. + + :return: The ID of the Algorand application + + + + .. py:property:: app_address + :type: str + + + Get the application's Algorand address. + + :return: The Algorand address associated with this application + + + + .. py:property:: app_name + :type: str + + + Get the application name. + + :return: The name of the application + + + + .. py:property:: app_spec + :type: algokit_utils.applications.app_spec.arc56.Arc56Contract + + + Get the application specification. + + :return: The ARC-56 contract specification for this application + + + + .. py:property:: state + :type: _StateAccessor + + + Get the state accessor. + + :return: The state accessor for this application + + + + .. py:property:: params + :type: _MethodParamsBuilder + + + Get the method parameters builder. + + :return: The method parameters builder for this application + + :example: + >>> # Create a transaction in the future using Algorand Client + >>> my_method_call = app_client.params.call(AppClientMethodCallParams( + method='my_method', + args=[123, 'hello'])) + >>> # ... + >>> await algorand.send.AppMethodCall(my_method_call) + >>> # Define a nested transaction as an ABI argument + >>> my_method_call = app_client.params.call(AppClientMethodCallParams( + method='my_method', + args=[123, 'hello'])) + >>> app_client.send.call(AppClientMethodCallParams(method='my_method2', args=[my_method_call])) + + + + .. py:property:: send + :type: _TransactionSender + + + Get the transaction sender. + + :return: The transaction sender for this application + + + + .. py:property:: create_transaction + :type: _TransactionCreator + + + Get the transaction creator. + + :return: The transaction creator for this application + + + + .. py:method:: normalise_app_spec(app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils.applications.app_spec.arc32.Arc32Contract | str) -> algokit_utils.applications.app_spec.arc56.Arc56Contract + :staticmethod: + + + Normalize an application specification to ARC-56 format. + + :param app_spec: The application specification to normalize. Can be raw arc32 or arc56 json, + or an Arc32Contract or Arc56Contract instance + :return: The normalized ARC-56 contract specification + :raises ValueError: If the app spec format is invalid + + :example: + >>> spec = AppClient.normalise_app_spec(app_spec_json) + + + + .. py:method:: from_network(app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils.applications.app_spec.arc32.Arc32Contract | str, algorand: algokit_utils.algorand.AlgorandClient, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> AppClient + :staticmethod: + + + Create an AppClient instance from network information. + + :param app_spec: The application specification + :param algorand: The Algorand client instance + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :return: A new AppClient instance + :raises Exception: If no app ID is found for the network + + :example: + >>> client = AppClient.from_network( + ... app_spec=Arc56Contract.from_json(app_spec_json), + ... algorand=algorand, + ... app_name="My App", + ... default_sender="SENDERADDRESS", + ... default_signer=TransactionSigner( + ... account="SIGNERACCOUNT", + ... private_key="SIGNERPRIVATEKEY", + ... ), + ... approval_source_map=SourceMap( + ... source="APPROVALSOURCE", + ... ), + ... clear_source_map=SourceMap( + ... source="CLEARSOURCE", + ... ), + ... ) + + + + .. py:method:: from_creator_and_name(creator_address: str, app_name: str, app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils.applications.app_spec.arc32.Arc32Contract | str, algorand: algokit_utils.algorand.AlgorandClient, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None, ignore_cache: bool | None = None, app_lookup_cache: algokit_utils.applications.app_deployer.ApplicationLookup | None = None) -> AppClient + :staticmethod: + + + Create an AppClient instance from creator address and application name. + + :param creator_address: The address of the application creator + :param app_name: The name of the application + :param app_spec: The application specification + :param algorand: The Algorand client instance + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :param ignore_cache: Optional flag to ignore cache + :param app_lookup_cache: Optional app lookup cache + :return: A new AppClient instance + :raises ValueError: If the app is not found for the creator and name + + :example: + >>> client = AppClient.from_creator_and_name( + ... creator_address="CREATORADDRESS", + ... app_name="APPNAME", + ... app_spec=Arc56Contract.from_json(app_spec_json), + ... algorand=algorand, + ... ) + + + + .. py:method:: compile(app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract, app_manager: algokit_utils.applications.app_manager.AppManager, compilation_params: AppClientCompilationParams | None = None) -> AppClientCompilationResult + :staticmethod: + + + Compile the application's TEAL code. + + :param app_spec: The application specification + :param app_manager: The application manager instance + :param compilation_params: Optional compilation parameters + :return: The compilation result + :raises ValueError: If attempting to compile without source or byte code + + + + .. py:method:: compile_app(compilation_params: AppClientCompilationParams | None = None) -> AppClientCompilationResult + + Compile the application's TEAL code. + + :param compilation_params: Optional compilation parameters + :return: The compilation result + + + + .. py:method:: clone(app_name: str | None = _MISSING, default_sender: str | None = _MISSING, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = _MISSING, approval_source_map: algosdk.source_map.SourceMap | None = _MISSING, clear_source_map: algosdk.source_map.SourceMap | None = _MISSING) -> AppClient + + Create a cloned AppClient instance with optionally overridden parameters. + + :param app_name: Optional new application name + :param default_sender: Optional new default sender + :param default_signer: Optional new default signer + :param approval_source_map: Optional new approval source map + :param clear_source_map: Optional new clear source map + :return: A new AppClient instance + + :example: + >>> client = AppClient(params) + >>> cloned_client = client.clone(app_name="Cloned App", default_sender="NEW_SENDER") + + + + .. py:method:: export_source_maps() -> algokit_utils.models.application.AppSourceMaps + + Export the application's source maps. + + :return: The application's source maps + :raises ValueError: If source maps haven't been loaded + + + + .. py:method:: import_source_maps(source_maps: algokit_utils.models.application.AppSourceMaps) -> None + + Import source maps for the application. + + :param source_maps: The source maps to import + :raises ValueError: If source maps are invalid or missing + + + + .. py:method:: get_local_state(address: str) -> dict[str, algokit_utils.models.application.AppState] + + Get local state for an account. + + :param address: The account address + :return: The account's local state for this application + + + + .. py:method:: get_global_state() -> dict[str, algokit_utils.models.application.AppState] + + Get the application's global state. + + :return: The application's global state + :example: + >>> global_state = client.get_global_state() + + + + .. py:method:: get_box_names() -> list[algokit_utils.models.state.BoxName] + + Get all box names for the application. + + :return: List of box names + + :example: + >>> box_names = client.get_box_names() + + + + .. py:method:: get_box_value(name: algokit_utils.models.state.BoxIdentifier) -> bytes + + Get the value of a box. + + :param name: The box identifier + :return: The box value as bytes + + :example: + >>> box_value = client.get_box_value(box_name) + + + + .. py:method:: get_box_value_from_abi_type(name: algokit_utils.models.state.BoxIdentifier, abi_type: algokit_utils.applications.abi.ABIType) -> algokit_utils.applications.abi.ABIValue + + Get a box value decoded according to an ABI type. + + :param name: The box identifier + :param abi_type: The ABI type to decode as + :return: The decoded box value + + :example: + >>> box_value = client.get_box_value_from_abi_type(box_name, abi_type) + + + + .. py:method:: get_box_values(filter_func: collections.abc.Callable[[algokit_utils.models.state.BoxName], bool] | None = None) -> list[algokit_utils.models.state.BoxValue] + + Get values for multiple boxes. + + :param filter_func: Optional function to filter box names + :return: List of box values + + :example: + >>> box_values = client.get_box_values() + + + + .. py:method:: get_box_values_from_abi_type(abi_type: algokit_utils.applications.abi.ABIType, filter_func: collections.abc.Callable[[algokit_utils.models.state.BoxName], bool] | None = None) -> list[algokit_utils.applications.abi.BoxABIValue] + + Get multiple box values decoded according to an ABI type. + + :param abi_type: The ABI type to decode as + :param filter_func: Optional function to filter box names + :return: List of decoded box values + + :example: + >>> box_values = client.get_box_values_from_abi_type(abi_type) + + + + .. py:method:: fund_app_account(params: FundAppAccountParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> algokit_utils.transactions.transaction_sender.SendSingleTransactionResult + + Fund the application's account. + + :param params: The funding parameters + :param send_params: Send parameters, defaults to None + :return: The transaction result + + :example: + >>> result = client.fund_app_account(params) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_deployer/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_deployer/index.rst.txt new file mode 100644 index 00000000..b2c68143 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_deployer/index.rst.txt @@ -0,0 +1,373 @@ +algokit_utils.applications.app_deployer +======================================= + +.. py:module:: algokit_utils.applications.app_deployer + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.applications.app_deployer.APP_DEPLOY_NOTE_DAPP + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.app_deployer.AppDeploymentMetaData + algokit_utils.applications.app_deployer.ApplicationReference + algokit_utils.applications.app_deployer.ApplicationMetaData + algokit_utils.applications.app_deployer.ApplicationLookup + algokit_utils.applications.app_deployer.AppDeployParams + algokit_utils.applications.app_deployer.AppDeployResult + algokit_utils.applications.app_deployer.AppDeployer + + +Module Contents +--------------- + +.. py:data:: APP_DEPLOY_NOTE_DAPP + :type: str + :value: 'ALGOKIT_DEPLOYER' + + +.. py:class:: AppDeploymentMetaData + + Metadata about an application stored in a transaction note during creation. + + + .. py:attribute:: name + :type: str + + + .. py:attribute:: version + :type: str + + + .. py:attribute:: deletable + :type: bool | None + + + .. py:attribute:: updatable + :type: bool | None + + + .. py:method:: dictify() -> dict[str, str | bool] + + +.. py:class:: ApplicationReference + + Information about an Algorand app + + + .. py:attribute:: app_id + :type: int + + + .. py:attribute:: app_address + :type: str + + +.. py:class:: ApplicationMetaData + + Complete metadata about a deployed app + + + .. py:attribute:: reference + :type: ApplicationReference + + + .. py:attribute:: deploy_metadata + :type: AppDeploymentMetaData + + + .. py:attribute:: created_round + :type: int + + + .. py:attribute:: updated_round + :type: int + + + .. py:attribute:: deleted + :type: bool + :value: False + + + + .. py:property:: app_id + :type: int + + + + .. py:property:: app_address + :type: str + + + + .. py:property:: name + :type: str + + + + .. py:property:: version + :type: str + + + + .. py:property:: deletable + :type: bool | None + + + + .. py:property:: updatable + :type: bool | None + + + +.. py:class:: ApplicationLookup + + Cache of {py:class}`ApplicationMetaData` for a specific `creator` + + Can be used as an argument to {py:class}`ApplicationClient` to reduce the number of calls when deploying multiple + apps or discovering multiple app_ids + + + .. py:attribute:: creator + :type: str + + + .. py:attribute:: apps + :type: dict[str, ApplicationMetaData] + + +.. py:class:: AppDeployParams + + Parameters for deploying an app + + + .. py:attribute:: metadata + :type: AppDeploymentMetaData + + The deployment metadata + + + + .. py:attribute:: deploy_time_params + :type: algokit_utils.models.state.TealTemplateParams | None + :value: None + + + Optional template parameters to use during compilation + + + + .. py:attribute:: on_schema_break + :type: Literal['replace', 'fail', 'append'] | algokit_utils.applications.enums.OnSchemaBreak | None + :value: None + + + Optional on schema break action + + + + .. py:attribute:: on_update + :type: Literal['update', 'replace', 'fail', 'append'] | algokit_utils.applications.enums.OnUpdate | None + :value: None + + + Optional on update action + + + + .. py:attribute:: create_params + :type: algokit_utils.transactions.transaction_composer.AppCreateParams | algokit_utils.transactions.transaction_composer.AppCreateMethodCallParams + + The creation parameters + + + + .. py:attribute:: update_params + :type: algokit_utils.transactions.transaction_composer.AppUpdateParams | algokit_utils.transactions.transaction_composer.AppUpdateMethodCallParams + + The update parameters + + + + .. py:attribute:: delete_params + :type: algokit_utils.transactions.transaction_composer.AppDeleteParams | algokit_utils.transactions.transaction_composer.AppDeleteMethodCallParams + + The deletion parameters + + + + .. py:attribute:: existing_deployments + :type: ApplicationLookup | None + :value: None + + + Optional existing deployments + + + + .. py:attribute:: ignore_cache + :type: bool + :value: False + + + Whether to ignore the cache + + + + .. py:attribute:: max_fee + :type: int | None + :value: None + + + Optional maximum fee + + + + .. py:attribute:: send_params + :type: algokit_utils.models.transaction.SendParams | None + :value: None + + + Optional send parameters + + + +.. py:class:: AppDeployResult + + The result of a deployment + + + .. py:attribute:: app + :type: ApplicationMetaData + + The application metadata + + + + .. py:attribute:: operation_performed + :type: algokit_utils.applications.enums.OperationPerformed + + The operation performed + + + + .. py:attribute:: create_result + :type: algokit_utils.transactions.transaction_sender.SendAppCreateTransactionResult[algokit_utils.applications.abi.ABIReturn] | None + :value: None + + + The create result + + + + .. py:attribute:: update_result + :type: algokit_utils.transactions.transaction_sender.SendAppUpdateTransactionResult[algokit_utils.applications.abi.ABIReturn] | None + :value: None + + + The update result + + + + .. py:attribute:: delete_result + :type: algokit_utils.transactions.transaction_sender.SendAppTransactionResult[algokit_utils.applications.abi.ABIReturn] | None + :value: None + + + The delete result + + + +.. py:class:: AppDeployer(app_manager: algokit_utils.applications.app_manager.AppManager, transaction_sender: algokit_utils.transactions.transaction_sender.AlgorandClientTransactionSender, indexer: algosdk.v2client.indexer.IndexerClient | None = None) + + Manages deployment and deployment metadata of applications + + :param app_manager: The app manager to use + :param transaction_sender: The transaction sender to use + :param indexer: The indexer to use + + :example: + >>> deployer = AppDeployer(app_manager, transaction_sender, indexer) + + + .. py:method:: deploy(deployment: AppDeployParams) -> AppDeployResult + + Idempotently deploy (create if not exists, update if changed) an app against the given name for the given + creator account, including deploy-time TEAL template placeholder substitutions (if specified). + + To understand the architecture decisions behind this functionality please see + https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md + + **Note:** When using the return from this function be sure to check `operation_performed` to get access to + return properties like `transaction`, `confirmation` and `delete_result`. + + **Note:** if there is a breaking state schema change to an existing app (and `on_schema_break` is set to + `'replace'`) the existing app will be deleted and re-created. + + **Note:** if there is an update (different TEAL code) to an existing app (and `on_update` is set to `'replace'`) + the existing app will be deleted and re-created. + + :param deployment: The arguments to control the app deployment + :returns: The result of the deployment + :raises ValueError: If the app spec format is invalid + + :example: + >>> deployer.deploy(AppDeployParams( + ... create_params=AppCreateParams( + ... sender='SENDER_ADDRESS', + ... approval_program='APPROVAL PROGRAM', + ... clear_state_program='CLEAR PROGRAM', + ... schema={ + ... 'global_byte_slices': 0, + ... 'global_ints': 0, + ... 'local_byte_slices': 0, + ... 'local_ints': 0 + ... } + ... ), + ... update_params=AppUpdateParams( + ... sender='SENDER_ADDRESS' + ... ), + ... delete_params=AppDeleteParams( + ... sender='SENDER_ADDRESS' + ... ), + ... metadata=AppDeploymentMetaData( + ... name='my_app', + ... version='2.0', + ... updatable=False, + ... deletable=False + ... ), + ... on_schema_break=OnSchemaBreak.AppendApp, + ... on_update=OnUpdate.AppendApp + ... ) + ... ) + + + + .. py:method:: get_creator_apps_by_name(*, creator_address: str, ignore_cache: bool = False) -> ApplicationLookup + + Returns a lookup of name => app metadata (id, address, ...metadata) for all apps created by the given account + that have an [ARC-2](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md) `AppDeployNote` as + the transaction note of the app creation transaction. + + This function caches the result for the given creator account so that subsequent calls won't require an indexer + lookup. + + If the `AppManager` instance wasn't created with an indexer client, this function will throw an error. + + :param creator_address: The address of the account that is the creator of the apps you want to search for + :param ignore_cache: Whether or not to ignore the cache and force a lookup, default: use the cache + :returns: A name-based lookup of the app metadata + :raises ValueError: If the app spec format is invalid + :example: + >>> result = await deployer.get_creator_apps_by_name(creator) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_factory/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_factory/index.rst.txt new file mode 100644 index 00000000..84c46f07 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_factory/index.rst.txt @@ -0,0 +1,429 @@ +algokit_utils.applications.app_factory +====================================== + +.. py:module:: algokit_utils.applications.app_factory + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.app_factory.AppFactoryParams + algokit_utils.applications.app_factory.AppFactoryCreateParams + algokit_utils.applications.app_factory.AppFactoryCreateMethodCallParams + algokit_utils.applications.app_factory.AppFactoryCreateMethodCallResult + algokit_utils.applications.app_factory.SendAppFactoryTransactionResult + algokit_utils.applications.app_factory.SendAppUpdateFactoryTransactionResult + algokit_utils.applications.app_factory.SendAppCreateFactoryTransactionResult + algokit_utils.applications.app_factory.AppFactoryDeployResult + algokit_utils.applications.app_factory.AppFactory + + +Module Contents +--------------- + +.. py:class:: AppFactoryParams + + .. py:attribute:: algorand + :type: algokit_utils.algorand.AlgorandClient + + + .. py:attribute:: app_spec + :type: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils._legacy_v2.application_specification.ApplicationSpecification | str + + + .. py:attribute:: app_name + :type: str | None + :value: None + + + + .. py:attribute:: default_sender + :type: str | None + :value: None + + + + .. py:attribute:: default_signer + :type: algosdk.atomic_transaction_composer.TransactionSigner | None + :value: None + + + + .. py:attribute:: version + :type: str | None + :value: None + + + + .. py:attribute:: compilation_params + :type: algokit_utils.applications.app_client.AppClientCompilationParams | None + :value: None + + + +.. py:class:: AppFactoryCreateParams + + Bases: :py:obj:`algokit_utils.applications.app_client.AppClientBareCallCreateParams` + + + Parameters for creating application with bare call. + + + .. py:attribute:: on_complete + :type: algokit_utils.applications.app_client.CreateOnComplete | None + :value: None + + + Optional on complete action + + + +.. py:class:: AppFactoryCreateMethodCallParams + + Bases: :py:obj:`algokit_utils.applications.app_client.AppClientMethodCallCreateParams` + + + Parameters for creating application with method call + + +.. py:class:: AppFactoryCreateMethodCallResult + + Bases: :py:obj:`algokit_utils.transactions.transaction_sender.SendSingleTransactionResult`, :py:obj:`Generic`\ [\ :py:obj:`ABIReturnT`\ ] + + + Base class for transaction results. + + Represents the result of sending a single transaction. + + + .. py:attribute:: app_id + :type: int + + + .. py:attribute:: app_address + :type: str + + + .. py:attribute:: compiled_approval + :type: Any | None + :value: None + + + + .. py:attribute:: compiled_clear + :type: Any | None + :value: None + + + + .. py:attribute:: abi_return + :type: ABIReturnT | None + :value: None + + + +.. py:class:: SendAppFactoryTransactionResult + + Bases: :py:obj:`algokit_utils.transactions.transaction_sender.SendAppTransactionResult`\ [\ :py:obj:`algokit_utils.applications.abi.Arc56ReturnValueType`\ ] + + + Result of an application transaction. + + Contains the ABI return value if applicable. + + +.. py:class:: SendAppUpdateFactoryTransactionResult + + Bases: :py:obj:`algokit_utils.transactions.transaction_sender.SendAppUpdateTransactionResult`\ [\ :py:obj:`algokit_utils.applications.abi.Arc56ReturnValueType`\ ] + + + Result of updating an application. + + Contains the compiled approval and clear programs. + + +.. py:class:: SendAppCreateFactoryTransactionResult + + Bases: :py:obj:`algokit_utils.transactions.transaction_sender.SendAppCreateTransactionResult`\ [\ :py:obj:`algokit_utils.applications.abi.Arc56ReturnValueType`\ ] + + + Result of creating a new application. + + Contains the app ID and address of the newly created application. + + +.. py:class:: AppFactoryDeployResult + + Result from deploying an application via AppFactory + + + .. py:attribute:: app + :type: algokit_utils.applications.app_deployer.ApplicationMetaData + + The application metadata + + + + .. py:attribute:: operation_performed + :type: algokit_utils.applications.app_deployer.OperationPerformed + + The operation performed + + + + .. py:attribute:: create_result + :type: SendAppCreateFactoryTransactionResult | None + :value: None + + + The create result + + + + .. py:attribute:: update_result + :type: SendAppUpdateFactoryTransactionResult | None + :value: None + + + The update result + + + + .. py:attribute:: delete_result + :type: SendAppFactoryTransactionResult | None + :value: None + + + The delete result + + + + .. py:method:: from_deploy_result(response: algokit_utils.applications.app_deployer.AppDeployResult, deploy_params: algokit_utils.applications.app_deployer.AppDeployParams, app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract, app_compilation_data: algokit_utils.applications.app_client.AppClientCompilationResult | None = None) -> typing_extensions.Self + :classmethod: + + + Construct an AppFactoryDeployResult from a deployment result. + + :param response: The deployment response. + :param deploy_params: The deployment parameters. + :param app_spec: The application specification. + :param app_compilation_data: Optional app compilation data. + :return: An instance of AppFactoryDeployResult. + + + +.. py:class:: AppFactory(params: AppFactoryParams) + + ARC-56/ARC-32 app factory that, for a given app spec, allows you to create + and deploy one or more app instances and to create one or more app clients + to interact with those (or other) app instances. + + :param params: The parameters for the factory + + :example: + >>> factory = AppFactory(AppFactoryParams( + >>> algorand=AlgorandClient.mainnet(), + >>> app_spec=app_spec, + >>> ) + >>> ) + + + .. py:property:: app_name + :type: str + + + The name of the app + + + + .. py:property:: app_spec + :type: algokit_utils.applications.app_spec.arc56.Arc56Contract + + + The app spec + + + + .. py:property:: algorand + :type: algokit_utils.algorand.AlgorandClient + + + The algorand client + + + + .. py:property:: params + :type: _MethodParamsBuilder + + + Get parameters to create transactions (create and deploy related calls) for the current app. + + A good mental model for this is that these parameters represent a deferred transaction creation. + + :example: Create a transaction in the future using Algorand Client + >>> create_app_params = app_factory.params.create( + ... AppFactoryCreateMethodCallParams( + ... method='create_method', + ... args=[123, 'hello'] + ... ) + ... ) + >>> # ... + >>> algorand.send.app_create_method_call(create_app_params) + + :example: Define a nested transaction as an ABI argument + >>> create_app_params = appFactory.params.create( + ... AppFactoryCreateMethodCallParams( + ... method='create_method', + ... args=[123, 'hello'] + ... ) + ... ) + >>> app_client.send.call( + ... AppClientMethodCallParams( + ... method='my_method', + ... args=[create_app_params] + ... ) + ... ) + + + + .. py:property:: send + :type: _TransactionSender + + + Get the transaction sender. + + :return: The _TransactionSender instance. + + + + .. py:property:: create_transaction + :type: _TransactionCreator + + + Get the transaction creator. + + :return: The _TransactionCreator instance. + + + + .. py:method:: deploy(*, on_update: algokit_utils.applications.app_deployer.OnUpdate | None = None, on_schema_break: algokit_utils.applications.app_deployer.OnSchemaBreak | None = None, create_params: algokit_utils.applications.app_client.AppClientMethodCallCreateParams | algokit_utils.applications.app_client.AppClientBareCallCreateParams | None = None, update_params: algokit_utils.applications.app_client.AppClientMethodCallParams | algokit_utils.applications.app_client.AppClientBareCallParams | None = None, delete_params: algokit_utils.applications.app_client.AppClientMethodCallParams | algokit_utils.applications.app_client.AppClientBareCallParams | None = None, existing_deployments: algokit_utils.applications.app_deployer.ApplicationLookup | None = None, ignore_cache: bool = False, app_name: str | None = None, send_params: algokit_utils.models.transaction.SendParams | None = None, compilation_params: algokit_utils.applications.app_client.AppClientCompilationParams | None = None) -> tuple[algokit_utils.applications.app_client.AppClient, AppFactoryDeployResult] + + Idempotently deploy (create if not exists, update if changed) an app against the given name for the given + creator account, including deploy-time TEAL template placeholder substitutions (if specified). + + **Note:** When using the return from this function be sure to check `operationPerformed` to get access to + various return properties like `transaction`, `confirmation` and `deleteResult`. + + **Note:** if there is a breaking state schema change to an existing app (and `onSchemaBreak` is set to + `'replace'`) the existing app will be deleted and re-created. + + **Note:** if there is an update (different TEAL code) to an existing app (and `onUpdate` is set to + `'replace'`) the existing app will be deleted and re-created. + + :param on_update: The action to take if there is an update to the app + :param on_schema_break: The action to take if there is a breaking state schema change to the app + :param create_params: The arguments to create the app + :param update_params: The arguments to update the app + :param delete_params: The arguments to delete the app + :param existing_deployments: The existing deployments to use + :param ignore_cache: Whether to ignore the cache + :param app_name: The name of the app + :param send_params: The parameters for the send call + :param compilation_params: The parameters for the compilation + :returns: The app client and the result of the deployment + + :example: + >>> app_client, result = factory.deploy({ + >>> create_params=AppClientMethodCallCreateParams( + >>> sender='SENDER_ADDRESS', + >>> approval_program='APPROVAL PROGRAM', + >>> clear_state_program='CLEAR PROGRAM', + >>> schema={ + >>> "global_byte_slices": 0, + >>> "global_ints": 0, + >>> "local_byte_slices": 0, + >>> "local_ints": 0 + >>> } + >>> ), + >>> update_params=AppClientMethodCallParams( + >>> sender='SENDER_ADDRESS' + >>> ), + >>> delete_params=AppClientMethodCallParams( + >>> sender='SENDER_ADDRESS' + >>> ), + >>> compilation_params=AppClientCompilationParams( + >>> updatable=False, + >>> deletable=False + >>> ), + >>> app_name='my_app', + >>> on_schema_break=OnSchemaBreak.AppendApp, + >>> on_update=OnUpdate.AppendApp + >>> }) + + + + .. py:method:: get_app_client_by_id(app_id: int, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> algokit_utils.applications.app_client.AppClient + + Returns a new `AppClient` client for an app instance of the given ID. + + :param app_id: The id of the app + :param app_name: The name of the app + :param default_sender: The default sender address + :param default_signer: The default signer + :param approval_source_map: The approval source map + :param clear_source_map: The clear source map + :return AppClient: The app client + + :example: + >>> app_client = factory.get_app_client_by_id(app_id=123) + + + + .. py:method:: get_app_client_by_creator_and_name(creator_address: str, app_name: str, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, ignore_cache: bool | None = None, app_lookup_cache: algokit_utils.applications.app_deployer.ApplicationLookup | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> algokit_utils.applications.app_client.AppClient + + Returns a new `AppClient` client, resolving the app by creator address and name + using AlgoKit app deployment semantics (i.e. looking for the app creation transaction note). + + :param creator_address: The creator address + :param app_name: The name of the app + :param default_sender: The default sender address + :param default_signer: The default signer + :param ignore_cache: Whether to ignore the cache and force a lookup + :param app_lookup_cache: Optional cache of existing app deployments to use instead of querying the indexer + :param approval_source_map: Optional source map for the approval program + :param clear_source_map: Optional source map for the clear state program + :return: An AppClient instance configured for the resolved application + + :example: + >>> app_client = factory.get_app_client_by_creator_and_name( + ... creator_address='SENDER_ADDRESS', + ... app_name='my_app' + ... ) + + + + .. py:method:: export_source_maps() -> algokit_utils.models.application.AppSourceMaps + + + .. py:method:: import_source_maps(source_maps: algokit_utils.models.application.AppSourceMaps) -> None + + Import the provided source maps into the factory. + + :param source_maps: An AppSourceMaps instance containing the approval and clear source maps. + + + + .. py:method:: compile(compilation_params: algokit_utils.applications.app_client.AppClientCompilationParams | None = None) -> algokit_utils.applications.app_client.AppClientCompilationResult + + Compile the app's TEAL code. + + :param compilation_params: The compilation parameters + :return AppClientCompilationResult: The compilation result + + :example: + >>> compilation_result = factory.compile() + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_manager/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_manager/index.rst.txt new file mode 100644 index 00000000..f9bade83 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_manager/index.rst.txt @@ -0,0 +1,337 @@ +algokit_utils.applications.app_manager +====================================== + +.. py:module:: algokit_utils.applications.app_manager + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.applications.app_manager.UPDATABLE_TEMPLATE_NAME + algokit_utils.applications.app_manager.DELETABLE_TEMPLATE_NAME + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.app_manager.AppManager + + +Module Contents +--------------- + +.. py:data:: UPDATABLE_TEMPLATE_NAME + :value: 'TMPL_UPDATABLE' + + + The name of the TEAL template variable for deploy-time immutability control. + + +.. py:data:: DELETABLE_TEMPLATE_NAME + :value: 'TMPL_DELETABLE' + + + The name of the TEAL template variable for deploy-time permanence control. + + +.. py:class:: AppManager(algod_client: algosdk.v2client.algod.AlgodClient) + + A manager class for interacting with Algorand applications. + + Provides functionality for compiling TEAL code, managing application state, + and interacting with application boxes. + + :param algod_client: The Algorand client instance to use for interacting with the network + + :example: + >>> app_manager = AppManager(algod_client) + + + .. py:method:: compile_teal(teal_code: str) -> algokit_utils.models.application.CompiledTeal + + Compile TEAL source code. + + :param teal_code: The TEAL source code to compile + :return: The compiled TEAL code and associated metadata + + + + .. py:method:: compile_teal_template(teal_template_code: str, template_params: algokit_utils.models.state.TealTemplateParams | None = None, deployment_metadata: collections.abc.Mapping[str, bool | None] | None = None) -> algokit_utils.models.application.CompiledTeal + + Compile a TEAL template with parameters. + + :param teal_template_code: The TEAL template code to compile + :param template_params: Parameters to substitute in the template + :param deployment_metadata: Deployment control parameters + :return: The compiled TEAL code and associated metadata + + :example: + >>> app_manager = AppManager(algod_client) + >>> teal_template_code = + ... # This is a TEAL template + ... # It can contain template variables like {TMPL_UPDATABLE} and {TMPL_DELETABLE} + ... + >>> compiled_teal = app_manager.compile_teal_template(teal_template_code) + + + + .. py:method:: get_compilation_result(teal_code: str) -> algokit_utils.models.application.CompiledTeal | None + + Get cached compilation result for TEAL code if available. + + :param teal_code: The TEAL source code + :return: The cached compilation result if available, None otherwise + + :example: + >>> app_manager = AppManager(algod_client) + >>> teal_code = "RETURN 1" + >>> compiled_teal = app_manager.compile_teal(teal_code) + >>> compilation_result = app_manager.get_compilation_result(teal_code) + + + + .. py:method:: get_by_id(app_id: int) -> algokit_utils.models.application.AppInformation + + Get information about an application by ID. + + :param app_id: The application ID + :return: Information about the application + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 1234567890 + >>> app_info = app_manager.get_by_id(app_id) + + + + .. py:method:: get_global_state(app_id: int) -> dict[str, algokit_utils.models.application.AppState] + + Get the global state of an application. + + :param app_id: The application ID + :return: The application's global state + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> global_state = app_manager.get_global_state(app_id) + + + + .. py:method:: get_local_state(app_id: int, address: str) -> dict[str, algokit_utils.models.application.AppState] + + Get the local state for an account in an application. + + :param app_id: The application ID + :param address: The account address + :return: The account's local state for the application + :raises ValueError: If local state is not found + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> address = "SENDER_ADDRESS" + >>> local_state = app_manager.get_local_state(app_id, address) + + + + .. py:method:: get_box_names(app_id: int) -> list[algokit_utils.models.state.BoxName] + + Get names of all boxes for an application. + + :param app_id: The application ID + :return: List of box names + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> box_names = app_manager.get_box_names(app_id) + + + + .. py:method:: get_box_value(app_id: int, box_name: algokit_utils.models.state.BoxIdentifier) -> bytes + + Get the value stored in a box. + + :param app_id: The application ID + :param box_name: The box identifier + :return: The box value as bytes + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> box_name = "BOX_NAME" + >>> box_value = app_manager.get_box_value(app_id, box_name) + + + + .. py:method:: get_box_values(app_id: int, box_names: list[algokit_utils.models.state.BoxIdentifier]) -> list[bytes] + + Get values for multiple boxes. + + :param app_id: The application ID + :param box_names: List of box identifiers + :return: List of box values as bytes + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> box_names = ["BOX_NAME_1", "BOX_NAME_2"] + >>> box_values = app_manager.get_box_values(app_id, box_names) + + + + .. py:method:: get_box_value_from_abi_type(app_id: int, box_name: algokit_utils.models.state.BoxIdentifier, abi_type: algokit_utils.applications.abi.ABIType) -> algokit_utils.applications.abi.ABIValue + + Get and decode a box value using an ABI type. + + :param app_id: The application ID + :param box_name: The box identifier + :param abi_type: The ABI type to decode with + :return: The decoded box value + :raises ValueError: If decoding fails + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> box_name = "BOX_NAME" + >>> abi_type = ABIType.UINT + >>> box_value = app_manager.get_box_value_from_abi_type(app_id, box_name, abi_type) + + + + .. py:method:: get_box_values_from_abi_type(app_id: int, box_names: list[algokit_utils.models.state.BoxIdentifier], abi_type: algokit_utils.applications.abi.ABIType) -> list[algokit_utils.applications.abi.ABIValue] + + Get and decode multiple box values using an ABI type. + + :param app_id: The application ID + :param box_names: List of box identifiers + :param abi_type: The ABI type to decode with + :return: List of decoded box values + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> box_names = ["BOX_NAME_1", "BOX_NAME_2"] + >>> abi_type = ABIType.UINT + >>> box_values = app_manager.get_box_values_from_abi_type(app_id, box_names, abi_type) + + + + .. py:method:: get_box_reference(box_id: algokit_utils.models.state.BoxIdentifier | algokit_utils.models.state.BoxReference) -> tuple[int, bytes] + :staticmethod: + + + Get standardized box reference from various identifier types. + + :param box_id: The box identifier + :return: Tuple of (app_id, box_name_bytes) + :raises ValueError: If box identifier type is invalid + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> box_name = "BOX_NAME" + >>> box_reference = app_manager.get_box_reference(box_name) + + + + .. py:method:: get_abi_return(confirmation: algosdk.v2client.algod.AlgodResponseType, method: algosdk.abi.Method | None = None) -> algokit_utils.applications.abi.ABIReturn | None + :staticmethod: + + + Get the ABI return value from a transaction confirmation. + + :param confirmation: The transaction confirmation + :param method: The ABI method + :return: The parsed ABI return value, or None if not available + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> method = "METHOD_NAME" + >>> confirmation = algod_client.pending_transaction_info(tx_id) + >>> abi_return = app_manager.get_abi_return(confirmation, method) + + + + .. py:method:: decode_app_state(state: list[dict[str, Any]]) -> dict[str, algokit_utils.models.application.AppState] + :staticmethod: + + + Decode application state from raw format. + + :param state: The raw application state + :return: Decoded application state + :raises ValueError: If unknown state data type is encountered + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> state = app_manager.get_global_state(app_id) + >>> decoded_state = app_manager.decode_app_state(state) + + + + .. py:method:: replace_template_variables(program: str, template_values: algokit_utils.models.state.TealTemplateParams) -> str + :staticmethod: + + + Replace template variables in TEAL code. + + :param program: The TEAL program code + :param template_values: Template variable values to substitute + :return: TEAL code with substituted values + :raises ValueError: If template value type is unexpected + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> program = "RETURN 1" + >>> template_values = {"TMPL_UPDATABLE": True, "TMPL_DELETABLE": True} + >>> updated_program = app_manager.replace_template_variables(program, template_values) + + + + .. py:method:: replace_teal_template_deploy_time_control_params(teal_template_code: str, params: collections.abc.Mapping[str, bool | None]) -> str + :staticmethod: + + + Replace deploy-time control parameters in TEAL template. + + :param teal_template_code: The TEAL template code + :param params: The deploy-time control parameters + :return: TEAL code with substituted control parameters + :raises ValueError: If template variables not found in code + + :example: + >>> app_manager = AppManager(algod_client) + >>> app_id = 123 + >>> teal_template_code = "RETURN 1" + >>> params = {"TMPL_UPDATABLE": True, "TMPL_DELETABLE": True} + >>> updated_teal_code = app_manager.replace_teal_template_deploy_time_control_params( + teal_template_code, params + ) + + + + .. py:method:: strip_teal_comments(teal_code: str) -> str + :staticmethod: + + + Strip comments from TEAL code. + + :param teal_code: The TEAL code to strip comments from + :return: The TEAL code with comments stripped + + :example: + >>> app_manager = AppManager(algod_client) + >>> teal_code = "RETURN 1" + >>> stripped_teal_code = app_manager.strip_teal_comments(teal_code) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/arc32/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/arc32/index.rst.txt new file mode 100644 index 00000000..ccca2384 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/arc32/index.rst.txt @@ -0,0 +1,244 @@ +algokit_utils.applications.app_spec.arc32 +========================================= + +.. py:module:: algokit_utils.applications.app_spec.arc32 + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.applications.app_spec.arc32.AppSpecStateDict + algokit_utils.applications.app_spec.arc32.OnCompleteActionName + algokit_utils.applications.app_spec.arc32.MethodConfigDict + algokit_utils.applications.app_spec.arc32.DefaultArgumentType + algokit_utils.applications.app_spec.arc32.StateDict + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.app_spec.arc32.CallConfig + algokit_utils.applications.app_spec.arc32.StructArgDict + algokit_utils.applications.app_spec.arc32.DefaultArgumentDict + algokit_utils.applications.app_spec.arc32.MethodHints + algokit_utils.applications.app_spec.arc32.Arc32Contract + + +Module Contents +--------------- + +.. py:data:: AppSpecStateDict + :type: TypeAlias + :value: dict[str, dict[str, dict]] + + + Type defining Application Specification state entries + + +.. py:class:: CallConfig + + Bases: :py:obj:`enum.IntFlag` + + + Describes the type of calls a method can be used for based on {py:class}`algosdk.transaction.OnComplete` type + + + .. py:attribute:: NEVER + :value: 0 + + + Never handle the specified on completion type + + + + .. py:attribute:: CALL + :value: 1 + + + Only handle the specified on completion type for application calls + + + + .. py:attribute:: CREATE + :value: 2 + + + Only handle the specified on completion type for application create calls + + + + .. py:attribute:: ALL + :value: 3 + + + Handle the specified on completion type for both create and normal application calls + + + +.. py:class:: StructArgDict + + Bases: :py:obj:`TypedDict` + + + dict() -> new empty dictionary + dict(mapping) -> new dictionary initialized from a mapping object's + (key, value) pairs + dict(iterable) -> new dictionary initialized as if via: + d = {} + for k, v in iterable: + d[k] = v + dict(**kwargs) -> new dictionary initialized with the name=value pairs + in the keyword argument list. For example: dict(one=1, two=2) + + + .. py:attribute:: name + :type: str + + + .. py:attribute:: elements + :type: list[list[str]] + + +.. py:data:: OnCompleteActionName + :type: TypeAlias + :value: Literal['no_op', 'opt_in', 'close_out', 'clear_state', 'update_application', 'delete_application'] + + + String literals representing on completion transaction types + + +.. py:data:: MethodConfigDict + :type: TypeAlias + :value: dict[OnCompleteActionName, CallConfig] + + + Dictionary of `dict[OnCompletionActionName, CallConfig]` representing allowed actions for each on completion type + + +.. py:data:: DefaultArgumentType + :type: TypeAlias + :value: Literal['abi-method', 'local-state', 'global-state', 'constant'] + + + Literal values describing the types of default argument sources + + +.. py:class:: DefaultArgumentDict + + Bases: :py:obj:`TypedDict` + + + DefaultArgument is a container for any arguments that may + be resolved prior to calling some target method + + + .. py:attribute:: source + :type: DefaultArgumentType + + + .. py:attribute:: data + :type: int | str | bytes | algosdk.abi.method.MethodDict + + +.. py:data:: StateDict + +.. py:class:: MethodHints + + MethodHints provides hints to the caller about how to call the method + + + .. py:attribute:: read_only + :type: bool + :value: False + + + + .. py:attribute:: structs + :type: dict[str, StructArgDict] + + + .. py:attribute:: default_arguments + :type: dict[str, DefaultArgumentDict] + + + .. py:attribute:: call_config + :type: MethodConfigDict + + + .. py:method:: empty() -> bool + + + .. py:method:: dictify() -> dict[str, Any] + + + .. py:method:: undictify(data: dict[str, Any]) -> MethodHints + :staticmethod: + + + +.. py:class:: Arc32Contract + + ARC-0032 application specification + + See + + + .. py:attribute:: approval_program + :type: str + + + .. py:attribute:: clear_program + :type: str + + + .. py:attribute:: contract + :type: algosdk.abi.Contract + + + .. py:attribute:: hints + :type: dict[str, MethodHints] + + + .. py:attribute:: schema + :type: StateDict + + + .. py:attribute:: global_state_schema + :type: algosdk.transaction.StateSchema + + + .. py:attribute:: local_state_schema + :type: algosdk.transaction.StateSchema + + + .. py:attribute:: bare_call_config + :type: MethodConfigDict + + + .. py:method:: dictify() -> dict + + + .. py:method:: to_json(indent: int | None = None) -> str + + + .. py:method:: from_json(application_spec: str) -> Arc32Contract + :staticmethod: + + + + .. py:method:: export(directory: pathlib.Path | str | None = None) -> None + + Write out the artifacts generated by the application to disk. + + Writes the approval program, clear program, contract specification and application specification + to files in the specified directory. + + :param directory: Path to the directory where the artifacts should be written. If not specified, + uses the current working directory + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/arc56/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/arc56/index.rst.txt new file mode 100644 index 00000000..b386bc23 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/arc56/index.rst.txt @@ -0,0 +1,1304 @@ +algokit_utils.applications.app_spec.arc56 +========================================= + +.. py:module:: algokit_utils.applications.app_spec.arc56 + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.app_spec.arc56.StructField + algokit_utils.applications.app_spec.arc56.CallEnum + algokit_utils.applications.app_spec.arc56.CreateEnum + algokit_utils.applications.app_spec.arc56.BareActions + algokit_utils.applications.app_spec.arc56.ByteCode + algokit_utils.applications.app_spec.arc56.Compiler + algokit_utils.applications.app_spec.arc56.CompilerVersion + algokit_utils.applications.app_spec.arc56.CompilerInfo + algokit_utils.applications.app_spec.arc56.Network + algokit_utils.applications.app_spec.arc56.ScratchVariables + algokit_utils.applications.app_spec.arc56.Source + algokit_utils.applications.app_spec.arc56.Global + algokit_utils.applications.app_spec.arc56.Local + algokit_utils.applications.app_spec.arc56.Schema + algokit_utils.applications.app_spec.arc56.TemplateVariables + algokit_utils.applications.app_spec.arc56.EventArg + algokit_utils.applications.app_spec.arc56.Event + algokit_utils.applications.app_spec.arc56.Actions + algokit_utils.applications.app_spec.arc56.DefaultValue + algokit_utils.applications.app_spec.arc56.MethodArg + algokit_utils.applications.app_spec.arc56.Boxes + algokit_utils.applications.app_spec.arc56.Recommendations + algokit_utils.applications.app_spec.arc56.Returns + algokit_utils.applications.app_spec.arc56.Method + algokit_utils.applications.app_spec.arc56.PcOffsetMethod + algokit_utils.applications.app_spec.arc56.SourceInfo + algokit_utils.applications.app_spec.arc56.StorageKey + algokit_utils.applications.app_spec.arc56.StorageMap + algokit_utils.applications.app_spec.arc56.Keys + algokit_utils.applications.app_spec.arc56.Maps + algokit_utils.applications.app_spec.arc56.State + algokit_utils.applications.app_spec.arc56.ProgramSourceInfo + algokit_utils.applications.app_spec.arc56.SourceInfoModel + algokit_utils.applications.app_spec.arc56.Arc56Contract + + +Module Contents +--------------- + +.. py:class:: StructField + + Represents a field in a struct type. + + + .. py:attribute:: name + :type: str + + The name of the struct field + + + + .. py:attribute:: type + :type: list[StructField] | str + + The type of the struct field, either a string or list of StructFields + + + + .. py:method:: from_dict(data: dict[str, Any]) -> StructField + :staticmethod: + + + +.. py:class:: CallEnum + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Enum representing different call types for application transactions. + + + .. py:attribute:: CLEAR_STATE + :value: 'ClearState' + + + + .. py:attribute:: CLOSE_OUT + :value: 'CloseOut' + + + + .. py:attribute:: DELETE_APPLICATION + :value: 'DeleteApplication' + + + + .. py:attribute:: NO_OP + :value: 'NoOp' + + + + .. py:attribute:: OPT_IN + :value: 'OptIn' + + + + .. py:attribute:: UPDATE_APPLICATION + :value: 'UpdateApplication' + + + +.. py:class:: CreateEnum + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Enum representing different create types for application transactions. + + + .. py:attribute:: DELETE_APPLICATION + :value: 'DeleteApplication' + + + + .. py:attribute:: NO_OP + :value: 'NoOp' + + + + .. py:attribute:: OPT_IN + :value: 'OptIn' + + + +.. py:class:: BareActions + + Represents bare call and create actions for an application. + + + .. py:attribute:: call + :type: list[CallEnum] + + The list of allowed call actions + + + + .. py:attribute:: create + :type: list[CreateEnum] + + The list of allowed create actions + + + + .. py:method:: from_dict(data: dict[str, Any]) -> BareActions + :staticmethod: + + + +.. py:class:: ByteCode + + Represents the approval and clear program bytecode. + + + .. py:attribute:: approval + :type: str + + The base64 encoded approval program bytecode + + + + .. py:attribute:: clear + :type: str + + The base64 encoded clear program bytecode + + + + .. py:method:: from_dict(data: dict[str, Any]) -> ByteCode + :staticmethod: + + + +.. py:class:: Compiler + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + Enum representing different compiler types. + + + .. py:attribute:: ALGOD + :value: 'algod' + + + + .. py:attribute:: PUYA + :value: 'puya' + + + +.. py:class:: CompilerVersion + + Represents compiler version information. + + + .. py:attribute:: commit_hash + :type: str | None + :value: None + + + The git commit hash of the compiler + + + + .. py:attribute:: major + :type: int | None + :value: None + + + The major version number + + + + .. py:attribute:: minor + :type: int | None + :value: None + + + The minor version number + + + + .. py:attribute:: patch + :type: int | None + :value: None + + + The patch version number + + + + .. py:method:: from_dict(data: dict[str, Any]) -> CompilerVersion + :staticmethod: + + + +.. py:class:: CompilerInfo + + Information about the compiler used. + + + .. py:attribute:: compiler + :type: Compiler + + The type of compiler used + + + + .. py:attribute:: compiler_version + :type: CompilerVersion + + Version information for the compiler + + + + .. py:method:: from_dict(data: dict[str, Any]) -> CompilerInfo + :staticmethod: + + + +.. py:class:: Network + + Network-specific application information. + + + .. py:attribute:: app_id + :type: int + + The application ID on the network + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Network + :staticmethod: + + + +.. py:class:: ScratchVariables + + Information about scratch space variables. + + + .. py:attribute:: slot + :type: int + + The scratch slot number + + + + .. py:attribute:: type + :type: str + + The type of the scratch variable + + + + .. py:method:: from_dict(data: dict[str, Any]) -> ScratchVariables + :staticmethod: + + + +.. py:class:: Source + + Source code for approval and clear programs. + + + .. py:attribute:: approval + :type: str + + The base64 encoded approval program source + + + + .. py:attribute:: clear + :type: str + + The base64 encoded clear program source + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Source + :staticmethod: + + + + .. py:method:: get_decoded_approval() -> str + + Get decoded approval program source. + + :return: Decoded approval program source code + + + + .. py:method:: get_decoded_clear() -> str + + Get decoded clear program source. + + :return: Decoded clear program source code + + + +.. py:class:: Global + + Global state schema. + + + .. py:attribute:: bytes + :type: int + + The number of byte slices in global state + + + + .. py:attribute:: ints + :type: int + + The number of integers in global state + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Global + :staticmethod: + + + +.. py:class:: Local + + Local state schema. + + + .. py:attribute:: bytes + :type: int + + The number of byte slices in local state + + + + .. py:attribute:: ints + :type: int + + The number of integers in local state + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Local + :staticmethod: + + + +.. py:class:: Schema + + Application state schema. + + + .. py:attribute:: global_state + :type: Global + + The global state schema + + + + .. py:attribute:: local_state + :type: Local + + The local state schema + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Schema + :staticmethod: + + + +.. py:class:: TemplateVariables + + Template variable information. + + + .. py:attribute:: type + :type: str + + The type of the template variable + + + + .. py:attribute:: value + :type: str | None + :value: None + + + The optional value of the template variable + + + + .. py:method:: from_dict(data: dict[str, Any]) -> TemplateVariables + :staticmethod: + + + +.. py:class:: EventArg + + Event argument information. + + + .. py:attribute:: type + :type: str + + The type of the event argument + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description of the argument + + + + .. py:attribute:: name + :type: str | None + :value: None + + + The optional name of the argument + + + + .. py:attribute:: struct + :type: str | None + :value: None + + + The optional struct type name + + + + .. py:method:: from_dict(data: dict[str, Any]) -> EventArg + :staticmethod: + + + +.. py:class:: Event + + Event information. + + + .. py:attribute:: args + :type: list[EventArg] + + The list of event arguments + + + + .. py:attribute:: name + :type: str + + The name of the event + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description of the event + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Event + :staticmethod: + + + +.. py:class:: Actions + + Method actions information. + + + .. py:attribute:: call + :type: list[CallEnum] | None + :value: None + + + The optional list of allowed call actions + + + + .. py:attribute:: create + :type: list[CreateEnum] | None + :value: None + + + The optional list of allowed create actions + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Actions + :staticmethod: + + + +.. py:class:: DefaultValue + + Default value information for method arguments. + + + .. py:attribute:: data + :type: str + + The default value data + + + + .. py:attribute:: source + :type: Literal['box', 'global', 'local', 'literal', 'method'] + + The source of the default value + + + + .. py:attribute:: type + :type: str | None + :value: None + + + The optional type of the default value + + + + .. py:method:: from_dict(data: dict[str, Any]) -> DefaultValue + :staticmethod: + + + +.. py:class:: MethodArg + + Method argument information. + + + .. py:attribute:: type + :type: str + + The type of the argument + + + + .. py:attribute:: default_value + :type: DefaultValue | None + :value: None + + + The optional default value + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description + + + + .. py:attribute:: name + :type: str | None + :value: None + + + The optional name + + + + .. py:attribute:: struct + :type: str | None + :value: None + + + The optional struct type name + + + + .. py:method:: from_dict(data: dict[str, Any]) -> MethodArg + :staticmethod: + + + +.. py:class:: Boxes + + Box storage requirements. + + + .. py:attribute:: key + :type: str + + The box key + + + + .. py:attribute:: read_bytes + :type: int + + The number of bytes to read + + + + .. py:attribute:: write_bytes + :type: int + + The number of bytes to write + + + + .. py:attribute:: app + :type: int | None + :value: None + + + The optional application ID + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Boxes + :staticmethod: + + + +.. py:class:: Recommendations + + Method execution recommendations. + + + .. py:attribute:: accounts + :type: list[str] | None + :value: None + + + The optional list of accounts + + + + .. py:attribute:: apps + :type: list[int] | None + :value: None + + + The optional list of applications + + + + .. py:attribute:: assets + :type: list[int] | None + :value: None + + + The optional list of assets + + + + .. py:attribute:: boxes + :type: Boxes | None + :value: None + + + The optional box storage requirements + + + + .. py:attribute:: inner_transaction_count + :type: int | None + :value: None + + + The optional inner transaction count + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Recommendations + :staticmethod: + + + +.. py:class:: Returns + + Method return information. + + + .. py:attribute:: type + :type: str + + The type of the return value + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description + + + + .. py:attribute:: struct + :type: str | None + :value: None + + + The optional struct type name + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Returns + :staticmethod: + + + +.. py:class:: Method + + Method information. + + + .. py:attribute:: actions + :type: Actions + + The allowed actions + + + + .. py:attribute:: args + :type: list[MethodArg] + + The method arguments + + + + .. py:attribute:: name + :type: str + + The method name + + + + .. py:attribute:: returns + :type: Returns + + The return information + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description + + + + .. py:attribute:: events + :type: list[Event] | None + :value: None + + + The optional list of events + + + + .. py:attribute:: readonly + :type: bool | None + :value: None + + + The optional readonly flag + + + + .. py:attribute:: recommendations + :type: Recommendations | None + :value: None + + + The optional execution recommendations + + + + .. py:method:: to_abi_method() -> algosdk.abi.Method + + Convert to ABI method. + + :raises ValueError: If underlying ABI method is not initialized + :return: ABI method + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Method + :staticmethod: + + + +.. py:class:: PcOffsetMethod + + Bases: :py:obj:`str`, :py:obj:`enum.Enum` + + + PC offset method types. + + + .. py:attribute:: CBLOCKS + :value: 'cblocks' + + + + .. py:attribute:: NONE + :value: 'none' + + + +.. py:class:: SourceInfo + + Source code location information. + + + .. py:attribute:: pc + :type: list[int] + + The list of program counter values + + + + .. py:attribute:: error_message + :type: str | None + :value: None + + + The optional error message + + + + .. py:attribute:: source + :type: str | None + :value: None + + + The optional source code + + + + .. py:attribute:: teal + :type: int | None + :value: None + + + The optional TEAL version + + + + .. py:method:: from_dict(data: dict[str, Any]) -> SourceInfo + :staticmethod: + + + +.. py:class:: StorageKey + + Storage key information. + + + .. py:attribute:: key + :type: str + + The storage key + + + + .. py:attribute:: key_type + :type: str + + The type of the key + + + + .. py:attribute:: value_type + :type: str + + The type of the value + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description + + + + .. py:method:: from_dict(data: dict[str, Any]) -> StorageKey + :staticmethod: + + + +.. py:class:: StorageMap + + Storage map information. + + + .. py:attribute:: key_type + :type: str + + The type of the map keys + + + + .. py:attribute:: value_type + :type: str + + The type of the map values + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional description + + + + .. py:attribute:: prefix + :type: str | None + :value: None + + + The optional key prefix + + + + .. py:method:: from_dict(data: dict[str, Any]) -> StorageMap + :staticmethod: + + + +.. py:class:: Keys + + Storage keys for different storage types. + + + .. py:attribute:: box + :type: dict[str, StorageKey] + + The box storage keys + + + + .. py:attribute:: global_state + :type: dict[str, StorageKey] + + The global state storage keys + + + + .. py:attribute:: local_state + :type: dict[str, StorageKey] + + The local state storage keys + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Keys + :staticmethod: + + + +.. py:class:: Maps + + Storage maps for different storage types. + + + .. py:attribute:: box + :type: dict[str, StorageMap] + + The box storage maps + + + + .. py:attribute:: global_state + :type: dict[str, StorageMap] + + The global state storage maps + + + + .. py:attribute:: local_state + :type: dict[str, StorageMap] + + The local state storage maps + + + + .. py:method:: from_dict(data: dict[str, Any]) -> Maps + :staticmethod: + + + +.. py:class:: State + + Application state information. + + + .. py:attribute:: keys + :type: Keys + + The storage keys + + + + .. py:attribute:: maps + :type: Maps + + The storage maps + + + + .. py:attribute:: schema + :type: Schema + + The state schema + + + + .. py:method:: from_dict(data: dict[str, Any]) -> State + :staticmethod: + + + +.. py:class:: ProgramSourceInfo + + Program source information. + + + .. py:attribute:: pc_offset_method + :type: PcOffsetMethod + + The PC offset method + + + + .. py:attribute:: source_info + :type: list[SourceInfo] + + The list of source info entries + + + + .. py:method:: from_dict(data: dict[str, Any]) -> ProgramSourceInfo + :staticmethod: + + + +.. py:class:: SourceInfoModel + + Source information for approval and clear programs. + + + .. py:attribute:: approval + :type: ProgramSourceInfo + + The approval program source info + + + + .. py:attribute:: clear + :type: ProgramSourceInfo + + The clear program source info + + + + .. py:method:: from_dict(data: dict[str, Any]) -> SourceInfoModel + :staticmethod: + + + +.. py:class:: Arc56Contract + + ARC-0056 application specification. + + See https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0056.md + + + .. py:attribute:: arcs + :type: list[int] + + The list of supported ARC version numbers + + + + .. py:attribute:: bare_actions + :type: BareActions + + The bare call and create actions + + + + .. py:attribute:: methods + :type: list[Method] + + The list of contract methods + + + + .. py:attribute:: name + :type: str + + The contract name + + + + .. py:attribute:: state + :type: State + + The contract state information + + + + .. py:attribute:: structs + :type: dict[str, list[StructField]] + + The contract struct definitions + + + + .. py:attribute:: byte_code + :type: ByteCode | None + :value: None + + + The optional bytecode for approval and clear programs + + + + .. py:attribute:: compiler_info + :type: CompilerInfo | None + :value: None + + + The optional compiler information + + + + .. py:attribute:: desc + :type: str | None + :value: None + + + The optional contract description + + + + .. py:attribute:: events + :type: list[Event] | None + :value: None + + + The optional list of contract events + + + + .. py:attribute:: networks + :type: dict[str, Network] | None + :value: None + + + The optional network deployment information + + + + .. py:attribute:: scratch_variables + :type: dict[str, ScratchVariables] | None + :value: None + + + The optional scratch variable information + + + + .. py:attribute:: source + :type: Source | None + :value: None + + + The optional source code + + + + .. py:attribute:: source_info + :type: SourceInfoModel | None + :value: None + + + The optional source code information + + + + .. py:attribute:: template_variables + :type: dict[str, TemplateVariables] | None + :value: None + + + The optional template variable information + + + + .. py:method:: from_dict(application_spec: dict) -> Arc56Contract + :staticmethod: + + + Create Arc56Contract from dictionary. + + :param application_spec: Dictionary containing contract specification + :return: Arc56Contract instance + + + + .. py:method:: from_json(application_spec: str) -> Arc56Contract + :staticmethod: + + + + .. py:method:: from_arc32(arc32_application_spec: str | algokit_utils.applications.app_spec.arc32.Arc32Contract) -> Arc56Contract + :staticmethod: + + + + .. py:method:: get_abi_struct_from_abi_tuple(decoded_tuple: Any, struct_fields: list[StructField], structs: dict[str, list[StructField]]) -> dict[str, Any] + :staticmethod: + + + + .. py:method:: to_json(indent: int | None = None) -> str + + + .. py:method:: dictify() -> dict + + + .. py:method:: get_arc56_method(method_name_or_signature: str) -> Method + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/index.rst.txt new file mode 100644 index 00000000..2aec2e36 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/app_spec/index.rst.txt @@ -0,0 +1,16 @@ +algokit_utils.applications.app_spec +=================================== + +.. py:module:: algokit_utils.applications.app_spec + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/applications/app_spec/arc32/index + /autoapi/algokit_utils/applications/app_spec/arc56/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/enums/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/enums/index.rst.txt new file mode 100644 index 00000000..f69e2fc0 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/enums/index.rst.txt @@ -0,0 +1,131 @@ +algokit_utils.applications.enums +================================ + +.. py:module:: algokit_utils.applications.enums + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.applications.enums.OnSchemaBreak + algokit_utils.applications.enums.OnUpdate + algokit_utils.applications.enums.OperationPerformed + + +Module Contents +--------------- + +.. py:class:: OnSchemaBreak(*args, **kwds) + + Bases: :py:obj:`enum.Enum` + + + Action to take if an Application's schema has breaking changes + + + .. py:attribute:: Fail + :value: 0 + + + Fail the deployment + + + + .. py:attribute:: ReplaceApp + :value: 2 + + + Create a new Application and delete the old Application in a single transaction + + + + .. py:attribute:: AppendApp + :value: 3 + + + Create a new Application + + + +.. py:class:: OnUpdate(*args, **kwds) + + Bases: :py:obj:`enum.Enum` + + + Action to take if an Application has been updated + + + .. py:attribute:: Fail + :value: 0 + + + Fail the deployment + + + + .. py:attribute:: UpdateApp + :value: 1 + + + Update the Application with the new approval and clear programs + + + + .. py:attribute:: ReplaceApp + :value: 2 + + + Create a new Application and delete the old Application in a single transaction + + + + .. py:attribute:: AppendApp + :value: 3 + + + Create a new application + + + +.. py:class:: OperationPerformed(*args, **kwds) + + Bases: :py:obj:`enum.Enum` + + + Describes the actions taken during deployment + + + .. py:attribute:: Nothing + :value: 0 + + + An existing Application was found + + + + .. py:attribute:: Create + :value: 1 + + + No existing Application was found, created a new Application + + + + .. py:attribute:: Update + :value: 2 + + + An existing Application was found, but was out of date, updated to latest version + + + + .. py:attribute:: Replace + :value: 3 + + + An existing Application was found, but was out of date, created a new Application and deleted the original + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/applications/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/applications/index.rst.txt new file mode 100644 index 00000000..102a7e6d --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/applications/index.rst.txt @@ -0,0 +1,21 @@ +algokit_utils.applications +========================== + +.. py:module:: algokit_utils.applications + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/applications/abi/index + /autoapi/algokit_utils/applications/app_client/index + /autoapi/algokit_utils/applications/app_deployer/index + /autoapi/algokit_utils/applications/app_factory/index + /autoapi/algokit_utils/applications/app_manager/index + /autoapi/algokit_utils/applications/app_spec/index + /autoapi/algokit_utils/applications/enums/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/assets/asset_manager/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/assets/asset_manager/index.rst.txt new file mode 100644 index 00000000..65741915 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/assets/asset_manager/index.rst.txt @@ -0,0 +1,310 @@ +algokit_utils.assets.asset_manager +================================== + +.. py:module:: algokit_utils.assets.asset_manager + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.assets.asset_manager.AccountAssetInformation + algokit_utils.assets.asset_manager.AssetInformation + algokit_utils.assets.asset_manager.BulkAssetOptInOutResult + algokit_utils.assets.asset_manager.AssetManager + + +Module Contents +--------------- + +.. py:class:: AccountAssetInformation + + Information about an account's holding of a particular asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: balance + :type: int + + The amount of the asset held by the account + + + + .. py:attribute:: frozen + :type: bool + + Whether the asset is frozen for this account + + + + .. py:attribute:: round + :type: int + + The round this information was retrieved at + + + +.. py:class:: AssetInformation + + Information about an Algorand Standard Asset (ASA). + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: creator + :type: str + + The address of the account that created the asset + + + + .. py:attribute:: total + :type: int + + The total amount of the smallest divisible units that were created of the asset + + + + .. py:attribute:: decimals + :type: int + + The amount of decimal places the asset was created with + + + + .. py:attribute:: default_frozen + :type: bool | None + :value: None + + + Whether the asset was frozen by default for all accounts, defaults to None + + + + .. py:attribute:: manager + :type: str | None + :value: None + + + The address of the optional account that can manage the configuration of the asset and destroy it, + defaults to None + + + + .. py:attribute:: reserve + :type: str | None + :value: None + + + The address of the optional account that holds the reserve (uncirculated supply) units of the asset, + defaults to None + + + + .. py:attribute:: freeze + :type: str | None + :value: None + + + The address of the optional account that can be used to freeze or unfreeze holdings of this asset, + defaults to None + + + + .. py:attribute:: clawback + :type: str | None + :value: None + + + The address of the optional account that can clawback holdings of this asset from any account, + defaults to None + + + + .. py:attribute:: unit_name + :type: str | None + :value: None + + + The optional name of the unit of this asset (e.g. ticker name), defaults to None + + + + .. py:attribute:: unit_name_b64 + :type: bytes | None + :value: None + + + The optional name of the unit of this asset as bytes, defaults to None + + + + .. py:attribute:: asset_name + :type: str | None + :value: None + + + The optional name of the asset, defaults to None + + + + .. py:attribute:: asset_name_b64 + :type: bytes | None + :value: None + + + The optional name of the asset as bytes, defaults to None + + + + .. py:attribute:: url + :type: str | None + :value: None + + + The optional URL where more information about the asset can be retrieved, defaults to None + + + + .. py:attribute:: url_b64 + :type: bytes | None + :value: None + + + The optional URL where more information about the asset can be retrieved as bytes, defaults to None + + + + .. py:attribute:: metadata_hash + :type: bytes | None + :value: None + + + The 32-byte hash of some metadata that is relevant to the asset and/or asset holders, defaults to None + + + +.. py:class:: BulkAssetOptInOutResult + + Result from performing a bulk opt-in or bulk opt-out for an account against a series of assets. + + :ivar asset_id: The ID of the asset opted into / out of + :ivar transaction_id: The transaction ID of the resulting opt in / out + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset opted into / out of + + + + .. py:attribute:: transaction_id + :type: str + + The transaction ID of the resulting opt in / out + + + +.. py:class:: AssetManager(algod_client: algosdk.v2client.algod.AlgodClient, new_group: collections.abc.Callable[[], algokit_utils.transactions.transaction_composer.TransactionComposer]) + + A manager for Algorand Standard Assets (ASAs). + + :param algod_client: An algod client + :param new_group: A function that creates a new TransactionComposer transaction group + + :example: + >>> asset_manager = AssetManager(algod_client) + + + .. py:method:: get_by_id(asset_id: int) -> AssetInformation + + Returns the current asset information for the asset with the given ID. + + :param asset_id: The ID of the asset + :return: The asset information + + :example: + >>> asset_manager = AssetManager(algod_client) + >>> asset_info = asset_manager.get_by_id(1234567890) + + + + .. py:method:: get_account_information(sender: str | algokit_utils.models.account.SigningAccount | algosdk.atomic_transaction_composer.TransactionSigner, asset_id: int) -> AccountAssetInformation + + Returns the given sender account's asset holding for a given asset. + + :param sender: The address of the sender/account to look up + :param asset_id: The ID of the asset to return a holding for + :return: The account asset holding information + + :example: + >>> asset_manager = AssetManager(algod_client) + >>> account_asset_info = asset_manager.get_account_information(sender, asset_id) + + + + .. py:method:: bulk_opt_in(account: str, asset_ids: list[int], signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, rekey_to: str | None = None, note: bytes | None = None, lease: bytes | None = None, static_fee: algokit_utils.models.amount.AlgoAmount | None = None, extra_fee: algokit_utils.models.amount.AlgoAmount | None = None, max_fee: algokit_utils.models.amount.AlgoAmount | None = None, validity_window: int | None = None, first_valid_round: int | None = None, last_valid_round: int | None = None, send_params: algokit_utils.models.transaction.SendParams | None = None) -> list[BulkAssetOptInOutResult] + + Opt an account in to a list of Algorand Standard Assets. + + :param account: The account to opt-in + :param asset_ids: The list of asset IDs to opt-in to + :param signer: The signer to use for the transaction, defaults to None + :param rekey_to: The address to rekey the account to, defaults to None + :param note: The note to include in the transaction, defaults to None + :param lease: The lease to include in the transaction, defaults to None + :param static_fee: The static fee to include in the transaction, defaults to None + :param extra_fee: The extra fee to include in the transaction, defaults to None + :param max_fee: The maximum fee to include in the transaction, defaults to None + :param validity_window: The validity window to include in the transaction, defaults to None + :param first_valid_round: The first valid round to include in the transaction, defaults to None + :param last_valid_round: The last valid round to include in the transaction, defaults to None + :param send_params: The send parameters to use for the transaction, defaults to None + :return: An array of records matching asset ID to transaction ID of the opt in + + :example: + >>> asset_manager = AssetManager(algod_client) + >>> results = asset_manager.bulk_opt_in(account, asset_ids) + + + + .. py:method:: bulk_opt_out(*, account: str, asset_ids: list[int], ensure_zero_balance: bool = True, signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, rekey_to: str | None = None, note: bytes | None = None, lease: bytes | None = None, static_fee: algokit_utils.models.amount.AlgoAmount | None = None, extra_fee: algokit_utils.models.amount.AlgoAmount | None = None, max_fee: algokit_utils.models.amount.AlgoAmount | None = None, validity_window: int | None = None, first_valid_round: int | None = None, last_valid_round: int | None = None, send_params: algokit_utils.models.transaction.SendParams | None = None) -> list[BulkAssetOptInOutResult] + + Opt an account out of a list of Algorand Standard Assets. + + :param account: The account to opt-out + :param asset_ids: The list of asset IDs to opt-out of + :param ensure_zero_balance: Whether to check if the account has a zero balance first, defaults to True + :param signer: The signer to use for the transaction, defaults to None + :param rekey_to: The address to rekey the account to, defaults to None + :param note: The note to include in the transaction, defaults to None + :param lease: The lease to include in the transaction, defaults to None + :param static_fee: The static fee to include in the transaction, defaults to None + :param extra_fee: The extra fee to include in the transaction, defaults to None + :param max_fee: The maximum fee to include in the transaction, defaults to None + :param validity_window: The validity window to include in the transaction, defaults to None + :param first_valid_round: The first valid round to include in the transaction, defaults to None + :param last_valid_round: The last valid round to include in the transaction, defaults to None + :param send_params: The send parameters to use for the transaction, defaults to None + :raises ValueError: If ensure_zero_balance is True and account has non-zero balance or is not opted in + :return: An array of records matching asset ID to transaction ID of the opt out + + :example: + >>> asset_manager = AssetManager(algod_client) + >>> results = asset_manager.bulk_opt_out(account, asset_ids) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/assets/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/assets/index.rst.txt new file mode 100644 index 00000000..5ec73261 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/assets/index.rst.txt @@ -0,0 +1,15 @@ +algokit_utils.assets +==================== + +.. py:module:: algokit_utils.assets + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/assets/asset_manager/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/clients/client_manager/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/clients/client_manager/index.rst.txt new file mode 100644 index 00000000..8482887c --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/clients/client_manager/index.rst.txt @@ -0,0 +1,520 @@ +algokit_utils.clients.client_manager +==================================== + +.. py:module:: algokit_utils.clients.client_manager + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.clients.client_manager.AlgoSdkClients + algokit_utils.clients.client_manager.NetworkDetail + algokit_utils.clients.client_manager.ClientManager + + +Module Contents +--------------- + +.. py:class:: AlgoSdkClients(algod: algosdk.v2client.algod.AlgodClient, indexer: algosdk.v2client.indexer.IndexerClient | None = None, kmd: algosdk.kmd.KMDClient | None = None) + + Container for Algorand SDK client instances. + + Holds references to Algod, Indexer and KMD clients. + + :param algod: Algod client instance + :param indexer: Optional Indexer client instance + :param kmd: Optional KMD client instance + + + .. py:attribute:: algod + + + .. py:attribute:: indexer + :value: None + + + + .. py:attribute:: kmd + :value: None + + + +.. py:class:: NetworkDetail + + Details about an Algorand network. + + Contains network type flags and genesis information. + + + .. py:attribute:: is_testnet + :type: bool + + Whether the network is a testnet + + + + .. py:attribute:: is_mainnet + :type: bool + + Whether the network is a mainnet + + + + .. py:attribute:: is_localnet + :type: bool + + Whether the network is a localnet + + + + .. py:attribute:: genesis_id + :type: str + + The genesis ID of the network + + + + .. py:attribute:: genesis_hash + :type: str + + The genesis hash of the network + + + +.. py:class:: ClientManager(clients_or_configs: algokit_utils.models.network.AlgoClientConfigs | AlgoSdkClients, algorand_client: algokit_utils.algorand.AlgorandClient) + + Manager for Algorand SDK clients. + + Provides access to Algod, Indexer and KMD clients and helper methods for working with them. + + :param clients_or_configs: Either client instances or client configurations + :param algorand_client: AlgorandClient instance + + :example: + >>> # Algod only + >>> client_manager = ClientManager(algod_client) + >>> # Algod and Indexer + >>> client_manager = ClientManager(algod_client, indexer_client) + >>> # Algod config only + >>> client_manager = ClientManager(ClientManager.get_algod_config_from_environment()) + >>> # Algod and Indexer config + >>> client_manager = ClientManager(ClientManager.get_algod_config_from_environment(), + ... ClientManager.get_indexer_config_from_environment()) + + + .. py:property:: algod + :type: algosdk.v2client.algod.AlgodClient + + + Returns an algosdk Algod API client. + + :return: Algod client instance + + + + .. py:property:: indexer + :type: algosdk.v2client.indexer.IndexerClient + + + Returns an algosdk Indexer API client. + + :raises ValueError: If no Indexer client is configured + :return: Indexer client instance + + + + .. py:property:: indexer_if_present + :type: algosdk.v2client.indexer.IndexerClient | None + + + Returns the Indexer client if configured, otherwise None. + + :return: Indexer client instance or None + + + + .. py:property:: kmd + :type: algosdk.kmd.KMDClient + + + Returns an algosdk KMD API client. + + :raises ValueError: If no KMD client is configured + :return: KMD client instance + + + + .. py:method:: network() -> NetworkDetail + + Get details about the connected Algorand network. + + :return: Network details including type and genesis information + + :example: + >>> client_manager = ClientManager(algod_client) + >>> network_detail = client_manager.network() + + + + .. py:method:: is_localnet() -> bool + + Check if connected to a local network. + + :return: True if connected to a local network + + + + .. py:method:: is_testnet() -> bool + + Check if connected to TestNet. + + :return: True if connected to TestNet + + + + .. py:method:: is_mainnet() -> bool + + Check if connected to MainNet. + + :return: True if connected to MainNet + + + + .. py:method:: get_testnet_dispenser(auth_token: str | None = None, request_timeout: int | None = None) -> algokit_utils.clients.dispenser_api_client.TestNetDispenserApiClient + + Get a TestNet dispenser API client. + + :param auth_token: Optional authentication token + :param request_timeout: Optional request timeout in seconds + :return: TestNet dispenser client instance + + + + .. py:method:: get_app_factory(app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils._legacy_v2.application_specification.ApplicationSpecification | str, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, version: str | None = None, compilation_params: algokit_utils.applications.app_client.AppClientCompilationParams | None = None) -> algokit_utils.applications.app_factory.AppFactory + + Get an application factory for deploying smart contracts. + + :param app_spec: Application specification + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param version: Optional version string + :param compilation_params: Optional compilation parameters + :raises ValueError: If no Algorand client is configured + :return: Application factory instance + + + + .. py:method:: get_app_client_by_id(app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils._legacy_v2.application_specification.ApplicationSpecification | str, app_id: int, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> algokit_utils.applications.app_client.AppClient + + Get an application client for an existing application by ID. + + :param app_spec: Application specification + :param app_id: Application ID + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :raises ValueError: If no Algorand client is configured + :return: Application client instance + + + + .. py:method:: get_app_client_by_network(app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils._legacy_v2.application_specification.ApplicationSpecification | str, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> algokit_utils.applications.app_client.AppClient + + Get an application client for an existing application by network. + + :param app_spec: Application specification + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :raises ValueError: If no Algorand client is configured + :return: Application client instance + + + + .. py:method:: get_app_client_by_creator_and_name(creator_address: str, app_name: str, app_spec: algokit_utils.applications.app_spec.arc56.Arc56Contract | algokit_utils._legacy_v2.application_specification.ApplicationSpecification | str, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, ignore_cache: bool | None = None, app_lookup_cache: algokit_utils.applications.app_deployer.ApplicationLookup | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> algokit_utils.applications.app_client.AppClient + + Get an application client by creator address and name. + + :param creator_address: Creator address + :param app_name: Application name + :param app_spec: Application specification + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param ignore_cache: Optional flag to ignore cache + :param app_lookup_cache: Optional app lookup cache + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :return: Application client instance + + + + .. py:method:: get_algod_client(config: algokit_utils.models.network.AlgoClientNetworkConfig) -> algosdk.v2client.algod.AlgodClient + :staticmethod: + + + Get an Algod client from config or environment. + + :param config: Optional client configuration + :return: Algod client instance + + + + .. py:method:: get_algod_client_from_environment() -> algosdk.v2client.algod.AlgodClient + :staticmethod: + + + Get an Algod client from environment variables. + + :return: Algod client instance + + + + .. py:method:: get_kmd_client(config: algokit_utils.models.network.AlgoClientNetworkConfig) -> algosdk.kmd.KMDClient + :staticmethod: + + + Get a KMD client from config or environment. + + :param config: Optional client configuration + :return: KMD client instance + + + + .. py:method:: get_kmd_client_from_environment() -> algosdk.kmd.KMDClient + :staticmethod: + + + Get a KMD client from environment variables. + + :return: KMD client instance + + + + .. py:method:: get_indexer_client(config: algokit_utils.models.network.AlgoClientNetworkConfig) -> algosdk.v2client.indexer.IndexerClient + :staticmethod: + + + Get an Indexer client from config or environment. + + :param config: Optional client configuration + :return: Indexer client instance + + + + .. py:method:: get_indexer_client_from_environment() -> algosdk.v2client.indexer.IndexerClient + :staticmethod: + + + Get an Indexer client from environment variables. + + :return: Indexer client instance + + + + .. py:method:: genesis_id_is_localnet(genesis_id: str | None) -> bool + :staticmethod: + + + Check if a genesis ID indicates a local network. + + :param genesis_id: Genesis ID to check + :return: True if genesis ID indicates a local network + + :example: + >>> ClientManager.genesis_id_is_localnet("devnet-v1") + + + + .. py:method:: get_typed_app_client_by_creator_and_name(typed_client: type[TypedAppClientT], *, creator_address: str, app_name: str, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, ignore_cache: bool | None = None, app_lookup_cache: algokit_utils.applications.app_deployer.ApplicationLookup | None = None) -> TypedAppClientT + + Get a typed application client by creator address and name. + + :param typed_client: Typed client class + :param creator_address: Creator address + :param app_name: Application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param ignore_cache: Optional flag to ignore cache + :param app_lookup_cache: Optional app lookup cache + :raises ValueError: If no Algorand client is configured + :return: Typed application client instance + + :example: + >>> client_manager = ClientManager(algod_client) + >>> typed_app_client = client_manager.get_typed_app_client_by_creator_and_name( + ... typed_client=MyAppClient, + ... creator_address="creator_address", + ... app_name="app_name", + ... ) + + + + .. py:method:: get_typed_app_client_by_id(typed_client: type[TypedAppClientT], *, app_id: int, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> TypedAppClientT + + Get a typed application client by ID. + + :param typed_client: Typed client class + :param app_id: Application ID + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :raises ValueError: If no Algorand client is configured + :return: Typed application client instance + + :example: + >>> client_manager = ClientManager(algod_client) + >>> typed_app_client = client_manager.get_typed_app_client_by_id( + ... typed_client=MyAppClient, + ... app_id=1234567890, + ... ) + + + + .. py:method:: get_typed_app_client_by_network(typed_client: type[TypedAppClientT], *, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) -> TypedAppClientT + + Returns a new typed client, resolves the app ID for the current network. + + Uses pre-determined network-specific app IDs specified in the ARC-56 app spec. + If no IDs are in the app spec or the network isn't recognised, an error is thrown. + + :param typed_client: The typed client class to instantiate + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param approval_source_map: Optional approval program source map + :param clear_source_map: Optional clear program source map + :raises ValueError: If no Algorand client is configured + :return: The typed client instance + + :example: + >>> client_manager = ClientManager(algod_client) + >>> typed_app_client = client_manager.get_typed_app_client_by_network( + ... typed_client=MyAppClient, + ... app_name="app_name", + ... ) + + + + .. py:method:: get_typed_app_factory(typed_factory: type[TypedFactoryT], *, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, version: str | None = None, compilation_params: algokit_utils.applications.app_client.AppClientCompilationParams | None = None) -> TypedFactoryT + + Get a typed application factory. + + :param typed_factory: Typed factory class + :param app_name: Optional application name + :param default_sender: Optional default sender address + :param default_signer: Optional default transaction signer + :param version: Optional version string + :param compilation_params: Optional compilation parameters + :raises ValueError: If no Algorand client is configured + :return: Typed application factory instance + + :example: + >>> client_manager = ClientManager(algod_client) + >>> typed_app_factory = client_manager.get_typed_app_factory( + ... typed_factory=MyAppFactory, + ... app_name="app_name", + ... ) + + + + .. py:method:: get_config_from_environment_or_localnet() -> algokit_utils.models.network.AlgoClientConfigs + :staticmethod: + + + Retrieve client configuration from environment variables or fallback to localnet defaults. + + If ALGOD_SERVER is set in environment variables, it will use environment configuration, + otherwise it will use default localnet configuration. + + :return: Configuration for algod, indexer, and optionally kmd + + :example: + >>> client_manager = ClientManager(algod_client) + >>> config = client_manager.get_config_from_environment_or_localnet() + + + + .. py:method:: get_default_localnet_config(config_or_port: Literal['algod', 'indexer', 'kmd'] | int) -> algokit_utils.models.network.AlgoClientNetworkConfig + :staticmethod: + + + Get default configuration for local network services. + + :param config_or_port: Service name or port number + :return: Client configuration for local network + + :example: + >>> client_manager = ClientManager(algod_client) + >>> config = client_manager.get_default_localnet_config("algod") + + + + .. py:method:: get_algod_config_from_environment() -> algokit_utils.models.network.AlgoClientNetworkConfig + :staticmethod: + + + Retrieve the algod configuration from environment variables. + Will raise an error if ALGOD_SERVER environment variable is not set + + :return: Algod client configuration + + :example: + >>> client_manager = ClientManager(algod_client) + >>> config = client_manager.get_algod_config_from_environment() + + + + .. py:method:: get_indexer_config_from_environment() -> algokit_utils.models.network.AlgoClientNetworkConfig + :staticmethod: + + + Retrieve the indexer configuration from environment variables. + Will raise an error if INDEXER_SERVER environment variable is not set + + :return: Indexer client configuration + + :example: + >>> client_manager = ClientManager(algod_client) + >>> config = client_manager.get_indexer_config_from_environment() + + + + .. py:method:: get_kmd_config_from_environment() -> algokit_utils.models.network.AlgoClientNetworkConfig + :staticmethod: + + + Retrieve the kmd configuration from environment variables. + + :return: KMD client configuration + + :example: + >>> client_manager = ClientManager(algod_client) + >>> config = client_manager.get_kmd_config_from_environment() + + + + .. py:method:: get_algonode_config(network: Literal['testnet', 'mainnet'], config: Literal['algod', 'indexer']) -> algokit_utils.models.network.AlgoClientNetworkConfig + :staticmethod: + + + Returns the Algorand configuration to point to the free tier of the AlgoNode service. + + :param network: Which network to connect to - TestNet or MainNet + :param config: Which algod config to return - Algod or Indexer + :return: Configuration for the specified network and service + + :example: + >>> client_manager = ClientManager(algod_client) + >>> config = client_manager.get_algonode_config("testnet", "algod") + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/clients/dispenser_api_client/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/clients/dispenser_api_client/index.rst.txt new file mode 100644 index 00000000..4e1ff351 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/clients/dispenser_api_client/index.rst.txt @@ -0,0 +1,159 @@ +algokit_utils.clients.dispenser_api_client +========================================== + +.. py:module:: algokit_utils.clients.dispenser_api_client + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.clients.dispenser_api_client.DISPENSER_ASSETS + algokit_utils.clients.dispenser_api_client.DISPENSER_REQUEST_TIMEOUT + algokit_utils.clients.dispenser_api_client.DISPENSER_ACCESS_TOKEN_KEY + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.clients.dispenser_api_client.DispenserApiConfig + algokit_utils.clients.dispenser_api_client.DispenserAssetName + algokit_utils.clients.dispenser_api_client.DispenserAsset + algokit_utils.clients.dispenser_api_client.DispenserFundResponse + algokit_utils.clients.dispenser_api_client.DispenserLimitResponse + algokit_utils.clients.dispenser_api_client.TestNetDispenserApiClient + + +Module Contents +--------------- + +.. py:class:: DispenserApiConfig + + .. py:attribute:: BASE_URL + :value: 'https://api.dispenser.algorandfoundation.tools' + + + +.. py:class:: DispenserAssetName + + Bases: :py:obj:`enum.IntEnum` + + + Enum where members are also (and must be) ints + + + .. py:attribute:: ALGO + :value: 0 + + + +.. py:class:: DispenserAsset + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: decimals + :type: int + + The amount of decimal places the asset was created with + + + + .. py:attribute:: description + :type: str + + The description of the asset + + + +.. py:class:: DispenserFundResponse + + .. py:attribute:: tx_id + :type: str + + The transaction ID of the funded transaction + + + + .. py:attribute:: amount + :type: int + + The amount of Algos funded + + + +.. py:class:: DispenserLimitResponse + + .. py:attribute:: amount + :type: int + + The amount of Algos that can be funded + + + +.. py:data:: DISPENSER_ASSETS + +.. py:data:: DISPENSER_REQUEST_TIMEOUT + :value: 15 + + +.. py:data:: DISPENSER_ACCESS_TOKEN_KEY + :value: 'ALGOKIT_DISPENSER_ACCESS_TOKEN' + + +.. py:class:: TestNetDispenserApiClient(auth_token: str | None = None, request_timeout: int = DISPENSER_REQUEST_TIMEOUT) + + Client for interacting with the [AlgoKit TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md). + To get started create a new access token via `algokit dispenser login --ci` + and pass it to the client constructor as `auth_token`. + Alternatively set the access token as environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN`, + and it will be auto loaded. If both are set, the constructor argument takes precedence. + + Default request timeout is 15 seconds. Modify by passing `request_timeout` to the constructor. + + + .. py:attribute:: auth_token + :type: str + + + .. py:attribute:: request_timeout + :value: 15 + + + + .. py:method:: fund(address: str, amount: int) -> DispenserFundResponse + fund(address: str, amount: int, asset_id: int | None = None) -> DispenserFundResponse + + Fund an account with Algos from the dispenser API + + :param address: The address to fund + :param amount: The amount of Algos to fund + :param asset_id: The asset ID to fund (deprecated) + :return: The transaction ID of the funded transaction + :raises Exception: If the dispenser API request fails + + :example: + >>> dispenser_client = TestNetDispenserApiClient() + >>> dispenser_client.fund(address="SENDER_ADDRESS", amount=1000000) + + + + .. py:method:: refund(refund_txn_id: str) -> None + + Register a refund for a transaction with the dispenser API + + + + .. py:method:: get_limit(address: str) -> DispenserLimitResponse + + Get current limit for an account with Algos from the dispenser API + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/clients/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/clients/index.rst.txt new file mode 100644 index 00000000..150b9635 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/clients/index.rst.txt @@ -0,0 +1,16 @@ +algokit_utils.clients +===================== + +.. py:module:: algokit_utils.clients + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/clients/client_manager/index + /autoapi/algokit_utils/clients/dispenser_api_client/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/config/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/config/index.rst.txt new file mode 100644 index 00000000..2f33b25a --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/config/index.rst.txt @@ -0,0 +1,145 @@ +algokit_utils.config +==================== + +.. py:module:: algokit_utils.config + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.config.ALGOKIT_PROJECT_ROOT + algokit_utils.config.ALGOKIT_CONFIG_FILENAME + algokit_utils.config.config + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.config.AlgoKitLogger + algokit_utils.config.UpdatableConfig + + +Module Contents +--------------- + +.. py:data:: ALGOKIT_PROJECT_ROOT + +.. py:data:: ALGOKIT_CONFIG_FILENAME + :value: '.algokit.toml' + + +.. py:class:: AlgoKitLogger(name: str = 'algokit-utils-py', level: int = logging.NOTSET) + + Bases: :py:obj:`logging.Logger` + + + Instances of the Logger class represent a single logging channel. A + "logging channel" indicates an area of an application. Exactly how an + "area" is defined is up to the application developer. Since an + application can have any number of areas, logging channels are identified + by a unique string. Application areas can be nested (e.g. an area + of "input processing" might include sub-areas "read CSV files", "read + XLS files" and "read Gnumeric files"). To cater for this natural nesting, + channel names are organized into a namespace hierarchy where levels are + separated by periods, much like the Java or Python package namespace. So + in the instance given above, channel names might be "input" for the upper + level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels. + There is no arbitrary limit to the depth of nesting. + + + .. py:method:: get_null_logger() -> logging.Logger + :classmethod: + + + Return a logger that does nothing (a null logger). + + + +.. py:class:: UpdatableConfig + + Class to manage and update configuration settings for the AlgoKit project. + + Attributes: + debug (bool): Indicates whether debug mode is enabled. + project_root (Path | None): The path to the project root directory. + trace_all (bool): Indicates whether to trace all operations. + trace_buffer_size_mb (int | float): The size of the trace buffer in megabytes. + max_search_depth (int): The maximum depth to search for a specific file. + populate_app_call_resources (bool): Whether to populate app call resources. + logger (logging.Logger): The logger instance to use. Defaults to an AlgoKitLogger instance. + + + .. py:property:: logger + :type: logging.Logger + + + Returns the logger instance. + + + + .. py:property:: debug + :type: bool + + + Returns the debug status. + + + + .. py:property:: project_root + :type: pathlib.Path | None + + + Returns the project root path. + + + + .. py:property:: trace_all + :type: bool + + + Indicates whether simulation traces for all operations should be stored. + + + + .. py:property:: trace_buffer_size_mb + :type: int | float + + + Returns the size of the trace buffer in megabytes. + + + + .. py:property:: populate_app_call_resource + :type: bool + + + Indicates whether or not to populate app call resources. + + + + .. py:method:: with_debug(func: collections.abc.Callable[[], str | None]) -> None + + Executes a function with debug mode temporarily enabled. + + + + .. py:method:: configure(*, debug: bool | None = None, project_root: pathlib.Path | None = None, trace_all: bool = False, trace_buffer_size_mb: float = 256, max_search_depth: int = 10, populate_app_call_resources: bool = False, logger: logging.Logger | None = None) -> None + + Configures various settings for the application. + + :param debug: Whether debug mode is enabled. + :param project_root: The path to the project root directory. + :param trace_all: Whether to trace all operations. Defaults to False. + :param trace_buffer_size_mb: The trace buffer size in megabytes. Defaults to 256. + :param max_search_depth: The maximum depth to search for a specific file. Defaults to 10. + :param populate_app_call_resources: Whether to populate app call resources. Defaults to False. + :param logger: A custom logger to use. Defaults to AlgoKitLogger instance. + + + +.. py:data:: config + diff --git a/docs/html/_sources/autoapi/algokit_utils/errors/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/errors/index.rst.txt new file mode 100644 index 00000000..578eccd9 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/errors/index.rst.txt @@ -0,0 +1,15 @@ +algokit_utils.errors +==================== + +.. py:module:: algokit_utils.errors + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/errors/logic_error/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/errors/logic_error/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/errors/logic_error/index.rst.txt new file mode 100644 index 00000000..48f59877 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/errors/logic_error/index.rst.txt @@ -0,0 +1,105 @@ +algokit_utils.errors.logic_error +================================ + +.. py:module:: algokit_utils.errors.logic_error + + +Exceptions +---------- + +.. autoapisummary:: + + algokit_utils.errors.logic_error.LogicError + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.errors.logic_error.LogicErrorData + + +Functions +--------- + +.. autoapisummary:: + + algokit_utils.errors.logic_error.parse_logic_error + + +Module Contents +--------------- + +.. py:class:: LogicErrorData + + Bases: :py:obj:`TypedDict` + + + dict() -> new empty dictionary + dict(mapping) -> new dictionary initialized from a mapping object's + (key, value) pairs + dict(iterable) -> new dictionary initialized as if via: + d = {} + for k, v in iterable: + d[k] = v + dict(**kwargs) -> new dictionary initialized with the name=value pairs + in the keyword argument list. For example: dict(one=1, two=2) + + + .. py:attribute:: transaction_id + :type: str + + + .. py:attribute:: message + :type: str + + + .. py:attribute:: pc + :type: int + + +.. py:function:: parse_logic_error(error_str: str) -> LogicErrorData | None + +.. py:exception:: LogicError(*, logic_error_str: str, program: str, source_map: AlgoSourceMap | None, transaction_id: str, message: str, pc: int, logic_error: Exception | None = None, traces: list[algokit_utils.models.simulate.SimulationTrace] | None = None, get_line_for_pc: collections.abc.Callable[[int], int | None] | None = None) + + Bases: :py:obj:`Exception` + + + Common base class for all non-exit exceptions. + + + .. py:attribute:: logic_error + :value: None + + + + .. py:attribute:: logic_error_str + + + .. py:attribute:: source_map + + + .. py:attribute:: lines + + + .. py:attribute:: transaction_id + + + .. py:attribute:: message + + + .. py:attribute:: pc + + + .. py:attribute:: traces + :value: None + + + + .. py:attribute:: line_no + + + .. py:method:: trace(lines: int = 5) -> str + + diff --git a/docs/html/_sources/autoapi/algokit_utils/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/index.rst.txt new file mode 100644 index 00000000..4752fb49 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/index.rst.txt @@ -0,0 +1,37 @@ +algokit_utils +============= + +.. py:module:: algokit_utils + +.. autoapi-nested-parse:: + + AlgoKit Python Utilities - a set of utilities for building solutions on Algorand + + This module provides commonly used utilities and types at the root level for convenience. + For more specific functionality, import directly from the relevant submodules: + + from algokit_utils.accounts import KmdAccountManager + from algokit_utils.applications import AppClient + from algokit_utils.applications.app_spec import Arc52Contract + etc. + + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/accounts/index + /autoapi/algokit_utils/algorand/index + /autoapi/algokit_utils/applications/index + /autoapi/algokit_utils/assets/index + /autoapi/algokit_utils/clients/index + /autoapi/algokit_utils/config/index + /autoapi/algokit_utils/errors/index + /autoapi/algokit_utils/models/index + /autoapi/algokit_utils/protocols/index + /autoapi/algokit_utils/transactions/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/account/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/account/index.rst.txt new file mode 100644 index 00000000..88de425f --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/account/index.rst.txt @@ -0,0 +1,229 @@ +algokit_utils.models.account +============================ + +.. py:module:: algokit_utils.models.account + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.models.account.DISPENSER_ACCOUNT_NAME + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.account.TransactionSignerAccount + algokit_utils.models.account.SigningAccount + algokit_utils.models.account.MultisigMetadata + algokit_utils.models.account.MultiSigAccount + algokit_utils.models.account.LogicSigAccount + + +Module Contents +--------------- + +.. py:data:: DISPENSER_ACCOUNT_NAME + :value: 'DISPENSER' + + +.. py:class:: TransactionSignerAccount + + A basic transaction signer account. + + + .. py:attribute:: address + :type: str + + + .. py:attribute:: signer + :type: algosdk.atomic_transaction_composer.TransactionSigner + + +.. py:class:: SigningAccount + + Holds the private key and address for an account. + + Provides access to the account's private key, address, public key and transaction signer. + + + .. py:attribute:: private_key + :type: str + + Base64 encoded private key + + + + .. py:attribute:: address + :type: str + :value: '' + + + Address for this account + + + + .. py:property:: public_key + :type: bytes + + + The public key for this account. + + :return: The public key as bytes + + + + .. py:property:: signer + :type: algosdk.atomic_transaction_composer.AccountTransactionSigner + + + Get an AccountTransactionSigner for this account. + + :return: A transaction signer for this account + + + + .. py:method:: new_account() -> SigningAccount + :staticmethod: + + + Create a new random account. + + :return: A new Account instance + + + +.. py:class:: MultisigMetadata + + Metadata for a multisig account. + + Contains the version, threshold and addresses for a multisig account. + + + .. py:attribute:: version + :type: int + + + .. py:attribute:: threshold + :type: int + + + .. py:attribute:: addresses + :type: list[str] + + +.. py:class:: MultiSigAccount(multisig_params: MultisigMetadata, signing_accounts: list[SigningAccount]) + + Account wrapper that supports partial or full multisig signing. + + Provides functionality to manage and sign transactions for a multisig account. + + :param multisig_params: The parameters for the multisig account + :param signing_accounts: The list of accounts that can sign + + + .. py:property:: multisig + :type: algosdk.transaction.Multisig + + + Get the underlying `algosdk.transaction.Multisig` object instance. + + :return: The `algosdk.transaction.Multisig` object instance + + + + .. py:property:: params + :type: MultisigMetadata + + + Get the parameters for the multisig account. + + :return: The multisig account parameters + + + + .. py:property:: signing_accounts + :type: list[SigningAccount] + + + Get the list of accounts that are present to sign. + + :return: The list of signing accounts + + + + .. py:property:: address + :type: str + + + Get the address of the multisig account. + + :return: The multisig account address + + + + .. py:property:: signer + :type: algosdk.atomic_transaction_composer.TransactionSigner + + + Get the transaction signer for this multisig account. + + :return: The multisig transaction signer + + + + .. py:method:: sign(transaction: algosdk.transaction.Transaction) -> algosdk.transaction.MultisigTransaction + + Sign the given transaction with all present signers. + + :param transaction: Either a transaction object or a raw, partially signed transaction + :return: The transaction signed by the present signers + + + +.. py:class:: LogicSigAccount(program: bytes, args: list[bytes] | None) + + Account wrapper that supports logic sig signing. + + Provides functionality to manage and sign transactions for a logic sig account. + + + .. py:property:: lsig + :type: algosdk.transaction.LogicSigAccount + + + Get the underlying `algosdk.transaction.LogicSigAccount` object instance. + + :return: The `algosdk.transaction.LogicSigAccount` object instance + + + + .. py:property:: address + :type: str + + + Get the address of the logic sig account. + + If the LogicSig is delegated to another account, this will return the address of that account. + + If the LogicSig is not delegated to another account, this will return an escrow address that is the hash of + the LogicSig's program code. + + :return: The logic sig account address + + + + .. py:property:: signer + :type: algosdk.atomic_transaction_composer.LogicSigTransactionSigner + + + Get the transaction signer for this multisig account. + + :return: The multisig transaction signer + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/amount/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/amount/index.rst.txt new file mode 100644 index 00000000..64b6b524 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/amount/index.rst.txt @@ -0,0 +1,121 @@ +algokit_utils.models.amount +=========================== + +.. py:module:: algokit_utils.models.amount + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.models.amount.ALGORAND_MIN_TX_FEE + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.amount.AlgoAmount + + +Functions +--------- + +.. autoapisummary:: + + algokit_utils.models.amount.algo + algokit_utils.models.amount.micro_algo + algokit_utils.models.amount.transaction_fees + + +Module Contents +--------------- + +.. py:class:: AlgoAmount(*, micro_algo: int) + AlgoAmount(*, algo: int | decimal.Decimal) + + Wrapper class to ensure safe, explicit conversion between µAlgo, Algo and numbers. + + :example: + >>> amount = AlgoAmount(algo=1) + >>> amount = AlgoAmount.from_algo(1) + >>> amount = AlgoAmount(micro_algo=1_000_000) + >>> amount = AlgoAmount.from_micro_algo(1_000_000) + + + .. py:property:: micro_algo + :type: int + + + Return the amount as a number in µAlgo. + + :returns: The amount in µAlgo. + + + + .. py:property:: algo + :type: decimal.Decimal + + + Return the amount as a number in Algo. + + :returns: The amount in Algo. + + + + .. py:method:: from_algo(amount: int | decimal.Decimal) -> AlgoAmount + :staticmethod: + + + Create an AlgoAmount object representing the given number of Algo. + + :param amount: The amount in Algo. + :returns: An AlgoAmount instance. + + :example: + >>> amount = AlgoAmount.from_algo(1) + + + + .. py:method:: from_micro_algo(amount: int) -> AlgoAmount + :staticmethod: + + + Create an AlgoAmount object representing the given number of µAlgo. + + :param amount: The amount in µAlgo. + :returns: An AlgoAmount instance. + + :example: + >>> amount = AlgoAmount.from_micro_algo(1_000_000) + + + +.. py:function:: algo(algo: int) -> AlgoAmount + + Create an AlgoAmount object representing the given number of Algo. + + :param algo: The number of Algo to create an AlgoAmount object for. + :return: An AlgoAmount object representing the given number of Algo. + + +.. py:function:: micro_algo(micro_algo: int) -> AlgoAmount + + Create an AlgoAmount object representing the given number of µAlgo. + + :param micro_algo: The number of µAlgo to create an AlgoAmount object for. + :return: An AlgoAmount object representing the given number of µAlgo. + + +.. py:data:: ALGORAND_MIN_TX_FEE + +.. py:function:: transaction_fees(number_of_transactions: int) -> AlgoAmount + + Calculate the total transaction fees for a given number of transactions. + + :param number_of_transactions: The number of transactions to calculate the fees for. + :return: The total transaction fees. + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/application/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/application/index.rst.txt new file mode 100644 index 00000000..dd512a80 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/application/index.rst.txt @@ -0,0 +1,216 @@ +algokit_utils.models.application +================================ + +.. py:module:: algokit_utils.models.application + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.application.AppState + algokit_utils.models.application.AppInformation + algokit_utils.models.application.CompiledTeal + algokit_utils.models.application.AppCompilationResult + algokit_utils.models.application.AppSourceMaps + + +Module Contents +--------------- + +.. py:class:: AppState + + .. py:attribute:: key_raw + :type: bytes + + The key of the state as raw bytes + + + + .. py:attribute:: key_base64 + :type: str + + The key of the state + + + + .. py:attribute:: value_raw + :type: bytes | None + + The value of the state as raw bytes + + + + .. py:attribute:: value_base64 + :type: str | None + + The value of the state as base64 encoded string + + + + .. py:attribute:: value + :type: str | int + + The value of the state as a string or integer + + + +.. py:class:: AppInformation + + .. py:attribute:: app_id + :type: int + + The ID of the application + + + + .. py:attribute:: app_address + :type: str + + The address of the application + + + + .. py:attribute:: approval_program + :type: bytes + + The approval program + + + + .. py:attribute:: clear_state_program + :type: bytes + + The clear state program + + + + .. py:attribute:: creator + :type: str + + The creator of the application + + + + .. py:attribute:: global_state + :type: dict[str, AppState] + + The global state of the application + + + + .. py:attribute:: local_ints + :type: int + + The number of local ints + + + + .. py:attribute:: local_byte_slices + :type: int + + The number of local byte slices + + + + .. py:attribute:: global_ints + :type: int + + The number of global ints + + + + .. py:attribute:: global_byte_slices + :type: int + + The number of global byte slices + + + + .. py:attribute:: extra_program_pages + :type: int | None + + The number of extra program pages + + + +.. py:class:: CompiledTeal + + The compiled teal code + + + .. py:attribute:: teal + :type: str + + The teal code + + + + .. py:attribute:: compiled + :type: str + + The compiled teal code + + + + .. py:attribute:: compiled_hash + :type: str + + The compiled hash + + + + .. py:attribute:: compiled_base64_to_bytes + :type: bytes + + The compiled base64 to bytes + + + + .. py:attribute:: source_map + :type: algosdk.source_map.SourceMap | None + + +.. py:class:: AppCompilationResult + + The compiled teal code + + + .. py:attribute:: compiled_approval + :type: CompiledTeal + + The compiled approval program + + + + .. py:attribute:: compiled_clear + :type: CompiledTeal + + The compiled clear state program + + + +.. py:class:: AppSourceMaps + + The source maps for the application + + + .. py:attribute:: approval_source_map + :type: algosdk.source_map.SourceMap | None + :value: None + + + The source map for the approval program + + + + .. py:attribute:: clear_source_map + :type: algosdk.source_map.SourceMap | None + :value: None + + + The source map for the clear state program + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/index.rst.txt new file mode 100644 index 00000000..72a26073 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/index.rst.txt @@ -0,0 +1,21 @@ +algokit_utils.models +==================== + +.. py:module:: algokit_utils.models + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/models/account/index + /autoapi/algokit_utils/models/amount/index + /autoapi/algokit_utils/models/application/index + /autoapi/algokit_utils/models/network/index + /autoapi/algokit_utils/models/simulate/index + /autoapi/algokit_utils/models/state/index + /autoapi/algokit_utils/models/transaction/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/network/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/network/index.rst.txt new file mode 100644 index 00000000..8ca64c28 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/network/index.rst.txt @@ -0,0 +1,66 @@ +algokit_utils.models.network +============================ + +.. py:module:: algokit_utils.models.network + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.network.AlgoClientNetworkConfig + algokit_utils.models.network.AlgoClientConfigs + + +Module Contents +--------------- + +.. py:class:: AlgoClientNetworkConfig + + Connection details for connecting to an {py:class}`algosdk.v2client.algod.AlgodClient` or + {py:class}`algosdk.v2client.indexer.IndexerClient` + + + .. py:attribute:: server + :type: str + + URL for the service e.g. `http://localhost` or `https://testnet-api.algonode.cloud` + + + + .. py:attribute:: token + :type: str | None + :value: None + + + API Token to authenticate with the service e.g '4001' or '8980' + + + + .. py:attribute:: port + :type: str | int | None + :value: None + + + + .. py:method:: full_url() -> str + + Returns the full URL for the service + + + +.. py:class:: AlgoClientConfigs + + .. py:attribute:: algod_config + :type: AlgoClientNetworkConfig + + + .. py:attribute:: indexer_config + :type: AlgoClientNetworkConfig | None + + + .. py:attribute:: kmd_config + :type: AlgoClientNetworkConfig | None + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/simulate/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/simulate/index.rst.txt new file mode 100644 index 00000000..8b95ecad --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/simulate/index.rst.txt @@ -0,0 +1,35 @@ +algokit_utils.models.simulate +============================= + +.. py:module:: algokit_utils.models.simulate + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.simulate.SimulationTrace + + +Module Contents +--------------- + +.. py:class:: SimulationTrace + + .. py:attribute:: app_budget_added + :type: int | None + + + .. py:attribute:: app_budget_consumed + :type: int | None + + + .. py:attribute:: failure_message + :type: str | None + + + .. py:attribute:: exec_trace + :type: dict[str, object] + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/state/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/state/index.rst.txt new file mode 100644 index 00000000..7e446c9b --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/state/index.rst.txt @@ -0,0 +1,114 @@ +algokit_utils.models.state +========================== + +.. py:module:: algokit_utils.models.state + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.models.state.TealTemplateParams + algokit_utils.models.state.BoxIdentifier + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.state.BoxName + algokit_utils.models.state.BoxValue + algokit_utils.models.state.DataTypeFlag + algokit_utils.models.state.BoxReference + + +Module Contents +--------------- + +.. py:class:: BoxName + + The name of the box + + + .. py:attribute:: name + :type: str + + The name of the box as a string + + + + .. py:attribute:: name_raw + :type: bytes + + The name of the box as raw bytes + + + + .. py:attribute:: name_base64 + :type: str + + The name of the box as a base64 encoded string + + + +.. py:class:: BoxValue + + The value of the box + + + .. py:attribute:: name + :type: BoxName + + The name of the box + + + + .. py:attribute:: value + :type: bytes + + The value of the box as raw bytes + + + +.. py:class:: DataTypeFlag + + Bases: :py:obj:`enum.IntEnum` + + + Enum where members are also (and must be) ints + + + .. py:attribute:: BYTES + :value: 1 + + + + .. py:attribute:: UINT + :value: 2 + + + +.. py:data:: TealTemplateParams + :type: TypeAlias + :value: Mapping[str, str | int | bytes] | dict[str, str | int | bytes] + + +.. py:data:: BoxIdentifier + :type: TypeAlias + :value: str | bytes | AccountTransactionSigner + + +.. py:class:: BoxReference(app_id: int, name: bytes | str) + + Bases: :py:obj:`algosdk.box_reference.BoxReference` + + + Represents a box reference with a foreign app index and the box name. + + Args: + app_index (int): index of the application in the foreign app array + name (bytes): key for the box in bytes + + diff --git a/docs/html/_sources/autoapi/algokit_utils/models/transaction/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/models/transaction/index.rst.txt new file mode 100644 index 00000000..9e5c0481 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/models/transaction/index.rst.txt @@ -0,0 +1,150 @@ +algokit_utils.models.transaction +================================ + +.. py:module:: algokit_utils.models.transaction + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.models.transaction.Arc2TransactionNote + algokit_utils.models.transaction.TransactionNoteData + algokit_utils.models.transaction.TransactionNote + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.models.transaction.BaseArc2Note + algokit_utils.models.transaction.StringFormatArc2Note + algokit_utils.models.transaction.JsonFormatArc2Note + algokit_utils.models.transaction.TransactionWrapper + algokit_utils.models.transaction.SendParams + + +Module Contents +--------------- + +.. py:class:: BaseArc2Note + + Bases: :py:obj:`TypedDict` + + + Base ARC-0002 transaction note structure + + + .. py:attribute:: dapp_name + :type: str + + +.. py:class:: StringFormatArc2Note + + Bases: :py:obj:`BaseArc2Note` + + + ARC-0002 note for string-based formats (m/b/u) + + + .. py:attribute:: format + :type: Literal['m', 'b', 'u'] + + + .. py:attribute:: data + :type: str + + +.. py:class:: JsonFormatArc2Note + + Bases: :py:obj:`BaseArc2Note` + + + ARC-0002 note for JSON format + + + .. py:attribute:: format + :type: Literal['j'] + + + .. py:attribute:: data + :type: str | dict[str, Any] | list[Any] | int | None + + +.. py:data:: Arc2TransactionNote + +.. py:data:: TransactionNoteData + +.. py:data:: TransactionNote + +.. py:class:: TransactionWrapper(transaction: algosdk.transaction.Transaction) + + Wrapper around algosdk.transaction.Transaction with optional property validators + + + .. py:property:: raw + :type: algosdk.transaction.Transaction + + + + .. py:property:: payment + :type: algosdk.transaction.PaymentTxn + + + + .. py:property:: keyreg + :type: algosdk.transaction.KeyregTxn + + + + .. py:property:: asset_config + :type: algosdk.transaction.AssetConfigTxn + + + + .. py:property:: asset_transfer + :type: algosdk.transaction.AssetTransferTxn + + + + .. py:property:: asset_freeze + :type: algosdk.transaction.AssetFreezeTxn + + + + .. py:property:: application_call + :type: algosdk.transaction.ApplicationCallTxn + + + + .. py:property:: state_proof + :type: algosdk.transaction.StateProofTxn + + + +.. py:class:: SendParams + + Bases: :py:obj:`TypedDict` + + + Parameters for sending a transaction + + + .. py:attribute:: max_rounds_to_wait + :type: int | None + + + .. py:attribute:: suppress_log + :type: bool | None + + + .. py:attribute:: populate_app_call_resources + :type: bool | None + + + .. py:attribute:: cover_app_call_inner_transaction_fees + :type: bool | None + + diff --git a/docs/html/_sources/autoapi/algokit_utils/protocols/account/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/protocols/account/index.rst.txt new file mode 100644 index 00000000..5d72bfef --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/protocols/account/index.rst.txt @@ -0,0 +1,42 @@ +algokit_utils.protocols.account +=============================== + +.. py:module:: algokit_utils.protocols.account + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.protocols.account.TransactionSignerAccountProtocol + + +Module Contents +--------------- + +.. py:class:: TransactionSignerAccountProtocol + + Bases: :py:obj:`Protocol` + + + An account that has a transaction signer. + Implemented by SigningAccount, LogicSigAccount, MultiSigAccount and TransactionSignerAccount abstractions. + + + .. py:property:: address + :type: str + + + The address of the account. + + + + .. py:property:: signer + :type: algosdk.atomic_transaction_composer.TransactionSigner + + + The transaction signer for the account. + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/protocols/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/protocols/index.rst.txt new file mode 100644 index 00000000..b03c5e89 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/protocols/index.rst.txt @@ -0,0 +1,16 @@ +algokit_utils.protocols +======================= + +.. py:module:: algokit_utils.protocols + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/protocols/account/index + /autoapi/algokit_utils/protocols/typed_clients/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/protocols/typed_clients/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/protocols/typed_clients/index.rst.txt new file mode 100644 index 00000000..bbf65f5c --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/protocols/typed_clients/index.rst.txt @@ -0,0 +1,105 @@ +algokit_utils.protocols.typed_clients +===================================== + +.. py:module:: algokit_utils.protocols.typed_clients + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.protocols.typed_clients.TypedAppClientProtocol + algokit_utils.protocols.typed_clients.TypedAppFactoryProtocol + + +Module Contents +--------------- + +.. py:class:: TypedAppClientProtocol(*, app_id: int, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, algorand: algokit_utils.algorand.AlgorandClient, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None) + + Bases: :py:obj:`Protocol` + + + Base class for protocol classes. + + Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing). + + For example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing.runtime_checkable act as simple-minded runtime protocols that check + only the presence of given attributes, ignoring their type signatures. + Protocol classes can be generic, they are defined as:: + + class GenProto[T](Protocol): + def meth(self) -> T: + ... + + + .. py:method:: from_creator_and_name(*, creator_address: str, app_name: str, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, ignore_cache: bool | None = None, app_lookup_cache: algokit_utils.applications.app_deployer.ApplicationLookup | None = None, algorand: algokit_utils.algorand.AlgorandClient) -> typing_extensions.Self + :classmethod: + + + + .. py:method:: from_network(*, app_name: str | None = None, default_sender: str | None = None, default_signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None, approval_source_map: algosdk.source_map.SourceMap | None = None, clear_source_map: algosdk.source_map.SourceMap | None = None, algorand: algokit_utils.algorand.AlgorandClient) -> typing_extensions.Self + :classmethod: + + + +.. py:class:: TypedAppFactoryProtocol(algorand: algokit_utils.algorand.AlgorandClient, **kwargs: Any) + + Bases: :py:obj:`Protocol`, :py:obj:`Generic`\ [\ :py:obj:`CreateParamsT`\ , :py:obj:`UpdateParamsT`\ , :py:obj:`DeleteParamsT`\ ] + + + Base class for protocol classes. + + Protocol classes are defined as:: + + class Proto(Protocol): + def meth(self) -> int: + ... + + Such classes are primarily used with static type checkers that recognize + structural subtyping (static duck-typing). + + For example:: + + class C: + def meth(self) -> int: + return 0 + + def func(x: Proto) -> int: + return x.meth() + + func(C()) # Passes static type check + + See PEP 544 for details. Protocol classes decorated with + @typing.runtime_checkable act as simple-minded runtime protocols that check + only the presence of given attributes, ignoring their type signatures. + Protocol classes can be generic, they are defined as:: + + class GenProto[T](Protocol): + def meth(self) -> T: + ... + + + .. py:method:: deploy(*, on_update: algokit_utils.applications.app_deployer.OnUpdate | None = None, on_schema_break: algokit_utils.applications.app_deployer.OnSchemaBreak | None = None, create_params: CreateParamsT | None = None, update_params: UpdateParamsT | None = None, delete_params: DeleteParamsT | None = None, existing_deployments: algokit_utils.applications.app_deployer.ApplicationLookup | None = None, ignore_cache: bool = False, app_name: str | None = None, send_params: algokit_utils.models.SendParams | None = None, compilation_params: algokit_utils.applications.app_client.AppClientCompilationParams | None = None) -> tuple[TypedAppClientProtocol, algokit_utils.applications.app_factory.AppFactoryDeployResult] + + diff --git a/docs/html/_sources/autoapi/algokit_utils/transactions/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/transactions/index.rst.txt new file mode 100644 index 00000000..7ab9a27f --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/transactions/index.rst.txt @@ -0,0 +1,17 @@ +algokit_utils.transactions +========================== + +.. py:module:: algokit_utils.transactions + + +Submodules +---------- + +.. toctree:: + :maxdepth: 1 + + /autoapi/algokit_utils/transactions/transaction_composer/index + /autoapi/algokit_utils/transactions/transaction_creator/index + /autoapi/algokit_utils/transactions/transaction_sender/index + + diff --git a/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_composer/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_composer/index.rst.txt new file mode 100644 index 00000000..1889d7cb --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_composer/index.rst.txt @@ -0,0 +1,1591 @@ +algokit_utils.transactions.transaction_composer +=============================================== + +.. py:module:: algokit_utils.transactions.transaction_composer + + +Attributes +---------- + +.. autoapisummary:: + + algokit_utils.transactions.transaction_composer.MethodCallParams + algokit_utils.transactions.transaction_composer.AppMethodCallTransactionArgument + algokit_utils.transactions.transaction_composer.TxnParams + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.transactions.transaction_composer.PaymentParams + algokit_utils.transactions.transaction_composer.AssetCreateParams + algokit_utils.transactions.transaction_composer.AssetConfigParams + algokit_utils.transactions.transaction_composer.AssetFreezeParams + algokit_utils.transactions.transaction_composer.AssetDestroyParams + algokit_utils.transactions.transaction_composer.OnlineKeyRegistrationParams + algokit_utils.transactions.transaction_composer.OfflineKeyRegistrationParams + algokit_utils.transactions.transaction_composer.AssetTransferParams + algokit_utils.transactions.transaction_composer.AssetOptInParams + algokit_utils.transactions.transaction_composer.AssetOptOutParams + algokit_utils.transactions.transaction_composer.AppCallParams + algokit_utils.transactions.transaction_composer.AppCreateSchema + algokit_utils.transactions.transaction_composer.AppCreateParams + algokit_utils.transactions.transaction_composer.AppUpdateParams + algokit_utils.transactions.transaction_composer.AppDeleteParams + algokit_utils.transactions.transaction_composer.AppCallMethodCallParams + algokit_utils.transactions.transaction_composer.AppCreateMethodCallParams + algokit_utils.transactions.transaction_composer.AppUpdateMethodCallParams + algokit_utils.transactions.transaction_composer.AppDeleteMethodCallParams + algokit_utils.transactions.transaction_composer.BuiltTransactions + algokit_utils.transactions.transaction_composer.TransactionComposerBuildResult + algokit_utils.transactions.transaction_composer.SendAtomicTransactionComposerResults + algokit_utils.transactions.transaction_composer.TransactionComposer + + +Functions +--------- + +.. autoapisummary:: + + algokit_utils.transactions.transaction_composer.calculate_extra_program_pages + algokit_utils.transactions.transaction_composer.populate_app_call_resources + algokit_utils.transactions.transaction_composer.prepare_group_for_sending + algokit_utils.transactions.transaction_composer.send_atomic_transaction_composer + + +Module Contents +--------------- + +.. py:class:: PaymentParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for a payment transaction. + + + .. py:attribute:: receiver + :type: str + + The account that will receive the ALGO + + + + .. py:attribute:: amount + :type: algokit_utils.models.amount.AlgoAmount + + Amount to send + + + + .. py:attribute:: close_remainder_to + :type: str | None + :value: None + + + If given, close the sender account and send the remaining balance to this address, defaults to None + + + +.. py:class:: AssetCreateParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for creating a new asset. + + + .. py:attribute:: total + :type: int + + The total amount of the smallest divisible unit to create + + + + .. py:attribute:: asset_name + :type: str | None + :value: None + + + The full name of the asset + + + + .. py:attribute:: unit_name + :type: str | None + :value: None + + + The short ticker name for the asset + + + + .. py:attribute:: url + :type: str | None + :value: None + + + The metadata URL for the asset + + + + .. py:attribute:: decimals + :type: int | None + :value: None + + + The amount of decimal places the asset should have + + + + .. py:attribute:: default_frozen + :type: bool | None + :value: None + + + Whether the asset is frozen by default in the creator address + + + + .. py:attribute:: manager + :type: str | None + :value: None + + + The address that can change the manager, reserve, clawback, and freeze addresses + + + + .. py:attribute:: reserve + :type: str | None + :value: None + + + The address that holds the uncirculated supply + + + + .. py:attribute:: freeze + :type: str | None + :value: None + + + The address that can freeze the asset in any account + + + + .. py:attribute:: clawback + :type: str | None + :value: None + + + The address that can clawback the asset from any account + + + + .. py:attribute:: metadata_hash + :type: bytes | None + :value: None + + + Hash of the metadata contained in the metadata URL + + + +.. py:class:: AssetConfigParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for configuring an existing asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: manager + :type: str | None + :value: None + + + The address that can change the manager, reserve, clawback, and freeze addresses, defaults to None + + + + .. py:attribute:: reserve + :type: str | None + :value: None + + + The address that holds the uncirculated supply, defaults to None + + + + .. py:attribute:: freeze + :type: str | None + :value: None + + + The address that can freeze the asset in any account, defaults to None + + + + .. py:attribute:: clawback + :type: str | None + :value: None + + + The address that can clawback the asset from any account, defaults to None + + + +.. py:class:: AssetFreezeParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for freezing an asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: account + :type: str + + The account to freeze or unfreeze + + + + .. py:attribute:: frozen + :type: bool + + Whether the assets in the account should be frozen + + + +.. py:class:: AssetDestroyParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for destroying an asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + +.. py:class:: OnlineKeyRegistrationParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for online key registration. + + + .. py:attribute:: vote_key + :type: str + + The root participation public key + + + + .. py:attribute:: selection_key + :type: str + + The VRF public key + + + + .. py:attribute:: vote_first + :type: int + + The first round that the participation key is valid + + + + .. py:attribute:: vote_last + :type: int + + The last round that the participation key is valid + + + + .. py:attribute:: vote_key_dilution + :type: int + + The dilution for the 2-level participation key + + + + .. py:attribute:: state_proof_key + :type: bytes | None + :value: None + + + The 64 byte state proof public key commitment, defaults to None + + + +.. py:class:: OfflineKeyRegistrationParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for offline key registration. + + + .. py:attribute:: prevent_account_from_ever_participating_again + :type: bool + + Whether to prevent the account from ever participating again + + + +.. py:class:: AssetTransferParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for transferring an asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: amount + :type: int + + The amount of the asset to transfer (smallest divisible unit) + + + + .. py:attribute:: receiver + :type: str + + The account to send the asset to + + + + .. py:attribute:: clawback_target + :type: str | None + :value: None + + + The account to take the asset from, defaults to None + + + + .. py:attribute:: close_asset_to + :type: str | None + :value: None + + + The account to close the asset to, defaults to None + + + +.. py:class:: AssetOptInParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for opting into an asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + +.. py:class:: AssetOptOutParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for opting out of an asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the asset + + + + .. py:attribute:: creator + :type: str + + The creator address of the asset + + + +.. py:class:: AppCallParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for calling an application. + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete + + The OnComplete action, defaults to None + + + + .. py:attribute:: app_id + :type: int | None + :value: None + + + The ID of the application, defaults to None + + + + .. py:attribute:: approval_program + :type: str | bytes | None + :value: None + + + The program to execute for all OnCompletes other than ClearState, defaults to None + + + + .. py:attribute:: clear_state_program + :type: str | bytes | None + :value: None + + + The program to execute for ClearState OnComplete, defaults to None + + + + .. py:attribute:: schema + :type: dict[str, int] | None + :value: None + + + The state schema for the app, defaults to None + + + + .. py:attribute:: args + :type: list[bytes] | None + :value: None + + + Application arguments, defaults to None + + + + .. py:attribute:: account_references + :type: list[str] | None + :value: None + + + Account references, defaults to None + + + + .. py:attribute:: app_references + :type: list[int] | None + :value: None + + + App references, defaults to None + + + + .. py:attribute:: asset_references + :type: list[int] | None + :value: None + + + Asset references, defaults to None + + + + .. py:attribute:: extra_pages + :type: int | None + :value: None + + + Number of extra pages required for the programs, defaults to None + + + + .. py:attribute:: box_references + :type: list[algokit_utils.models.state.BoxReference | algokit_utils.models.state.BoxIdentifier] | None + :value: None + + + Box references, defaults to None + + + +.. py:class:: AppCreateSchema + + Bases: :py:obj:`TypedDict` + + + dict() -> new empty dictionary + dict(mapping) -> new dictionary initialized from a mapping object's + (key, value) pairs + dict(iterable) -> new dictionary initialized as if via: + d = {} + for k, v in iterable: + d[k] = v + dict(**kwargs) -> new dictionary initialized with the name=value pairs + in the keyword argument list. For example: dict(one=1, two=2) + + + .. py:attribute:: global_ints + :type: int + + The number of global ints in the schema + + + + .. py:attribute:: global_byte_slices + :type: int + + The number of global byte slices in the schema + + + + .. py:attribute:: local_ints + :type: int + + The number of local ints in the schema + + + + .. py:attribute:: local_byte_slices + :type: int + + The number of local byte slices in the schema + + + +.. py:class:: AppCreateParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for creating an application. + + + .. py:attribute:: approval_program + :type: str | bytes + + The program to execute for all OnCompletes other than ClearState + + + + .. py:attribute:: clear_state_program + :type: str | bytes + + The program to execute for ClearState OnComplete + + + + .. py:attribute:: schema + :type: AppCreateSchema | None + :value: None + + + The state schema for the app, defaults to None + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete | None + :value: None + + + The OnComplete action, defaults to None + + + + .. py:attribute:: args + :type: list[bytes] | None + :value: None + + + Application arguments, defaults to None + + + + .. py:attribute:: account_references + :type: list[str] | None + :value: None + + + Account references, defaults to None + + + + .. py:attribute:: app_references + :type: list[int] | None + :value: None + + + App references, defaults to None + + + + .. py:attribute:: asset_references + :type: list[int] | None + :value: None + + + Asset references, defaults to None + + + + .. py:attribute:: box_references + :type: list[algokit_utils.models.state.BoxReference | algokit_utils.models.state.BoxIdentifier] | None + :value: None + + + Box references, defaults to None + + + + .. py:attribute:: extra_program_pages + :type: int | None + :value: None + + + Number of extra pages required for the programs, defaults to None + + + +.. py:class:: AppUpdateParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for updating an application. + + + .. py:attribute:: app_id + :type: int + + The ID of the application + + + + .. py:attribute:: approval_program + :type: str | bytes + + The program to execute for all OnCompletes other than ClearState + + + + .. py:attribute:: clear_state_program + :type: str | bytes + + The program to execute for ClearState OnComplete + + + + .. py:attribute:: args + :type: list[bytes] | None + :value: None + + + Application arguments, defaults to None + + + + .. py:attribute:: account_references + :type: list[str] | None + :value: None + + + Account references, defaults to None + + + + .. py:attribute:: app_references + :type: list[int] | None + :value: None + + + App references, defaults to None + + + + .. py:attribute:: asset_references + :type: list[int] | None + :value: None + + + Asset references, defaults to None + + + + .. py:attribute:: box_references + :type: list[algokit_utils.models.state.BoxReference | algokit_utils.models.state.BoxIdentifier] | None + :value: None + + + Box references, defaults to None + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete | None + :value: None + + + The OnComplete action, defaults to None + + + +.. py:class:: AppDeleteParams + + Bases: :py:obj:`_CommonTxnParams` + + + Parameters for deleting an application. + + + .. py:attribute:: app_id + :type: int + + The ID of the application + + + + .. py:attribute:: args + :type: list[bytes] | None + :value: None + + + Application arguments, defaults to None + + + + .. py:attribute:: account_references + :type: list[str] | None + :value: None + + + Account references, defaults to None + + + + .. py:attribute:: app_references + :type: list[int] | None + :value: None + + + App references, defaults to None + + + + .. py:attribute:: asset_references + :type: list[int] | None + :value: None + + + Asset references, defaults to None + + + + .. py:attribute:: box_references + :type: list[algokit_utils.models.state.BoxReference | algokit_utils.models.state.BoxIdentifier] | None + :value: None + + + Box references, defaults to None + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete + + The OnComplete action, defaults to DeleteApplicationOC + + + +.. py:class:: AppCallMethodCallParams + + Bases: :py:obj:`_BaseAppMethodCall` + + + Parameters for a regular ABI method call. + + + .. py:attribute:: app_id + :type: int + + The ID of the application + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete | None + :value: None + + + The OnComplete action, defaults to None + + + +.. py:class:: AppCreateMethodCallParams + + Bases: :py:obj:`_BaseAppMethodCall` + + + Parameters for an ABI method call that creates an application. + + + .. py:attribute:: approval_program + :type: str | bytes + + The program to execute for all OnCompletes other than ClearState + + + + .. py:attribute:: clear_state_program + :type: str | bytes + + The program to execute for ClearState OnComplete + + + + .. py:attribute:: schema + :type: AppCreateSchema | None + :value: None + + + The state schema for the app, defaults to None + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete | None + :value: None + + + The OnComplete action (cannot be ClearState), defaults to None + + + + .. py:attribute:: extra_program_pages + :type: int | None + :value: None + + + Number of extra pages required for the programs, defaults to None + + + +.. py:class:: AppUpdateMethodCallParams + + Bases: :py:obj:`_BaseAppMethodCall` + + + Parameters for an ABI method call that updates an application. + + + .. py:attribute:: app_id + :type: int + + The ID of the application + + + + .. py:attribute:: approval_program + :type: str | bytes + + The program to execute for all OnCompletes other than ClearState + + + + .. py:attribute:: clear_state_program + :type: str | bytes + + The program to execute for ClearState OnComplete + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete + + The OnComplete action + + + +.. py:class:: AppDeleteMethodCallParams + + Bases: :py:obj:`_BaseAppMethodCall` + + + Parameters for an ABI method call that deletes an application. + + + .. py:attribute:: app_id + :type: int + + The ID of the application + + + + .. py:attribute:: on_complete + :type: algosdk.transaction.OnComplete + + The OnComplete action + + + +.. py:data:: MethodCallParams + +.. py:data:: AppMethodCallTransactionArgument + +.. py:data:: TxnParams + +.. py:class:: BuiltTransactions + + Set of transactions built by TransactionComposer. + + + .. py:attribute:: transactions + :type: list[algosdk.transaction.Transaction] + + The built transactions + + + + .. py:attribute:: method_calls + :type: dict[int, algosdk.abi.Method] + + Map of transaction index to ABI method + + + + .. py:attribute:: signers + :type: dict[int, algosdk.atomic_transaction_composer.TransactionSigner] + + Map of transaction index to TransactionSigner + + + +.. py:class:: TransactionComposerBuildResult + + Result of building transactions with TransactionComposer. + + + .. py:attribute:: atc + :type: algosdk.atomic_transaction_composer.AtomicTransactionComposer + + The AtomicTransactionComposer instance + + + + .. py:attribute:: transactions + :type: list[algosdk.atomic_transaction_composer.TransactionWithSigner] + + The list of transactions with signers + + + + .. py:attribute:: method_calls + :type: dict[int, algosdk.abi.Method] + + Map of transaction index to ABI method + + + +.. py:class:: SendAtomicTransactionComposerResults + + Results from sending an AtomicTransactionComposer transaction group. + + + .. py:attribute:: group_id + :type: str + + The group ID if this was a transaction group + + + + .. py:attribute:: confirmations + :type: list[algosdk.v2client.algod.AlgodResponseType] + + The confirmation info for each transaction + + + + .. py:attribute:: tx_ids + :type: list[str] + + The transaction IDs that were sent + + + + .. py:attribute:: transactions + :type: list[algokit_utils.models.transaction.TransactionWrapper] + + The transactions that were sent + + + + .. py:attribute:: returns + :type: list[algokit_utils.applications.abi.ABIReturn] + + The ABI return values from any ABI method calls + + + + .. py:attribute:: simulate_response + :type: dict[str, Any] | None + :value: None + + + The simulation response if simulation was performed, defaults to None + + + +.. py:function:: calculate_extra_program_pages(approval: bytes | None, clear: bytes | None) -> int + + Calculate minimum number of extra_pages required for provided approval and clear programs + + +.. py:function:: populate_app_call_resources(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, algod: algosdk.v2client.algod.AlgodClient) -> algosdk.atomic_transaction_composer.AtomicTransactionComposer + + Populate application call resources based on simulation results. + + :param atc: The AtomicTransactionComposer containing transactions + :param algod: Algod client for simulation + :return: Modified AtomicTransactionComposer with populated resources + + +.. py:function:: prepare_group_for_sending(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, algod: algosdk.v2client.algod.AlgodClient, populate_app_call_resources: bool | None = None, cover_app_call_inner_transaction_fees: bool | None = None, additional_atc_context: AdditionalAtcContext | None = None) -> algosdk.atomic_transaction_composer.AtomicTransactionComposer + + Prepare a transaction group for sending by handling execution info and resources. + + :param atc: The AtomicTransactionComposer containing transactions + :param algod: Algod client for simulation + :param populate_app_call_resources: Whether to populate app call resources + :param cover_app_call_inner_transaction_fees: Whether to cover inner txn fees + :param additional_atc_context: Additional context for the AtomicTransactionComposer + :return: Modified AtomicTransactionComposer ready for sending + + +.. py:function:: send_atomic_transaction_composer(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer, algod: algosdk.v2client.algod.AlgodClient, *, max_rounds_to_wait: int | None = 5, skip_waiting: bool = False, suppress_log: bool | None = None, populate_app_call_resources: bool | None = None, cover_app_call_inner_transaction_fees: bool | None = None, additional_atc_context: AdditionalAtcContext | None = None) -> SendAtomicTransactionComposerResults + + Send an AtomicTransactionComposer transaction group. + + Executes a group of transactions atomically using the AtomicTransactionComposer. + + :param atc: The AtomicTransactionComposer instance containing the transaction group to send + :param algod: The Algod client to use for sending the transactions + :param max_rounds_to_wait: Maximum number of rounds to wait for confirmation, defaults to 5 + :param skip_waiting: If True, don't wait for transaction confirmation, defaults to False + :param suppress_log: If True, suppress logging, defaults to None + :param populate_app_call_resources: If True, populate app call resources, defaults to None + :param cover_app_call_inner_transaction_fees: If True, cover app call inner transaction fees, defaults to None + :param additional_atc_context: Additional context for the AtomicTransactionComposer + :return: Results from sending the transaction group + :raises Exception: If there is an error sending the transactions + :raises error: If there is an error from the Algorand node + + +.. py:class:: TransactionComposer(algod: algosdk.v2client.algod.AlgodClient, get_signer: collections.abc.Callable[[str], algosdk.atomic_transaction_composer.TransactionSigner], get_suggested_params: collections.abc.Callable[[], algosdk.transaction.SuggestedParams] | None = None, default_validity_window: int | None = None, app_manager: algokit_utils.applications.app_manager.AppManager | None = None) + + A class for composing and managing Algorand transactions. + + Provides a high-level interface for building and executing transaction groups using the Algosdk library. + Supports various transaction types including payments, asset operations, application calls, and key registrations. + + :param algod: An instance of AlgodClient used to get suggested params and send transactions + :param get_signer: A function that takes an address and returns a TransactionSigner for that address + :param get_suggested_params: Optional function to get suggested transaction parameters, + defaults to using algod.suggested_params() + :param default_validity_window: Optional default validity window for transactions in rounds, defaults to 10 + :param app_manager: Optional AppManager instance for compiling TEAL programs, defaults to None + + + .. py:method:: add_transaction(transaction: algosdk.transaction.Transaction, signer: algosdk.atomic_transaction_composer.TransactionSigner | None = None) -> TransactionComposer + + Add a raw transaction to the composer. + + :param transaction: The transaction to add + :param signer: Optional transaction signer, defaults to getting signer from transaction sender + :return: The transaction composer instance for chaining + + :example: + >>> composer.add_transaction(transaction) + + + + .. py:method:: add_payment(params: PaymentParams) -> TransactionComposer + + Add a payment transaction. + + :example: + >>> params = PaymentParams( + ... sender="SENDER_ADDRESS", + ... receiver="RECEIVER_ADDRESS", + ... amount=AlgoAmount.from_algo(1), + ... close_remainder_to="CLOSE_ADDRESS" + ... ... (see PaymentParams for more options) + ... ) + >>> composer.add_payment(params) + + :param params: The payment transaction parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_create(params: AssetCreateParams) -> TransactionComposer + + Add an asset creation transaction. + + :example: + >>> params = AssetCreateParams( + ... sender="SENDER_ADDRESS", + ... total=1000, + ... asset_name="MyAsset", + ... unit_name="MA", + ... url="https://example.com", + ... decimals=0, + ... default_frozen=False, + ... manager="MANAGER_ADDRESS", + ... reserve="RESERVE_ADDRESS", + ... freeze="FREEZE_ADDRESS", + ... clawback="CLAWBACK_ADDRESS" + ... ... (see AssetCreateParams for more options) + >>> composer.add_asset_create(params) + + :param params: The asset creation parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_config(params: AssetConfigParams) -> TransactionComposer + + Add an asset configuration transaction. + + :example: + >>> params = AssetConfigParams( + ... sender="SENDER_ADDRESS", + ... asset_id=123456, + ... manager="NEW_MANAGER_ADDRESS", + ... reserve="NEW_RESERVE_ADDRESS", + ... freeze="NEW_FREEZE_ADDRESS", + ... clawback="NEW_CLAWBACK_ADDRESS" + ... ... (see AssetConfigParams for more options) + ... ) + >>> composer.add_asset_config(params) + + :param params: The asset configuration parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_freeze(params: AssetFreezeParams) -> TransactionComposer + + Add an asset freeze transaction. + + :example: + >>> params = AssetFreezeParams( + ... sender="SENDER_ADDRESS", + ... asset_id=123456, + ... account="ACCOUNT_TO_FREEZE", + ... frozen=True + ... ... (see AssetFreezeParams for more options) + ... ) + >>> composer.add_asset_freeze(params) + + :param params: The asset freeze parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_destroy(params: AssetDestroyParams) -> TransactionComposer + + Add an asset destruction transaction. + + :example: + >>> params = AssetDestroyParams( + ... sender="SENDER_ADDRESS", + ... asset_id=123456 + ... ... (see AssetDestroyParams for more options) + >>> composer.add_asset_destroy(params) + + :param params: The asset destruction parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_transfer(params: AssetTransferParams) -> TransactionComposer + + Add an asset transfer transaction. + + :example: + >>> params = AssetTransferParams( + ... sender="SENDER_ADDRESS", + ... asset_id=123456, + ... amount=10, + ... receiver="RECEIVER_ADDRESS", + ... clawback_target="CLAWBACK_TARGET_ADDRESS", + ... close_asset_to="CLOSE_ADDRESS" + ... ... (see AssetTransferParams for more options) + >>> composer.add_asset_transfer(params) + + :param params: The asset transfer parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_opt_in(params: AssetOptInParams) -> TransactionComposer + + Add an asset opt-in transaction. + + :example: + >>> params = AssetOptInParams( + ... sender="SENDER_ADDRESS", + ... asset_id=123456 + ... ... (see AssetOptInParams for more options) + ... ) + >>> composer.add_asset_opt_in(params) + + :param params: The asset opt-in parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_asset_opt_out(params: AssetOptOutParams) -> TransactionComposer + + Add an asset opt-out transaction. + + :example: + >>> params = AssetOptOutParams( + ... sender="SENDER_ADDRESS", + ... asset_id=123456, + ... creator="CREATOR_ADDRESS" + ... ... (see AssetOptOutParams for more options) + >>> composer.add_asset_opt_out(params) + + :param params: The asset opt-out parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_create(params: AppCreateParams) -> TransactionComposer + + Add an application creation transaction. + + :example: + >>> params = AppCreateParams( + ... sender="SENDER_ADDRESS", + ... approval_program="TEAL_APPROVAL_CODE", + ... clear_state_program="TEAL_CLEAR_CODE", + ... schema={'global_ints': 1, 'global_byte_slices': 1, 'local_ints': 1, 'local_byte_slices': 1}, + ... on_complete=OnComplete.NoOpOC, + ... args=[b'arg1'], + ... account_references=["ACCOUNT1"], + ... app_references=[789], + ... asset_references=[123], + ... box_references=[], + ... extra_program_pages=0 + ... ... (see AppCreateParams for more options) + ... ) + >>> composer.add_app_create(params) + + :param params: The application creation parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_update(params: AppUpdateParams) -> TransactionComposer + + Add an application update transaction. + + :example: + >>> params = AppUpdateParams( + ... sender="SENDER_ADDRESS", + ... app_id=789, + ... approval_program="TEAL_NEW_APPROVAL_CODE", + ... clear_state_program="TEAL_NEW_CLEAR_CODE", + ... args=[b'new_arg1'], + ... account_references=["ACCOUNT1"], + ... app_references=[789], + ... asset_references=[123], + ... box_references=[], + ... on_complete=OnComplete.UpdateApplicationOC + ... ... (see AppUpdateParams for more options) + >>> composer.add_app_update(params) + + :param params: The application update parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_delete(params: AppDeleteParams) -> TransactionComposer + + Add an application deletion transaction. + + :example: + >>> params = AppDeleteParams( + ... sender="SENDER_ADDRESS", + ... app_id=789, + ... args=[b'delete_arg'], + ... account_references=["ACCOUNT1"], + ... app_references=[789], + ... asset_references=[123], + ... box_references=[], + ... on_complete=OnComplete.DeleteApplicationOC + ... ... (see AppDeleteParams for more options) + >>> composer.add_app_delete(params) + + :param params: The application deletion parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_call(params: AppCallParams) -> TransactionComposer + + Add an application call transaction. + + :example: + >>> params = AppCallParams( + ... sender="SENDER_ADDRESS", + ... on_complete=OnComplete.NoOpOC, + ... app_id=789, + ... approval_program="TEAL_APPROVAL_CODE", + ... clear_state_program="TEAL_CLEAR_CODE", + ... schema={'global_ints': 1, 'global_byte_slices': 1, 'local_ints': 1, 'local_byte_slices': 1}, + ... ... (see AppCallParams for more options) + ... ) + >>> composer.add_app_call(params) + + :param params: The application call parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_create_method_call(params: AppCreateMethodCallParams) -> TransactionComposer + + Add an application creation method call transaction. + + :param params: The application creation method call parameters + :return: The transaction composer instance for chaining + + :example: + >>> # Basic example + >>> method = algosdk.abi.Method( + ... name="method", + ... args=[...], + ... returns="string" + ... ) + >>> composer.add_app_create_method_call( + ... AppCreateMethodCallParams( + ... sender="CREATORADDRESS", + ... approval_program="TEALCODE", + ... clear_state_program="TEALCODE", + ... method=method, + ... args=["arg1_value"] + ... ) + ... ) + >>> + >>> # Advanced example + >>> method = ABIMethod( + ... name="method", + ... args=[{"name": "arg1", "type": "string"}], + ... returns={"type": "string"} + ... ) + >>> composer.add_app_create_method_call( + ... AppCreateMethodCallParams( + ... sender="CREATORADDRESS", + ... method=method, + ... args=["arg1_value"], + ... approval_program="TEALCODE", + ... clear_state_program="TEALCODE", + ... schema={ + ... "global_ints": 1, + ... "global_byte_slices": 2, + ... "local_ints": 3, + ... "local_byte_slices": 4 + ... }, + ... extra_pages=1, + ... on_complete=OnComplete.OptInOC, + ... args=[bytes([1, 2, 3, 4])], + ... account_references=["ACCOUNT_1"], + ... app_references=[123, 1234], + ... asset_references=[12345], + ... box_references=["box1", {"app_id": 1234, "name": "box2"}], + ... lease="lease", + ... note="note", + ... first_valid_round=1000, + ... validity_window=10, + ... extra_fee=AlgoAmount.from_micro_algos(1000), + ... static_fee=AlgoAmount.from_micro_algos(1000), + ... max_fee=AlgoAmount.from_micro_algos(3000) + ... ) + ... ) + + + + .. py:method:: add_app_update_method_call(params: AppUpdateMethodCallParams) -> TransactionComposer + + Add an application update method call transaction. + + :param params: The application update method call parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_delete_method_call(params: AppDeleteMethodCallParams) -> TransactionComposer + + Add an application deletion method call transaction. + + :param params: The application deletion method call parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_app_call_method_call(params: AppCallMethodCallParams) -> TransactionComposer + + Add an application call method call transaction. + + :param params: The application call method call parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_online_key_registration(params: OnlineKeyRegistrationParams) -> TransactionComposer + + Add an online key registration transaction. + + :param params: The online key registration parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_offline_key_registration(params: OfflineKeyRegistrationParams) -> TransactionComposer + + Add an offline key registration transaction. + + :param params: The offline key registration parameters + :return: The transaction composer instance for chaining + + + + .. py:method:: add_atc(atc: algosdk.atomic_transaction_composer.AtomicTransactionComposer) -> TransactionComposer + + Add an existing AtomicTransactionComposer's transactions. + + :param atc: The AtomicTransactionComposer to add + :return: The transaction composer instance for chaining + + :example: + >>> atc = AtomicTransactionComposer() + >>> atc.add_transaction(TransactionWithSigner(transaction, signer)) + >>> composer.add_atc(atc) + + + + .. py:method:: count() -> int + + Get the total number of transactions. + + :return: The number of transactions + + + + .. py:method:: build() -> TransactionComposerBuildResult + + Build the transaction group. + + :return: The built transaction group result + + + + .. py:method:: rebuild() -> TransactionComposerBuildResult + + Rebuild the transaction group from scratch. + + :return: The rebuilt transaction group result + + + + .. py:method:: build_transactions() -> BuiltTransactions + + Build and return the transactions without executing them. + + :return: The built transactions result + + + + .. py:method:: execute(*, max_rounds_to_wait: int | None = None) -> SendAtomicTransactionComposerResults + + + .. py:method:: send(params: algokit_utils.models.transaction.SendParams | None = None) -> SendAtomicTransactionComposerResults + + Send the transaction group to the network. + + :param params: Parameters for the send operation + :return: The transaction send results + :raises Exception: If the transaction fails + + + + .. py:method:: simulate(allow_more_logs: bool | None = None, allow_empty_signatures: bool | None = None, allow_unnamed_resources: bool | None = None, extra_opcode_budget: int | None = None, exec_trace_config: algosdk.v2client.models.SimulateTraceConfig | None = None, simulation_round: int | None = None, skip_signatures: bool | None = None) -> SendAtomicTransactionComposerResults + + Simulate transaction group execution with configurable validation rules. + + :param allow_more_logs: Whether to allow more logs than the standard limit + :param allow_empty_signatures: Whether to allow transactions with empty signatures + :param allow_unnamed_resources: Whether to allow unnamed resources. + :param extra_opcode_budget: Additional opcode budget to allocate + :param exec_trace_config: Configuration for execution tracing + :param simulation_round: Round number to simulate at + :param skip_signatures: Whether to skip signature validation + :return: The simulation results + + :example: + >>> result = composer.simulate(extra_opcode_budget=1000, skip_signatures=True, ...) + + + + .. py:method:: arc2_note(note: algokit_utils.models.transaction.Arc2TransactionNote) -> bytes + :staticmethod: + + + Create an encoded transaction note that follows the ARC-2 spec. + + https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md + + :param note: The ARC-2 note to encode + :return: The encoded note bytes + :raises ValueError: If the dapp_name is invalid + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_creator/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_creator/index.rst.txt new file mode 100644 index 00000000..4f799016 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_creator/index.rst.txt @@ -0,0 +1,685 @@ +algokit_utils.transactions.transaction_creator +============================================== + +.. py:module:: algokit_utils.transactions.transaction_creator + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.transactions.transaction_creator.AlgorandClientTransactionCreator + + +Module Contents +--------------- + +.. py:class:: AlgorandClientTransactionCreator(new_group: collections.abc.Callable[[], algokit_utils.transactions.transaction_composer.TransactionComposer]) + + A creator for Algorand transactions. + + Provides methods to create various types of Algorand transactions including payments, + asset operations, application calls and key registrations. + + :param new_group: A lambda that starts a new TransactionComposer transaction group + + :example: + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> creator.payment(PaymentParams(sender="sender", receiver="receiver", amount=AlgoAmount.from_algo(1))) + + + .. py:property:: payment + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.PaymentParams], algosdk.transaction.Transaction] + + + Create a payment transaction to transfer Algo between accounts. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> creator.payment(PaymentParams(sender="sender", receiver="receiver", amount=AlgoAmount.from_algo(4))) + :example: + >>> #Advanced example + >>> creator.payment(PaymentParams( + sender="SENDERADDRESS", + receiver="RECEIVERADDRESS", + amount=AlgoAmount.from_algo(4), + close_remainder_to="CLOSEREMAINDERTOADDRESS", + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_create + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetCreateParams], algosdk.transaction.Transaction] + + + Create a create Algorand Standard Asset transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetCreateParams(sender="SENDER_ADDRESS", total=1000) + >>> txn = creator.asset_create(params) + :example: + >>> #Advanced example + >>> creator.asset_create(AssetCreateParams( + sender="SENDER_ADDRESS", + total=1000, + asset_name="MyAsset", + unit_name="MA", + url="https://example.com/asset", + decimals=0, + default_frozen=False, + manager="MANAGER_ADDRESS", + reserve="RESERVE_ADDRESS", + freeze="FREEZE_ADDRESS", + clawback="CLAWBACK_ADDRESS", + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_config + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetConfigParams], algosdk.transaction.Transaction] + + + Create an asset config transaction to reconfigure an existing Algorand Standard Asset. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetConfigParams(sender="SENDER_ADDRESS", asset_id=123456, manager="NEW_MANAGER_ADDRESS") + >>> txn = creator.asset_config(params) + :example: + >>> #Advanced example + >>> creator.asset_config(AssetConfigParams( + sender="SENDER_ADDRESS", + asset_id=123456, + manager="NEW_MANAGER_ADDRESS", + reserve="NEW_RESERVE_ADDRESS", + freeze="NEW_FREEZE_ADDRESS", + clawback="NEW_CLAWBACK_ADDRESS", + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_freeze + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetFreezeParams], algosdk.transaction.Transaction] + + + Create an Algorand Standard Asset freeze transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetFreezeParams(sender="SENDER_ADDRESS", + asset_id=123456, + account="ACCOUNT_TO_FREEZE", + frozen=True) + >>> txn = creator.asset_freeze(params) + + :example: + >>> #Advanced example + >>> creator.asset_freeze(AssetFreezeParams( + sender="SENDER_ADDRESS", + asset_id=123456, + account="ACCOUNT_TO_FREEZE", + frozen=True, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_destroy + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetDestroyParams], algosdk.transaction.Transaction] + + + Create an Algorand Standard Asset destroy transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetDestroyParams(sender="SENDER_ADDRESS", asset_id=123456) + >>> txn = creator.asset_destroy(params) + + :example: + >>> #Advanced example + >>> creator.asset_destroy(AssetDestroyParams( + sender="SENDER_ADDRESS", + asset_id=123456, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_transfer + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetTransferParams], algosdk.transaction.Transaction] + + + Create an Algorand Standard Asset transfer transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetTransferParams(sender="SENDER_ADDRESS", + asset_id=123456, + amount=10, + receiver="RECEIVER_ADDRESS") + >>> txn = creator.asset_transfer(params) + + :example: + >>> #Advanced example + >>> creator.asset_transfer(AssetTransferParams( + sender="SENDER_ADDRESS", + asset_id=123456, + amount=10, + receiver="RECEIVER_ADDRESS", + clawback_target="CLAWBACK_TARGET_ADDRESS", + close_asset_to="CLOSE_ASSET_TO_ADDRESS", + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_opt_in + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetOptInParams], algosdk.transaction.Transaction] + + + Create an Algorand Standard Asset opt-in transaction. + + :example: + >>> # Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetOptInParams(sender="SENDER_ADDRESS", asset_id=123456) + >>> txn = creator.asset_opt_in(params) + + :example: + >>> # Advanced example + >>> creator.asset_opt_in(AssetOptInParams( + sender="SENDER_ADDRESS", + asset_id=123456, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: asset_opt_out + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AssetOptOutParams], algosdk.transaction.Transaction] + + + Create an asset opt-out transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AssetOptOutParams(sender="SENDER_ADDRESS", asset_id=123456, creator="CREATOR_ADDRESS") + >>> txn = creator.asset_opt_out(params) + + :example: + >>> #Advanced example + >>> creator.asset_opt_out(AssetOptOutParams( + sender="SENDER_ADDRESS", + asset_id=123456, + creator="CREATOR_ADDRESS", + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_create + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppCreateParams], algosdk.transaction.Transaction] + + + Create an application create transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppCreateParams( + ... sender="SENDER_ADDRESS", + ... approval_program="TEAL_APPROVAL_CODE", + ... clear_state_program="TEAL_CLEAR_CODE", + ... schema={ + ... 'global_ints': 1, + ... 'global_byte_slices': 1, + ... 'local_ints': 1, + ... 'local_byte_slices': 1 + ... } + ... ) + >>> txn = creator.app_create(params) + + :example: + >>> #Advanced example + >>> creator.app_create(AppCreateParams( + sender="SENDER_ADDRESS", + approval_program="TEAL_APPROVAL_CODE", + clear_state_program="TEAL_CLEAR_CODE", + schema={'global_ints': 1, 'global_byte_slices': 1, 'local_ints': 1, 'local_byte_slices': 1}, + on_complete=OnComplete.NoOpOC, + args=[b'arg1', b'arg2'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + extra_program_pages=0, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_update + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppUpdateParams], algosdk.transaction.Transaction] + + + Create an application update transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> txn = creator.app_update(AppUpdateParams(sender="SENDER_ADDRESS", + app_id=789, + approval_program="TEAL_NEW_APPROVAL_CODE", + clear_state_program="TEAL_NEW_CLEAR_CODE", + args=[b'new_arg1', b'new_arg2'])) + + :example: + >>> #Advanced example + >>> creator.app_update(AppUpdateParams( + sender="SENDER_ADDRESS", + app_id=789, + approval_program="TEAL_NEW_APPROVAL_CODE", + clear_state_program="TEAL_NEW_CLEAR_CODE", + args=[b'new_arg1', b'new_arg2'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + on_complete=OnComplete.UpdateApplicationOC, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_delete + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppDeleteParams], algosdk.transaction.Transaction] + + + Create an application delete transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppDeleteParams(sender="SENDER_ADDRESS", app_id=789, args=[b'delete_arg']) + >>> txn = creator.app_delete(params) + + :example: + >>> #Advanced example + >>> creator.app_delete(AppDeleteParams( + sender="SENDER_ADDRESS", + app_id=789, + args=[b'delete_arg'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + on_complete=OnComplete.DeleteApplicationOC, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_call + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppCallParams], algosdk.transaction.Transaction] + + + Create an application call transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppCallParams( + ... sender="SENDER_ADDRESS", + ... on_complete=OnComplete.NoOpOC, + ... app_id=789, + ... approval_program="TEAL_APPROVAL_CODE", + ... clear_state_program="TEAL_CLEAR_CODE", + ... schema={ + ... 'global_ints': 1, + ... 'global_byte_slices': 1, + ... 'local_ints': 1, + ... 'local_byte_slices': 1 + ... }, + ... args=[b'arg1', b'arg2'], + ... account_references=["ACCOUNT1"], + ... app_references=[789], + ... asset_references=[123], + ... extra_pages=0, + ... box_references=[] + ... ) + >>> txn = creator.app_call(params) + + :example: + >>> #Advanced example + >>> creator.app_call(AppCallParams( + sender="SENDER_ADDRESS", + on_complete=OnComplete.NoOpOC, + app_id=789, + approval_program="TEAL_APPROVAL_CODE", + clear_state_program="TEAL_CLEAR_CODE", + schema={'global_ints': 1, 'global_byte_slices': 1, 'local_ints': 1, 'local_byte_slices': 1}, + args=[b'arg1', b'arg2'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + extra_pages=0, + box_references=[], + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_create_method_call + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppCreateMethodCallParams], algokit_utils.transactions.transaction_composer.BuiltTransactions] + + + Create an application create call with ABI method call transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppCreateMethodCallParams(sender="SENDER_ADDRESS", app_id=0, method=some_abi_method_object) + >>> built_txns = creator.app_create_method_call(params) + + :example: + >>> #Advanced example + >>> creator.app_create_method_call(AppCreateMethodCallParams( + sender="SENDER_ADDRESS", + app_id=0, + method=some_abi_method_object, + args=[b'method_arg'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + schema={'global_ints': 1, 'global_byte_slices': 1, 'local_ints': 1, 'local_byte_slices': 1}, + approval_program="TEAL_APPROVAL_CODE", + clear_state_program="TEAL_CLEAR_CODE", + on_complete=OnComplete.NoOpOC, + extra_program_pages=0, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_update_method_call + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppUpdateMethodCallParams], algokit_utils.transactions.transaction_composer.BuiltTransactions] + + + Create an application update call with ABI method call transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppUpdateMethodCallParams(sender="SENDER_ADDRESS", app_id=789, method=some_abi_method_object) + >>> built_txns = creator.app_update_method_call(params) + + :example: + >>> #Advanced example + >>> creator.app_update_method_call(AppUpdateMethodCallParams( + sender="SENDER_ADDRESS", + app_id=789, + method=some_abi_method_object, + args=[b'method_arg'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + schema={'global_ints': 1, 'global_byte_slices': 1, 'local_ints': 1, 'local_byte_slices': 1}, + approval_program="TEAL_NEW_APPROVAL_CODE", + clear_state_program="TEAL_NEW_CLEAR_CODE", + on_complete=OnComplete.UpdateApplicationOC, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_delete_method_call + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppDeleteMethodCallParams], algokit_utils.transactions.transaction_composer.BuiltTransactions] + + + Create an application delete call with ABI method call transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppDeleteMethodCallParams(sender="SENDER_ADDRESS", app_id=789, method=some_abi_method_object) + >>> built_txns = creator.app_delete_method_call(params) + + :example: + >>> #Advanced example + >>> creator.app_delete_method_call(AppDeleteMethodCallParams( + sender="SENDER_ADDRESS", + app_id=789, + method=some_abi_method_object, + args=[b'method_arg'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: app_call_method_call + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.AppCallMethodCallParams], algokit_utils.transactions.transaction_composer.BuiltTransactions] + + + Create an application call with ABI method call transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = AppCallMethodCallParams(sender="SENDER_ADDRESS", app_id=789, method=some_abi_method_object) + >>> built_txns = creator.app_call_method_call(params) + :example: Advanced example + >>> creator.app_call_method_call(AppCallMethodCallParams( + sender="SENDER_ADDRESS", + app_id=789, + method=some_abi_method_object, + args=[b'method_arg'], + account_references=["ACCOUNT1"], + app_references=[789], + asset_references=[123], + box_references=[], + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: online_key_registration + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.OnlineKeyRegistrationParams], algosdk.transaction.Transaction] + + + Create an online key registration transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> params = OnlineKeyRegistrationParams( + sender="SENDER_ADDRESS", + vote_key="VOTE_KEY", + selection_key="SELECTION_KEY", + vote_first=1000, + vote_last=2000, + vote_key_dilution=10, + state_proof_key=b"state_proof_key_bytes" + ) + >>> txn = creator.online_key_registration(params) + + :example: + >>> #Advanced example + >>> creator.online_key_registration(OnlineKeyRegistrationParams( + sender="SENDER_ADDRESS", + vote_key="VOTE_KEY", + selection_key="SELECTION_KEY", + vote_first=1000, + vote_last=2000, + vote_key_dilution=10, + state_proof_key=b"state_proof_key_bytes", + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + + .. py:property:: offline_key_registration + :type: collections.abc.Callable[[algokit_utils.transactions.transaction_composer.OfflineKeyRegistrationParams], algosdk.transaction.Transaction] + + + Create an offline key registration transaction. + + :example: + >>> #Basic example + >>> creator = AlgorandClientTransactionCreator(lambda: TransactionComposer()) + >>> txn = creator.offline_key_registration(OfflineKeyRegistrationParams(sender="SENDER_ADDRESS", + prevent_account_from_ever_participating_again=True)) + + :example: + >>> #Advanced example + >>> creator.offline_key_registration(OfflineKeyRegistrationParams( + sender="SENDER_ADDRESS", + prevent_account_from_ever_participating_again=True, + lease="lease", + note=b"note", + rekey_to="REKEYTOADDRESS", + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount.from_micro_algo(1000), + static_fee=AlgoAmount.from_micro_algo(1000), + max_fee=AlgoAmount.from_micro_algo(3000) + )) + + + diff --git a/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_sender/index.rst.txt b/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_sender/index.rst.txt new file mode 100644 index 00000000..2133f700 --- /dev/null +++ b/docs/html/_sources/autoapi/algokit_utils/transactions/transaction_sender/index.rst.txt @@ -0,0 +1,1004 @@ +algokit_utils.transactions.transaction_sender +============================================= + +.. py:module:: algokit_utils.transactions.transaction_sender + + +Classes +------- + +.. autoapisummary:: + + algokit_utils.transactions.transaction_sender.SendSingleTransactionResult + algokit_utils.transactions.transaction_sender.SendSingleAssetCreateTransactionResult + algokit_utils.transactions.transaction_sender.SendAppTransactionResult + algokit_utils.transactions.transaction_sender.SendAppUpdateTransactionResult + algokit_utils.transactions.transaction_sender.SendAppCreateTransactionResult + algokit_utils.transactions.transaction_sender.AlgorandClientTransactionSender + + +Module Contents +--------------- + +.. py:class:: SendSingleTransactionResult + + Base class for transaction results. + + Represents the result of sending a single transaction. + + + .. py:attribute:: transaction + :type: algokit_utils.models.transaction.TransactionWrapper + + The last transaction + + + + .. py:attribute:: confirmation + :type: algosdk.v2client.algod.AlgodResponseType + + The last confirmation + + + + .. py:attribute:: group_id + :type: str + + The group ID + + + + .. py:attribute:: tx_id + :type: str | None + :value: None + + + The transaction ID + + + + .. py:attribute:: tx_ids + :type: list[str] + + The full array of transaction IDs + + + + .. py:attribute:: transactions + :type: list[algokit_utils.models.transaction.TransactionWrapper] + + The full array of transactions + + + + .. py:attribute:: confirmations + :type: list[algosdk.v2client.algod.AlgodResponseType] + + The full array of confirmations + + + + .. py:attribute:: returns + :type: list[algokit_utils.applications.abi.ABIReturn] | None + :value: None + + + The ABI return value if applicable + + + + .. py:method:: from_composer_result(result: algokit_utils.transactions.transaction_composer.SendAtomicTransactionComposerResults, index: int = -1) -> typing_extensions.Self + :classmethod: + + + +.. py:class:: SendSingleAssetCreateTransactionResult + + Bases: :py:obj:`SendSingleTransactionResult` + + + Result of creating a new ASA (Algorand Standard Asset). + + Contains the asset ID of the newly created asset. + + + .. py:attribute:: asset_id + :type: int + + The ID of the newly created asset + + + +.. py:class:: SendAppTransactionResult + + Bases: :py:obj:`SendSingleTransactionResult`, :py:obj:`Generic`\ [\ :py:obj:`ABIReturnT`\ ] + + + Result of an application transaction. + + Contains the ABI return value if applicable. + + + .. py:attribute:: abi_return + :type: ABIReturnT | None + :value: None + + + The ABI return value if applicable + + + +.. py:class:: SendAppUpdateTransactionResult + + Bases: :py:obj:`SendAppTransactionResult`\ [\ :py:obj:`ABIReturnT`\ ] + + + Result of updating an application. + + Contains the compiled approval and clear programs. + + + .. py:attribute:: compiled_approval + :type: Any | None + :value: None + + + The compiled approval program + + + + .. py:attribute:: compiled_clear + :type: Any | None + :value: None + + + The compiled clear state program + + + +.. py:class:: SendAppCreateTransactionResult + + Bases: :py:obj:`SendAppUpdateTransactionResult`\ [\ :py:obj:`ABIReturnT`\ ] + + + Result of creating a new application. + + Contains the app ID and address of the newly created application. + + + .. py:attribute:: app_id + :type: int + + The ID of the newly created application + + + + .. py:attribute:: app_address + :type: str + + The address of the newly created application + + + +.. py:class:: AlgorandClientTransactionSender(new_group: collections.abc.Callable[[], algokit_utils.transactions.transaction_composer.TransactionComposer], asset_manager: algokit_utils.assets.asset_manager.AssetManager, app_manager: algokit_utils.applications.app_manager.AppManager, algod_client: algosdk.v2client.algod.AlgodClient) + + Orchestrates sending transactions for AlgorandClient. + + Provides methods to send various types of transactions including payments, + asset operations, and application calls. + + + .. py:method:: new_group() -> algokit_utils.transactions.transaction_composer.TransactionComposer + + Create a new transaction group. + + :return: A new TransactionComposer instance + + :example: + >>> sender = AlgorandClientTransactionSender(new_group, asset_manager, app_manager, algod_client) + >>> composer = sender.new_group() + >>> composer(PaymentParams(sender="sender", receiver="receiver", amount=AlgoAmount(algo=1))) + >>> composer.send() + + + + .. py:method:: payment(params: algokit_utils.transactions.transaction_composer.PaymentParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Send a payment transaction to transfer Algo between accounts. + + :param params: Payment transaction parameters + :param send_params: Send parameters + :return: Result of the payment transaction + + :example: + >>> result = algorand.send.payment(PaymentParams( + >>> sender="SENDERADDRESS", + >>> receiver="RECEIVERADDRESS", + >>> amount=AlgoAmount(algo=4), + >>> )) + + >>> # Advanced example + >>> result = algorand.send.payment(PaymentParams( + >>> amount=AlgoAmount(algo=4), + >>> receiver="RECEIVERADDRESS", + >>> sender="SENDERADDRESS", + >>> close_remainder_to="CLOSEREMAINDERTOADDRESS", + >>> lease="lease", + >>> note="note", + >>> rekey_to="REKEYTOADDRESS", + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_create(params: algokit_utils.transactions.transaction_composer.AssetCreateParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleAssetCreateTransactionResult + + Create a new Algorand Standard Asset. + + :param params: Asset creation parameters + :param send_params: Send parameters + :return: Result containing the new asset ID + + :example: + >>> result = algorand.send.asset_create(AssetCreateParams( + >>> sender="SENDERADDRESS", + >>> asset_name="ASSETNAME", + >>> unit_name="UNITNAME", + >>> total=1000, + >>> )) + + >>> # Advanced example + >>> result = algorand.send.asset_create(AssetCreateParams( + >>> sender="CREATORADDRESS", + >>> total=100, + >>> decimals=2, + >>> asset_name="asset", + >>> unit_name="unit", + >>> url="url", + >>> metadata_hash="metadataHash", + >>> default_frozen=False, + >>> manager="MANAGERADDRESS", + >>> reserve="RESERVEADDRESS", + >>> freeze="FREEZEADDRESS", + >>> clawback="CLAWBACKADDRESS", + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_config(params: algokit_utils.transactions.transaction_composer.AssetConfigParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Configure an existing Algorand Standard Asset. + + :param params: Asset configuration parameters + :param send_params: Send parameters + :return: Result of the configuration transaction + + :example: + >>> result = algorand.send.asset_config(AssetConfigParams( + >>> sender="MANAGERADDRESS", + >>> asset_id=123456, + >>> manager="MANAGERADDRESS", + >>> reserve="RESERVEADDRESS", + >>> freeze="FREEZEADDRESS", + >>> clawback="CLAWBACKADDRESS", + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_freeze(params: algokit_utils.transactions.transaction_composer.AssetFreezeParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Freeze or unfreeze an Algorand Standard Asset for an account. + + :param params: Asset freeze parameters + :param send_params: Send parameters + :return: Result of the freeze transaction + + :example: + >>> result = algorand.send.asset_freeze(AssetFreezeParams( + >>> sender="MANAGERADDRESS", + >>> asset_id=123456, + >>> account="ACCOUNTADDRESS", + >>> frozen=True, + >>> )) + + >>> # Advanced example + >>> result = algorand.send.asset_freeze(AssetFreezeParams( + >>> sender="MANAGERADDRESS", + >>> asset_id=123456, + >>> account="ACCOUNTADDRESS", + >>> frozen=True, + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_destroy(params: algokit_utils.transactions.transaction_composer.AssetDestroyParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Destroys an Algorand Standard Asset. + + :param params: Asset destruction parameters + :param send_params: Send parameters + :return: Result of the destroy transaction + + :example: + >>> result = algorand.send.asset_destroy(AssetDestroyParams( + >>> sender="MANAGERADDRESS", + >>> asset_id=123456, + >>> )) + + >>> # Advanced example + >>> result = algorand.send.asset_destroy(AssetDestroyParams( + >>> sender="MANAGERADDRESS", + >>> asset_id=123456, + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_transfer(params: algokit_utils.transactions.transaction_composer.AssetTransferParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Transfer an Algorand Standard Asset. + + :param params: Asset transfer parameters + :param send_params: Send parameters + :return: Result of the transfer transaction + + :example: + >>> result = algorand.send.asset_transfer(AssetTransferParams( + >>> sender="HOLDERADDRESS", + >>> asset_id=123456, + >>> amount=1, + >>> receiver="RECEIVERADDRESS", + >>> )) + + >>> # Advanced example (with clawback) + >>> result = algorand.send.asset_transfer(AssetTransferParams( + >>> sender="CLAWBACKADDRESS", + >>> asset_id=123456, + >>> amount=1, + >>> receiver="RECEIVERADDRESS", + >>> clawback_target="HOLDERADDRESS", + >>> # This field needs to be used with caution + >>> close_asset_to="ADDRESSTOCLOSETO", + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_opt_in(params: algokit_utils.transactions.transaction_composer.AssetOptInParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Opt an account into an Algorand Standard Asset. + + :param params: Asset opt-in parameters + :param send_params: Send parameters + :return: Result of the opt-in transaction + + :example: + >>> result = algorand.send.asset_opt_in(AssetOptInParams( + >>> sender="SENDERADDRESS", + >>> asset_id=123456, + >>> )) + + >>> # Advanced example + >>> result = algorand.send.asset_opt_in(AssetOptInParams( + >>> sender="SENDERADDRESS", + >>> asset_id=123456, + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: asset_opt_out(*, params: algokit_utils.transactions.transaction_composer.AssetOptOutParams, send_params: algokit_utils.models.transaction.SendParams | None = None, ensure_zero_balance: bool = True) -> SendSingleTransactionResult + + Opt an account out of an Algorand Standard Asset. + + :param params: Asset opt-out parameters + :param send_params: Send parameters + :param ensure_zero_balance: Check if account has zero balance before opt-out, defaults to True + :raises ValueError: If account has non-zero balance or is not opted in + :return: Result of the opt-out transaction + + :example: + >>> result = algorand.send.asset_opt_out(AssetOptOutParams( + >>> sender="SENDERADDRESS", + >>> creator="CREATORADDRESS", + >>> asset_id=123456, + >>> ensure_zero_balance=True, + >>> )) + + >>> # Advanced example + >>> result = algorand.send.asset_opt_out(AssetOptOutParams( + >>> sender="SENDERADDRESS", + >>> asset_id=123456, + >>> creator="CREATORADDRESS", + >>> ensure_zero_balance=True, + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: app_create(params: algokit_utils.transactions.transaction_composer.AppCreateParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppCreateTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Create a new application. + + :param params: Application creation parameters + :param send_params: Send parameters + :return: Result containing the new application ID and address + + :example: + >>> result = algorand.send.app_create(AppCreateParams( + >>> sender="CREATORADDRESS", + >>> approval_program="TEALCODE", + >>> clear_state_program="TEALCODE", + >>> )) + + >>> # Advanced example + >>> result = algorand.send.app_create(AppCreateParams( + >>> sender="CREATORADDRESS", + >>> approval_program="TEALCODE", + >>> clear_state_program="TEALCODE", + >>> )) + >>> # algorand.send.appCreate(AppCreateParams( + >>> # sender='CREATORADDRESS', + >>> # approval_program="TEALCODE", + >>> # clear_state_program="TEALCODE", + >>> # schema={ + >>> # "global_ints": 1, + >>> # "global_byte_slices": 2, + >>> # "local_ints": 3, + >>> # "local_byte_slices": 4 + >>> # }, + >>> # extra_program_pages: 1, + >>> # on_complete: algosdk.transaction.OnComplete.OptInOC, + >>> # args: [b'some_bytes'] + >>> # account_references: ["ACCOUNT_1"] + >>> # app_references: [123, 1234] + >>> # asset_references: [12345] + >>> # box_references: ["box1", {app_id: 1234, name: "box2"}] + >>> # lease: 'lease', + >>> # note: 'note', + >>> # # You wouldn't normally set this field + >>> # first_valid_round: 1000, + >>> # validity_window: 10, + >>> # extra_fee: AlgoAmount(micro_algo=1000), + >>> # static_fee: AlgoAmount(micro_algo=1000), + >>> # # Max fee doesn't make sense with extraFee AND staticFee + >>> # # already specified, but here for completeness + >>> # max_fee: AlgoAmount(micro_algo=3000), + >>> # # Signer only needed if you want to provide one, + >>> # # generally you'd register it with AlgorandClient + >>> # # against the sender and not need to pass it in + >>> # signer: transactionSigner + >>> #}, send_params=SendParams( + >>> # max_rounds_to_wait_for_confirmation=5, + >>> # suppress_log=True, + >>> #)) + + + + .. py:method:: app_update(params: algokit_utils.transactions.transaction_composer.AppUpdateParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppUpdateTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Update an application. + + :param params: Application update parameters + :param send_params: Send parameters + :return: Result containing the compiled programs + + :example: + >>> # Basic example + >>> algorand.send.app_update(AppUpdateParams( + >>> sender="CREATORADDRESS", + >>> approval_program="TEALCODE", + >>> clear_state_program="TEALCODE", + >>> )) + >>> # Advanced example + >>> algorand.send.app_update(AppUpdateParams( + >>> sender="CREATORADDRESS", + >>> approval_program="TEALCODE", + >>> clear_state_program="TEALCODE", + >>> on_complete=OnComplete.UpdateApplicationOC, + >>> args=[b'some_bytes'], + >>> account_references=["ACCOUNT_1"], + >>> app_references=[123, 1234], + >>> asset_references=[12345], + >>> box_references=[...], + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: app_delete(params: algokit_utils.transactions.transaction_composer.AppDeleteParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Delete an application. + + :param params: Application deletion parameters + :param send_params: Send parameters + :return: Result of the deletion transaction + + :example: + >>> # Basic example + >>> algorand.send.app_delete(AppDeleteParams( + >>> sender="CREATORADDRESS", + >>> app_id=123456, + >>> )) + >>> # Advanced example + >>> algorand.send.app_delete(AppDeleteParams( + >>> sender="CREATORADDRESS", + >>> on_complete=OnComplete.DeleteApplicationOC, + >>> args=[b'some_bytes'], + >>> account_references=["ACCOUNT_1"], + >>> app_references=[123, 1234], + >>> asset_references=[12345], + >>> box_references=[...], + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner, + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: app_call(params: algokit_utils.transactions.transaction_composer.AppCallParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Call an application. + + :param params: Application call parameters + :param send_params: Send parameters + :return: Result containing any ABI return value + + :example: + >>> # Basic example + >>> algorand.send.app_call(AppCallParams( + >>> sender="CREATORADDRESS", + >>> app_id=123456, + >>> )) + >>> # Advanced example + >>> algorand.send.app_call(AppCallParams( + >>> sender="CREATORADDRESS", + >>> on_complete=OnComplete.OptInOC, + >>> args=[b'some_bytes'], + >>> account_references=["ACCOUNT_1"], + >>> app_references=[123, 1234], + >>> asset_references=[12345], + >>> box_references=[...], + >>> lease="lease", + >>> note="note", + >>> # You wouldn't normally set this field + >>> first_valid_round=1000, + >>> validity_window=10, + >>> extra_fee=AlgoAmount(micro_algo=1000), + >>> static_fee=AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee=AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer=transactionSigner, + >>> ), send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: app_create_method_call(params: algokit_utils.transactions.transaction_composer.AppCreateMethodCallParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppCreateTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Call an application's create method. + + :param params: Method call parameters for application creation + :param send_params: Send parameters + :return: Result containing the new application ID and address + + :example: + >>> # Note: you may prefer to use `algorand.client` to get an app client for more advanced functionality. + >>> # + >>> # @param params The parameters for the app creation transaction + >>> # Basic example + >>> method = algorand.abi.Method( + >>> name='method', + >>> args=[b'arg1'], + >>> returns='string' + >>> ) + >>> result = algorand.send.app_create_method_call({ sender: 'CREATORADDRESS', + >>> approval_program: 'TEALCODE', + >>> clear_state_program: 'TEALCODE', + >>> method: method, + >>> args: ["arg1_value"] }) + >>> created_app_id = result.app_id + >>> ... + >>> # Advanced example + >>> method = algorand.abi.Method( + >>> name='method', + >>> args=[b'arg1'], + >>> returns='string' + >>> ) + >>> result = algorand.send.app_create_method_call({ + >>> sender: 'CREATORADDRESS', + >>> method: method, + >>> args: ["arg1_value"], + >>> approval_program: "TEALCODE", + >>> clear_state_program: "TEALCODE", + >>> schema: { + >>> "global_ints": 1, + >>> "global_byte_slices": 2, + >>> "local_ints": 3, + >>> "local_byte_slices": 4 + >>> }, + >>> extra_program_pages: 1, + >>> on_complete: algosdk.transaction.OnComplete.OptInOC, + >>> args: [new Uint8Array(1, 2, 3, 4)], + >>> account_references: ["ACCOUNT_1"], + >>> app_references: [123, 1234], + >>> asset_references: [12345], + >>> box_references: [...], + >>> lease: 'lease', + >>> note: 'note', + >>> # You wouldn't normally set this field + >>> first_valid_round: 1000, + >>> validity_window: 10, + >>> extra_fee: AlgoAmount(micro_algo=1000), + >>> static_fee: AlgoAmount(micro_algo=1000), + >>> # Max fee doesn't make sense with extraFee AND staticFee + >>> # already specified, but here for completeness + >>> max_fee: AlgoAmount(micro_algo=3000), + >>> # Signer only needed if you want to provide one, + >>> # generally you'd register it with AlgorandClient + >>> # against the sender and not need to pass it in + >>> signer: transactionSigner, + >>> }, send_params=SendParams( + >>> max_rounds_to_wait_for_confirmation=5, + >>> suppress_log=True, + >>> )) + + + + .. py:method:: app_update_method_call(params: algokit_utils.transactions.transaction_composer.AppUpdateMethodCallParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppUpdateTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Call an application's update method. + + :param params: Method call parameters for application update + :param send_params: Send parameters + :return: Result containing the compiled programs + + :example: + # Basic example: + >>> method = algorand.abi.Method( + ... name="updateMethod", + ... args=[{"type": "string", "name": "arg1"}], + ... returns="string" + ... ) + >>> params = AppUpdateMethodCallParams( + ... sender="CREATORADDRESS", + ... app_id=123, + ... method=method, + ... args=["new_value"], + ... approval_program="TEALCODE", + ... clear_state_program="TEALCODE" + ... ) + >>> result = algorand.send.app_update_method_call(params) + >>> print(result.compiled_approval, result.compiled_clear) + + # Advanced example: + >>> method = algorand.abi.Method( + ... name="updateMethod", + ... args=[{"type": "string", "name": "arg1"}, {"type": "uint64", "name": "arg2"}], + ... returns="string" + ... ) + >>> params = AppUpdateMethodCallParams( + ... sender="CREATORADDRESS", + ... app_id=456, + ... method=method, + ... args=["new_value", 42], + ... approval_program="TEALCODE_ADVANCED", + ... clear_state_program="TEALCLEAR_ADVANCED", + ... account_references=["ACCOUNT1", "ACCOUNT2"], + ... app_references=[789], + ... asset_references=[101112] + ... ) + >>> result = algorand.send.app_update_method_call(params) + >>> print(result.compiled_approval, result.compiled_clear) + + + + .. py:method:: app_delete_method_call(params: algokit_utils.transactions.transaction_composer.AppDeleteMethodCallParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Call an application's delete method. + + :param params: Method call parameters for application deletion + :param send_params: Send parameters + :return: Result of the deletion transaction + + :example: + # Basic example: + >>> method = algorand.abi.Method( + ... name="deleteMethod", + ... args=[], + ... returns="void" + ... ) + >>> params = AppDeleteMethodCallParams( + ... sender="CREATORADDRESS", + ... app_id=123, + ... method=method + ... ) + >>> result = algorand.send.app_delete_method_call(params) + >>> print(result.tx_id) + + # Advanced example: + >>> method = algorand.abi.Method( + ... name="deleteMethod", + ... args=[{"type": "uint64", "name": "confirmation"}], + ... returns="void" + ... ) + >>> params = AppDeleteMethodCallParams( + ... sender="CREATORADDRESS", + ... app_id=123, + ... method=method, + ... args=[1], + ... account_references=["ACCOUNT1"], + ... app_references=[456] + ... ) + >>> result = algorand.send.app_delete_method_call(params) + >>> print(result.tx_id) + + + + .. py:method:: app_call_method_call(params: algokit_utils.transactions.transaction_composer.AppCallMethodCallParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendAppTransactionResult[algokit_utils.applications.abi.ABIReturn] + + Call an application's call method. + + :param params: Method call parameters + :param send_params: Send parameters + :return: Result containing any ABI return value + + :example: + # Basic example: + >>> method = algorand.abi.Method( + ... name="callMethod", + ... args=[{"type": "uint64", "name": "arg1"}], + ... returns="uint64" + ... ) + >>> params = AppCallMethodCallParams( + ... sender="CALLERADDRESS", + ... app_id=123, + ... method=method, + ... args=[12345] + ... ) + >>> result = algorand.send.app_call_method_call(params) + >>> print(result.abi_return) + + # Advanced example: + >>> method = algorand.abi.Method( + ... name="callMethod", + ... args=[{"type": "uint64", "name": "arg1"}, {"type": "string", "name": "arg2"}], + ... returns="uint64" + ... ) + >>> params = AppCallMethodCallParams( + ... sender="CALLERADDRESS", + ... app_id=123, + ... method=method, + ... args=[12345, "extra"], + ... account_references=["ACCOUNT1"], + ... asset_references=[101112], + ... app_references=[789] + ... ) + >>> result = algorand.send.app_call_method_call(params) + >>> print(result.abi_return) + + + + .. py:method:: online_key_registration(params: algokit_utils.transactions.transaction_composer.OnlineKeyRegistrationParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Register an online key. + + :param params: Key registration parameters + :param send_params: Send parameters + :return: Result of the registration transaction + + :example: + # Basic example: + >>> params = OnlineKeyRegistrationParams( + ... sender="ACCOUNTADDRESS", + ... vote_key="VOTEKEY", + ... selection_key="SELECTIONKEY", + ... vote_first=1000, + ... vote_last=2000, + ... vote_key_dilution=10 + ... ) + >>> result = algorand.send.online_key_registration(params) + >>> print(result.tx_id) + + # Advanced example: + >>> params = OnlineKeyRegistrationParams( + ... sender="ACCOUNTADDRESS", + ... vote_key="VOTEKEY", + ... selection_key="SELECTIONKEY", + ... vote_first=1000, + ... vote_last=2100, + ... vote_key_dilution=10, + ... state_proof_key=b'' * 64 + ... ) + >>> result = algorand.send.online_key_registration(params) + >>> print(result.tx_id) + + + + .. py:method:: offline_key_registration(params: algokit_utils.transactions.transaction_composer.OfflineKeyRegistrationParams, send_params: algokit_utils.models.transaction.SendParams | None = None) -> SendSingleTransactionResult + + Register an offline key. + + :param params: Key registration parameters + :param send_params: Send parameters + :return: Result of the registration transaction + + :example: + # Basic example: + >>> params = OfflineKeyRegistrationParams( + ... sender="ACCOUNTADDRESS", + ... prevent_account_from_ever_participating_again=True + ... ) + >>> result = algorand.send.offline_key_registration(params) + >>> print(result.tx_id) + + # Advanced example: + >>> params = OfflineKeyRegistrationParams( + ... sender="ACCOUNTADDRESS", + ... prevent_account_from_ever_participating_again=True, + ... note=b'Offline registration' + ... ) + >>> result = algorand.send.offline_key_registration(params) + >>> print(result.tx_id) + + + diff --git a/docs/html/_sources/autoapi/index.rst.txt b/docs/html/_sources/autoapi/index.rst.txt new file mode 100644 index 00000000..fda982fe --- /dev/null +++ b/docs/html/_sources/autoapi/index.rst.txt @@ -0,0 +1,11 @@ +API Reference +============= + +This page contains auto-generated API reference documentation [#f1]_. + +.. toctree:: + :titlesonly: + + /autoapi/algokit_utils/index + +.. [#f1] Created with `sphinx-autoapi `_ \ No newline at end of file diff --git a/docs/html/_sources/capabilities/account.md.txt b/docs/html/_sources/capabilities/account.md.txt index d0c2b42f..8a3b405a 100644 --- a/docs/html/_sources/capabilities/account.md.txt +++ b/docs/html/_sources/capabilities/account.md.txt @@ -1,31 +1,215 @@ # Account management -Account management is one of the core capabilities provided by AlgoKit Utils. It allows you to create mnemonic, idempotent KMD and environment variable injected accounts -that can be used to sign transactions as well as representing a sender address at the same time. - -(account)= -## `Account` - -Encapsulates a private key with convenience properties for `address`, `signer` and `public_key`. - -There are various methods of obtaining an `Account` instance - -* `get_account`: Returns an `Account` instance with the private key loaded by convention based on the given name identifier: - * from an environment variable containing a mnemonic `{NAME}_MNEMONIC` OR - * loading the account from KMD ny name if it exists (LocalNet only) OR - * creating the account in KMD with associated name (LocalNet only) - - This allows you to have powerful code that will automatically create and fund an account by name locally and when deployed against - TestNet/MainNet will automatically resolve from environment variables - -* `Account.new_account`: Returns a new `Account` using `algosdk.account.generate_account()` -* `Account(private_key)`: Load an existing account from a private key -* `Account(private_key, address)`: Load an existing account from a private key and address, useful for re-keyed accounts -* `get_account_from_mnemonic`: Load an existing account from a mnemonic -* `get_dispenser_account`: Gets a dispenser account that is funded by either: - * Using the LocalNet default account (LocalNet only) OR - * Loading an account from `DISPENSER_MNEMONIC` - -If working with a LocalNet instance, there are some additional functions that rely on a KMD service being exposed: -* `create_kmd_wallet_account`, `get_kmd_wallet_account` or `get_or_create_kmd_wallet_account`: These functions allow retrieving a KMD wallet account by name, -* `get_localnet_default_account`: Gets default localnet account that is funded with algos +Account management is one of the core capabilities provided by AlgoKit Utils. It allows you to create mnemonic, rekeyed, multisig, transaction signer, idempotent KMD and environment variable injected accounts that can be used to sign transactions as well as representing a sender address at the same time. This significantly simplifies management of transaction signing. + +## `AccountManager` + +The {py:obj}`AccountManager ` is a class that is used to get, create, and fund accounts and perform account-related actions such as funding. The `AccountManager` also keeps track of signers for each address so when using the [`TransactionComposer`](./transaction-composer.md) to send transactions, a signer function does not need to manually be specified for each transaction - instead it can be inferred from the sender address automatically! + +To get an instance of `AccountManager`, you can use either [`AlgorandClient`](./algorand-client.md) via `algorand.account` or instantiate it directly: + +```python +from algokit_utils import AccountManager + +account_manager = AccountManager(client_manager) +``` + +## `TransactionSignerAccountProtocol` + +The core internal type that holds information about a signer/sender pair for a transaction is {py:obj}`TransactionSignerAccountProtocol `, which represents an `algosdk.transaction.TransactionSigner` (`signer`) along with a sender address (`address`) as the encoded string address. + +The following conform to `TransactionSignerAccountProtocol`: + +- {py:obj}`TransactionSignerAccount ` - a basic transaction signer account that holds an address and a signer conforming to `TransactionSignerAccountProtocol` +- {py:obj}`SigningAccount ` - an abstraction that used to be available under `Account` in previous versions of AlgoKit Utils. Renamed for consistency with equivalent `ts` version. Holds private key and conforms to `TransactionSignerAccountProtocol` +- {py:obj}`LogicSigAccount ` - a wrapper class around `algosdk` logicsig abstractions conforming to `TransactionSignerAccountProtocol` +- {py:obj}`MultisigAccount ` - a wrapper class around `algosdk` multisig abstractions conforming to `TransactionSignerAccountProtocol` + +## Registering a signer + +The `AccountManager` keeps track of which signer is associated with a given sender address. This is used by [`AlgorandClient`](./algorand-client.md) to automatically sign transactions by that sender. Any of the [methods](#accounts) within `AccountManager` that return an account will automatically register the signer with the sender. + +There are two methods that can be used for this, `set_signer_from_account`, which takes any number of [account based objects](#underlying-account-classes) that combine signer and sender (`TransactionSignerAccount` | `SigningAccount` | `LogicSigAccount` | `MultisigAccount`), or `set_signer` which takes the sender address and the `TransactionSigner`: + +```python +algorand.account + .set_signer_from_account(TransactionSignerAccount(your_address, your_signer)) + .set_signer_from_account(SigningAccount.new_account()) + .set_signer_from_account( + LogicSigAccount(algosdk.transaction.LogicSigAccount(program, args)) + ) + .set_signer_from_account( + MultisigAccount( + MultisigMetadata( + version = 1, + threshold = 1, + addresses = ["ADDRESS1...", "ADDRESS2..."] + ), + [account1, account2] + ) + ) + .set_signer("SENDERADDRESS", transaction_signer) +``` + +## Default signer + +If you want to have a default signer that is used to sign transactions without a registered signer (rather than throwing an exception) then you can {py:meth}`set_default_signer `: + +```python +algorand.account.set_default_signer(my_default_signer) +``` + +## Get a signer + +[`AlgorandClient`](./algorand-client.md) will automatically retrieve a signer when signing a transaction, but if you need to get a `TransactionSigner` externally to do something more custom then you can {py:meth}`get_signer ` for a given sender address: + +```python +signer = algorand.account.get_signer("SENDER_ADDRESS") +``` + +If there is no signer registered for that sender address it will either return the default signer ([if registered](#default-signer)) or throw an exception. + +## Accounts + +In order to get/register accounts for signing operations you can use the following methods on [`AccountManager`](#accountmanager) (expressed here as `algorand.account` to denote the syntax via an [`AlgorandClient`](./algorand-client.md)): + +- {py:meth}`from_environment ` - Registers and returns an account with private key loaded by convention based on the given name identifier - either by idempotently creating the account in KMD or from environment variable via `process.env['{NAME}_MNEMONIC']` and (optionally) `process.env['{NAME}_SENDER']` (if account is rekeyed) + - This allows you to have powerful code that will automatically create and fund an account by name locally and when deployed against TestNet/MainNet will automatically resolve from environment variables, without having to have different code + - Note: `fund_with` allows you to control how many Algo are seeded into an account created in KMD +- {py:meth}`from_mnemonic ` - Registers and returns an account with secret key loaded by taking the mnemonic secret +- {py:meth}`multisig ` - Registers and returns a multisig account with one or more signing keys loaded +- {py:meth}`rekeyed ` - Registers and returns an account representing the given rekeyed sender/signer combination +- {py:meth}`random ` - Returns a new, cryptographically randomly generated account with private key loaded +- {py:meth}`from_kmd ` - Returns an account with private key loaded from the given KMD wallet (identified by name) +- {py:meth}`logicsig ` - Returns an account that represents a logic signature + +### Underlying account classes + +While `TransactionSignerAccount` is the main class used to represent an account that can sign, there are underlying account classes that can underpin the signer within the transaction signer account. + +- {py:obj}`TransactionSignerAccount ` - A default class conforming to `TransactionSignerAccountProtocol` that holds an address and a signer +- {py:obj}`SigningAccount ` - An abstraction around `algosdk.Account` that supports rekeyed accounts +- {py:obj}`LogicSigAccount ` - An abstraction around `algosdk.LogicSigAccount` and `algosdk.LogicSig` that supports logic sig signing. Exposes access to the underlying algosdk `algosdk.transaction.LogicSigAccount` object instance via `lsig` property. +- {py:obj}`MultisigAccount ` - An abstraction around `algosdk.MultisigMetadata`, `algosdk.makeMultiSigAccountTransactionSigner`, `algosdk.multisigAddress`, `algosdk.signMultisigTransaction` and `algosdk.appendSignMultisigTransaction` that supports multisig accounts with one or more signers present. Exposes access to the underlying algosdk `algosdk.transaction.Multisig` object instance via `multisig` property. + +### Dispenser + +- {py:meth}`dispenser_from_environment ` - Returns an account (with private key loaded) that can act as a dispenser from environment variables, or against default LocalNet if no environment variables present +- {py:meth}`localnet_dispenser ` - Returns an account with private key loaded that can act as a dispenser for the default LocalNet dispenser account + +## Rekey account + +One of the unique features of Algorand is the ability to change the private key that can authorise transactions for an account. This is called [rekeying](https://developer.algorand.org/docs/get-details/accounts/rekey/). + +> [!WARNING] +> Rekeying should be done with caution as a rekey transaction can result in permanent loss of control of an account. + +You can issue a transaction to rekey an account by using the {py:meth}`rekey_account ` function: + +- `account: string | TransactionSignerAccount` - The account address or signing account of the account that will be rekeyed +- `rekeyTo: string | TransactionSignerAccount` - The account address or signing account of the account that will be used to authorise transactions for the rekeyed account going forward. If a signing account is provided that will now be tracked as the signer for `account` in the `AccountManager` instance. +- An `options` object, which has: + - [Common transaction parameters](./algorand-client.md#transaction-parameters) + - [Execution parameters](./algorand-client.md#sending-a-single-transaction) + +You can also pass in `rekeyTo` as a [common transaction parameter](./algorand-client.md#transaction-parameters) to any transaction. + +### Examples + +```python +# Basic example (with string addresses) + +algorand.account.rekey_account({ + account: "ACCOUNTADDRESS", + rekey_to: "NEWADDRESS", +}) + +# Basic example (with signer accounts) + +algorand.account.rekey_account({ + account: account1, + rekey_to: new_signer_account, +}) + +# Advanced example + +algorand.account.rekey_account({ + account: "ACCOUNTADDRESS", + rekey_to: "NEWADDRESS", + lease: "lease", + note: "note", + first_valid_round: 1000, + validity_window: 10, + extra_fee: AlgoAmount.from_micro_algos(1000), + static_fee: AlgoAmount.from_micro_algos(1000), + # Max fee doesn't make sense with extra_fee AND static_fee + # already specified, but here for completeness + max_fee: AlgoAmount.from_micro_algos(3000), + max_rounds_to_wait_for_confirmation: 5, + suppress_log: True, +}) + + +# Using a rekeyed account + +Note: if a signing account is passed into `algorand.account.rekey_account` then you don't need to call `rekeyed_account` to register the new signer + +rekeyed_account = algorand.account.rekey_account(account, new_account) +# rekeyed_account can be used to sign transactions on behalf of account... +``` + +## KMD account management + +When running LocalNet, you have an instance of the [Key Management Daemon](https://github.com/algorand/go-algorand/blob/master/daemon/kmd/README.md), which is useful for: + +- Accessing the private key of the default accounts that are pre-seeded with Algo so that other accounts can be funded and it's possible to use LocalNet +- Idempotently creating new accounts against a name that will stay intact while the LocalNet instance is running without you needing to store private keys anywhere (i.e. completely automated) + +The KMD SDK is fairly low level so to make use of it there is a fair bit of boilerplate code that's needed. This code has been abstracted away into the `KmdAccountManager` class. + +To get an instance of the `KmdAccountManager` class you can access it from [`AlgorandClient`](./algorand-client.md) via `algorand.account.kmd` or instantiate it directly (passing in a [`ClientManager`](./client.md)): + +```python +from algokit_utils import KmdAccountManager + +kmd_account_manager = KmdAccountManager(client_manager) +``` + +The methods that are available are: + +- {py:meth}`get_wallet_account ` - Returns an Algorand signing account with private key loaded from the given KMD wallet (identified by name). +- {py:meth}`get_or_create_wallet_account ` - Gets an account with private key loaded from a KMD wallet of the given name, or alternatively creates one with funds in it via a KMD wallet of the given name. +- {py:meth}`get_localnet_dispenser_account ` - Returns an Algorand account with private key loaded for the default LocalNet dispenser account (that can be used to fund other accounts) + +```python +# Get a wallet account that seeded the LocalNet network +default_dispenser_account = kmd_account_manager.get_wallet_account( + "unencrypted-default-wallet", + lambda a: a["status"] != "Offline" and a["amount"] > 1_000_000_000 +) +# Same as above, but dedicated method call for convenience +localnet_dispenser_account = kmd_account_manager.get_localnet_dispenser_account() +# Idempotently get (if exists) or create (if it doesn't exist yet) an account by name using KMD +# if creating it then fund it with 2 ALGO from the default dispenser account +new_account = kmd_account_manager.get_or_create_wallet_account( + "account1", + AlgoAmount.from_algos(2) +) +# This will return the same account as above since the name matches +existing_account = kmd_account_manager.get_or_create_wallet_account( + "account1" +) +``` + +Some of this functionality is directly exposed from [`AccountManager`](#accountmanager), which has the added benefit of registering the account as a signer so they can be automatically used to sign transactions when using via [`AlgorandClient`](./algorand-client.md): + +```python +# Get and register LocalNet dispenser +localnet_dispenser = algorand.account.localnet_dispenser() +# Get and register a dispenser by environment variable, or if not set then LocalNet dispenser via KMD +dispenser = algorand.account.dispenser_from_environment() +# Get an account from KMD idempotently by name. In this case we'll get the default dispenser account +dispenser_via_kmd = algorand.account.from_kmd('unencrypted-default-wallet', lambda a: a.status != 'Offline' and a.amount > 1_000_000_000) +# Get / create and register account from KMD idempotently by name +fresh_account_via_kmd = algorand.account.kmd.get_or_create_wallet_account('account1', AlgoAmount.from_algos(2)) +``` diff --git a/docs/html/_sources/capabilities/algorand-client.md.txt b/docs/html/_sources/capabilities/algorand-client.md.txt new file mode 100644 index 00000000..adde5f4b --- /dev/null +++ b/docs/html/_sources/capabilities/algorand-client.md.txt @@ -0,0 +1,190 @@ +# Algorand client + +`AlgorandClient` is a client class that brokers easy access to Algorand functionality. It's the [default entrypoint](../index.md#usage) into AlgoKit Utils functionality. + +The main entrypoint to the bulk of the functionality in AlgoKit Utils is the `AlgorandClient` class, most of the time you can get started by typing `AlgorandClient.` and choosing one of the static initialisation methods to create an {py:class}`algokit_utils.algorand.AlgorandClient`, e.g.: + +```python +# Point to the network configured through environment variables or +# if no environment variables it will point to the default LocalNet +# configuration +algorand = AlgorandClient.from_environment() +# Point to default LocalNet configuration +algorand = AlgorandClient.default_localnet() +# Point to TestNet using AlgoNode free tier +algorand = AlgorandClient.testnet() +# Point to MainNet using AlgoNode free tier +algorand = AlgorandClient.mainnet() +# Point to a pre-created algod client +algorand = AlgorandClient.from_clients(algod=algod) +# Point to pre-created algod, indexer and kmd clients +algorand = AlgorandClient.from_clients(algod=algod, indexer=indexer, kmd=kmd) +# Point to custom configuration for algod +algorand = AlgorandClient.from_config(algod_config=algod_config) +# Point to custom configuration for algod, indexer and kmd +algorand = AlgorandClient.from_config( + algod_config=algod_config, + indexer_config=indexer_config, + kmd_config=kmd_config +) +``` + +## Accessing SDK clients + +Once you have an `AlgorandClient` instance, you can access the SDK clients for the various Algorand APIs via the `algorand.client` property. + +```py +algorand = AlgorandClient.default_localnet() + +algod_client = algorand.client.algod +indexer_client = algorand.client.indexer +kmd_client = algorand.client.kmd +``` + +## Accessing manager class instances + +The `AlgorandClient` has a number of manager class instances that help you quickly use intellisense to get access to advanced functionality. + +- [`AccountManager`](./account.md) via `algorand.account`, there are also some chainable convenience methods which wrap specific methods in `AccountManager`: + - `algorand.setDefaultSigner(signer)` - + - `algorand.setSignerFromAccount(account)` - + - `algorand.setSigner(sender, signer)` +- [`AssetManager`](./asset.md) via `algorand.asset` +- [`ClientManager`](./client.md) via `algorand.client` + +## Creating and issuing transactions + +`AlgorandClient` exposes a series of methods that allow you to create, execute, and compose groups of transactions (all via the [`TransactionComposer`](./transaction-composer.md)). + +### Creating transactions + +You can compose a transaction via `algorand.create_transaction.`, which gives you an instance of the {py:class}`algokit_utils.transactions.AlgorandClientTransactionCreator` class. Intellisense will guide you on the different options. + +The signature for the calls to send a single transaction usually look like: + +```python +algorand.create_transaction.{method}(params=TxnParams(...), send_params=SendParams(...)) -> Transaction: +``` + +- `TxnParams` is a union type that can be any of the Algorand transaction types, exact dataclasses can be imported from `algokit_utils` and consist of: + - `AppCallParams`, + - `AppCreateParams`, + - `AppDeleteParams`, + - `AppUpdateParams`, + - `AssetConfigParams`, + - `AssetCreateParams`, + - `AssetDestroyParams`, + - `AssetFreezeParams`, + - `AssetOptInParams`, + - `AssetOptOutParams`, + - `AssetTransferParams`, + - `OfflineKeyRegistrationParams`, + - `OnlineKeyRegistrationParams`, + - `PaymentParams`, +- `SendParams` is a typed dictionary exposing setting to apply during send operation: + - `max_rounds_to_wait_for_confirmation: int | None` - The number of rounds to wait for confirmation. By default until the latest lastValid has past. + - `suppress_log: bool | None` - Whether to suppress log messages from transaction send, default: do not suppress. + - `populate_app_call_resources: bool | None` - Whether to use simulate to automatically populate app call resources in the txn objects. Defaults to `Config.populateAppCallResources`. + - `cover_app_call_inner_transaction_fees: bool | None` - Whether to use simulate to automatically calculate required app call inner transaction fees and cover them in the parent app call transaction fee + +The return type for the ABI method call methods are slightly different: + +```python +algorand.createTransaction.app{call_type}_method_call(params=MethodCallParams(...), send_params=SendParams(...)) -> BuiltTransactions +``` + +MethodCallParams is a union type that can be any of the Algorand method call types, exact dataclasses can be imported from `algokit_utils` and consist of: + +- `AppCreateMethodCallParams`, +- `AppCallMethodCallParams`, +- `AppDeleteMethodCallParams`, +- `AppUpdateMethodCallParams`, + +Where `BuiltTransactions` looks like this: + +```python +@dataclass(frozen=True) +class BuiltTransactions: + transactions: list[algosdk.transaction.Transaction] + method_calls: dict[int, Method] + signers: dict[int, TransactionSigner] +``` + +This signifies the fact that an ABI method call can actually result in multiple transactions (which in turn may have different signers), that you need ABI metadata to be able to extract the return value from the transaction result. + +### Sending a single transaction + +You can compose a single transaction via `algorand.send...`, which gives you an instance of the {py:class}`algokit_utils.transactions.AlgorandClientTransactionSender` class. Intellisense will guide you on the different options. + +Further documentation is present in the related capabilities: + +- [App management](./app.md) +- [Asset management](./asset.md) +- [Algo transfers](./transfer.md) + +The signature for the calls to send a single transaction usually look like: + +`algorand.send.{method}(params=TxnParams, send_params=SendParams) -> SingleSendTransactionResult` + +- To get intellisense on the params, use your IDE's intellisense keyboard shortcut (e.g. ctrl+space). +- `TxnParams` is a union type that can be any of the Algorand transaction types, exact dataclasses can be imported from `algokit_utils`. +- {py:class}`algokit_utils.transactions.SendParams` a typed dictionary exposing setting to apply during send operation. +- {py:class}`algokit_utils.transactions.SendSingleTransactionResult` is all of the information that is relevant when [sending a single transaction to the network](./transaction.md#transaction-results) + +Generally, the functions to immediately send a single transaction will emit log messages before and/or after sending the transaction. You can opt-out of this by sending `suppressLog: true`. + +### Composing a group of transactions + +You can compose a group of transactions for execution by using the `new_group()` method on `AlgorandClient` and then use the various `.add_{Type}()` methods on [`TransactionComposer`](./transaction-composer.md) to add a series of transactions. + +```python +result = (algorand + .new_group() + .add_payment( + PaymentParams( + sender="SENDERADDRESS", + receiver="RECEIVERADDRESS", + amount=1_000_000 # 1 Algo in microAlgos + ) + ) + .add_asset_opt_in( + AssetOptInParams( + sender="SENDERADDRESS", + asset_id=12345 + ) + ) + .send()) +``` + +`new_group()` returns a new [`TransactionComposer`](./transaction-composer.md) instance, which can also return the group of transactions, simulate them and other things. + +### Transaction parameters + +To create a transaction you instantiate a relevant Transaction parameters dataclass from `algokit_utils.transactions import *` or `from algokit_utils import PaymentParams, AssetOptInParams, etc`. + +All transaction parameters share the following common base parameters: + +- `sender: str` - The address of the account sending the transaction. +- `signer: algosdk.TransactionSigner | TransactionSignerAccount | None` - The function used to sign transaction(s); if not specified then an attempt will be made to find a registered signer for the given `sender` or use a default signer (if configured). +- `rekey_to: string | None` - Change the signing key of the sender to the given address. **Warning:** Please be careful with this parameter and be sure to read the [official rekey guidance](https://developer.algorand.org/docs/get-details/accounts/rekey/). +- `note: bytes | str | None` - Note to attach to the transaction. Max of 1000 bytes. +- `lease: bytes | str | None` - Prevent multiple transactions with the same lease being included within the validity window. A [lease](https://developer.algorand.org/articles/leased-transactions-securing-advanced-smart-contract-design/) enforces a mutually exclusive transaction (useful to prevent double-posting and other scenarios). +- Fee management + - `static_fee: AlgoAmount | None` - The static transaction fee. In most cases you want to use `extra_fee` unless setting the fee to 0 to be covered by another transaction. + - `extra_fee: AlgoAmount | None` - The fee to pay IN ADDITION to the suggested fee. Useful for covering inner transaction fees. + - `max_fee: AlgoAmount | None` - Throw an error if the fee for the transaction is more than this amount; prevents overspending on fees during high congestion periods. +- Round validity management + - `validity_window: int | None` - How many rounds the transaction should be valid for, if not specified then the registered default validity window will be used. + - `first_valid_round: int | None` - Set the first round this transaction is valid. If left undefined, the value from algod will be used. We recommend you only set this when you intentionally want this to be some time in the future. + - `last_valid_round: int | None` - The last round this transaction is valid. It is recommended to use `validity_window` instead. + +Then on top of that the base type gets extended for the specific type of transaction you are issuing. These are all defined as part of [`TransactionComposer`](./transaction-composer.md) and we recommend reading these docs, especially when leveraging either `populate_app_call_resources` or `cover_app_call_inner_transaction_fees`. + +### Transaction configuration + +AlgorandClient caches network provided transaction values for you automatically to reduce network traffic. It has a set of default configurations that control this behaviour, but you have the ability to override and change the configuration of this behaviour: + +- `algorand.set_default_validity_window(validity_window)` - Set the default validity window (number of rounds from the current known round that the transaction will be valid to be accepted for), having a smallish value for this is usually ideal to avoid transactions that are valid for a long future period and may be submitted even after you think it failed to submit if waiting for a particular number of rounds for the transaction to be successfully submitted. The validity window defaults to `10`, except localnet environments where it's set to `1000`. +- `algorand.set_suggested_params(suggested_params, until?)` - Set the suggested network parameters to use (optionally until the given time) +- `algorand.set_suggested_params_timeout(timeout)` - Set the timeout that is used to cache the suggested network parameters (by default 3 seconds) +- `algorand.get_suggested_params()` - Get the current suggested network parameters object, either the cached value, or if the cache has expired a fresh value diff --git a/docs/html/_sources/capabilities/amount.md.txt b/docs/html/_sources/capabilities/amount.md.txt new file mode 100644 index 00000000..9030f612 --- /dev/null +++ b/docs/html/_sources/capabilities/amount.md.txt @@ -0,0 +1,55 @@ +# Algo amount handling + +Algo amount handling is one of the core capabilities provided by AlgoKit Utils. It allows you to reliably and tersely specify amounts of microAlgo and Algo and safely convert between them. + +Any AlgoKit Utils function that needs an Algo amount will take an `AlgoAmount` object, which ensures that there is never any confusion about what value is being passed around. Whenever an AlgoKit Utils function calls into an underlying algosdk function, or if you need to take an `AlgoAmount` and pass it into an underlying algosdk function (per the {ref}`modularity principle `) you can safely and explicitly convert to microAlgo or Algo. + +To see some usage examples check out the automated tests. Alternatively, you can see the reference documentation for `AlgoAmount`. + +## `AlgoAmount` + +The `AlgoAmount` class provides a safe wrapper around an underlying amount of microAlgo where any value entering or existing the `AlgoAmount` class must be explicitly stated to be in microAlgo or Algo. This makes it much safer to handle Algo amounts rather than passing them around as raw numbers where it's easy to make a (potentially costly!) mistake and not perform a conversion when one is needed (or perform one when it shouldn't be!). + +To import the AlgoAmount class you can access it via: + +```python +from algokit_utils import AlgoAmount +``` + +### Creating an `AlgoAmount` + +There are a few ways to create an `AlgoAmount`: + +- Algo + - Constructor: `AlgoAmount(algo=10)` + - Static helper: `AlgoAmount.from_algo(10)` +- microAlgo + - Constructor: `AlgoAmount(micro_algo=10_000)` + - Static helper: `AlgoAmount.from_micro_algo(10_000)` + +### Extracting a value from `AlgoAmount` + +The `AlgoAmount` class has properties to return Algo and microAlgo: + +- `amount.algo` - Returns the value in Algo as a python `Decimal` object +- `amount.micro_algo` - Returns the value in microAlgo as an integer + +`AlgoAmount` will coerce to an integer automatically (in microAlgo) when using `int(amount)`, which allows you to use `AlgoAmount` objects in comparison operations such as `<` and `>=` etc. + +You can also call `str(amount)` or use an `AlgoAmount` directly in string interpolation to convert it to a nice user-facing formatted amount expressed in microAlgo. + +### Additional Features + +The `AlgoAmount` class supports arithmetic operations: + +- Addition: `amount1 + amount2` +- Subtraction: `amount1 - amount2` +- Comparison operations: `<`, `<=`, `>`, `>=`, `==`, `!=` + +Example: + +```python +amount1 = AlgoAmount(algo=1) +amount2 = AlgoAmount(micro_algo=500_000) +total = amount1 + amount2 # Results in 1.5 Algo +``` diff --git a/docs/html/_sources/capabilities/app-client.md.txt b/docs/html/_sources/capabilities/app-client.md.txt index d76f27b7..0b257d34 100644 --- a/docs/html/_sources/capabilities/app-client.md.txt +++ b/docs/html/_sources/capabilities/app-client.md.txt @@ -1,157 +1,347 @@ -# App client +# App client and App factory -Application client that works with ARC-0032 application spec defined smart contracts (e.g. via Beaker). +> [!NOTE] +> This page covers the untyped app client, but we recommend using typed clients (coming soon), which will give you a better developer experience with strong typing specific to the app itself. -App client is a high productivity application client that works with ARC-0032 application spec defined smart contracts, which you can use to create, update, delete, deploy and call a smart contract and access state data for it. +App client and App factory are higher-order use case capabilities provided by AlgoKit Utils that builds on top of the core capabilities, particularly [App deployment](./app-deploy.md) and [App management](./app.md). They allow you to access high productivity application clients that work with [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258) and [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) application spec defined smart contracts, which you can use to create, update, delete, deploy and call a smart contract and access state data for it. -To see some usage examples check out the [automated tests](https://github.com/algorandfoundation/algokit-utils-py/blob/main/tests/test_app_client_call.py). +> [!NOTE] +> If you are confused about when to use the factory vs client the mental model is: use the client if you know the app ID, use the factory if you don't know the app ID (deferred knowledge or the instance doesn't exist yet on the blockchain) or you have multiple app IDs -## Design +## `AppFactory` -The design for the app client is based on a wrapper for parsing an [ARC-0032](https://github.com/algorandfoundation/ARCs/pull/150) application spec and wrapping the [App deployment](./app-deploy.md) functionality and corresponding [design](./app-deploy.md#design). +The `AppFactory` is a class that, for a given app spec, allows you to create and deploy one or more app instances and to create one or more app clients to interact with those (or other) app instances. -## Creating an application client +To get an instance of `AppFactory` you can use `AlgorandClient` via `algorand.get_app_factory`: -There are two key ways of instantiating an ApplicationClient: +```python +# Minimal example +factory = algorand.get_app_factory( + app_spec="{/* ARC-56 or ARC-32 compatible JSON */}", +) + +# Advanced example +factory = algorand.get_app_factory( + app_spec=parsed_arc32_or_arc56_app_spec, + default_sender="SENDERADDRESS", + app_name="OverriddenAppName", + version="2.0.0", + compilation_params={ + "updatable": True, + "deletable": False, + "deploy_time_params": { "ONE": 1, "TWO": "value" }, + } +) +``` + +## `AppClient` + +The `AppClient` is a class that, for a given app spec, allows you to manage calls and state for a specific deployed instance of an app (with a known app ID). + +To get an instance of `AppClient` you can use either `AlgorandClient` or instantiate it directly: -1. By app ID - When needing to call an existing app by app ID or unconditionally create a new app. -The signature `ApplicationClient(algod_client, app_spec, app_id=..., ...)` requires: - * `algod_client`: An `AlgodClient` - * `app_spec`: An `ApplicationSpecification` - * `app_id`: The app_id of an existing application, or 0 if creating a new app +```python +# Minimal examples +app_client = AppClient.from_creator_and_name( + app_spec="{/* ARC-56 or ARC-32 compatible JSON */}", + creator_address="CREATORADDRESS", + algorand=algorand, +) + +app_client = AppClient( + AppClientParams( + app_spec="{/* ARC-56 or ARC-32 compatible JSON */}", + app_id=12345, + algorand=algorand, + ) +) + +app_client = AppClient.from_network( + app_spec="{/* ARC-56 or ARC-32 compatible JSON */}", + algorand=algorand, +) + +# Advanced example +app_client = AppClient( + AppClientParams( + app_spec=parsed_app_spec, + app_id=12345, + algorand=algorand, + app_name="OverriddenAppName", + default_sender="SENDERADDRESS", + approval_source_map=approval_teal_source_map, + clear_source_map=clear_teal_source_map, + ) +) +``` -2. By creator and app name - When needing to deploy or find an app associated with a specific creator account and app name. -The signature `ApplicationClient(algod_client, app_spec, creator=..., indexer=..., app_lookup)` requires: - * `algod_client`: An `AlgodClient` - * `app_spec`: An `ApplicationSpecification` - * `creator`: The address or `Account` of the creator of the app for which to search for the deployed app under - * `indexer`: - * `app_lookup`: Optional if an indexer is provided, - * `app_name`: An overridden name to identify the contract with, otherwise `contract.name` is used from the app spec +You can access `app_id`, `app_address`, `app_name` and `app_spec` as properties on the `AppClient`. -Both approaches also allow specifying the following parameters that will be used as defaults for all application calls: -* `signer`: `TransactionSigner` to sign transactions with. -* `sender`: Address to use for transaction signing, will be derived from the signer if not provided. -* `suggested_params`: Default `SuggestedParams` to use, will use current network suggested params by default - -Both approaches also allow specifying a mapping of template values via the `template_values` parameter, this will be used before compiling the application to replace any -`TMPL_` variables that may be in the TEAL. The `TMPL_UPDATABLE` and `TMPL_DELETABLE` variables used in some AlgoKit templates are handled by the `deploy` method, but should be included if -using `create` or `update` directly. +## Dynamically creating clients for a given app spec -## Calling methods on the app +The `AppFactory` allows you to conveniently create multiple `AppClient` instances on-the-fly with information pre-populated. -There are various methods available on `ApplicationClient` that can be used to call an app: +This is possible via two methods on the app factory: -* `call`: Used to call methods with an on complete action of `no_op` -* `create`: Used to create an instance of the app, by using an `app_id` of 0, includes the approval and clear programs in the call -* `update`: Used to update an existing app, includes the approval and clear programs in the call, and is called with an on complete action of `update_application` -* `delete`: Used to remove an existing app, is called with an on complete action of `delete_application` -* `opt_in`: Used to opt in to an existing app, is called with an on complete action of `opt_in` -* `close_out`: Used to close out of an existing app, is called with an on complete action of `opt_in` -* `clear_state`: Used to unconditionally close out from an app, calls the clear program of an app +- `factory.get_app_client_by_id(app_id, ...)` - Returns a new `AppClient` for an app instance of the given ID. Automatically populates app_name, default_sender and source maps from the factory if not specified. +- `factory.get_app_client_by_creator_and_name(creator_address, app_name, ...)` - Returns a new `AppClient`, resolving the app by creator address and name using AlgoKit app deployment semantics. Automatically populates app_name, default_sender and source maps from the factory if not specified. -### Specifying which method +```python +app_client1 = factory.get_app_client_by_id(app_id=12345) +app_client2 = factory.get_app_client_by_id(app_id=12346) +app_client3 = factory.get_app_client_by_id( + app_id=12345, + default_sender="SENDER2ADDRESS" +) + +app_client4 = factory.get_app_client_by_creator_and_name( + creator_address="CREATORADDRESS" +) +app_client5 = factory.get_app_client_by_creator_and_name( + creator_address="CREATORADDRESS", + app_name="NonDefaultAppName" +) +app_client6 = factory.get_app_client_by_creator_and_name( + creator_address="CREATORADDRESS", + app_name="NonDefaultAppName", + ignore_cache=True, # Perform fresh indexer lookups + default_sender="SENDER2ADDRESS" +) +``` -All methods for calling an app that support ABI methods (everything except `clear_state`) take a parameter `call_abi_method` which can be used to specify which method to call. -The method selected can be specified explicitly, or allow the client to infer the method where possible, supported values are: +## Creating and deploying an app -* `None`: The default value, when `None` is passed the client will attempt to find any ABI method or bare method that is compatible with the provided arguments -* `False`: Indicates that an ABI method should not be used, and instead a bare method call is made -* `True`: Indicates that an ABI method should be used, and the client will attempt to find an ABI method that is compatible with the provided arguments -* `str`: If a string is provided, it will be interpreted as either an ABI signature specifying a method, or as an ABI method name -* `algosdk.abi.Method`: The specified ABI method will be called -* `ABIReturnSubroutine`: Any type that has a `method_spec` function that returns an `algosd.abi.Method` +Once you have an app factory you can perform the following actions: -### ABI arguments +- `factory.send.bare.create(...)` - Signs and sends a transaction to create an app and returns the result of that call and an `AppClient` instance for the created app +- `factory.deploy(...)` - Uses the creator address and app name pattern to find if the app has already been deployed or not and either creates, updates or replaces that app based on the deployment rules (i.e. it's an idempotent deployment) and returns the result of the deployment and an `AppClient` instance for the created/updated/existing app. -ABI arguments are passed as python keyword arguments e.g. to pass the ABI parameter `name` for the ABI method `hello` the following syntax is used `client.call("hello", name="world")` +> See {py:func}`API docs ` for details on parameter signatures. -### Transaction Parameters +### Create -All methods for calling an app take an optional `transaction_parameters` argument, with the following supported parameters: +The create method is a wrapper over the `app_create` (bare calls) and `app_create_method_call` (ABI method calls) methods, with the following differences: -* `signer`: The `TransactionSigner` to use on the call. This overrides any signer specified on the client -* `sender`: The address of the sender to use on the call, must be able to be signed for by the `signer`. This overrides any sender specified on the client -* `suggested_params`: `SuggestedParams` to use on the call. This overrides any suggested_params specified on the client -* `note`: Note to include in the transaction -* `lease`: Lease parameter for the transaction -* `boxes`: A sequence of boxes to use in the transaction, this is a list of (app_index, box_name) tuples `[(0, "box_name"), (0, ...)]` -* `accounts`: Account references to include in the transaction -* `foreign_apps`: Foreign apps to include in the transaction -* `foreign_assets`: Foreign assets to include in the transaction -* `on_complete`: The on complete action to use for the transaction, only available when using `call` or `create` -* `extra_pages`: Additional pages to allocate when calling `create`, by default a sufficient amount will be calculated based on the current approval and clear. This can be overridden, if more is required - for a future update +- You don't need to specify the `approval_program`, `clear_state_program`, or `schema` because these are all specified or calculated from the app spec +- `sender` is optional and if not specified then the `default_sender` from the `AppFactory` constructor is used +- `deploy_time_params`, `updatable` and `deletable` can be passed in to control deploy-time parameter replacements and deploy-time immutability and permanence control. Note these are consolidated under the `compilation_params` `TypedDict`, see {py:func}`API docs ` for details. -Parameters can be passed as one of the dataclasses `CommonCallParameters`, `OnCompleteCallParameters`, `CreateCallParameters` (exact type depends on method used) ```python -client.call("hello", transaction_parameters=algokit_utils.OnCompleteCallParameters(signer=...)) +# Use no-argument bare-call +result, app_client = factory.send.bare.create() + +# Specify parameters for bare-call and override other parameters +result, app_client = factory.send.bare.create( + params=AppClientBareCallParams( + args=[bytes([1, 2, 3, 4])], + static_fee=AlgoAmount.from_microalgos(3000), + on_complete=OnComplete.OptIn, + ), + compilation_params={ + "deploy_time_params": { + "ONE": 1, + "TWO": "two", + }, + "updatable": True, + "deletable": False, + } +) + +# Specify parameters for ABI method call +result, app_client = factory.send.create( + AppClientMethodCallParams( + method="create_application", + args=[1, "something"] + ) +) ``` -Alternatively, parameters can be passed as a dictionary e.g. +## Updating and deleting an app + +Deploy method aside, the ability to make update and delete calls happens after there is an instance of an app created via `AppClient`. The semantics of this are no different than other calls, with the caveat that the update call is a bit different since the code will be compiled when constructing the update params and the update calls thus optionally takes compilation parameters (`compilation_params`) for deploy-time parameter replacements and deploy-time immutability and permanence control. + +## Calling the app + +You can construct a params object, transaction(s) and sign and send a transaction to call the app that a given `AppClient` instance is pointing to. + +This is done via the following properties: + +- `app_client.params.{method}(params)` - Params for an ABI method call +- `app_client.params.bare.{method}(params)` - Params for a bare call +- `app_client.create_transaction.{method}(params)` - Transaction(s) for an ABI method call +- `app_client.create_transaction.bare.{method}(params)` - Transaction for a bare call +- `app_client.send.{method}(params)` - Sign and send an ABI method call +- `app_client.send.bare.{method}(params)` - Sign and send a bare call + +Where `{method}` is one of: + +- `update` - An update call +- `opt_in` - An opt-in call +- `delete` - A delete application call +- `clear_state` - A clear state call (note: calls the clear program and only applies to bare calls) +- `close_out` - A close-out call +- `call` - A no-op call (or other call if `on_complete` is specified to anything other than update) + ```python -client.call("hello", transaction_parameters={"signer":...}) +call1 = app_client.send.update( + AppClientMethodCallParams( + method="update_abi", + args=["string_io"], + ), + compilation_params={"deploy_time_params": deploy_time_params} +) + +call2 = app_client.send.delete( + AppClientMethodCallParams( + method="delete_abi", + args=["string_io"] + ) +) + +call3 = app_client.send.opt_in( + AppClientMethodCallParams(method="opt_in") +) + +call4 = app_client.send.bare.clear_state() + +transaction = app_client.create_transaction.bare.close_out( + AppClientBareCallParams( + args=[bytes([1, 2, 3])] + ) +) + +params = app_client.params.opt_in( + AppClientMethodCallParams(method="optin") +) ``` -## Composing calls +## Funding the app account -If multiple calls need to be made in a single transaction, the `compose_` method variants can be used. All these methods take an `AtomicTransactionComposer` as their first argument. -Once all the calls have been added to the ATC, it can then be executed. For example: +Often there is a need to fund an app account to cover minimum balance requirements for boxes and other scenarios. There is an app client method that will do this for you via `fund_app_account(params)`. -```python -from algokit_utils import ApplicationClient -from algosdk.atomic_transaction_composer import AtomicTransactionComposer +The input parameters are: -client = ApplicationClient(...) -atc = AtomicTransactionComposer() -client.compose_call(atc, "hello", name="world") -... # additional compose calls +- A `FundAppAccountParams` object, which has the same properties as a payment transaction except `receiver` is not required and `sender` is optional (if not specified then it will be set to the app client's default sender if configured). -response = client.execute_atc(atc) +Note: If you are passing the funding payment in as an ABI argument so it can be validated by the ABI method then you'll want to get the funding call as a transaction, e.g.: + +```python +result = app_client.send.call( + AppClientMethodCallParams( + method="bootstrap", + args=[ + app_client.create_transaction.fund_app_account( + FundAppAccountParams( + amount=AlgoAmount.from_microalgos(200_000) + ) + ) + ], + box_references=["Box1"] + ) +) ``` +You can also get the funding call as a params object via `app_client.params.fund_app_account(params)`. ## Reading state +`AppClient` has a number of mechanisms to read state (global, local and box storage) from the app instance. + +### App spec methods + +The ARC-56 app spec can specify detailed information about the encoding format of state values and as such allows for a more advanced ability to automatically read state values and decode them as their high-level language types rather than the limited `int` / `bytes` / `str` ability that the generic methods give you. + +You can access this functionality via: + +- `app_client.state.global_state.{method}()` - Global state +- `app_client.state.local_state(address).{method}()` - Local state +- `app_client.state.box.{method}()` - Box storage + +Where `{method}` is one of: + +- `get_all()` - Returns all single-key state values in a dict keyed by the key name and the value a decoded ABI value. +- `get_value(name)` - Returns a single state value for the current app with the value a decoded ABI value. +- `get_map_value(map_name, key)` - Returns a single value from the given map for the current app with the value a decoded ABI value. Key can either be bytes with the binary value of the key value on-chain (without the map prefix) or the high level (decoded) value that will be encoded to bytes for the app spec specified `key_type` +- `get_map(map_name)` - Returns all map values for the given map in a key=>value dict. It's recommended that this is only done when you have a unique `prefix` for the map otherwise there's a high risk that incorrect values will be included in the map. + +```python +values = app_client.state.global_state.get_all() +value = app_client.state.local_state("ADDRESS").get_value("value1") +map_value = app_client.state.box.get_map_value("map1", "mapKey") +map_dict = app_client.state.global_state.get_map("myMap") +``` + +### Generic methods + There are various methods defined that let you read state from the smart contract app: -* `get_global_state` - Gets the current global state of the app -* `get_local_state` - Gets the current local state for the given account address +- `get_global_state()` - Gets the current global state using {py:func}`algorand.app.get_global_state `. +- `get_local_state(address: str)` - Gets the current local state for the given account address using {py:func}`algorand.app.get_local_state `. +- `get_box_names()` - Gets the current box names using {py:func}`algorand.app.get_box_names `. +- `get_box_value(name)` - Gets the current value of the given box using {py:func}`algorand.app.get_box_value `. +- `get_box_value_from_abi_type(name)` - Gets the current value of the given box from an ABI type using {py:func}`algorand.app.get_box_value_from_abi_type `. +- `get_box_values(filter)` - Gets the current values of the boxes using {py:func}`algorand.app.get_box_values `. +- `get_box_values_from_abi_type(type, filter)` - Gets the current values of the boxes from an ABI type using {py:func}`algorand.app.get_box_values_from_abi_type `. + +```python +global_state = app_client.get_global_state() +local_state = app_client.get_local_state("ACCOUNTADDRESS") + +box_name: BoxReference = BoxReference(app_id=app_client.app_id, name="my-box") +box_name2: BoxReference = BoxReference(app_id=app_client.app_id, name="my-box2") + +box_names = app_client.get_box_names() +box_value = app_client.get_box_value(box_name) +box_values = app_client.get_box_values([box_name, box_name2]) +box_abi_value = app_client.get_box_value_from_abi_type( + box_name, + algosdk.ABIStringType +) +box_abi_values = app_client.get_box_values_from_abi_type( + [box_name, box_name2], + algosdk.ABIStringType +) +``` ## Handling logic errors and diagnosing errors -Often when calling a smart contract during development you will get logic errors that cause an exception to throw. This may be because of a failing assertion, a lack of fees, -exhaustion of opcode budget, or any number of other reasons. +Often when calling a smart contract during development you will get logic errors that cause an exception to throw. This may be because of a failing assertion, a lack of fees, exhaustion of opcode budget, or any number of other reasons. When this occurs, you will generally get an error that looks something like: `TransactionPool.Remember: transaction {TRANSACTION_ID}: logic eval error: {ERROR_MESSAGE}. Details: pc={PROGRAM_COUNTER_VALUE}, opcodes={LIST_OF_OP_CODES}`. -The information in that error message can be parsed and when combined with the [source map from compilation](./app-deploy.md#compilation-and-template-substitution) you can expose debugging -information that makes it much easier to understand what's happening. +The information in that error message can be parsed and when combined with the [source map from compilation](./app-deploy.md#compilation-and-template-substitution) you can expose debugging information that makes it much easier to understand what's happening. The ARC-56 app spec, if provided, can also specify human-readable error messages against certain program counter values and further augment the error message. + +The app client and app factory automatically provide this functionality for all smart contract calls. They also expose a function that can be used for any custom calls you manually construct and need to add into your own try/catch `expose_logic_error(e: Error, is_clear: bool = False)`. -When an error is thrown then the resulting error that is re-thrown will be a `LogicError`, which has the following fields: +When an error is thrown then the resulting error that is re-thrown will be a {py:obj}`LogicError `, which has the following fields: -* `logic_error`: Original exception -* `program`: Program source (if available) -* `source_map`: Source map used (if available) -* `transaction_id`: Transaction ID of failing transaction -* `message`: The error message -* `line_no`: The line number in the TEAL program that -* `traces`: A list of Trace objects providing additional insights on simulation when debug mode is active. +- `logic_error: Exception` - The original logic error exception +- `logic_error_str: str` - The string representation of the logic error +- `program: str` - The TEAL program source code +- `source_map: AlgoSourceMap | None` - The source map if available +- `transaction_id: str` - The transaction ID that triggered the error +- `message: str` - Combined error message with debugging information +- `pc: int` - The program counter value where error occurred +- `traces: list[SimulationTrace] | None` - Simulation traces if debug enabled +- `line_no: int | None` - The line number in the TEAL source code +- `lines: list[str]` - The TEAL program split into individual lines -The function `trace()` will provide a formatted output of the surrounding TEAL where the error occurred. +Note: This information will only show if the app client / app factory has a source map. This will occur if: -```{note} -The extended information will only show if the Application Client has a source map. This will occur if: +- You have called `create`, `update` or `deploy` +- You have called `import_source_maps(source_maps)` and provided the source maps (which you can get by calling `export_source_maps()` after variously calling `create`, `update`, or `deploy` and it returns a serialisable value) +- You had source maps present in an app factory and then used it to [create an app client](#dynamically-creating-clients-for-a-given-app-spec) (they are automatically passed through) -1.) The ApplicationClient instance has already called, `create, `update` or `deploy` OR -2.) `template_values` are provided when creating the ApplicationClient, so a SourceMap can be obtained automatically OR -3.) `approval_source_map` on `ApplicationClient` has been set from a previously compiled approval program OR -4.) A source map has been exported/imported using `export_source_map`/`import_source_map`""" +If you want to go a step further and automatically issue a [simulated transaction](https://algorand.github.io/js-algorand-sdk/classes/modelsv2.SimulateTransactionResult.html) and get trace information when there is an error when an ABI method is called you can turn on debug mode: + +```python +config.configure(debug=True) ``` -### Debug Mode and traces Field -When debug mode is active, the LogicError will contain a field named traces. This field will include raw simulate execution traces, providing a detailed account of the transaction simulation. These traces are crucial for diagnosing complex issues and are automatically included in all application client calls when debug mode is active. +If you do that then the exception will have the `traces` property within the underlying exception will have key information from the simulation within it and this will get populated into the `led.traces` property of the thrown error. + +When this debug flag is set, it will also emit debugging symbols to allow break-point debugging of the calls if the [project root is also configured](./debugging.md). + +## Default arguments -```{note} -Remember to enable debug mode (`config.debug = True`) to include raw simulate execution traces in the `LogicError`. -``` \ No newline at end of file +If an ABI method call specifies default argument values for any of its arguments you can pass in `None` for the value of that argument for the default value to be automatically populated. diff --git a/docs/html/_sources/capabilities/app-deploy.md.txt b/docs/html/_sources/capabilities/app-deploy.md.txt index d041576e..3f85dd52 100644 --- a/docs/html/_sources/capabilities/app-deploy.md.txt +++ b/docs/html/_sources/capabilities/app-deploy.md.txt @@ -1,19 +1,16 @@ # App deployment -Idempotent (safely retryable) deployment of an app, including deploy-time immutability and permanence control and TEAL template substitution +AlgoKit contains advanced smart contract deployment capabilities that allow you to have idempotent (safely retryable) deployment of a named app, including deploy-time immutability and permanence control and TEAL template substitution. This allows you to control the smart contract development lifecycle of a single-instance app across multiple environments (e.g. LocalNet, TestNet, MainNet). -App deployment is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities, -particularly [App management](./app-client.md). It allows you to idempotently (with safe retryability) deploy an app, including deploy-time immutability and permanence control and -TEAL template substitution. +It's optional to use this functionality, since you can construct your own deployment logic using create / update / delete calls and your own mechanism to maintaining app metadata (like app IDs etc.), but this capability is an opinionated out-of-the-box solution that takes care of the heavy lifting for you. -To see some usage examples check out the [automated tests](https://github.com/algorandfoundation/algokit-utils-py/blob/main/tests/test_deploy_scenarios.py). +App deployment is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities, particularly [App management](./app.md). -(design)= +To see some usage examples check out the [automated tests](https://github.com/algorandfoundation/algokit-utils-py/blob/main/tests/test_deploy_scenarios.py). -## Design +## Smart contract development lifecycle -The architecture design behind app deployment is articulated in an [architecture decision record](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md). -While the implementation will naturally evolve over time and diverge from this record, the principles and design goals behind the design are comprehensively explained. +The design behind the deployment capability is unique. The architecture design behind app deployment is articulated in an [architecture decision record](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/architecture-decisions/2023-01-12_smart-contract-deployment.md). While the implementation will naturally evolve over time and diverge from this record, the principles and design goals behind the design are comprehensively explained. Namely, it described the concept of a smart contract development lifecycle: @@ -36,86 +33,182 @@ The App deployment capability provided by AlgoKit Utils helps implement **#2 Dep Furthermore, the implementation contains the following implementation characteristics per the original architecture design: - Deploy-time parameters can be provided and substituted into a TEAL Template by convention (by replacing `TMPL_{KEY}`) -- Contracts can be built by any smart contract framework that supports [ARC-0032](https://arc.algorand.foundation/ARCs/arc-0032) and - [ARC-0004](https://arc.algorand.foundation/ARCs/arc-0004) ([Beaker](https://beaker.algo.xyz/) or otherwise), which also means the deployment language can be - different to the development language e.g. you can deploy a Python smart contract with TypeScript for instance -- There is explicit control of the immutability (updatability / upgradeability) and permanence (deletability) of the smart contract, which can be varied per environment to allow for easier - development and testing in non-MainNet environments (by replacing `TMPL_UPDATABLE` and `TMPL_DELETABLE` at deploy-time by convention, if present) -- Contracts are resolvable by a string "name" for a given creator to allow automated determination of whether that contract had been deployed previously or not, but can also be resolved by ID - instead +- Contracts can be built by any smart contract framework that supports [ARC-56](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0056.md) and [ARC-32](https://github.com/algorandfoundation/ARCs/pull/150), which also means the deployment language can be different to the development language e.g. you can deploy a Python smart contract with TypeScript for instance +- There is explicit control of the immutability (updatability / upgradeability) and permanence (deletability) of the smart contract, which can be varied per environment to allow for easier development and testing in non-MainNet environments (by replacing `TMPL_UPDATABLE` and `TMPL_DELETABLE` at deploy-time by convention, if present) +- Contracts are resolvable by a string "name" for a given creator to allow automated determination of whether that contract had been deployed previously or not, but can also be resolved by ID instead + +This design allows you to have the same deployment code across environments without having to specify an ID for each environment. This makes it really easy to apply [continuous delivery](https://continuousdelivery.com/) practices to your smart contract deployment and make the deployment process completely automated. + +## `AppDeployer` -## Finding apps by creator +The {py:obj}`AppDeployer ` is a class that is used to manage app deployments and deployment metadata. -There is a method `algokit.get_creator_apps(creatorAccount, indexer)`, which performs a series of indexer lookups that return all apps created by the given creator. These are indexed by the name it -was deployed under if the creation transaction contained the following payload in the transaction note field: +To get an instance of `AppDeployer` you can use either [`AlgorandClient`](./algorand-client.md) via `algorand.appDeployer` or instantiate it directly (passing in an [`AppManager`](./app.md#appmanager), [`AlgorandClientTransactionSender`](./algorand-client.md#sending-a-single-transaction) and optionally an indexer client instance): +```python +from algokit_utils.app_deployer import AppDeployer + +app_deployer = AppDeployer(app_manager, transaction_sender, indexer) ``` -ALGOKIT_DEPLOYER:j{name:string, version:string, updatable?:boolean, deletable?:boolean} + +## Deployment metadata + +When AlgoKit performs a deployment of an app it creates metadata to describe that deployment and includes this metadata in an [ARC-2](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0002.md) transaction note on any creation and update transactions. + +The deployment metadata is defined in {py:obj}`AppDeployMetadata `, which is an object with: + +- `name: str` - The unique name identifier of the app within the creator account +- `version: str` - The version of app that is / will be deployed; can be an arbitrary string, but we recommend using [semver](https://semver.org/) +- `deletable: bool | None` - Whether or not the app is deletable (`true`) / permanent (`false`) / unspecified (`None`) +- `updatable: bool | None` - Whether or not the app is updatable (`true`) / immutable (`false`) / unspecified (`None`) + +An example of the ARC-2 transaction note that is attached as an app creation / update transaction note to specify this metadata is: + +``` +ALGOKIT_DEPLOYER:j{name:"MyApp",version:"1.0",updatable:true,deletable:false} ``` -Any creation transactions or update transactions are then retrieved and processed in chronological order to result in an `AppLookup` object +## Lookup deployed apps by name -Given there are a number of indexer calls to retrieve this data it's a non-trivial object to create, and it's recommended that for the duration you are performing a single deployment -you hold a value of it rather than recalculating it. Most AlgoKit Utils functions that need it will also take an optional value of it that will be used in preference to retrieving a -fresh version. +In order to resolve what apps have been previously deployed and their metadata, AlgoKit provides a method that does a series of indexer lookups and returns a map of name to app metadata via `get_creator_apps_by_name(creator_address)`. -## Deploying an application +```python +app_lookup = algorand.app_deployer.get_creator_apps_by_name("CREATORADDRESS") +app1_metadata = app_lookup.apps["app1"] +``` -The method that performs the deployment logic is the instance method `ApplicationClient.deploy`. It performs an idempotent (safely retryable) deployment. It will detect if the app already -exists and if it doesn't it will create it. If the app does already exist then it will: +This method caches the result of the lookup, since it's a reasonably heavyweight call (N+1 indexer calls for N deployed apps by the creator). If you want to skip the cache to get a fresh version then you can pass in a second parameter `ignore_cache=True`. This should only be needed if you are performing parallel deployments outside of the current `AppDeployer` instance, since it will keep its cache updated based on its own deployments. -- Detect if the app has been updated (i.e. the logic has changed) and either fail or perform either an update or a replacement based on the deployment configuration. -- Detect if the app has a breaking schema change (i.e. more global or local storage is needed than was originally requested) and either fail or perform a replacement based on the - deployment configuration. +The return type of `get_creator_apps_by_name` is {py:obj}`ApplicationLookup `, which is an object with: -It will automatically add metadata to the transaction note of the create or update calls that indicates the name, version, updatability and deletability of the contract. -This metadata works in concert with `get_creator_apps` to allow the app to be reliably retrieved against that creator in it's currently deployed state. +```python +@dataclasses.dataclass +class ApplicationLookup: + creator: str + apps: dict[str, ApplicationMetaData] = dataclasses.field(default_factory=dict) +``` -`deploy` automatically executes [template substitution](#compilation-and-template-substitution) including deploy-time control of permanence and immutability. +The `apps` property contains a lookup by app name that resolves to the current {py:obj}`ApplicationMetaData `. + +> Refer to the {py:obj}`ApplicationLookup ` for latest information on exact types. + +## Performing a deployment + +In order to perform a deployment, AlgoKit provides the {py:meth}`deploy ` method. + +For example: + +```python +deployment_result = algorand.app_deployer.deploy( + AppDeployParams( + metadata=AppDeploymentMetaData( + name="MyApp", + version="1.0.0", + deletable=False, + updatable=False, + ), + create_params=AppCreateParams( + sender="CREATORADDRESS", + approval_program=approval_teal_template_or_byte_code, + clear_state_program=clear_state_teal_template_or_byte_code, + schema=StateSchema( + global_ints=1, + global_byte_slices=2, + local_ints=3, + local_byte_slices=4, + ), + # Other parameters if a create call is made... + ), + update_params=AppUpdateParams( + sender="SENDERADDRESS", + # Other parameters if an update call is made... + ), + delete_params=AppDeleteParams( + sender="SENDERADDRESS", + # Other parameters if a delete call is made... + ), + deploy_time_params={ + "VALUE": 1, # TEAL template variables to replace + }, + on_schema_break=OnSchemaBreak.Append, + on_update=OnUpdate.Update, + send_params=SendParams( + populate_app_call_resources=True, + # Other execution control parameters + ), + ) +) +``` + +This method performs an idempotent (safely retryable) deployment. It will detect if the app already exists and if it doesn't it will create it. If the app does already exist then it will: + +- Detect if the app has been updated (i.e. the program logic has changed) and either fail, perform an update, deploy a new version or perform a replacement (delete old app and create new app) based on the deployment configuration. +- Detect if the app has a breaking schema change (i.e. more global or local storage is needed than were originally requested) and either fail, deploy a new version or perform a replacement (delete old app and create new app) based on the deployment configuration. + +It will automatically [add metadata to the transaction note of the create or update transactions](#deployment-metadata) that indicates the name, version, updatability and deletability of the contract. This metadata works in concert with [`appDeployer.get_creator_apps_by_name`](#lookup-deployed-apps-by-name) to allow the app to be reliably retrieved against that creator in it's currently deployed state. It will automatically update it's lookup cache so subsequent calls to `get_creator_apps_by_name` or `deploy` will use the latest metadata without needing to call indexer again. + +`deploy` also automatically executes [template substitution](#compilation-and-template-substitution) including deploy-time control of permanence and immutability if the requisite template parameters are specified in the provided TEAL template. ### Input parameters -The following inputs are used when deploying an App +The first parameter `deployment` is an {py:obj}`AppDeployParams `, which is an object with: -- `version`: The version string for the app defined in app_spec, if not specified the version will automatically increment for existing apps that are updated, and set to 1.0 for new apps -- `signer`, `sender`: Optional signer and sender for deployment operations, sender must be the same as the creator specified -- `allow_update`, `allow_delete`: Control the updatability and deletability of the app, used to populate `TMPL_UPDATABLE` and `TMPL_DELETABLE` template values -- `on_update`: Determines what should happen if an update to the smart contract is detected (e.g. the TEAL code has changed since last deployment) -- `on_schema_break`: Determines what should happen if a breaking change to the schema is detected (e.g. if you need more global or local state that was previously requested when the contract was originally created) -- `create_args`: Args to use if a create operation is performed -- `update_args`: Args to use if an update operation is performed -- `delete_args`: Args to use if a delete operation is performed -- `template_values`: Values to use for automatic substitution of [deploy-time parameter values](#design) is mapping of `key: value` that will result in `TMPL_{key}` being replaced with `value` +- `metadata: AppDeployMetadata` - determines the [deployment metadata](#deployment-metadata) of the deployment +- `create_params: AppCreateParams | CreateCallABI` - the parameters for an [app creation call](./app.md) (raw parameters or ABI method call) +- `update_params: AppUpdateParams | UpdateCallABI` - the parameters for an [app update call](./app.md) (raw parameters or ABI method call) without the `app_id`, `approval_program`, or `clear_state_program` as these are handled by the deploy logic +- `delete_params: AppDeleteParams | DeleteCallABI` - the parameters for an [app delete call](./app.md) (raw parameters or ABI method call) without the `app_id` parameter +- `deploy_time_params: TealTemplateParams | None` - optional parameters for [TEAL template substitution](#compilation-and-template-substitution) + - {py:obj}`TealTemplateParams ` is a dict that replaces `TMPL_{key}` with `value` (strings/Uint8Arrays are properly encoded) +- `on_schema_break: OnSchemaBreak | str | None` - determines {py:obj}`OnSchemaBreak ` if schema requirements increase (values: 'replace', 'fail', 'append') +- `on_update: OnUpdate | str | None` - determines {py:obj}`OnUpdate ` if contract logic changes (values: 'update', 'replace', 'fail', 'append') +- `existing_deployments: ApplicationLookup | None` - optional pre-fetched app lookup data to skip indexer queries +- `ignore_cache: bool | None` - if True, bypasses cached deployment metadata +- Additional fields from {py:obj}`SendParams ` - transaction execution parameters ### Idempotency -`deploy` is idempotent which means you can safely call it again multiple times, and it will only apply any changes it detects. If you call it again straight after calling it then it will -do nothing. This also means it can be used to find an existing app based on the supplied creator and app_spec or name. - -(compilation-and-template-substitution)= +`deploy` is idempotent which means you can safely call it again multiple times and it will only apply any changes it detects. If you call it again straight after calling it then it will do nothing. ### Compilation and template substitution -When compiling TEAL template code, the capabilities described in the [design above](#design) are present, namely the ability to supply deploy-time parameters and the ability to control immutability and permanence of the smart contract at deploy-time. +When compiling TEAL template code, the capabilities described in the [above design](#smart-contract-development-lifecycle) are present, namely the ability to supply deploy-time parameters and the ability to control immutability and permanence of the smart contract at deploy-time. -In order for a smart contract to be able to use this functionality, it must have a TEAL Template that contains the following: +In order for a smart contract to opt-in to use this functionality, it must have a TEAL Template that contains the following: -- `TMPL_{key}` - Which can be replaced with a number or a string / byte array which wil be automatically hexadecimal encoded +- `TMPL_{key}` - Which can be replaced with a number or a string / byte array which will be automatically hexadecimal encoded (for any number of `{key}` => `{value}` pairs) - `TMPL_UPDATABLE` - Which will be replaced with a `1` if an app should be updatable and `0` if it shouldn't (immutable) - `TMPL_DELETABLE` - Which will be replaced with a `1` if an app should be deletable and `0` if it shouldn't (permanent) -If you are building a smart contract using the [beaker_production AlgoKit template](https://github.com/algorandfoundation/algokit-beaker-default-template) if provides a reference implementation out of the box for the deploy-time immutability and permanence control. +If you are building a smart contract using the production [AlgoKit init templates](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/init.md) provide a reference implementation out of the box for the deploy-time immutability and permanence control. + +If you passed in a TEAL template for the `approval_program` or `clear_state_program` (i.e. a `str` rather than a `bytes`) then `deploy` will return the {py:obj}`CompiledTeal ` of substituting then compiling the TEAL template(s) in the following properties of the return value: + +- `compiled_approval: CompiledTeal | None` +- `compiled_clear: CompiledTeal | None` + +Template substitution is done by executing `algorand.app.compile_teal_template(teal_template_code, template_params, deployment_metadata)`, which in turn calls the following in order and returns the compilation result per above (all of which can also be invoked directly): + +- `AppManager.strip_teal_comments(teal_code)` - Strips out any TEAL comments to reduce the payload that is sent to algod and reduce the likelihood of hitting the max payload limit +- `AppManager.replace_template_variables(teal_template_code, template_values)` - Replaces the template variables by looking for `TMPL_{key}` +- `AppManager.replace_teal_template_deploy_time_control_params(teal_template_code, params)` - If `params` is provided, it allows for deploy-time immutability and permanence control by replacing `TMPL_UPDATABLE` with `params.get("updatable")` if not `None` and replacing `TMPL_DELETABLE` with `params.get("deletable")` if not `None` +- `algorand.app.compile_teal(teal_code)` - Sends the final TEAL to algod for compilation and returns the result including the source map and caches the compilation result within the `AppManager` instance ### Return value -`deploy` returns a `DeployResponse` object, that describes the action taken. - -- `action_taken`: Describes what happened during deployment - - `Create` - The smart contract app is created. - - `Update` - The smart contract app is updated - - `Replace` - The smart contract app was deleted and created again (in an atomic transaction) - - `Nothing` - Nothing was done since an existing up-to-date app was found -- `create_response`: If action taken was `Create` or `Replace`, the result of the create transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used -- `update_response`: If action taken was `Update`, the result of the update transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used -- `delete_response`: If action taken was `Replace`, the result of the delete transaction. Can be a `TransactionResponse` or `ABITransactionResponse` depending on the method used -- `app`: An `AppMetaData` object, describing the final app state +When `deploy` executes it will return a {py:obj}`AppDeployResult ` object that describes exactly what it did and has comprehensive metadata to describe the end result of the deployed app. + +The `deploy` call itself may do one of the following (which you can determine by looking at the `operation_performed` field on the return value from the function): + +- `OperationPerformed.CREATE` - The smart contract app was created +- `OperationPerformed.UPDATE` - The smart contract app was updated +- `OperationPerformed.REPLACE` - The smart contract app was deleted and created again (in an atomic transaction) +- `OperationPerformed.NOTHING` - Nothing was done since it was detected the existing smart contract app deployment was up to date + +As well as the `operation_performed` parameter and the [optional compilation result](#compilation-and-template-substitution), the return value will have the {py:obj}`ApplicationMetaData ` [fields](#deployment-metadata) present. + +Based on the value of `operation_performed`, there will be other data available in the return value: + +- If `CREATE`, `UPDATE` or `REPLACE` then it will have the relevant {py:obj}`SendAppTransactionResult ` values: + - `create_result` for create operations + - `update_result` for update operations +- If `REPLACE` then it will also have `delete_result` to capture the result of deleting the existing app diff --git a/docs/html/_sources/capabilities/app.md.txt b/docs/html/_sources/capabilities/app.md.txt new file mode 100644 index 00000000..55d30b17 --- /dev/null +++ b/docs/html/_sources/capabilities/app.md.txt @@ -0,0 +1,163 @@ +# App management + +App management is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities. It allows you to create, update, delete, call (ABI and otherwise) smart contract apps and the metadata associated with them (including state and boxes). + +## `AppManager` + +The `AppManager` is a class that is used to manage app information. To get an instance of `AppManager` you can use either [`AlgorandClient`](./algorand-client.md) via `algorand.app` or instantiate it directly (passing in an algod client instance): + +```python +from algokit_utils import AppManager + +app_manager = AppManager(algod_client) +``` + +## Calling apps + +### App Clients + +The recommended way of interacting with apps is via [App clients](./app-client.md) and [App factory](./app-client.md#appfactory). The methods shown on this page are the underlying mechanisms that app clients use and are for advanced use cases when you want more control. + +### Compilation + +The `AppManager` class allows you to compile TEAL code with caching semantics that allows you to avoid duplicate compilation and keep track of source maps from compiled code. + +```python +# Basic compilation +teal_code = "return 1" +compilation_result = app_manager.compile_teal(teal_code) + +# Get cached compilation result +cached_result = app_manager.get_compilation_result(teal_code) + +# Compile with template substitution +template_code = "int TMPL_VALUE" +template_params = {"VALUE": 1} +compilation_result = app_manager.compile_teal_template( + template_code, + template_params=template_params +) + +# Compile with deployment control (updatable/deletable) +control_template = f"""#pragma version 8 +int {UPDATABLE_TEMPLATE_NAME} +int {DELETABLE_TEMPLATE_NAME}""" +deployment_metadata = {"updatable": True, "deletable": True} +compilation_result = app_manager.compile_teal_template( + control_template, + deployment_metadata=deployment_metadata +) +``` + +The compilation result contains: + +- `teal` - Original TEAL code +- `compiled` - Base64 encoded compiled bytecode +- `compiled_hash` - Hash of compiled bytecode +- `compiled_base64_to_bytes` - Raw bytes of compiled bytecode +- `source_map` - Source map for debugging + +## Accessing state + +### Global state + +To access global state you can use: + +```python +# Get global state for app +global_state = app_manager.get_global_state(app_id) + +# Parse raw state from algod +decoded_state = AppManager.decode_app_state(raw_state) + +# Access state values +key_raw = decoded_state["value1"].key_raw # Raw bytes +key_base64 = decoded_state["value1"].key_base64 # Base64 encoded +value = decoded_state["value1"].value # Parsed value (str or int) +value_raw = decoded_state["value1"].value_raw # Raw bytes if bytes value +value_base64 = decoded_state["value1"].value_base64 # Base64 if bytes value +``` + +### Local state + +To access local state you can use: + +```python +local_state = app_manager.get_local_state(app_id, "ACCOUNT_ADDRESS") +``` + +### Boxes + +To access box storage: + +```python +# Get box names +box_names = app_manager.get_box_names(app_id) + +# Get box values +box_value = app_manager.get_box_value(app_id, box_name) +box_values = app_manager.get_box_values(app_id, [box_name1, box_name2]) + +# Get decoded ABI values +abi_value = app_manager.get_box_value_from_abi_type( + app_id, box_name, algosdk.abi.StringType() +) +abi_values = app_manager.get_box_values_from_abi_type( + app_id, [box_name1, box_name2], algosdk.abi.StringType() +) + +# Get box reference for transaction +box_ref = AppManager.get_box_reference(box_id) +``` + +## Getting app information + +To get app information: + +```python +# Get app info by ID +app_info = app_manager.get_by_id(app_id) + +# Get ABI return value from transaction +abi_return = AppManager.get_abi_return(confirmation, abi_method) +``` + +## Box references + +Box references can be specified in several ways: + +```python +# String name (encoded to bytes) +box_ref = "my_box" + +# Raw bytes +box_ref = b"my_box" + +# Account signer (uses address as name) +box_ref = account_signer + +# Box reference with app ID +box_ref = BoxReference(app_id=123, name=b"my_box") +``` + +## Common app parameters + +When interacting with apps (creating, updating, deleting, calling), there are common parameters that can be passed: + +- `app_id` - ID of the application +- `sender` - Address of transaction sender +- `signer` - Transaction signer (optional) +- `args` - Arguments to pass to the smart contract +- `account_references` - Account addresses to reference +- `app_references` - App IDs to reference +- `asset_references` - Asset IDs to reference +- `box_references` - Box references to load +- `on_complete` - On complete action +- Other common transaction parameters like `note`, `lease`, etc. + +For ABI method calls, additional parameters: + +- `method` - The ABI method to call +- `args` - ABI typed arguments to pass + +See [App client](./app-client.md) for more details on constructing app calls. diff --git a/docs/html/_sources/capabilities/asset.md.txt b/docs/html/_sources/capabilities/asset.md.txt new file mode 100644 index 00000000..731af016 --- /dev/null +++ b/docs/html/_sources/capabilities/asset.md.txt @@ -0,0 +1,134 @@ +# Assets + +The Algorand Standard Asset (ASA) management functions include creating, opting in and transferring assets, which are fundamental to asset interaction in a blockchain environment. + +## `AssetManager` + +The `AssetManager` class provides functionality for managing Algorand Standard Assets (ASAs). It can be accessed through the `AlgorandClient` via `algorand.asset` or instantiated directly: + +```python +from algokit_utils import AssetManager, TransactionComposer +from algosdk.v2client import algod + +asset_manager = AssetManager( + algod_client=algod_client, + new_group=lambda: TransactionComposer() +) +``` + +## Asset Information + +The `AssetManager` provides two key data classes for asset information: + +### `AssetInformation` + +Contains details about an Algorand Standard Asset (ASA): + +```python +@dataclass +class AssetInformation: + asset_id: int # The ID of the asset + creator: str # Address of the creator account + total: int # Total units created + decimals: int # Number of decimal places + default_frozen: bool | None = None # Whether asset is frozen by default + manager: str | None = None # Optional manager address + reserve: str | None = None # Optional reserve address + freeze: str | None = None # Optional freeze address + clawback: str | None = None # Optional clawback address + unit_name: str | None = None # Optional unit name (e.g. ticker) + asset_name: str | None = None # Optional asset name + url: str | None = None # Optional URL for more info + metadata_hash: bytes | None = None # Optional 32-byte metadata hash +``` + +### `AccountAssetInformation` + +Contains information about an account's holding of a particular asset: + +```python +@dataclass +class AccountAssetInformation: + asset_id: int # The ID of the asset + balance: int # Amount held by the account + frozen: bool # Whether frozen for this account + round: int # Round this info was retrieved at +``` + +## Bulk Operations + +The `AssetManager` provides methods for bulk opt-in/opt-out operations: + +### Bulk Opt-In + +```python +# Basic example +result = asset_manager.bulk_opt_in( + account="ACCOUNT_ADDRESS", + asset_ids=[12345, 67890] +) + +# Advanced example with optional parameters +result = asset_manager.bulk_opt_in( + account="ACCOUNT_ADDRESS", + asset_ids=[12345, 67890], + signer=transaction_signer, + note=b"opt-in note", + lease=b"lease", + static_fee=AlgoAmount(1000), + extra_fee=AlgoAmount(500), + max_fee=AlgoAmount(2000), + validity_window=10, + send_params=SendParams(...) +) +``` + +### Bulk Opt-Out + +```python +# Basic example +result = asset_manager.bulk_opt_out( + account="ACCOUNT_ADDRESS", + asset_ids=[12345, 67890] +) + +# Advanced example with optional parameters +result = asset_manager.bulk_opt_out( + account="ACCOUNT_ADDRESS", + asset_ids=[12345, 67890], + ensure_zero_balance=True, + signer=transaction_signer, + note=b"opt-out note", + lease=b"lease", + static_fee=AlgoAmount(1000), + extra_fee=AlgoAmount(500), + max_fee=AlgoAmount(2000), + validity_window=10, + send_params=SendParams(...) +) +``` + +The bulk operations return a list of `BulkAssetOptInOutResult` objects containing: + +- `asset_id`: The ID of the asset opted into/out of +- `transaction_id`: The transaction ID of the opt-in/out + +## Get Asset Information + +### Getting Asset Parameters + +You can get the current parameters of an asset from algod using `get_by_id()`: + +```python +asset_info = asset_manager.get_by_id(12345) +``` + +### Getting Account Holdings + +You can get an account's current holdings of an asset using `get_account_information()`: + +```python +address = "XBYLS2E6YI6XXL5BWCAMOA4GTWHXWENZMX5UHXMRNWWUQ7BXCY5WC5TEPA" +asset_id = 12345 +account_info = asset_manager.get_account_information(address, asset_id) +``` diff --git a/docs/html/_sources/capabilities/client.md.txt b/docs/html/_sources/capabilities/client.md.txt index 851c66ff..00b92731 100644 --- a/docs/html/_sources/capabilities/client.md.txt +++ b/docs/html/_sources/capabilities/client.md.txt @@ -1,29 +1,111 @@ # Client management -Client management is one of the core capabilities provided by AlgoKit Utils. -It allows you to create [algod](https://developer.algorand.org/docs/rest-apis/algod), [indexer](https://developer.algorand.org/docs/rest-apis/indexer) -and [kmd](https://developer.algorand.org/docs/rest-apis/kmd) clients against various networks resolved from environment or specified configuration. +Client management is one of the core capabilities provided by AlgoKit Utils. It allows you to create (auto-retry) [algod](https://developer.algorand.org/docs/rest-apis/algod), [indexer](https://developer.algorand.org/docs/rest-apis/indexer) and [kmd](https://developer.algorand.org/docs/rest-apis/kmd) clients against various networks resolved from environment or specified configuration. -Any AlgoKit Utils function that needs one of these clients will take the underlying `algosdk` classes (`algosdk.v2client.algod.AlgodClient`, `algosdk.v2client.indexer.IndexerClient`, -`algosdk.kmd.KMDClient`) so inline with the [Modularity](../index.md#core-principles) principle you can use existing logic to get instances of these clients without needing to use the -Client management capability if you prefer. +Any AlgoKit Utils function that needs one of these clients will take the underlying algosdk classes (`algosdk.v2client.algod.AlgodClient`, `algosdk.v2client.indexer.IndexerClient`, `algosdk.kmd.KMDClient`) so inline with the [Modularity](../index.md#core-principles) principle you can use existing logic to get instances of these clients without needing to use the Client management capability if you prefer. To see some usage examples check out the [automated tests](https://github.com/algorandfoundation/algokit-utils-py/blob/main/tests/test_network_clients.py). +## `ClientManager` + +The `ClientManager` is a class that is used to manage client instances. + +To get an instance of `ClientManager` you can instantiate it directly: + +```python +from algokit_utils import ClientManager, AlgoSdkClients, AlgoClientConfigs +from algosdk.v2client.algod import AlgodClient + +# Using AlgoSdkClients +algod_client = AlgodClient(...) +algorand_client = ... # Get AlgorandClient instance from somewhere +clients = AlgoSdkClients(algod=algod_client, indexer=indexer_client, kmd=kmd_client) +client_manager = ClientManager(clients, algorand_client) + +# Using AlgoClientConfigs +algod_config = AlgoClientNetworkConfig(server="https://...", token="") +configs = AlgoClientConfigs(algod_config=algod_config) +client_manager = ClientManager(configs, algorand_client) +``` + ## Network configuration -The network configuration is specified using the `AlgoClientConfig` class. This same interface is used to specify the config for algod, indexer and kmd clients. +The network configuration is specified using the `AlgoClientConfig` type. This same type is used to specify the config for [algod](https://developer.algorand.org/docs/sdks/python/), [indexer](https://developer.algorand.org/docs/sdks/python/) and [kmd](https://developer.algorand.org/docs/sdks/python/) SDK clients. There are a number of ways to produce one of these configuration objects: -- Manually creating the object, e.g. `AlgoClientConfig(server="https://myalgodnode.com", token="SECRET_TOKEN")` -- `algokit_utils.get_algonode_config(network, config, token)`: Loads an Algod or indexer config against [Nodely](https://nodely.io/docs/free/start) to either MainNet or TestNet -- `algokit_utils.get_default_localnet_config(configOrPort)`: Loads an Algod, Indexer or Kmd config against [LocalNet](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md) using the default configuration +- Manually specifying a dataclass, e.g. + + ```python + from algokit_utils import AlgoClientNetworkConfig + + config = AlgoClientNetworkConfig( + server="https://myalgodnode.com", + token="SECRET_TOKEN" # optional + ) + ``` + +- `ClientManager.get_config_from_environment_or_localnet()` - Loads the Algod client config, the Indexer client config and the Kmd config from well-known environment variables or if not found then default LocalNet; this is useful to have code that can work across multiple blockchain environments (including LocalNet), without having to change +- `ClientManager.get_algod_config_from_environment()` - Loads an Algod client config from well-known environment variables +- `ClientManager.get_indexer_config_from_environment()` - Loads an Indexer client config from well-known environment variables; useful to have code that can work across multiple blockchain environments (including LocalNet), without having to change +- `ClientManager.get_algonode_config(network)` - Loads an Algod or indexer config against [AlgoNode free tier](https://nodely.io/docs/free/start) to either MainNet or TestNet +- `ClientManager.get_default_localnet_config()` - Loads an Algod, Indexer or Kmd config against [LocalNet](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md) using the default configuration ## Clients -Once you have the configuration for a client, to get the client you can use the following functions: +### Creating an SDK client instance + +Once you have the configuration for a client, to get a new client you can use the following functions: + +- `ClientManager.get_algod_client(config)` - Returns an Algod client for the given configuration; the client automatically retries on transient HTTP errors +- `ClientManager.get_indexer_client(config)` - Returns an Indexer client for given configuration +- `ClientManager.get_kmd_client(config)` - Returns a Kmd client for the given configuration + +You can also shortcut needing to write the likes of `ClientManager.get_algod_client(ClientManager.get_algod_config_from_environment())` with environment shortcut methods: + +- `ClientManager.get_algod_client_from_environment()` - Returns an Algod client by loading the config from environment variables +- `ClientManager.get_indexer_client_from_environment()` - Returns an indexer client by loading the config from environment variables +- `ClientManager.get_kmd_client_from_environment()` - Returns a kmd client by loading the config from environment variables + +### Accessing SDK clients via ClientManager instance + +Once you have a `ClientManager` instance, you can access the SDK clients: + +```python +client_manager = ClientManager(algod=algod_client, indexer=indexer_client, kmd=kmd_client) + +algod_client = client_manager.algod +indexer_client = client_manager.indexer +kmd_client = client_manager.kmd +``` + +If the method to create the `ClientManager` doesn't configure indexer or kmd (both of which are optional), then accessing those clients will trigger an error. + +### Creating a TestNet dispenser API client instance + +You can also create a [TestNet dispenser API client instance](./dispenser-client.md) from `ClientManager` too. + +## Automatic retry + +When receiving an Algod or Indexer client from AlgoKit Utils, it will be a special wrapper client that handles retrying transient failures. + +## Network information + +You can get information about the current network you are connected to: + +```python +# Get network information +network = client_manager.network() +print(f"Is mainnet: {network.is_mainnet}") +print(f"Is testnet: {network.is_testnet}") +print(f"Is localnet: {network.is_localnet}") +print(f"Genesis ID: {network.genesis_id}") +print(f"Genesis hash: {network.genesis_hash}") + +# Convenience methods +is_mainnet = client_manager.is_mainnet() +is_testnet = client_manager.is_testnet() +is_localnet = client_manager.is_localnet() +``` -- `algokit_utils.get_algod_client(config)`: Returns an Algod client for the given configuration or if none is provided retrieves a configuration from the environment using `ALGOD_SERVER`, `ALGOD_TOKEN` and optionally `ALGOD_PORT`. -- `algokit_utils.get_indexer_client(config)`: Returns an Indexer client for given configuration or if none is provided retrieves a configuration from the environment using `INDEXER_SERVER`, `INDEXER_TOKEN` and optionally `INDEXER_PORT` -- `algokit_utils.get_kmd_client_from_algod_client(config)`: - Returns a Kmd client based on the provided algod client configuration, with the assumption the KMD services is running on the same host but a different port (either `KMD_PORT` environment variable or `4002` by default) +The first time `network()` is called it will make a HTTP call to algod to get the network parameters, but from then on it will be cached within that `ClientManager` instance for subsequent calls. diff --git a/docs/html/_sources/capabilities/debugging.md.txt b/docs/html/_sources/capabilities/debugging.md.txt new file mode 100644 index 00000000..996cee6b --- /dev/null +++ b/docs/html/_sources/capabilities/debugging.md.txt @@ -0,0 +1,93 @@ +# Debugger + +The AlgoKit Python Utilities package provides a set of debugging tools that can be used to simulate and trace transactions on the Algorand blockchain. These tools and methods are optimized for developers who are building applications on Algorand and need to test and debug their smart contracts via [AlgoKit AVM Debugger extension](https://marketplace.visualstudio.com/items?itemName=algorandfoundation.algokit-avm-vscode-debugger). + +## Configuration + +The `config.py` file contains the `UpdatableConfig` class which manages and updates configuration settings for the AlgoKit project. + +- `debug`: Indicates whether debug mode is enabled. +- `project_root`: The path to the project root directory. Can be ignored if you are using `algokit_utils` inside an `algokit` compliant project (containing `.algokit.toml` file). For non algokit compliant projects, simply provide the path to the folder where you want to store sourcemaps and traces to be used with [`AlgoKit AVM Debugger`](https://github.com/algorandfoundation/algokit-avm-vscode-debugger). Alternatively you can also set the value via the `ALGOKIT_PROJECT_ROOT` environment variable. +- `trace_all`: Indicates whether to trace all operations. Defaults to false, this means that when debug mode is enabled, any (or all) application client calls performed via `algokit_utils` will store responses from `simulate` endpoint. These files are called traces, and can be used with `AlgoKit AVM Debugger` to debug TEAL source codes, transactions in the atomic group and etc. +- `trace_buffer_size_mb`: The size of the trace buffer in megabytes. By default uses 256 megabytes. When output folder containing debug trace files exceedes the size, oldest files are removed to optimize for storage consumption. +- `max_search_depth`: The maximum depth to search for a an `algokit` config file. By default it will traverse at most 10 folders searching for `.algokit.toml` file which will be used to assume algokit compliant project root path. +- `populate_app_call_resources`: Indicates whether to populate app call resources. Defaults to false, which means that when debug mode is enabled, any (or all) application client calls performed via `algokit_utils` will not populate app call resources. +- `logger`: A custom logger to use. Defaults to {py:class}`algokit_utils.config.AlgoKitLogger` instance. + +The `configure` method can be used to set these attributes. + +To enable debug mode in your project you can configure it as follows: + +```python +from algokit_utils.config import config + +config.configure( + debug=True, + project_root=Path("./my-project"), + trace_all=True, + trace_buffer_size_mb=512, + max_search_depth=15, + populate_app_call_resources=True, +) +``` + +## `AlgoKitLogger` + +The `AlgoKitLogger` is a custom logger that is used to log messages in the AlgoKit project. +It is a subclass of the `logging.Logger` class and extends it to provide additional functionality. + +### Suppressing log messages per log call + +To supress log messages for individual log calls you can pass `'suppress_log':True` to the log call's `extra` argument. + +### Suppressing log messages globally + +To supress log messages globally you can configure the config object to use a custom logger that does not log anything. + +```python +config.configure(logger=AlgoKitLogger.get_null_logger()) +``` + +## Debugging Utilities + +When debug mode is enabled, AlgoKit Utils will automatically: + +- Generate transaction traces compatible with the AVM Debugger +- Manage trace file storage with automatic cleanup +- Provide source map generation for TEAL contracts + +The following methods are provided for manual debugging operations: + +- `persist_sourcemaps`: Persists sourcemaps for given TEAL contracts as AVM Debugger-compliant artifacts. Parameters: + + - `sources`: List of TEAL sources to generate sourcemaps for + - `project_root`: Project root directory for storage + - `client`: AlgodClient instance + - `with_sources`: Whether to include TEAL source files (default: True) + +- `simulate_and_persist_response`: Simulates transactions and persists debug traces. Parameters: + - `atc`: AtomicTransactionComposer containing transactions + - `project_root`: Project root directory for storage + - `algod_client`: AlgodClient instance + - `buffer_size_mb`: Maximum trace storage in MB (default: 256) + - `allow_empty_signatures`: Allow unsigned transactions (default: True) + - `allow_unnamed_resources`: Allow unnamed resources (default: True) + - `extra_opcode_budget`: Additional opcode budget + - `exec_trace_config`: Custom trace configuration + - `simulation_round`: Specific round to simulate + +### Trace filename format + +The trace files are named in a specific format to provide useful information about the transactions they contain. The format is as follows: + +``` +${timestamp}_lr${last_round}_${transaction_types}.trace.avm.json +``` + +Where: + +- `timestamp`: The time when the trace file was created, in ISO 8601 format, with colons and periods removed. +- `last_round`: The last round when the simulation was performed. +- `transaction_types`: A string representing the types and counts of transactions in the atomic group. Each transaction type is represented as `${count}${type}`, and different transaction types are separated by underscores. + +For example, a trace file might be named `20220301T123456Z_lr1000_2pay_1axfer.trace.avm.json`, indicating that the trace file was created at `2022-03-01T12:34:56Z`, the last round was `1000`, and the atomic group contained 2 payment transactions and 1 asset transfer transaction. diff --git a/docs/html/_sources/capabilities/dispenser-client.md.txt b/docs/html/_sources/capabilities/dispenser-client.md.txt index 315b52f4..d9370f0a 100644 --- a/docs/html/_sources/capabilities/dispenser-client.md.txt +++ b/docs/html/_sources/capabilities/dispenser-client.md.txt @@ -7,54 +7,85 @@ The TestNet Dispenser Client is a utility for interacting with the AlgoKit TestN To create a Dispenser Client, you need to provide an authorization token. This can be done in two ways: 1. Pass the token directly to the client constructor as `auth_token`. -2. Set the token as an environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN` (see [docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md#error-handling) on how to obtain the token). +2. Set the token as an environment variable `ALGOKIT_DISPENSER_ACCESS_TOKEN` (see [docs](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#login) on how to obtain the token). If both methods are used, the constructor argument takes precedence. -```py +```python +import algokit_utils + +# With auth token +dispenser = algorand.client.get_testnet_dispenser( + auth_token="your_auth_token", +) + +# With auth token and timeout +dispenser = algorand.client.get_testnet_dispenser( + auth_token="your_auth_token", + request_timeout=2, # seconds +) + +# From environment variables +# i.e. os.environ['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' +dispenser = algorand.client.get_testnet_dispenser_from_environment() + +# Alternatively, you can construct it directly from algokit_utils import TestNetDispenserApiClient # Using constructor argument - client = TestNetDispenserApiClient(auth_token="your_auth_token") # Using environment variable - import os -os.environ["ALGOKIT_DISPENSER_ACCESS_TOKEN"] = "your_auth_token" +os.environ['ALGOKIT_DISPENSER_ACCESS_TOKEN'] = 'your_auth_token' client = TestNetDispenserApiClient() ``` ## Funding an Account -To fund an account with Algos from the dispenser API, use the `fund` method. This method requires the receiver's address, the amount to be funded, and the asset ID. +To fund an account with Algo from the dispenser API, use the `fund` method. This method requires the receiver's address and the amount to be funded. -```py -response = client.fund(address="receiver_address", amount=1000, asset_id=0) +```python +response = dispenser.fund( + receiver="RECEIVER_ADDRESS", + amount=1000, # Amount in microAlgos +) ``` -The `fund` method returns a `FundResponse` object, which contains the transaction ID (`tx_id`) and the amount funded. +The `fund` method returns a `DispenserFundResponse` object, which contains the transaction ID (`tx_id`) and the amount funded. ## Registering a Refund To register a refund for a transaction with the dispenser API, use the `refund` method. This method requires the transaction ID of the refund transaction. -```py -client.refund(refund_txn_id="transaction_id") +```python +dispenser.refund("transaction_id") ``` -> Keep in mind, to perform a refund you need to perform a payment transaction yourself first by send funds back to TestNet Dispenser, then you can invoke this `refund` endpoint and pass the txn_id of your refund txn. You can obtain dispenser address by inspecting the `sender` field of any issued `fund` transaction initiated via [`fund`](#funding-an-account). +> Keep in mind, to perform a refund you need to perform a payment transaction yourself first by sending funds back to TestNet Dispenser, then you can invoke this refund endpoint and pass the txn_id of your refund txn. You can obtain dispenser address by inspecting the sender field of any issued fund transaction initiated via [fund](#funding-an-account). ## Getting Current Limit -To get the current limit for an account with Algos from the dispenser API, use the `get_limit` method. This method requires the account address. +To get the current limit for an account with Algo from the dispenser API, use the `get_limit` method. -```py -response = client.get_limit(address="account_address") +```python +response = dispenser.get_limit() ``` -The `get_limit` method returns a `LimitResponse` object, which contains the current limit amount. +The `get_limit` method returns a `DispenserLimitResponse` object, which contains the current limit amount. ## Error Handling If an error occurs while making a request to the dispenser API, an exception will be raised with a message indicating the type of error. Refer to [Error Handling docs](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md#error-handling) for details on how you can handle each individual error `code`. + +Here's an example of handling errors: + +```python +try: + response = dispenser.fund( + receiver="RECEIVER_ADDRESS", + amount=1000, + ) +except Exception as e: + print(f"Error occurred: {str(e)}") +``` diff --git a/docs/html/_sources/capabilities/testing.md.txt b/docs/html/_sources/capabilities/testing.md.txt new file mode 100644 index 00000000..bdc6ff7a --- /dev/null +++ b/docs/html/_sources/capabilities/testing.md.txt @@ -0,0 +1,204 @@ +# Testing + +The following is a collection of useful snippets that can help you get started with testing your Algorand applications using AlgoKit utils. For the sake of simplicity, we'll use [pytest](https://docs.pytest.org/en/latest/) in the examples below. + +## Basic Test Setup + +Here's a basic test setup using pytest fixtures that provides common testing utilities: + +```python +import pytest +from algokit_utils import Account, SigningAccount +from algokit_utils.algorand import AlgorandClient +from algokit_utils.models.amount import AlgoAmount + +@pytest.fixture +def algorand() -> AlgorandClient: + """Get an AlgorandClient instance configured for LocalNet""" + return AlgorandClient.default_localnet() + +@pytest.fixture +def funded_account(algorand: AlgorandClient) -> SigningAccount: + """Create and fund a test account with ALGOs""" + new_account = algorand.account.random() + dispenser = algorand.account.localnet_dispenser() + algorand.account.ensure_funded( + new_account, + dispenser, + min_spending_balance=AlgoAmount.from_algos(100), + min_funding_increment=AlgoAmount.from_algos(1) + ) + algorand.set_signer(sender=new_account.address, signer=new_account.signer) + return new_account +``` + +Refer to [pytest fixture scopes](https://docs.pytest.org/en/latest/how-to/fixtures.html#fixture-scopes) for more information on how to control lifecycle of fixtures. + +## Creating Test Assets + +Here's a helper function to create test ASAs (Algorand Standard Assets): + +```python +def generate_test_asset(algorand: AlgorandClient, sender: Account, total: int | None = None) -> int: + """Create a test asset and return its ID""" + if total is None: + total = random.randint(20, 120) + + create_result = algorand.send.asset_create( + AssetCreateParams( + sender=sender.address, + total=total, + decimals=0, + default_frozen=False, + unit_name="TST", + asset_name=f"Test Asset {random.randint(1,100)}", + url="https://example.com", + manager=sender.address, + reserve=sender.address, + freeze=sender.address, + clawback=sender.address, + ) + ) + + return int(create_result.confirmation["asset-index"]) +``` + +## Testing Application Deployments + +Here's how one can test smart contract application deployments: + +```python +def test_app_deployment(algorand: AlgorandClient, funded_account: SigningAccount): + """Test deploying a smart contract application""" + + # Load the application spec + app_spec = Path("artifacts/application.json").read_text() + + # Create app factory + factory = algorand.client.get_app_factory( + app_spec=app_spec, + default_sender=funded_account.address + ) + + # Deploy the app + app_client, deploy_response = factory.deploy( + compilation_params={ + "deletable": True, + "updatable": True, + "deploy_time_params": {"VERSION": 1}, + }, + ) + + # Verify deployment + assert deploy_response.app.app_id > 0 + assert deploy_response.app.app_address +``` + +## Testing Asset Transfers + +Here's how one can test ASA transfers between accounts: + +```python +def test_asset_transfer(algorand: AlgorandClient, funded_account: SigningAccount): + """Test ASA transfers between accounts""" + + # Create receiver account + receiver = algorand.account.random() + algorand.account.ensure_funded( + account_to_fund=receiver, + dispenser_account=funded_account, + min_spending_balance=AlgoAmount.from_algos(1) + ) + + # Create test asset + asset_id = generate_test_asset(algorand, funded_account, 100) + + # Opt receiver into asset + algorand.send.asset_opt_in( + AssetOptInParams( + sender=receiver.address, + asset_id=asset_id, + signer=receiver.signer + ) + ) + + # Transfer asset + transfer_amount = 5 + result = algorand.send.asset_transfer( + AssetTransferParams( + sender=funded_account.address, + receiver=receiver.address, + asset_id=asset_id, + amount=transfer_amount + ) + ) + + # Verify transfer + receiver_balance = algorand.asset.get_account_information(receiver, asset_id) + assert receiver_balance.balance == transfer_amount +``` + +## Testing Application Calls + +Here's how to test application method calls: + +```python +def test_app_method_call(algorand: AlgorandClient, funded_account: SigningAccount): + """Test calling ABI methods on an application""" + + # Deploy application first + app_spec = Path("artifacts/application.json").read_text() + factory = algorand.client.get_app_factory( + app_spec=app_spec, + default_sender=funded_account.address + ) + app_client, _ = factory.deploy() + + # Call application method + result = app_client.send.call( + AppClientMethodCallParams( + method="hello", + args=["world"] + ) + ) + + # Verify result + assert result.abi_return == "Hello, world" +``` + +## Testing Box Storage + +Here's how to test application box storage: + +```python +def test_box_storage(algorand: AlgorandClient, funded_account: SigningAccount): + """Test application box storage""" + + # Deploy application + app_spec = Path("artifacts/application.json").read_text() + factory = algorand.client.get_app_factory( + app_spec=app_spec, + default_sender=funded_account.address + ) + app_client, _ = factory.deploy() + + # Fund app account for box storage MBR + app_client.fund_app_account( + FundAppAccountParams(amount=AlgoAmount.from_algos(1)) + ) + + # Store value in box + box_name = b"test_box" + box_value = "test_value" + app_client.send.call( + AppClientMethodCallParams( + method="set_box", + args=[box_name, box_value], + box_references=[box_name] + ) + ) + + # Verify box value + stored_value = app_client.get_box_value(box_name) + assert stored_value == box_value.encode() +``` diff --git a/docs/html/_sources/capabilities/transaction-composer.md.txt b/docs/html/_sources/capabilities/transaction-composer.md.txt new file mode 100644 index 00000000..ad4ba9a6 --- /dev/null +++ b/docs/html/_sources/capabilities/transaction-composer.md.txt @@ -0,0 +1,345 @@ +# Transaction composer + +The `TransactionComposer` class allows you to easily compose one or more compliant Algorand transactions and execute and/or simulate them. + +It's the core of how the `AlgorandClient` class composes and sends transactions. + +```python +from algokit_utils import TransactionComposer, AppManager +from algokit_utils.transactions import ( + PaymentParams, + AppCallMethodCallParams, + AssetCreateParams, + AppCreateParams, + # ... other transaction parameter types +) +``` + +To get an instance of `TransactionComposer` you can either get it from an app client, from an `AlgorandClient`, or by instantiating via the constructor. + +```python +# From AlgorandClient +composer_from_algorand = algorand.new_group() + +# From AppClient +composer_from_app_client = app_client.algorand.new_group() + +# From constructor +composer_from_constructor = TransactionComposer( + algod=algod, + # Return the TransactionSigner for this address + get_signer=lambda address: signer +) + +# From constructor with optional params +composer_from_constructor = TransactionComposer( + algod=algod, + # Return the TransactionSigner for this address + get_signer=lambda address: signer, + # Custom function to get suggested params + get_suggested_params=lambda: algod.suggested_params(), + # Number of rounds the transaction should be valid for + default_validity_window=1000, + # Optional AppManager instance for TEAL compilation + app_manager=AppManager(algod) +) +``` + +## Constructing a transaction + +To construct a transaction you need to add it to the composer, passing in the relevant params object for that transaction. Params are Python dataclasses aavailable for import from `algokit_utils.transactions`. + +Parameter types include: + +- `PaymentParams` - For ALGO transfers +- `AssetCreateParams` - For creating ASAs +- `AssetConfigParams` - For reconfiguring ASAs +- `AssetTransferParams` - For ASA transfers +- `AssetOptInParams` - For opting in to ASAs +- `AssetOptOutParams` - For opting out of ASAs +- `AssetDestroyParams` - For destroying ASAs +- `AssetFreezeParams` - For freezing ASA balances +- `AppCreateParams` - For creating applications +- `AppCreateMethodCallParams` - For creating applications with ABI method calls +- `AppCallParams` - For calling applications +- `AppCallMethodCallParams` - For calling ABI methods on applications +- `AppUpdateParams` - For updating applications +- `AppUpdateMethodCallParams` - For updating applications with ABI method calls +- `AppDeleteParams` - For deleting applications +- `AppDeleteMethodCallParams` - For deleting applications with ABI method calls +- `OnlineKeyRegistrationParams` - For online key registration transactions +- `OfflineKeyRegistrationParams` - For offline key registration transactions + +The methods to construct a transaction are all named `add_{transaction_type}` and return an instance of the composer so they can be chained together fluently to construct a transaction group. + +For example: + +```python +from algokit_utils import AlgoAmount +from algokit_utils.transactions import AppCallMethodCallParams, PaymentParams + +result = ( + algorand.new_group() + .add_payment(PaymentParams( + sender="SENDER", + receiver="RECEIVER", + amount=AlgoAmount.from_micro_algos(100), + note=b"Payment note" + )) + .add_app_call_method_call(AppCallMethodCallParams( + sender="SENDER", + app_id=123, + method=abi_method, + args=[1, 2, 3], + boxes=[box_reference] # Optional box references + )) +) +``` + +## Simulating a transaction + +Transactions can be simulated using the simulate endpoint in algod, which enables evaluating the transaction on the network without it actually being committed to a block. +This is a powerful feature, which has a number of options which are detailed in the [simulate API docs](https://developer.algorand.org/docs/rest-apis/algod/#post-v2transactionssimulate). + +The `simulate()` method accepts several optional parameters that are passed through to the algod simulate endpoint: + +- `allow_more_logs: bool | None` - Allow more logs than standard +- `allow_empty_signatures: bool | None` - Allow transactions without signatures +- `allow_unnamed_resources: bool | None` - Allow unnamed resources in app calls +- `extra_opcode_budget: int | None` - Additional opcode budget +- `exec_trace_config: SimulateTraceConfig | None` - Execution trace configuration +- `simulation_round: int | None` - Round to simulate at +- `skip_signatures: int | None` - Skip signature verification + +For example: + +```python +result = ( + algorand.new_group() + .add_payment(PaymentParams( + sender="SENDER", + receiver="RECEIVER", + amount=AlgoAmount.from_micro_algos(100) + )) + .add_app_call_method_call(AppCallMethodCallParams( + sender="SENDER", + app_id=123, + method=abi_method, + args=[1, 2, 3] + )) + .simulate() +) + +# Access simulation results +simulate_response = result.simulate_response +confirmations = result.confirmations +transactions = result.transactions +returns = result.returns # ABI returns if any +``` + +### Simulate without signing + +There are situations where you may not be able to (or want to) sign the transactions when executing simulate. +In these instances you should set `skip_signatures=True` which automatically builds empty transaction signers and sets both `fix-signers` and `allow-empty-signatures` to `True` when sending the algod API call. + +For example: + +```python +result = ( + algorand.new_group() + .add_payment(PaymentParams( + sender="SENDER", + receiver="RECEIVER", + amount=AlgoAmount.from_micro_algos(100) + )) + .add_app_call_method_call(AppCallMethodCallParams( + sender="SENDER", + app_id=123, + method=abi_method, + args=[1, 2, 3] + )) + .simulate( + skip_signatures=True, + allow_more_logs=True, # Optional: allow more logs + extra_opcode_budget=700 # Optional: increase opcode budget + ) +) +``` + +### Resource Population + +The `TransactionComposer` includes automatic resource population capabilities for application calls. When sending or simulating transactions, it can automatically detect and populate required references for: + +- Account references +- Application references +- Asset references +- Box references + +This happens automatically when either: + +1. The global `algokit_utils.config` instance is set to `populate_app_call_resources=True` (default is `False`) +2. The `populate_app_call_resources` parameter is explicitly passed as `True` when sending transactions + +```python +# Automatic resource population +result = ( + algorand.new_group() + .add_app_call_method_call(AppCallMethodCallParams( + sender="SENDER", + app_id=123, + method=abi_method, + args=[1, 2, 3] + # Resources will be automatically populated! + )) + .send(params=SendParams(populate_app_call_resources=True)) +) + +# Or disable automatic population +result = ( + algorand.new_group() + .add_app_call_method_call(AppCallMethodCallParams( + sender="SENDER", + app_id=123, + method=abi_method, + args=[1, 2, 3], + # Explicitly specify required resources + account_references=["ACCOUNT"], + app_references=[456], + asset_references=[789], + box_references=[box_reference] + )) + .send(params=SendParams(populate_app_call_resources=False)) +) +``` + +The resource population: + +- Respects the maximum limits (4 for accounts, 8 for foreign references) +- Handles cross-reference resources efficiently (e.g., asset holdings and local state) +- Automatically distributes resources across multiple transactions in a group when needed +- Raises descriptive errors if resource limits are exceeded + +This feature is particularly useful when: + +- Working with complex smart contracts that access various resources +- Building transaction groups where resources need to be coordinated +- Developing applications where resource requirements may change dynamically + +Note: Resource population uses simulation under the hood to detect required resources, so it may add a small overhead to transaction preparation time. + +### Covering App Call Inner Transaction Fees + +`cover_app_call_inner_transaction_fees` automatically calculate the required fee for a parent app call transaction that sends inner transactions. It leverages the simulate endpoint to discover the inner transactions sent and calculates a fee delta to resolve the optimal fee. This feature also takes care of accounting for any surplus transaction fee at the various levels, so as to effectively minimise the fees needed to successfully handle complex scenarios. This setting only applies when you have constucted at least one app call transaction. + +For example: + +```python +myMethod = algosdk.ABIMethod.fromSignature('my_method()void') +result = algorand + .new_group() + .add_app_call_method_call(AppCallMethodCallParams( + sender: 'SENDER', + app_id=123, + method=myMethod, + args=[1, 2, 3], + max_fee=AlgoAmount.from_micro_algo(5000), # NOTE: a maxFee value is required when enabling coverAppCallInnerTransactionFees + )) + .send(send_params={"cover_app_call_inner_transaction_fees": True}) +``` + +Assuming the app account is not covering any of the inner transaction fees, if `my_method` in the above example sends 2 inner transactions, then the fee calculated for the parent transaction will be 3000 µALGO when the transaction is sent to the network. + +The above example also has a `max_fee` of 5000 µALGO specified. An exception will be thrown if the transaction fee execeeds that value, which allows you to set fee limits. The `max_fee` field is required when enabling `cover_app_call_inner_transaction_fees`. + +Because `max_fee` is required and an `algosdk.Transaction` does not hold any max fee information, you cannot use the generic `add_transaction()` method on the composer with `cover_app_call_inner_transaction_fees` enabled. Instead use the below, which provides a better overall experience: + +```python +my_method = algosdk.abi.Method.from_signature('my_method()void') + +# Does not work +result = algorand + .new_group() + .add_transaction(localnet.algorand.create_transaction.app_call_method_call( + AppCallMethodCallParams( + sender='SENDER', + app_id=123, + method=my_method, + args=[1, 2, 3], + max_fee=AlgoAmount.from_micro_algos(5000), # This is only used to create the algosdk.Transaction object and isn't made available to the composer. + ) + ).transactions[0] + ) + .send(send_params={"cover_app_call_inner_transaction_fees": True}) + +# Works as expected +result = algorand + .new_group() + .add_app_call_method_call(AppCallMethodCallParams( + sender='SENDER', + app_id=123, + method=my_method, + args=[1, 2, 3], + max_fee=AlgoAmount.from_micro_algos(5000), + )) + .send(send_params={"cover_app_call_inner_transaction_fees": True}) +``` + +A more complex valid scenario which leverages an app client to send an ABI method call with ABI method call transactions argument is below: + +```python +app_factory = algorand.client.get_app_factory( + app_spec='APP_SPEC', + default_sender=sender.addr, +) + +app_client_1, _ = app_factory.send.bare.create() +app_client_2, _ = app_factory.send.bare.create() + +payment_arg = algorand.create_transaction.payment( + PaymentParams( + sender=sender.addr, + receiver=receiver.addr, + amount=AlgoAmount.from_micro_algos(1), + ) +) + +# Note the use of .params. here, this ensure that maxFee is still available to the composer +app_call_arg = app_client_2.params.call( + AppCallMethodCallParams( + method='my_other_method', + args=[], + max_fee=AlgoAmount.from_micro_algos(2000), + ) +) + +result = app_client_1.algorand + .new_group() + .add_app_call_method_call( + app_client_1.params.call( + AppClientMethodCallParams( + method='my_method', + args=[payment_arg, app_call_arg], + max_fee=AlgoAmount.from_micro_algos(5000), + ) + ), + ) + .send({"cover_app_call_inner_transaction_fees": True}) +``` + +This feature should efficiently calculate the minimum fee needed to execute an app call transaction with inners, however we always recommend testing your specific scenario behaves as expected before releasing. + +#### Read-only calls + +When invoking a readonly method, the transaction is simulated rather than being fully processed by the network. This allows users to call these methods without paying a fee. + +Even though no actual fee is paid, the simulation still evaluates the transaction as if a fee was being paid, therefore op budget and fee coverage checks are still performed. + +Because no fee is actually paid, calculating the minimum fee required to successfully execute the transaction is not required, and therefore we don't need to send an additional simulate call to calculate the minimum fee, like we do with a non readonly method call. + +The behaviour of enabling `cover_app_call_inner_transaction_fees` for readonly method calls is very similar to non readonly method calls, however is subtly different as we use `max_fee` as the transaction fee when executing the readonly method call. + +### Covering App Call Op Budget + +The high level Algorand contract authoring languages all have support for ensuring appropriate app op budget is available via `ensure_budget` in Algorand Python, `ensureBudget` in Algorand TypeScript and `increaseOpcodeBudget` in TEALScript. This is great, as it allows contract authors to ensure appropriate budget is available by automatically sending op-up inner transactions to increase the budget available. These op-up inner transactions require the fees to be covered by an account, which is generally the responsibility of the application consumer. + +Application consumers may not be immediately aware of the number of op-up inner transactions sent, so it can be difficult for them to determine the exact fees required to successfully execute an application call. Fortunately the `cover_app_call_inner_transaction_fees` setting above can be leveraged to automatically cover the fees for any op-up inner transaction that an application sends. Additionally if a contract author decides to cover the fee for an op-up inner transaction, then the application consumer will not be charged a fee for that transaction. diff --git a/docs/html/_sources/capabilities/transaction.md.txt b/docs/html/_sources/capabilities/transaction.md.txt new file mode 100644 index 00000000..e10a8e7b --- /dev/null +++ b/docs/html/_sources/capabilities/transaction.md.txt @@ -0,0 +1,147 @@ +# Transaction management + +Transaction management is one of the core capabilities provided by AlgoKit Utils. It allows you to construct, simulate and send single or grouped transactions with consistent and highly configurable semantics, including configurable control of transaction notes, logging, fees, multiple sender account types, and sending behavior. + +## Transaction Results + +All AlgoKit Utils functions that send transactions return either a `SendSingleTransactionResult` or `SendAtomicTransactionComposerResults`, providing consistent mechanisms to interpret transaction outcomes. + +### SendSingleTransactionResult + +The base `SendSingleTransactionResult` class is used for single transactions: + +```python +@dataclass(frozen=True, kw_only=True) +class SendSingleTransactionResult: + transaction: TransactionWrapper # Last transaction + confirmation: AlgodResponseType # Last confirmation + group_id: str + tx_id: str | None = None # Transaction ID of the last transaction + tx_ids: list[str] # All transaction IDs in the group + transactions: list[TransactionWrapper] + confirmations: list[AlgodResponseType] + returns: list[ABIReturn] | None = None # ABI returns if applicable +``` + +Common variations include: + +- `SendSingleAssetCreateTransactionResult` - Adds `asset_id` +- `SendAppTransactionResult` - Adds `abi_return` +- `SendAppUpdateTransactionResult` - Adds compilation results +- `SendAppCreateTransactionResult` - Adds `app_id` and `app_address` + +### SendAtomicTransactionComposerResults + +When using the atomic transaction composer directly via `TransactionComposer.send()` or `TransactionComposer.simulate()`, you'll receive a `SendAtomicTransactionComposerResults`: + +```python +@dataclass +class SendAtomicTransactionComposerResults: + group_id: str # The group ID if this was a transaction group + confirmations: list[AlgodResponseType] # The confirmation info for each transaction + tx_ids: list[str] # The transaction IDs that were sent + transactions: list[TransactionWrapper] # The transactions that were sent + returns: list[ABIReturn] # The ABI return values from any ABI method calls + simulate_response: dict[str, Any] | None = None # Simulation response if simulated +``` + +### Application-specific Result Types + +When working with applications via `AppClient` or `AppFactory`, you'll get enhanced result types that provide direct access to parsed ABI values: + +- `SendAppFactoryTransactionResult` +- `SendAppUpdateFactoryTransactionResult` +- `SendAppCreateFactoryTransactionResult` + +These types extend the base transaction results to add an `abi_value` field that contains the parsed ABI return value according to the ARC-56 specification. The `Arc56ReturnValueType` can be: + +- A primitive ABI value (bool, int, str, bytes) +- An ABI struct (as a Python dict) +- None (for void returns) + +### Where You'll Encounter Each Result Type + +Different interfaces return different result types: + +1. **Direct Transaction Composer** + + - `TransactionComposer.send()` → `SendAtomicTransactionComposerResults` + - `TransactionComposer.simulate()` → `SendAtomicTransactionComposerResults` + +2. **AlgorandClient Methods** + + - `.send.payment()` → `SendSingleTransactionResult` + - `.send.asset_create()` → `SendSingleAssetCreateTransactionResult` + - `.send.app_call()` → `SendAppTransactionResult` (contains raw ABI return) + - `.send.app_create()` → `SendAppCreateTransactionResult` (with app ID/address) + - `.send.app_update()` → `SendAppUpdateTransactionResult` (with compilation info) + +3. **AppClient Methods** + + - `.call()` → `SendAppTransactionResult` + - `.create()` → `SendAppCreateTransactionResult` + - `.update()` → `SendAppUpdateTransactionResult` + +4. **AppFactory Methods** + - `.create()` → `SendAppCreateFactoryTransactionResult` + - `.call()` → `SendAppFactoryTransactionResult` + - `.update()` → `SendAppUpdateFactoryTransactionResult` + +Example usage with AppFactory for easy access to ABI returns: + +```python +# Using AppFactory +result = app_factory.send.call(AppCallMethodCallParams( + method="my_method", + args=[1, 2, 3], + sender=sender +)) +# Access the parsed ABI return value directly +parsed_value = result.abi_value # Already decoded per ARC-56 spec + +# Compared to base AppClient where you need to parse manually +base_result = app_client.send.call(AppCallMethodCallParams( + method="my_method", + args=[1, 2, 3], + sender=sender +)) +# Need to manually handle ABI return parsing +if base_result.abi_return: + parsed_value = base_result.abi_return.value +``` + +Key differences between result types: + +1. **Base Transaction Results** (`SendSingleTransactionResult`) + + - Focus on transaction confirmation details + - Include group support but optimized for single transactions + - No direct ABI value parsing + +2. **Atomic Transaction Results** (`SendAtomicTransactionComposerResults`) + + - Built for transaction groups + - Include simulation support + - Raw ABI returns via `.returns` + - No single transaction convenience fields + +3. **Application Results** (`SendAppTransactionResult` family) + + - Add application-specific fields (`app_id`, compilation results) + - Include raw ABI returns via `.abi_return` + - Base application transaction support + +4. **Factory Results** (`SendAppFactoryTransactionResult` family) + - Highest level of abstraction + - Direct access to parsed ABI values via `.abi_value` + - Automatic ARC-56 compliant value parsing + - Combines app-specific fields with parsed ABI returns + +## Further reading + +To understand how to create, simulate and send transactions consult: + +- The [`TransactionComposer`](./transaction-composer.md) documentation for composing transaction groups +- The [`AlgorandClient`](./algorand-client.md) documentation for a high-level interface to send transactions + +The transaction composer documentation covers the details of constructing transactions and transaction groups, while the Algorand client documentation covers the high-level interface for sending transactions. diff --git a/docs/html/_sources/capabilities/transfer.md.txt b/docs/html/_sources/capabilities/transfer.md.txt index af28b6d1..2f61e8e2 100644 --- a/docs/html/_sources/capabilities/transfer.md.txt +++ b/docs/html/_sources/capabilities/transfer.md.txt @@ -1,58 +1,151 @@ -# Algo transfers - -Algo transfers is a higher-order use case capability provided by AlgoKit Utils allows you to easily initiate algo transfers between accounts, including dispenser management and -idempotent account funding. - -To see some usage examples check out the [automated tests](https://github.com/algorandfoundation/algokit-utils-py/blob/main/tests/test_transfer.py). - -## Transferring Algos - -The key function to facilitate Algo transfers is `algokit.transfer(algod_client, transfer_parameters)`, which returns the underlying `EnsureFundedResponse` and takes a `TransferParameters` - -The following fields on `TransferParameters` are required to transfer ALGOs: - -- `from_account`: The account or signer that will send the ALGOs -- `to_address`: The address of the account that will receive the ALGOs -- `micro_algos`: The amount of micro ALGOs to send - -## Ensuring minimum Algos - -The ability to automatically fund an account to have a minimum amount of disposable ALGOs to spend is incredibly useful for automation and deployment scripts. -The function to facilitate this is `ensure_funded(client, parameters)`, which takes an `EnsureBalanceParameters` instance and returns the underlying `EnsureFundedResponse` if a payment was made, a string if the dispenser API was used, or None otherwise. - -The following fields on `EnsureBalanceParameters` are required to ensure minimum ALGOs: - -- `account_to_fund`: The account address that will receive the ALGOs. This can be an `Account` instance, an `AccountTransactionSigner` instance, or a string. -- `min_spending_balance_micro_algos`: The minimum balance of micro ALGOs that the account should have available to spend (i.e. on top of minimum balance requirement). -- `min_funding_increment_micro_algos`: When issuing a funding amount, the minimum amount to transfer (avoids many small transfers if this gets called often on an active account). Default is 0. -- `funding_source`: The account (with private key) or signer that will send the ALGOs. If not set, it will use `get_dispenser_account`. This can be an `Account` instance, an `AccountTransactionSigner` instance, [`TestNetDispenserApiClient`](https://github.com/algorandfoundation/algokit-utils-py/blob/main/docs/source/capabilities/dispenser-client.md) instance, or None. -- `suggested_params`: (optional) Transaction parameters, an instance of `SuggestedParams`. -- `note`: (optional) The transaction note, default is "Funding account to meet minimum requirement". -- `fee_micro_algos`: (optional) The flat fee you want to pay, useful for covering extra fees in a transaction group or app call. -- `max_fee_micro_algos`: (optional) The maximum fee that you are happy to pay (default: unbounded). If this is set it's possible the transaction could get rejected during network congestion. - -The function calls Algod to find the current balance and minimum balance requirement, gets the difference between those two numbers and checks to see if it's more than the `min_spending_balance_micro_algos`. If so, it will send the difference, or the `min_funding_increment_micro_algos` if that is specified. If the account is on TestNet and `use_dispenser_api` is True, the [AlgoKit TestNet Dispenser API](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md) will be used to fund the account. - -> Please note, if you are attempting to fund via Dispenser API, make sure to set `ALGOKIT_DISPENSER_ACCESS_TOKEN` environment variable prior to invoking `ensure_funded`. To generate the token refer to [AlgoKit CLI documentation](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/dispenser.md#login) - -## Transfering Assets - -The key function to facilitate asset transfers is `transfer_asset(algod_client, transfer_parameters)`, which returns a `AssetTransferTxn` and takes a `TransferAssetParameters`: - -The following fields on `TransferAssetParameters` are required to transfer assets: - -- `from_account`: The account or signer that will send the ALGOs -- `to_address`: The address of the account that will receive the ALGOs -- `asset_id`: The asset id that will be transfered -- `amount`: The amount to send as the smallest divisible unit value +# Algo transfers (payments) + +Algo transfers, or [payments](https://developer.algorand.org/docs/get-details/transactions/#payment-transaction), is a higher-order use case capability provided by AlgoKit Utils that builds on top of the core capabilities, particularly [Algo amount handling](./amount.md) and [Transaction management](./transaction.md). It allows you to easily initiate Algo transfers between accounts, including dispenser management and idempotent account funding. + +To see some usage examples check out the automated tests in the repository. + +## `payment` + +The key function to facilitate Algo transfers is `algorand.send.payment(params)` (immediately send a single payment transaction), `algorand.create_transaction.payment(params)` (construct a payment transaction), or `algorand.new_group().add_payment(params)` (add payment to a group of transactions) per [`AlgorandClient`](./algorand-client.md) [transaction semantics](./algorand-client.md#creating-and-issuing-transactions). + +The base type for specifying a payment transaction is `PaymentParams`, which has the following parameters in addition to the [common transaction parameters](./algorand-client.md#transaction-parameters): + +- `receiver: str` - The address of the account that will receive the Algo +- `amount: AlgoAmount` - The amount of Algo to send +- `close_remainder_to: Optional[str]` - If given, close the sender account and send the remaining balance to this address (**warning:** use this carefully as it can result in loss of funds if used incorrectly) + +```python +# Minimal example +result = algorand_client.send.payment( + PaymentParams( + sender="SENDERADDRESS", + receiver="RECEIVERADDRESS", + amount=AlgoAmount(4, "algo") + ) +) + +# Advanced example +result2 = algorand_client.send.payment( + PaymentParams( + sender="SENDERADDRESS", + receiver="RECEIVERADDRESS", + amount=AlgoAmount(4, "algo"), + close_remainder_to="CLOSEREMAINDERTOADDRESS", + lease="lease", + note=b"note", + # Use this with caution, it's generally better to use algorand_client.account.rekey_account + rekey_to="REKEYTOADDRESS", + # You wouldn't normally set this field + first_valid_round=1000, + validity_window=10, + extra_fee=AlgoAmount(1000, "microalgo"), + static_fee=AlgoAmount(1000, "microalgo"), + # Max fee doesn't make sense with extra_fee AND static_fee + # already specified, but here for completeness + max_fee=AlgoAmount(3000, "microalgo"), + # Signer only needed if you want to provide one, + # generally you'd register it with AlgorandClient + # against the sender and not need to pass it in + signer=transaction_signer, + ), + send_params=SendParams( + max_rounds_to_wait=5, + suppress_log=True, + ) +) +``` + +## `ensure_funded` + +The `ensure_funded` function automatically funds an account to maintain a minimum amount of [disposable Algo](https://developer.algorand.org/docs/get-details/accounts/#minimum-balance). This is particularly useful for automation and deployment scripts that get run multiple times and consume Algo when run. + +There are 3 variants of this function: + +- `algorand_client.account.ensure_funded(account_to_fund, dispenser_account, min_spending_balance, options)` - Funds a given account using a dispenser account as a funding source such that the given account has a certain amount of Algo free to spend (accounting for Algo locked in minimum balance requirement). +- `algorand_client.account.ensure_funded_from_environment(account_to_fund, min_spending_balance, options)` - Funds a given account using a dispenser account retrieved from the environment, per the `dispenser_from_environment` method, as a funding source such that the given account has a certain amount of Algo free to spend (accounting for Algo locked in minimum balance requirement). + - **Note:** requires environment variables to be set. + - The dispenser account is retrieved from the account mnemonic stored in `DISPENSER_MNEMONIC` and optionally `DISPENSER_SENDER` + if it's a rekeyed account, or against default LocalNet if no environment variables present. +- `algorand_client.account.ensure_funded_from_testnet_dispenser_api(account_to_fund, dispenser_client, min_spending_balance, options)` - Funds a given account using the [TestNet Dispenser API](https://github.com/algorandfoundation/algokit/blob/main/docs/testnet_api.md) as a funding source such that the account has a certain amount of Algo free to spend (accounting for Algo locked in minimum balance requirement). + +The general structure of these calls is similar, they all take: + +- `account_to_fund: str | Account` - Address or signing account of the account to fund +- The source (dispenser): + - In `ensure_funded`: `dispenser_account: str | Account` - the address or signing account of the account to use as a dispenser + - In `ensure_funded_from_environment`: Not specified, loaded automatically from the ephemeral environment + - In `ensure_funded_from_testnet_dispenser_api`: `dispenser_client: TestNetDispenserApiClient` - a client instance of the TestNet dispenser API +- `min_spending_balance: AlgoAmount` - The minimum balance of Algo that the account should have available to spend (i.e., on top of the minimum balance requirement) +- An `options` object, which has: + - [Common transaction parameters](./algorand-client.md#transaction-parameters) (not for TestNet Dispenser API) + - [Execution parameters](./algorand-client.md#sending-a-single-transaction) (not for TestNet Dispenser API) + - `min_funding_increment: Optional[AlgoAmount]` - When issuing a funding amount, the minimum amount to transfer; this avoids many small transfers if this function gets called often on an active account + +### Examples + +```python +# From account + +# Basic example +algorand_client.account.ensure_funded("ACCOUNTADDRESS", "DISPENSERADDRESS", AlgoAmount(1, "algo")) +# With configuration +algorand_client.account.ensure_funded( + "ACCOUNTADDRESS", + "DISPENSERADDRESS", + AlgoAmount(1, "algo"), + min_funding_increment=AlgoAmount(2, "algo"), + fee=AlgoAmount(1000, "microalgo"), + send_params=SendParams( + suppress_log=True, + ), +) + +# From environment + +# Basic example +algorand_client.account.ensure_funded_from_environment("ACCOUNTADDRESS", AlgoAmount(1, "algo")) +# With configuration +algorand_client.account.ensure_funded_from_environment( + "ACCOUNTADDRESS", + AlgoAmount(1, "algo"), + min_funding_increment=AlgoAmount(2, "algo"), + fee=AlgoAmount(1000, "microalgo"), + send_params=SendParams( + suppress_log=True, + ), +) + +# TestNet Dispenser API + +# Basic example +algorand_client.account.ensure_funded_from_testnet_dispenser_api( + "ACCOUNTADDRESS", + algorand_client.client.get_testnet_dispenser_from_environment(), + AlgoAmount(1, "algo") +) +# With configuration +algorand_client.account.ensure_funded_from_testnet_dispenser_api( + "ACCOUNTADDRESS", + algorand_client.client.get_testnet_dispenser_from_environment(), + AlgoAmount(1, "algo"), + min_funding_increment=AlgoAmount(2, "algo"), +) +``` + +All 3 variants return an `EnsureFundedResponse` (and the first two also return a [single transaction result](./algorand-client.md#sending-a-single-transaction)) if a funding transaction was needed, or `None` if no transaction was required: + +- `amount_funded: AlgoAmount` - The number of Algo that was paid +- `transaction_id: str` - The ID of the transaction that funded the account + +If you are using the TestNet Dispenser API then the `transaction_id` is useful if you want to use the [refund functionality](./dispenser-client.md#registering-a-refund). ## Dispenser -If you want to programmatically send funds then you will often need a "dispenser" account that has a store of ALGOs that can be sent and a private key available for that dispenser account. +If you want to programmatically send funds to an account so it can transact then you will often need a "dispenser" account that has a store of Algo that can be sent and a private key available for that dispenser account. -There is a standard AlgoKit Utils function to get access to a [dispenser account](./account.md#account): `get_dispenser_account`. When running against -[LocalNet](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md), the dispenser account can be automatically determined using the -[Kmd API](https://developer.algorand.org/docs/rest-apis/kmd). When running against other networks like TestNet or MainNet the mnemonic of the dispenser account can be provided via environment -variable `DISPENSER_MNEMONIC` +There's a number of ways to get a dispensing account in AlgoKit Utils: -Please note that this does not refer to the [AlgoKit TestNet Dispenser API](./dispenser-client.md) which is a separate abstraction that can be used to fund accounts on TestNet via dedicated API service. +- Get a dispenser via [account manager](./account.md#dispenser) - either automatically from [LocalNet](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/localnet.md) or from the environment +- By programmatically creating one of the many account types via [account manager](./account.md#accounts) +- By programmatically interacting with [KMD](./account.md#kmd-account-management) if running against LocalNet +- By using the [AlgoKit TestNet Dispenser API client](./dispenser-client.md) which can be used to fund accounts on TestNet via a dedicated API service diff --git a/docs/html/_sources/capabilities/typed-app-clients.md.txt b/docs/html/_sources/capabilities/typed-app-clients.md.txt new file mode 100644 index 00000000..710323c1 --- /dev/null +++ b/docs/html/_sources/capabilities/typed-app-clients.md.txt @@ -0,0 +1,200 @@ +# Typed application clients + +Typed application clients are automatically generated, typed Python deployment and invocation clients for smart contracts that have a defined [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258) or [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) application specification so that the development experience is easier with less upskill ramp-up and less deployment errors. These clients give you a type-safe, intellisense-driven experience for invoking the smart contract. + +Typed application clients are the recommended way of interacting with smart contracts. If you don't have/want a typed client, but have an ARC-56/ARC-32 app spec then you can use the [non-typed application clients](./app-client.md) and if you want to call a smart contract you don't have an app spec file for you can use the underlying [app management](./app.md) and [app deployment](./app-deploy.md) functionality to manually construct transactions. + +## Generating an app spec + +You can generate an app spec file: + +- Using [Algorand Python](https://algorandfoundation.github.io/puya/#quick-start) +- Using [TEALScript](https://tealscript.netlify.app/tutorials/hello-world/0004-artifacts/) +- By hand by following the specification [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258)/[ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) +- Using [Beaker](https://algorand-devrel.github.io/beaker/html/usage.html) (PyTEAL) _(DEPRECATED)_ + +## Generating a typed client + +To generate a typed client from an app spec file you can use [AlgoKit CLI](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/generate.md#1-typed-clients): + +``` +> algokit generate client application.json --output /absolute/path/to/client.py +``` + +Note: AlgoKit Utils >= 3.0.0 is compatible with the older 1.x.x generated typed clients, however if you want to utilise the new features or leverage ARC-56 support, you will need to generate using >= 2.x.x. See [AlgoKit CLI generator version pinning](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/generate.md#version-pinning) for more information on how to lock to a specific version. + +## Getting a typed client instance + +To get an instance of a typed client you can use an [`AlgorandClient`](./algorand-client.md) instance or a typed app [`Factory`](#creating-a-typed-factory-instance) instance. + +The approach to obtaining a client instance depends on how many app clients you require for a given app spec and if the app has already been deployed, which is summarised below: + +### App is deployed + + + + + + + + + + + + + + + + + + + + + + +
Resolve App by IDResolve App by Creator and Name
Single App Client InstanceMultiple App Client InstancesSingle App Client InstanceMultiple App Client Instances
+ +```python +app_client = algorand.client.get_typed_app_client_by_id(MyContractClient, { + app_id=1234, + # ... +}) +# or +app_client = MyContractClient({ + algorand, + app_id=1234, + # ... +}) +``` + + + +```python +app_client1 = factory.get_app_client_by_id( + app_id=1234, + # ... +) +app_client2 = factory.get_app_client_by_id( + app_id=4321, + # ... +) +``` + + + +```python +app_client = algorand.client.get_typed_app_client_by_creator_and_name( + MyContractClient, + creator_address="CREATORADDRESS", + app_name="contract-name", + # ... +) +# or +app_client = MyContractClient.from_creator_and_name( + algorand, + creator_address="CREATORADDRESS", + app_name="contract-name", + # ... +) +``` + + + +```python +app_client1 = factory.get_app_client_by_creator_and_name( + creator_address="CREATORADDRESS", + app_name="contract-name", + # ... +) +app_client2 = factory.get_app_client_by_creator_and_name( + creator_address="CREATORADDRESS", + app_name="contract-name-2", + # ... +) +``` + +
+ +To understand the difference between resolving by ID vs by creator and name see the underlying [app client documentation](./app-client.md#appclient). + +### App is not deployed + + + + + + + + + + + + + + +
Deploy a New AppDeploy or Resolve App Idempotently by Creator and Name
+ +```python +app_client, response = factory.deploy( + args=[], + # ... +) +# or +app_client, response = factory.send.create.METHODNAME( + args=[], + # ... +) +``` + + + +```python +app_client, response = factory.deploy( + app_name="contract-name", + # ... +) +``` + +
+ +### Creating a typed factory instance + +If your scenario calls for an app factory, you can create one using the below: + +```python +factory = algorand.client.get_typed_app_factory(MyContractFactory) +# or +factory = MyContractFactory(algorand) +``` + +## Client usage + +See the [official usage docs](https://github.com/algorandfoundation/algokit-client-generator-py/blob/main/docs/usage.md) for full details. + +For a simple example that deploys a contract and calls a `"hello"` method, see below: + +```python +# A similar working example can be seen in the AlgoKit init production smart contract templates, when using Python deployment +# In this case the generated factory is called `HelloWorldAppFactory` and is in `./artifacts/HelloWorldApp/client.py` +from artifacts.hello_world_app.client import HelloWorldAppClient, HelloArgs +from algokit_utils import AlgorandClient + +# These require environment variables to be present, or it will retrieve from default LocalNet +algorand = AlgorandClient.from_environment() +deployer = algorand.account.from_environment("DEPLOYER", AlgoAmount.from_algo(1)) + +# Create the typed app factory +factory = algorand.client.get_typed_app_factory(HelloWorldAppFactory, + creator_address=deployer, + default_sender=deployer, +) + +# Create the app and get a typed app client for the created app (note: this creates a new instance of the app every time, +# you can use .deploy() to deploy idempotently if the app wasn't previously +# deployed or needs to be updated if that's allowed) +app_client, response = factory.send.create() + +# Make a call to an ABI method and print the result +response = app_client.send.hello(args=HelloArgs(name="world")) +print(response) +``` diff --git a/docs/html/_sources/index.md.txt b/docs/html/_sources/index.md.txt index ede55d1b..f3378aed 100644 --- a/docs/html/_sources/index.md.txt +++ b/docs/html/_sources/index.md.txt @@ -1,16 +1,14 @@ # AlgoKit Python Utilities -A set of core Algorand utilities written in Python and released via PyPi that make it easier to build solutions on Algorand. -This project is part of [AlgoKit](https://github.com/algorandfoundation/algokit-cli). +A set of core Algorand utilities written in Python and released via PyPi that make it easier to build solutions on Algorand. This project is part of [AlgoKit](https://github.com/algorandfoundation/algokit-cli). -The goal of this library is to provide intuitive, productive utility functions that make it easier, quicker and safer to build applications on Algorand. -Largely these functions wrap the underlying Algorand SDK, but provide a higher level interface with sensible defaults and capabilities for common tasks. +The goal of this library is to provide intuitive, productive utility functions that make it easier, quicker and safer to build applications on Algorand. Largely these functions wrap the underlying Algorand SDK, but provide a higher level interface with sensible defaults and capabilities for common tasks. ```{note} If you prefer TypeScript there's an equivalent [TypeScript utility library](https://github.com/algorandfoundation/algokit-utils-ts). ``` -[Core principles](#core-principles) | [Installation](#installation) | [Usage](#usage) | [Capabilities](#capabilities) | [Reference docs](#reference-documentation) +{ref}`Core principles ` | {ref}`Installation ` | {ref}`Usage ` | {ref}`Config and logging ` | {ref}`Capabilities ` | {ref}`Reference docs ` ```{toctree} --- @@ -19,34 +17,42 @@ caption: Contents --- capabilities/account -capabilities/client +capabilities/algorand-client +capabilities/amount capabilities/app-client capabilities/app-deploy -capabilities/transfer +capabilities/app +capabilities/asset +capabilities/client +capabilities/debugging capabilities/dispenser-client -capabilities/debugger -apidocs/algokit_utils/algokit_utils +capabilities/testing +capabilities/transaction-composer +capabilities/transaction +capabilities/transfer +capabilities/typed-app-clients +v3-migration-guide ``` (core-principles)= # Core principles -This library is designed with the following principles: +This library follows the [Guiding Principles of AlgoKit](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/algokit.md#guiding-principles) and is designed with the following principles: -- **Modularity** - This library is a thin wrapper of modular building blocks over the Algorand SDK; the primitives from the underlying Algorand SDK are - exposed and used wherever possible so you can opt-in to which parts of this library you want to use without having to use an all or nothing approach. -- **Type-safety** - This library provides strong TypeScript support with effort put into creating types that provide good type safety and intellisense. -- **Productivity** - This library is built to make solution developers highly productive; it has a number of mechanisms to make common code easier and terser to write +- **Modularity** - This library is a thin wrapper of modular building blocks over the Algorand SDK; the primitives from the underlying Algorand SDK are exposed and used wherever possible so you can opt-in to which parts of this library you want to use without having to use an all or nothing approach. +- **Type-safety** - This library provides strong type hints with effort put into creating types that provide good type safety and intellisense when used with tools like MyPy. +- **Productivity** - This library is built to make solution developers highly productive; it has a number of mechanisms to make common code easier and terser to write. (installation)= # Installation -This library can be installed from PyPi using pip or poetry, e.g.: +This library can be installed from PyPi using pip or poetry: -``` +```bash pip install algokit-utils +# or poetry add algokit-utils ``` @@ -54,50 +60,96 @@ poetry add algokit-utils # Usage -To use this library simply include the following at the top of your file: +The main entrypoint to the bulk of the functionality in AlgoKit Utils is the `AlgorandClient` class. You can get started by using one of the static initialization methods to create an Algorand client: ```python -import algokit_utils +# Point to the network configured through environment variables or +# if no environment variables it will point to the default LocalNet configuration +algorand = AlgorandClient.from_environment() +# Point to default LocalNet configuration +algorand = AlgorandClient.default_localnet() +# Point to TestNet using AlgoNode free tier +algorand = AlgorandClient.testnet() +# Point to MainNet using AlgoNode free tier +algorand = AlgorandClient.mainnet() +# Point to a pre-created algod client +algorand = AlgorandClient.from_clients(algod=...) +# Point to a pre-created algod and indexer client +algorand = AlgorandClient.from_clients(algod=..., indexer=..., kmd=...) +# Point to custom configuration for algod +algod_config = AlgoClientNetworkConfig(server=..., token=..., port=...) +algorand = AlgorandClient.from_config(algod_config=algod_config) +# Point to custom configuration for algod and indexer and kmd +algod_config = AlgoClientNetworkConfig(server=..., token=..., port=...) +indexer_config = AlgoClientNetworkConfig(server=..., token=..., port=...) +kmd_config = AlgoClientNetworkConfig(server=..., token=..., port=...) +algorand = AlgorandClient.from_config(algod_config=algod_config, indexer_config=indexer_config, kmd_config=kmd_config) ``` -Then you can use intellisense to auto-complete the various functions and types that are available by typing `algokit_utils.` in your favourite Integrated Development Environment (IDE), -or you can refer to the [reference documentation](apidocs/algokit_utils/algokit_utils.md). +# Testing -## Types +AlgoKit Utils provides a dedicated documentation page on various useful snippets that can be reused for testing with tools like [Pytest](https://docs.pytest.org/en/latest/): -The library contains extensive type hinting combined with a tool like MyPy this can help identify issues where incorrect types have been used, or used incorrectly. +- [Testing](capabilities/testing) -(capabilities)= +# Types -# Capabilities +The library leverages Python's native type hints and is fully compatible with [MyPy](https://mypy-lang.org/) for static type checking. -The library helps you with the following capabilities: +All public abstractions and methods are organized in logical modules matching their domain functionality. You can import types either directly from the root module or from their source submodules. Refer to [API documentation](autoapi/index) for more details. -- Core capabilities - - [**Client management**](capabilities/client.md) - Creation of algod, indexer and kmd clients against various networks resolved from environment or specified configuration - - [**Account management**](capabilities/account.md) - Creation and use of accounts including mnemonic, multisig, transaction signer, idempotent KMD accounts and environment variable injected -- Higher-order use cases - - [**ARC-0032 Application Spec client**](capabilities/app-client.md) - Builds on top of the App management and App deployment capabilities to provide a high productivity application client that works with ARC-0032 application spec defined smart contracts (e.g. via Beaker) - - [**App deployment**](capabilities/app-deploy.md) - Idempotent (safely retryable) deployment of an app, including deploy-time immutability and permanence control and TEAL template substitution - - [**Algo transfers**](capabilities/transfer.md) - Ability to easily initiate algo transfers between accounts, including dispenser management and idempotent account funding - - [**Debugger**](capabilities/debugger.md) - Provides a set of debugging tools that can be used to simulate and trace transactions on the Algorand blockchain. These tools and methods are optimized for developers who are building applications on Algorand and need to test and debug their smart contracts via [AVM Debugger extension](https://github.com/algorandfoundation/algokit-avm-vscode-debugger). +(config-logging)= -(reference-documentation)= +# Config and logging -# Reference documentation +To configure the AlgoKit Utils library you can make use of the [`Config`](autoapi/algokit_utils/config/index) object, which has a configure method that lets you configure some or all of the configuration options. + +## Config singleton + +The AlgoKit Utils configuration singleton can be updated using `config.configure()`. Refer to the [Config API documentation](autoapi/algokit_utils/config/index) for more details. + +## Logging -We have [auto-generated reference documentation for the code](apidocs/algokit_utils/algokit_utils.md). +AlgoKit has an in-built logging abstraction through the {py:obj}`algokit_utils.config.AlgoKitLogger` class that provides standardized logging capabilities. The logger is accessible through the `config.logger` property and provides various logging levels. -# Roadmap +Each method supports optional suppression of output using the `suppress_log` parameter. -This library will naturally evolve with any logical developer experience improvements needed to facilitate the [AlgoKit](https://github.com/algorandfoundation/algokit-cli) roadmap as it evolves. +## Debug mode -Likely future capability additions include: +To turn on debug mode you can use the following: -- Typed application client -- Asset management -- Expanded indexer API wrapper support +```python +from algokit_utils.config import config +config.configure(debug=True) +``` + +To retrieve the current debug state you can use `debug` property. + +This will turn on things like automatic tracing, more verbose logging and [advanced debugging](capabilities/debugging). It's likely this option will result in extra HTTP calls to algod and it's worth being careful when it's turned on. + +(capabilities)= -# Indices and tables +# Capabilities + +The library helps you interact with and develop against the Algorand blockchain with a series of end-to-end capabilities as described below: + +- [**AlgorandClient**](./capabilities/algorand-client.md) - The key entrypoint to the AlgoKit Utils functionality +- **Core capabilities** + - [**Client management**](./capabilities/client.md) - Creation of (auto-retry) algod, indexer and kmd clients against various networks resolved from environment or specified configuration, and creation of other API clients (e.g. TestNet Dispenser API and app clients) + - [**Account management**](./capabilities/account.md) - Creation, use, and management of accounts including mnemonic, rekeyed, multisig, transaction signer, idempotent KMD accounts and environment variable injected + - [**Algo amount handling**](./capabilities/amount.md) - Reliable, explicit, and terse specification of microAlgo and Algo amounts and safe conversion between them + - [**Transaction management**](./capabilities/transaction.md) - Ability to construct, simulate and send transactions with consistent and highly configurable semantics, including configurable control of transaction notes, logging, fees, validity, signing, and sending behaviour +- **Higher-order use cases** + - [**Asset management**](./capabilities/asset.md) - Creation, transfer, destroying, opting in and out and managing Algorand Standard Assets + - [**Typed application clients**](./capabilities/typed-app-clients.md) - Type-safe application clients that are [generated](https://github.com/algorandfoundation/algokit-cli/blob/main/docs/features/generate.md#1-typed-clients) from ARC-56 or ARC-32 application spec files and allow you to intuitively and productively interact with a deployed app, which is the recommended way of interacting with apps and builds on top of the following capabilities: + - [**ARC-56 / ARC-32 App client and App factory**](./capabilities/app-client.md) - Builds on top of the App management and App deployment capabilities (below) to provide a high productivity application client that works with ARC-56 and ARC-32 application spec defined smart contracts + - [**App management**](./capabilities/app.md) - Creation, updating, deleting, calling (ABI and otherwise) smart contract apps and the metadata associated with them (including state and boxes) + - [**App deployment**](./capabilities/app-deploy.md) - Idempotent (safely retryable) deployment of an app, including deploy-time immutability and permanence control and TEAL template substitution + - [**Algo transfers (payments)**](./capabilities/transfer.md) - Ability to easily initiate Algo transfers between accounts, including dispenser management and idempotent account funding + - [**Automated testing**](./capabilities/testing.md) - Reusable snippets to leverage AlgoKit Utils abstractions in a manner that are useful for when writing tests in tools like [Pytest](https://docs.pytest.org/en/latest/). + +(reference-documentation)= + +# Reference documentation -- {ref}`genindex` +For detailed API documentation, see the {py:obj}`algokit_utils` diff --git a/docs/html/_sources/v3-migration-guide.md.txt b/docs/html/_sources/v3-migration-guide.md.txt new file mode 100644 index 00000000..674ae165 --- /dev/null +++ b/docs/html/_sources/v3-migration-guide.md.txt @@ -0,0 +1,284 @@ +# Migration Guide - v3 + +Version 3 of `algokit-utils-ts` moved from a stateless function-based interface to a stateful class-based interfaces. This change allows for: + +- Easier and simpler consumption experience guided by IDE autocompletion +- Less redundant parameter passing (e.g., `algod` client) +- Better performance through caching of commonly retrieved values like transaction parameters +- More consistent and intuitive API design +- Stronger type safety and better error messages +- Improved ARC-56 compatibility +- Feature parity with `algokit-utils-ts` >= `v7` interfaces + +The entry point to most functionality in AlgoKit Utils is now available via a single entry-point, the `AlgorandClient` class. + +The v2 interfaces and abstractions will be removed in future major version bumps, however in order to ensure gradual migration, _all v2 abstractions are available_ with respective deprecation warnings. The new way to use AlgoKit Utils is via the `AlgorandClient` class, which is easier, simpler, and more convenient to use and has powerful new features. + +> BREAKING CHANGE: the `beta` module is now removed, any imports from `algokit_utils.beta` will now raise an error with a link to a new expected import path. This is due to the fact that the interfaces introduced in `beta` are now refined and available in the main module. + +## Migration Steps + +In general, your codebase might fall into one of the following migration scenarios: + +- Using `algokit-utils-py` v2.x only without use of abstractions from `beta` module +- Using `algokit-utils-py` v2.x only and with use of abstractions from `beta` module +- Using `algokit-utils-py` v2.x with `algokit-client-generator-py` v1.x +- Using `algokit-client-generator-py` v1.x only (implies implicit dependency on `algokit-utils-py` v2.x) + +Given that `algokit-utils-py` v3.x is backwards compatible with `algokit-client-generator-py` v1.x, the following general guidelines are applicable to all scenarios (note that the order of operations is important to ensure straight-forward migration): + +1. Upgrade to `algokit-utils-py` v3.x + - 1.1 (If used) Update imports from `algokit_utils.beta` to `algokit_utils` + - 1.2 Follow hints in deprecation warnings to update your codebase to rely on latest v3 interfaces +2. Upgrade to `algokit-client-generator-py` v2.x and regenerate typed clients + - 2.1 Follow `algokit-client-generator-py` [v2.x migration guide](https://github.com/algorandfoundation/algokit-client-generator-py/blob/main/docs/v2-migration-guide.md) + +The remaining set of guidelines are outlining migrations for specific abstractions that had direct equivalents in `algokit-utils-py` v2.x. + +### Prerequisites + +It is important to reiterate that if you have previously relied on `beta` versions of `algokit-utils-py` v2.x, you will need to update your imports to rely on the new interfaces. Errors thrown during import from `beta` will provide a description of the new expected import path. + +> As with `v2.x` all public abstractions in `algokit_utils` are available for direct imports `from algokit_utils import ...`, however underlying modules have been refined to be structured loosely around common AVM domains such as `applications`, `transactions`, `accounts`, `assets`, etc. See [API reference](https://algokit-utils-py.readthedocs.io/en/latest/api_reference/index.html) for latest and detailed overview. + +### Step 1 - Replace SDK Clients with AlgorandClient + +First, replace your SDK client initialization with `AlgorandClient`. Look for `get_algod_client` calls and replace with an appropriate `AlgorandClient` initialization: + +```python +"""Before""" +import algokit_utils +algod = algokit_utils.get_algod_client() +indexer = algokit_utils.get_indexer_client() + +"""After""" +from algokit_utils import AlgorandClient +algorand = AlgorandClient.from_environment() # or .testnet(), .mainnet(), etc. +``` + +During migration, you can still access SDK clients if needed: + +```python +algod = algorand.client.algod +indexer = algorand.client.indexer +kmd = algorand.client.kmd +``` + +### Step 2 - Update Account Management + +Account management has moved to `algorand.account`: + +#### Before: + +```python +account = algokit_utils.get_account_from_mnemonic( + mnemonic=os.getenv("MY_ACCOUNT_MNEMONIC"), +) +dispenser = algokit_utils.get_dispenser_account(algod) +``` + +#### After: + +```python +account = algorand.account.from_mnemonic(os.getenv("MY_ACCOUNT_MNEMONIC")) +dispenser = algorand.account.dispenser_from_environment() +``` + +Key changes: + +- `get_account` → `account.from_environment` +- `get_account_from_mnemonic` → `account.from_mnemonic` +- `get_dispenser_account` → `account.dispenser_from_environment` +- `get_localnet_default_account` → `account.localnet_dispenser` + +### Step 3 - Update Transaction Management + +Transaction creation and sending is now more structured: + +#### Before: + +```python +# Single transaction +result = algokit_utils.transfer_algos( + from_account=account, + to_addr="RECEIVER", + amount=algokit_utils.algos(1), + algod_client=algod, +) + +# Transaction groups +atc = AtomicTransactionComposer() +# ... add transactions ... +result = algokit_utils.execute_atc_with_logic_error(atc, algod) +``` + +#### After: + +```python +# Single transaction +result = algorand.send.payment( + sender=account.address, + receiver="RECEIVER", + amount=AlgoAmount.from_algo(1), +) + +# Transaction groups +composer = algorand.new_group() +# ... add transactions ... +result = composer.send() +``` + +Key changes: + +- `transfer_algos` → `algorand.send.payment` +- `transfer_asset` → `algorand.send.asset_transfer` +- `execute_atc_with_logic_error` → `composer.send()` +- Transaction parameters are now more consistently named (e.g., `sender` instead of `from_account`) +- Improved amount handling with dedicated `AlgoAmount` class (e.g., `AlgoAmount.from_algo(1)`) + +### Step 4 - Update `ApplicationSpecification` usage + +`ApplicationSpecification` abstraction is largely identical to v2, however it's been renamed to `Arc32Contract` to better reflect the fact that it's a contract specification for a specific ARC and addition of `Arc56Contract` supporting the latest recommended conventions. Hence the main actionable change is to update your import to `from algokit_utils import Arc32Contract` and rename `ApplicationSpecification` to `Arc32Contract`. + +You can instantiate an `Arc56Contract` instance from an `Arc32Contract` instance using the `Arc56Contract.from_arc32` method. For instance: + +```python +testing_app_arc32_app_spec = Arc32Contract.from_json(app_spec_json) +arc56_app_spec = Arc56Contract.from_arc32(testing_app_arc32_app_spec) +``` + +> Despite auto conversion of ARC-32 to ARC-56, we recommend recompiling your contract to a fully compliant ARC-56 specification given that auto conversion would skip populating information that can't be parsed from raw ARC-32. + +### Step 5 - Replace `ApplicationClient` usage + +The existing `ApplicationClient` (untyped app client) class is still present until at least v4, but it's worthwhile migrating to the new [`AppClient` and `AppFactory` classes](./capabilities/app-client.md). These new clients are [ARC-56](https://github.com/algorandfoundation/ARCs/pull/258) compatible, but also support [ARC-32](https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0032.md) app specs and will continue to support this indefinitely until such time the community deems they are deprecated. + +All of the functionality in `ApplicationClient` is available within the new classes, but their interface is slightly different to make it easier to use and more consistent with the new `AlgorandClient` functionality. The key existing methods that have changed all have `@deprecation` notices to help guide you on this, but broadly the changes are: + +- The app resolution semantics, now have static methods that determine different ways of constructing a client and the constructor itself is very simple (requiring `app_id`) +- If you want to call `create` or `deploy` then you need an `AppFactory` to do that, and then it will in turn give you an `AppClient` instance that is connected to the app you just created / deployed. This significantly simplifies the app client because now the app client has a clear operating purpose: allow for calls and state management for an _instance_ of an app, whereas the app factory handles all of the calls when you don't have an instance yet (or may or may not have an instance in the case of `deploy`). +- This means that you can simply access `client.app_id` and `client.app_address` on `AppClient` since these values are known statically and won't change (previously associated calls to `app_address`, `app_id` properties potentially required extra API calls as the values weren't always available). +- Adding `fund_app_account` which serves as a convenience method to top up the balance of address associated with application. +- All of the methods that return or execute a transaction (`update`, `call`, `opt_in`, etc.) are now exposed in an interface similar to the one in [`AlgorandClient`](./capabilities/algorand-client.md#creating-and-issuing-transactions), namely (where `{call_type}` is one of: `update` / `delete` / `opt_in` / `close_out` / `clear_state` / `call`): + - `appClient.create_transaction.{callType}` to get a transaction for an ABI method call + - `appClient.send.{call_type}` to sign and send a transaction for an ABI method call + - `appClient.params.{call_type}` to get a [params object](./capabilities/algorand-client.md#transaction-parameters) for an ABI method call + - `appClient.create_transaction.bare.{call_type}` to get a transaction for a bare app call + - `appClient.send.bare.{call_type}` to sign and send a transaction for a bare app call + - `appClient.params.bare.{call_type}` to get a [params object](./capabilities/algorand-client.md#transaction-parameters) for a bare app call +- The semantics to resolve the application is now available via [simpler entrypoints within `algorand.client`](./capabilities/app-client.md#appclient) +- When making an ABI method call, the method arguments property is are now passed via explicit `args` field in a parameters dataclass applicable to the method call. +- The foreign reference arrays have been renamed to align with typed parameters on `ts` and related core `algosdk`: + - `boxes` -> `box_references` + - `apps` -> `app_references` + - `assets` -> `asset_references` + - `accounts` -> `account_references` +- The return value for methods that send a transaction will have any ABI return value directly in the `abi_return` property rather than the low level algosdk `ABIResult` type while also automatically decoding values based on provided ARC56 spec. + +### Step 6 - Replace typed app client usage + +Version 2 of the Python typed app client generator introduces breaking changes to the generated client that support the new `AppFactory` and `AppClient` functionality along with adding ARC-56 support. The generated client has better typing support for things like state commensurate with the new capabilities within ARC-56. + +It's worth noting that because we have maintained backwards compatibility with the pre v2 `algokit-utils-py` stateless functions, older typed clients generated using version 1 of the Python typed client generator will work against v3 of utils, however you won't have access to the new features or ARC-56 support. + +If you want to convert from an older typed client to a new one you will need to make certain changes. Refer to [client generator v2 migration guide](https://github.com/algorandfoundation/algokit-client-generator-py/blob/main/docs/v2-migration.md). + +### Step 7 - Update `AppClient` State Management + +State management is now more structured and type-safe: + +```python +"""Before""" +global_state = app_client.get_global_state() +local_state = app_client.get_local_state(account_address) +box_value = app_client.get_box_value("box_name") + +"""After""" +# Global state +global_state = app_client.state.global_state.get_all() +value = app_client.state.global_state.get_value("key_name") +map_value = app_client.state.global_state.get_map_value("map_name", "key") + +# Local state +local_state = app_client.state.local_state(account_address).get_all() +value = app_client.state.local_state(account_address).get_value("key_name") +map_value = app_client.state.local_state(account_address).get_map_value("map_name", "key") + +# Box storage +box_value = app_client.state.box.get_value("box_name") +boxes = app_client.state.box.get_all() +map_value = app_client.state.box.get_map_value("map_name", "key") +``` + +### Step 8 - Update Asset Management + +Asset management is now more consistent: + +```python +"""Before""" +result = algokit_utils.opt_in(algod, account, [asset_id]) + +"""After""" +result = algorand.send.asset_opt_in( + params=AssetOptInParams( + sender=account.address, + asset_id=asset_id, + ) +) +``` + +## Breaking Changes + +1. **Client Management** + + - Removal of standalone client creation functions + - All clients now accessed through `AlgorandClient` + +2. **Account Management** + + - Account creation functions moved to `AccountManager` accessible via `algorand.account` property + - Unified `TransactionSignerAccountProtocol` with compliant and typed `SigningAccount`, `TransactionSignerAccount`, `LogicSigAccount`, `MultiSigAccount` classes encapsulating low level `algosdk` abstractions. + - Improved typing for account operations, such as obtaining account information from `algod`, returning a typed information object. + +3. **Transaction Management** + + - Consistent and intuitive transaction creation and sending interface accessible via `algorand.{send|params|create_transaction}` properties + - New transaction composition interface accessible via `algorand.new_group` + - Removing necessity to interact with low level and untyped `algosdk` abstractions for assembling, signing and sending transaction(s). + +4. **Application Client** + + - Split into `AppClient`, `AppDeployer` and `AppFactory` + - New intuitive structured interface for creating or sending `AppCall`|`AppMethodCall` transactions + - ARC-56 support along with automatic conversion of specs from ARC-32 to ARC-56 + +5. **State Management** + + - New hierarchical state access available via `app_client.state.{global_state|local_state|box}` properties + - Improved typing for state values + - Support for ARC-56 state schemas + +6. **Asset Management** + - Dedicated `AssetManager` class for asset management accessible via `algorand.asset` property + - Improved typing for asset operations, such as obtaining asset information from `algod`, returning a typed information object. + - Consistent interface for asset opt-in, transfer, freeze, etc. + +## Best Practices + +1. Use the new `AlgorandClient` as the main entry point +2. Leverage IDE autocompletion to discover available functionality, consult with [API reference](https://algokit-utils-py.readthedocs.io/en/latest/api_reference/index.html) when unsure +3. Use the transaction parameter builders for type-safe transaction creation (`algorand.params.{}`) +4. Use the state accessor patterns for cleaner state management {`algorand.state.{}`} +5. Use high level `TransactionComposer` interface over low level `algosdk` abstractions (where possible) +6. Use source maps and debug mode to quickly troubleshoot on-chain errors +7. Use idempotent deployment patterns with versioning + +## Troubleshooting + +### A v2 interface/method/class does not display a deprecation warning correctly or at all + +Submit an issue to [algokit-utils-py](https://github.com/algorandfoundation/algokit-utils-py/issues) with a description of the problem and the code that is causing it. + +### Useful scenario of converting v2 to v3 not covered in generic migration guide + +If you have a scenario that you think is useful and not covered in the generic migration guide, please submit an issue to [algokit-utils-py](https://github.com/algorandfoundation/algokit-utils-py/issues) with a scenario. diff --git a/docs/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/html/_static/_sphinx_javascript_frameworks_compat.js deleted file mode 100644 index 81415803..00000000 --- a/docs/html/_static/_sphinx_javascript_frameworks_compat.js +++ /dev/null @@ -1,123 +0,0 @@ -/* Compatability shim for jQuery and underscores.js. - * - * Copyright Sphinx contributors - * Released under the two clause BSD licence - */ - -/** - * small helper function to urldecode strings - * - * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL - */ -jQuery.urldecode = function(x) { - if (!x) { - return x - } - return decodeURIComponent(x.replace(/\+/g, ' ')); -}; - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s === 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node, addItems) { - if (node.nodeType === 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && - !jQuery(node.parentNode).hasClass(className) && - !jQuery(node.parentNode).hasClass("nohighlight")) { - var span; - var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); - if (isInSVG) { - span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); - } else { - span = document.createElement("span"); - span.className = className; - } - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - if (isInSVG) { - var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); - var bbox = node.parentElement.getBBox(); - rect.x.baseVal.value = bbox.x; - rect.y.baseVal.value = bbox.y; - rect.width.baseVal.value = bbox.width; - rect.height.baseVal.value = bbox.height; - rect.setAttribute('class', className); - addItems.push({ - "parent": node.parentNode, - "target": rect}); - } - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this, addItems); - }); - } - } - var addItems = []; - var result = this.each(function() { - highlight(this, addItems); - }); - for (var i = 0; i < addItems.length; ++i) { - jQuery(addItems[i].parent).before(addItems[i].target); - } - return result; -}; - -/* - * backward compatibility for jQuery.browser - * This will be supported until firefox bug is fixed. - */ -if (!jQuery.browser) { - jQuery.uaMatch = function(ua) { - ua = ua.toLowerCase(); - - var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || - /(webkit)[ \/]([\w.]+)/.exec(ua) || - /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || - /(msie) ([\w.]+)/.exec(ua) || - ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || - []; - - return { - browser: match[ 1 ] || "", - version: match[ 2 ] || "0" - }; - }; - jQuery.browser = {}; - jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; -} diff --git a/docs/html/_static/basic.css b/docs/html/_static/basic.css index 7577acb1..7ebbd6d0 100644 --- a/docs/html/_static/basic.css +++ b/docs/html/_static/basic.css @@ -1,12 +1,5 @@ /* - * basic.css - * ~~~~~~~~~ - * * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ /* -- main layout ----------------------------------------------------------- */ @@ -115,15 +108,11 @@ img { /* -- search page ----------------------------------------------------------- */ ul.search { - margin: 10px 0 0 20px; - padding: 0; + margin-top: 10px; } ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; + padding: 5px 0; } ul.search li a { @@ -237,6 +226,10 @@ a.headerlink { visibility: hidden; } +a:visited { + color: #551A8B; +} + h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, @@ -670,6 +663,16 @@ dd { margin-left: 30px; } +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + dl > dd:last-child, dl > dd:last-child > :last-child { margin-bottom: 0; @@ -738,6 +741,14 @@ abbr, acronym { cursor: help; } +.translated { + background-color: rgba(207, 255, 207, 0.2) +} + +.untranslated { + background-color: rgba(255, 207, 207, 0.2) +} + /* -- code displays --------------------------------------------------------- */ pre { diff --git a/docs/html/_static/css/badge_only.css b/docs/html/_static/css/badge_only.css deleted file mode 100644 index c718cee4..00000000 --- a/docs/html/_static/css/badge_only.css +++ /dev/null @@ -1 +0,0 @@ -.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff deleted file mode 100644 index 6cb60000..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 deleted file mode 100644 index 7059e231..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Bold.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff deleted file mode 100644 index f815f63f..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 deleted file mode 100644 index f2c76e5b..00000000 Binary files a/docs/html/_static/css/fonts/Roboto-Slab-Regular.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.eot b/docs/html/_static/css/fonts/fontawesome-webfont.eot deleted file mode 100644 index e9f60ca9..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.svg b/docs/html/_static/css/fonts/fontawesome-webfont.svg deleted file mode 100644 index 855c845e..00000000 --- a/docs/html/_static/css/fonts/fontawesome-webfont.svg +++ /dev/null @@ -1,2671 +0,0 @@ - - - - -Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - By ,,, -Copyright Dave Gandy 2016. All rights reserved. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/html/_static/css/fonts/fontawesome-webfont.ttf deleted file mode 100644 index 35acda2f..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.ttf and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.woff b/docs/html/_static/css/fonts/fontawesome-webfont.woff deleted file mode 100644 index 400014a4..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/html/_static/css/fonts/fontawesome-webfont.woff2 deleted file mode 100644 index 4d13fc60..00000000 Binary files a/docs/html/_static/css/fonts/fontawesome-webfont.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold-italic.woff b/docs/html/_static/css/fonts/lato-bold-italic.woff deleted file mode 100644 index 88ad05b9..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold-italic.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/html/_static/css/fonts/lato-bold-italic.woff2 deleted file mode 100644 index c4e3d804..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold-italic.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold.woff b/docs/html/_static/css/fonts/lato-bold.woff deleted file mode 100644 index c6dff51f..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-bold.woff2 b/docs/html/_static/css/fonts/lato-bold.woff2 deleted file mode 100644 index bb195043..00000000 Binary files a/docs/html/_static/css/fonts/lato-bold.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal-italic.woff b/docs/html/_static/css/fonts/lato-normal-italic.woff deleted file mode 100644 index 76114bc0..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal-italic.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/html/_static/css/fonts/lato-normal-italic.woff2 deleted file mode 100644 index 3404f37e..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal-italic.woff2 and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal.woff b/docs/html/_static/css/fonts/lato-normal.woff deleted file mode 100644 index ae1307ff..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal.woff and /dev/null differ diff --git a/docs/html/_static/css/fonts/lato-normal.woff2 b/docs/html/_static/css/fonts/lato-normal.woff2 deleted file mode 100644 index 3bf98433..00000000 Binary files a/docs/html/_static/css/fonts/lato-normal.woff2 and /dev/null differ diff --git a/docs/html/_static/css/theme.css b/docs/html/_static/css/theme.css deleted file mode 100644 index 19a446a0..00000000 --- a/docs/html/_static/css/theme.css +++ /dev/null @@ -1,4 +0,0 @@ -html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! - * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/html/_static/debug.css b/docs/html/_static/debug.css new file mode 100644 index 00000000..74d4aec3 --- /dev/null +++ b/docs/html/_static/debug.css @@ -0,0 +1,69 @@ +/* + This CSS file should be overridden by the theme authors. It's + meant for debugging and developing the skeleton that this theme provides. +*/ +body { + font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji"; + background: lavender; +} +.sb-announcement { + background: rgb(131, 131, 131); +} +.sb-announcement__inner { + background: black; + color: white; +} +.sb-header { + background: lightskyblue; +} +.sb-header__inner { + background: royalblue; + color: white; +} +.sb-header-secondary { + background: lightcyan; +} +.sb-header-secondary__inner { + background: cornflowerblue; + color: white; +} +.sb-sidebar-primary { + background: lightgreen; +} +.sb-main { + background: blanchedalmond; +} +.sb-main__inner { + background: antiquewhite; +} +.sb-header-article { + background: lightsteelblue; +} +.sb-article-container { + background: snow; +} +.sb-article-main { + background: white; +} +.sb-footer-article { + background: lightpink; +} +.sb-sidebar-secondary { + background: lightgoldenrodyellow; +} +.sb-footer-content { + background: plum; +} +.sb-footer-content__inner { + background: palevioletred; +} +.sb-footer { + background: pink; +} +.sb-footer__inner { + background: salmon; +} +.sb-article { + background: white; +} diff --git a/docs/html/_static/doctools.js b/docs/html/_static/doctools.js index d06a71d7..0398ebb9 100644 --- a/docs/html/_static/doctools.js +++ b/docs/html/_static/doctools.js @@ -1,12 +1,5 @@ /* - * doctools.js - * ~~~~~~~~~~~ - * * Base JavaScript utilities for all Sphinx HTML documentation. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; diff --git a/docs/html/_static/documentation_options.js b/docs/html/_static/documentation_options.js index a7f754b6..61cd8eec 100644 --- a/docs/html/_static/documentation_options.js +++ b/docs/html/_static/documentation_options.js @@ -1,6 +1,5 @@ -var DOCUMENTATION_OPTIONS = { - URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), - VERSION: '1.0', +const DOCUMENTATION_OPTIONS = { + VERSION: '3.0', LANGUAGE: 'en', COLLAPSE_INDEX: false, BUILDER: 'html', diff --git a/docs/html/_static/graphviz.css b/docs/html/_static/graphviz.css new file mode 100644 index 00000000..30f3837b --- /dev/null +++ b/docs/html/_static/graphviz.css @@ -0,0 +1,12 @@ +/* + * Sphinx stylesheet -- graphviz extension. + */ + +img.graphviz { + border: 0; + max-width: 100%; +} + +object.graphviz { + max-width: 100%; +} diff --git a/docs/html/_static/jquery.js b/docs/html/_static/jquery.js deleted file mode 100644 index c4c6022f..00000000 --- a/docs/html/_static/jquery.js +++ /dev/null @@ -1,2 +0,0 @@ -/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ -!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/html/_static/js/html5shiv.min.js b/docs/html/_static/js/html5shiv.min.js deleted file mode 100644 index cd1c674f..00000000 --- a/docs/html/_static/js/html5shiv.min.js +++ /dev/null @@ -1,4 +0,0 @@ -/** -* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed -*/ -!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/html/_static/js/theme.js b/docs/html/_static/js/theme.js deleted file mode 100644 index 1fddb6ee..00000000 --- a/docs/html/_static/js/theme.js +++ /dev/null @@ -1 +0,0 @@ -!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t{var t={856:function(t,e,n){var o,r;r=void 0!==n.g?n.g:"undefined"!=typeof window?window:this,o=function(){return function(t){"use strict";var e={navClass:"active",contentClass:"active",nested:!1,nestedClass:"active",offset:0,reflow:!1,events:!0},n=function(t,e,n){if(n.settings.events){var o=new CustomEvent(t,{bubbles:!0,cancelable:!0,detail:n});e.dispatchEvent(o)}},o=function(t){var e=0;if(t.offsetParent)for(;t;)e+=t.offsetTop,t=t.offsetParent;return e>=0?e:0},r=function(t){t&&t.sort((function(t,e){return o(t.content)=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},l=function(t,e){var n=t[t.length-1];if(function(t,e){return!(!s()||!c(t.content,e,!0))}(n,e))return n;for(var o=t.length-1;o>=0;o--)if(c(t[o].content,e))return t[o]},a=function(t,e){if(e.nested&&t.parentNode){var n=t.parentNode.closest("li");n&&(n.classList.remove(e.nestedClass),a(n,e))}},i=function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.remove(e.navClass),t.content.classList.remove(e.contentClass),a(o,e),n("gumshoeDeactivate",o,{link:t.nav,content:t.content,settings:e}))}},u=function(t,e){if(e.nested){var n=t.parentNode.closest("li");n&&(n.classList.add(e.nestedClass),u(n,e))}};return function(o,c){var s,a,d,f,m,v={setup:function(){s=document.querySelectorAll(o),a=[],Array.prototype.forEach.call(s,(function(t){var e=document.getElementById(decodeURIComponent(t.hash.substr(1)));e&&a.push({nav:t,content:e})})),r(a)},detect:function(){var t=l(a,m);t?d&&t.content===d.content||(i(d,m),function(t,e){if(t){var o=t.nav.closest("li");o&&(o.classList.add(e.navClass),t.content.classList.add(e.contentClass),u(o,e),n("gumshoeActivate",o,{link:t.nav,content:t.content,settings:e}))}}(t,m),d=t):d&&(i(d,m),d=null)}},h=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame(v.detect)},g=function(e){f&&t.cancelAnimationFrame(f),f=t.requestAnimationFrame((function(){r(a),v.detect()}))};return v.destroy=function(){d&&i(d,m),t.removeEventListener("scroll",h,!1),m.reflow&&t.removeEventListener("resize",g,!1),a=null,s=null,d=null,f=null,m=null},m=function(){var t={};return Array.prototype.forEach.call(arguments,(function(e){for(var n in e){if(!e.hasOwnProperty(n))return;t[n]=e[n]}})),t}(e,c||{}),v.setup(),v.detect(),t.addEventListener("scroll",h,!1),m.reflow&&t.addEventListener("resize",g,!1),v}}(r)}.apply(e,[]),void 0===o||(t.exports=o)}},e={};function n(o){var r=e[o];if(void 0!==r)return r.exports;var c=e[o]={exports:{}};return t[o].call(c.exports,c,c.exports,n),c.exports}n.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return n.d(e,{a:e}),e},n.d=(t,e)=>{for(var o in e)n.o(e,o)&&!n.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),n.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),(()=>{"use strict";var t=n(856),e=n.n(t),o=null,r=null,c=document.documentElement.scrollTop;const s=64;function l(){const t=localStorage.getItem("theme")||"auto";var e;"light"!==(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"auto"===t?"light":"light"==t?"dark":"auto":"auto"===t?"dark":"dark"==t?"light":"auto")&&"dark"!==e&&"auto"!==e&&(console.error(`Got invalid theme mode: ${e}. Resetting to auto.`),e="auto"),document.body.dataset.theme=e,localStorage.setItem("theme",e),console.log(`Changed to ${e} mode.`)}function a(){!function(){const t=document.getElementsByClassName("theme-toggle");Array.from(t).forEach((t=>{t.addEventListener("click",l)}))}(),function(){let t=0,e=!1;window.addEventListener("scroll",(function(n){t=window.scrollY,e||(window.requestAnimationFrame((function(){var n;(function(t){const e=Math.floor(r.getBoundingClientRect().top);console.log(`headerTop: ${e}`),0==e&&t!=e?r.classList.add("scrolled"):r.classList.remove("scrolled")})(n=t),function(t){tc&&document.documentElement.classList.remove("show-back-to-top"),c=t}(n),function(t){null!==o&&(0==t?o.scrollTo(0,0):Math.ceil(t)>=Math.floor(document.documentElement.scrollHeight-window.innerHeight)?o.scrollTo(0,o.scrollHeight):document.querySelector(".scroll-current"))}(n),e=!1})),e=!0)})),window.scroll()}(),null!==o&&new(e())(".toc-tree a",{reflow:!0,recursive:!0,navClass:"scroll-current",offset:()=>{let t=parseFloat(getComputedStyle(document.documentElement).fontSize);return r.getBoundingClientRect().height+2.5*t+1}})}document.addEventListener("DOMContentLoaded",(function(){document.body.parentNode.classList.remove("no-js"),r=document.querySelector("header"),o=document.querySelector(".toc-scroll"),a()}))})()})(); +//# sourceMappingURL=furo.js.map \ No newline at end of file diff --git a/docs/html/_static/scripts/furo.js.LICENSE.txt b/docs/html/_static/scripts/furo.js.LICENSE.txt new file mode 100644 index 00000000..1632189c --- /dev/null +++ b/docs/html/_static/scripts/furo.js.LICENSE.txt @@ -0,0 +1,7 @@ +/*! + * gumshoejs v5.1.2 (patched by @pradyunsg) + * A simple, framework-agnostic scrollspy script. + * (c) 2019 Chris Ferdinandi + * MIT License + * http://github.com/cferdinandi/gumshoe + */ diff --git a/docs/html/_static/scripts/furo.js.map b/docs/html/_static/scripts/furo.js.map new file mode 100644 index 00000000..80ea12b8 --- /dev/null +++ b/docs/html/_static/scripts/furo.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scripts/furo.js","mappings":";iCAAA,MAQWA,SAWS,IAAX,EAAAC,EACH,EAAAA,EACkB,oBAAXC,OACLA,OACAC,KAbO,EAAF,WACP,OAaJ,SAAUD,GACR,aAMA,IAAIE,EAAW,CAEbC,SAAU,SACVC,aAAc,SAGdC,QAAQ,EACRC,YAAa,SAGbC,OAAQ,EACRC,QAAQ,EAGRC,QAAQ,GA6BNC,EAAY,SAAUC,EAAMC,EAAMC,GAEpC,GAAKA,EAAOC,SAASL,OAArB,CAGA,IAAIM,EAAQ,IAAIC,YAAYL,EAAM,CAChCM,SAAS,EACTC,YAAY,EACZL,OAAQA,IAIVD,EAAKO,cAAcJ,EAVgB,CAWrC,EAOIK,EAAe,SAAUR,GAC3B,IAAIS,EAAW,EACf,GAAIT,EAAKU,aACP,KAAOV,GACLS,GAAYT,EAAKW,UACjBX,EAAOA,EAAKU,aAGhB,OAAOD,GAAY,EAAIA,EAAW,CACpC,EAMIG,EAAe,SAAUC,GACvBA,GACFA,EAASC,MAAK,SAAUC,EAAOC,GAG7B,OAFcR,EAAaO,EAAME,SACnBT,EAAaQ,EAAMC,UACF,EACxB,CACT,GAEJ,EAwCIC,EAAW,SAAUlB,EAAME,EAAUiB,GACvC,IAAIC,EAASpB,EAAKqB,wBACd1B,EAnCU,SAAUO,GAExB,MAA+B,mBAApBA,EAASP,OACX2B,WAAWpB,EAASP,UAItB2B,WAAWpB,EAASP,OAC7B,CA2Be4B,CAAUrB,GACvB,OAAIiB,EAEAK,SAASJ,EAAOD,OAAQ,KACvB/B,EAAOqC,aAAeC,SAASC,gBAAgBC,cAG7CJ,SAASJ,EAAOS,IAAK,KAAOlC,CACrC,EAMImC,EAAa,WACf,OACEC,KAAKC,KAAK5C,EAAOqC,YAAcrC,EAAO6C,cAnCjCF,KAAKG,IACVR,SAASS,KAAKC,aACdV,SAASC,gBAAgBS,aACzBV,SAASS,KAAKE,aACdX,SAASC,gBAAgBU,aACzBX,SAASS,KAAKP,aACdF,SAASC,gBAAgBC,aAkC7B,EAmBIU,EAAY,SAAUzB,EAAUX,GAClC,IAAIqC,EAAO1B,EAASA,EAAS2B,OAAS,GACtC,GAbgB,SAAUC,EAAMvC,GAChC,SAAI4B,MAAgBZ,EAASuB,EAAKxB,QAASf,GAAU,GAEvD,CAUMwC,CAAYH,EAAMrC,GAAW,OAAOqC,EACxC,IAAK,IAAII,EAAI9B,EAAS2B,OAAS,EAAGG,GAAK,EAAGA,IACxC,GAAIzB,EAASL,EAAS8B,GAAG1B,QAASf,GAAW,OAAOW,EAAS8B,EAEjE,EAOIC,EAAmB,SAAUC,EAAK3C,GAEpC,GAAKA,EAAST,QAAWoD,EAAIC,WAA7B,CAGA,IAAIC,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASR,aAG7BkD,EAAiBG,EAAI7C,GAV0B,CAWjD,EAOIiD,EAAa,SAAUC,EAAOlD,GAEhC,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUC,OAAOhD,EAASX,UAC7B6D,EAAMnC,QAAQgC,UAAUC,OAAOhD,EAASV,cAGxCoD,EAAiBG,EAAI7C,GAGrBJ,EAAU,oBAAqBiD,EAAI,CACjCM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,EAOIoD,EAAiB,SAAUT,EAAK3C,GAElC,GAAKA,EAAST,OAAd,CAGA,IAAIsD,EAAKF,EAAIC,WAAWE,QAAQ,MAC3BD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASR,aAG1B4D,EAAeP,EAAI7C,GAVS,CAW9B,EA6LA,OA1JkB,SAAUsD,EAAUC,GAKpC,IACIC,EAAU7C,EAAU8C,EAASC,EAAS1D,EADtC2D,EAAa,CAUjBA,MAAmB,WAEjBH,EAAWhC,SAASoC,iBAAiBN,GAGrC3C,EAAW,GAGXkD,MAAMC,UAAUC,QAAQC,KAAKR,GAAU,SAAUjB,GAE/C,IAAIxB,EAAUS,SAASyC,eACrBC,mBAAmB3B,EAAK4B,KAAKC,OAAO,KAEjCrD,GAGLJ,EAAS0D,KAAK,CACZ1B,IAAKJ,EACLxB,QAASA,GAEb,IAGAL,EAAaC,EACf,EAKAgD,OAAoB,WAElB,IAAIW,EAASlC,EAAUzB,EAAUX,GAG5BsE,EASDb,GAAWa,EAAOvD,UAAY0C,EAAQ1C,UAG1CkC,EAAWQ,EAASzD,GAzFT,SAAUkD,EAAOlD,GAE9B,GAAKkD,EAAL,CAGA,IAAIL,EAAKK,EAAMP,IAAIG,QAAQ,MACtBD,IAGLA,EAAGE,UAAUM,IAAIrD,EAASX,UAC1B6D,EAAMnC,QAAQgC,UAAUM,IAAIrD,EAASV,cAGrC8D,EAAeP,EAAI7C,GAGnBJ,EAAU,kBAAmBiD,EAAI,CAC/BM,KAAMD,EAAMP,IACZ5B,QAASmC,EAAMnC,QACff,SAAUA,IAjBM,CAmBpB,CAqEIuE,CAASD,EAAQtE,GAGjByD,EAAUa,GAfJb,IACFR,EAAWQ,EAASzD,GACpByD,EAAU,KAchB,GAMIe,EAAgB,SAAUvE,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,sBAAsBf,EAAWgB,OACpD,EAMIC,EAAgB,SAAU3E,GAExByD,GACFxE,EAAOuF,qBAAqBf,GAI9BA,EAAUxE,EAAOwF,uBAAsB,WACrChE,EAAaC,GACbgD,EAAWgB,QACb,GACF,EAkDA,OA7CAhB,EAAWkB,QAAU,WAEfpB,GACFR,EAAWQ,EAASzD,GAItBd,EAAO4F,oBAAoB,SAAUN,GAAe,GAChDxE,EAASN,QACXR,EAAO4F,oBAAoB,SAAUF,GAAe,GAItDjE,EAAW,KACX6C,EAAW,KACXC,EAAU,KACVC,EAAU,KACV1D,EAAW,IACb,EAOEA,EA3XS,WACX,IAAI+E,EAAS,CAAC,EAOd,OANAlB,MAAMC,UAAUC,QAAQC,KAAKgB,WAAW,SAAUC,GAChD,IAAK,IAAIC,KAAOD,EAAK,CACnB,IAAKA,EAAIE,eAAeD,GAAM,OAC9BH,EAAOG,GAAOD,EAAIC,EACpB,CACF,IACOH,CACT,CAkXeK,CAAOhG,EAAUmE,GAAW,CAAC,GAGxCI,EAAW0B,QAGX1B,EAAWgB,SAGXzF,EAAOoG,iBAAiB,SAAUd,GAAe,GAC7CxE,EAASN,QACXR,EAAOoG,iBAAiB,SAAUV,GAAe,GAS9CjB,CACT,CAOF,CArcW4B,CAAQvG,EAChB,UAFM,SAEN,uBCXDwG,EAA2B,CAAC,EAGhC,SAASC,EAAoBC,GAE5B,IAAIC,EAAeH,EAAyBE,GAC5C,QAAqBE,IAAjBD,EACH,OAAOA,EAAaE,QAGrB,IAAIC,EAASN,EAAyBE,GAAY,CAGjDG,QAAS,CAAC,GAOX,OAHAE,EAAoBL,GAAU1B,KAAK8B,EAAOD,QAASC,EAAQA,EAAOD,QAASJ,GAGpEK,EAAOD,OACf,CCrBAJ,EAAoBO,EAAKF,IACxB,IAAIG,EAASH,GAAUA,EAAOI,WAC7B,IAAOJ,EAAiB,QACxB,IAAM,EAEP,OADAL,EAAoBU,EAAEF,EAAQ,CAAEG,EAAGH,IAC5BA,CAAM,ECLdR,EAAoBU,EAAI,CAACN,EAASQ,KACjC,IAAI,IAAInB,KAAOmB,EACXZ,EAAoBa,EAAED,EAAYnB,KAASO,EAAoBa,EAAET,EAASX,IAC5EqB,OAAOC,eAAeX,EAASX,EAAK,CAAEuB,YAAY,EAAMC,IAAKL,EAAWnB,IAE1E,ECNDO,EAAoBxG,EAAI,WACvB,GAA0B,iBAAf0H,WAAyB,OAAOA,WAC3C,IACC,OAAOxH,MAAQ,IAAIyH,SAAS,cAAb,EAChB,CAAE,MAAOC,GACR,GAAsB,iBAAX3H,OAAqB,OAAOA,MACxC,CACA,CAPuB,GCAxBuG,EAAoBa,EAAI,CAACrB,EAAK6B,IAAUP,OAAOzC,UAAUqB,eAAenB,KAAKiB,EAAK6B,4CCK9EC,EAAY,KACZC,EAAS,KACTC,EAAgBzF,SAASC,gBAAgByF,UAC7C,MAAMC,EAAmB,GA8EzB,SAASC,IACP,MAAMC,EAAeC,aAAaC,QAAQ,UAAY,OAZxD,IAAkBC,EACH,WADGA,EAaItI,OAAOuI,WAAW,gCAAgCC,QAI/C,SAAjBL,EACO,QACgB,SAAhBA,EACA,OAEA,OAIU,SAAjBA,EACO,OACgB,QAAhBA,EACA,QAEA,SA9BoB,SAATG,GAA4B,SAATA,IACzCG,QAAQC,MAAM,2BAA2BJ,yBACzCA,EAAO,QAGThG,SAASS,KAAK4F,QAAQC,MAAQN,EAC9BF,aAAaS,QAAQ,QAASP,GAC9BG,QAAQK,IAAI,cAAcR,UA0B5B,CAkDA,SAASnC,KART,WAEE,MAAM4C,EAAUzG,SAAS0G,uBAAuB,gBAChDrE,MAAMsE,KAAKF,GAASlE,SAASqE,IAC3BA,EAAI9C,iBAAiB,QAAS8B,EAAe,GAEjD,CAGEiB,GA9CF,WAEE,IAAIC,EAA6B,EAC7BC,GAAU,EAEdrJ,OAAOoG,iBAAiB,UAAU,SAAUuB,GAC1CyB,EAA6BpJ,OAAOsJ,QAE/BD,IACHrJ,OAAOwF,uBAAsB,WAzDnC,IAAuB+D,GAxDvB,SAAgCA,GAC9B,MAAMC,EAAY7G,KAAK8G,MAAM3B,EAAO7F,wBAAwBQ,KAE5DgG,QAAQK,IAAI,cAAcU,KACT,GAAbA,GAAkBD,GAAaC,EACjC1B,EAAOjE,UAAUM,IAAI,YAErB2D,EAAOjE,UAAUC,OAAO,WAE5B,EAgDE4F,CADqBH,EA0DDH,GAvGtB,SAAmCG,GAC7BA,EAAYtB,EACd3F,SAASC,gBAAgBsB,UAAUC,OAAO,oBAEtCyF,EAAYxB,EACdzF,SAASC,gBAAgBsB,UAAUM,IAAI,oBAC9BoF,EAAYxB,GACrBzF,SAASC,gBAAgBsB,UAAUC,OAAO,oBAG9CiE,EAAgBwB,CAClB,CAoCEI,CAA0BJ,GAlC5B,SAA6BA,GACT,OAAd1B,IAKa,GAAb0B,EACF1B,EAAU+B,SAAS,EAAG,GAGtBjH,KAAKC,KAAK2G,IACV5G,KAAK8G,MAAMnH,SAASC,gBAAgBS,aAAehD,OAAOqC,aAE1DwF,EAAU+B,SAAS,EAAG/B,EAAU7E,cAGhBV,SAASuH,cAAc,mBAc3C,CAKEC,CAAoBP,GAwDdF,GAAU,CACZ,IAEAA,GAAU,EAEd,IACArJ,OAAO+J,QACT,CA6BEC,GA1BkB,OAAdnC,GAKJ,IAAI,IAAJ,CAAY,cAAe,CACzBrH,QAAQ,EACRyJ,WAAW,EACX9J,SAAU,iBACVI,OAAQ,KACN,IAAI2J,EAAMhI,WAAWiI,iBAAiB7H,SAASC,iBAAiB6H,UAChE,OAAOtC,EAAO7F,wBAAwBoI,OAAS,IAAMH,EAAM,CAAC,GAiBlE,CAcA5H,SAAS8D,iBAAiB,oBAT1B,WACE9D,SAASS,KAAKW,WAAWG,UAAUC,OAAO,SAE1CgE,EAASxF,SAASuH,cAAc,UAChChC,EAAYvF,SAASuH,cAAc,eAEnC1D,GACF","sources":["webpack:///./src/furo/assets/scripts/gumshoe-patched.js","webpack:///webpack/bootstrap","webpack:///webpack/runtime/compat get default export","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/global","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./src/furo/assets/scripts/furo.js"],"sourcesContent":["/*!\n * gumshoejs v5.1.2 (patched by @pradyunsg)\n * A simple, framework-agnostic scrollspy script.\n * (c) 2019 Chris Ferdinandi\n * MIT License\n * http://github.com/cferdinandi/gumshoe\n */\n\n(function (root, factory) {\n if (typeof define === \"function\" && define.amd) {\n define([], function () {\n return factory(root);\n });\n } else if (typeof exports === \"object\") {\n module.exports = factory(root);\n } else {\n root.Gumshoe = factory(root);\n }\n})(\n typeof global !== \"undefined\"\n ? global\n : typeof window !== \"undefined\"\n ? window\n : this,\n function (window) {\n \"use strict\";\n\n //\n // Defaults\n //\n\n var defaults = {\n // Active classes\n navClass: \"active\",\n contentClass: \"active\",\n\n // Nested navigation\n nested: false,\n nestedClass: \"active\",\n\n // Offset & reflow\n offset: 0,\n reflow: false,\n\n // Event support\n events: true,\n };\n\n //\n // Methods\n //\n\n /**\n * Merge two or more objects together.\n * @param {Object} objects The objects to merge together\n * @returns {Object} Merged values of defaults and options\n */\n var extend = function () {\n var merged = {};\n Array.prototype.forEach.call(arguments, function (obj) {\n for (var key in obj) {\n if (!obj.hasOwnProperty(key)) return;\n merged[key] = obj[key];\n }\n });\n return merged;\n };\n\n /**\n * Emit a custom event\n * @param {String} type The event type\n * @param {Node} elem The element to attach the event to\n * @param {Object} detail Any details to pass along with the event\n */\n var emitEvent = function (type, elem, detail) {\n // Make sure events are enabled\n if (!detail.settings.events) return;\n\n // Create a new event\n var event = new CustomEvent(type, {\n bubbles: true,\n cancelable: true,\n detail: detail,\n });\n\n // Dispatch the event\n elem.dispatchEvent(event);\n };\n\n /**\n * Get an element's distance from the top of the Document.\n * @param {Node} elem The element\n * @return {Number} Distance from the top in pixels\n */\n var getOffsetTop = function (elem) {\n var location = 0;\n if (elem.offsetParent) {\n while (elem) {\n location += elem.offsetTop;\n elem = elem.offsetParent;\n }\n }\n return location >= 0 ? location : 0;\n };\n\n /**\n * Sort content from first to last in the DOM\n * @param {Array} contents The content areas\n */\n var sortContents = function (contents) {\n if (contents) {\n contents.sort(function (item1, item2) {\n var offset1 = getOffsetTop(item1.content);\n var offset2 = getOffsetTop(item2.content);\n if (offset1 < offset2) return -1;\n return 1;\n });\n }\n };\n\n /**\n * Get the offset to use for calculating position\n * @param {Object} settings The settings for this instantiation\n * @return {Float} The number of pixels to offset the calculations\n */\n var getOffset = function (settings) {\n // if the offset is a function run it\n if (typeof settings.offset === \"function\") {\n return parseFloat(settings.offset());\n }\n\n // Otherwise, return it as-is\n return parseFloat(settings.offset);\n };\n\n /**\n * Get the document element's height\n * @private\n * @returns {Number}\n */\n var getDocumentHeight = function () {\n return Math.max(\n document.body.scrollHeight,\n document.documentElement.scrollHeight,\n document.body.offsetHeight,\n document.documentElement.offsetHeight,\n document.body.clientHeight,\n document.documentElement.clientHeight,\n );\n };\n\n /**\n * Determine if an element is in view\n * @param {Node} elem The element\n * @param {Object} settings The settings for this instantiation\n * @param {Boolean} bottom If true, check if element is above bottom of viewport instead\n * @return {Boolean} Returns true if element is in the viewport\n */\n var isInView = function (elem, settings, bottom) {\n var bounds = elem.getBoundingClientRect();\n var offset = getOffset(settings);\n if (bottom) {\n return (\n parseInt(bounds.bottom, 10) <\n (window.innerHeight || document.documentElement.clientHeight)\n );\n }\n return parseInt(bounds.top, 10) <= offset;\n };\n\n /**\n * Check if at the bottom of the viewport\n * @return {Boolean} If true, page is at the bottom of the viewport\n */\n var isAtBottom = function () {\n if (\n Math.ceil(window.innerHeight + window.pageYOffset) >=\n getDocumentHeight()\n )\n return true;\n return false;\n };\n\n /**\n * Check if the last item should be used (even if not at the top of the page)\n * @param {Object} item The last item\n * @param {Object} settings The settings for this instantiation\n * @return {Boolean} If true, use the last item\n */\n var useLastItem = function (item, settings) {\n if (isAtBottom() && isInView(item.content, settings, true)) return true;\n return false;\n };\n\n /**\n * Get the active content\n * @param {Array} contents The content areas\n * @param {Object} settings The settings for this instantiation\n * @return {Object} The content area and matching navigation link\n */\n var getActive = function (contents, settings) {\n var last = contents[contents.length - 1];\n if (useLastItem(last, settings)) return last;\n for (var i = contents.length - 1; i >= 0; i--) {\n if (isInView(contents[i].content, settings)) return contents[i];\n }\n };\n\n /**\n * Deactivate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var deactivateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested || !nav.parentNode) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Remove the active class\n li.classList.remove(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n deactivateNested(li, settings);\n };\n\n /**\n * Deactivate a nav and content area\n * @param {Object} items The nav item and content to deactivate\n * @param {Object} settings The settings for this instantiation\n */\n var deactivate = function (items, settings) {\n // Make sure there are items to deactivate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Remove the active class from the nav and content\n li.classList.remove(settings.navClass);\n items.content.classList.remove(settings.contentClass);\n\n // Deactivate any parent navs in a nested navigation\n deactivateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeDeactivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Activate parent navs in a nested navigation\n * @param {Node} nav The starting navigation element\n * @param {Object} settings The settings for this instantiation\n */\n var activateNested = function (nav, settings) {\n // If nesting isn't activated, bail\n if (!settings.nested) return;\n\n // Get the parent navigation\n var li = nav.parentNode.closest(\"li\");\n if (!li) return;\n\n // Add the active class\n li.classList.add(settings.nestedClass);\n\n // Apply recursively to any parent navigation elements\n activateNested(li, settings);\n };\n\n /**\n * Activate a nav and content area\n * @param {Object} items The nav item and content to activate\n * @param {Object} settings The settings for this instantiation\n */\n var activate = function (items, settings) {\n // Make sure there are items to activate\n if (!items) return;\n\n // Get the parent list item\n var li = items.nav.closest(\"li\");\n if (!li) return;\n\n // Add the active class to the nav and content\n li.classList.add(settings.navClass);\n items.content.classList.add(settings.contentClass);\n\n // Activate any parent navs in a nested navigation\n activateNested(li, settings);\n\n // Emit a custom event\n emitEvent(\"gumshoeActivate\", li, {\n link: items.nav,\n content: items.content,\n settings: settings,\n });\n };\n\n /**\n * Create the Constructor object\n * @param {String} selector The selector to use for navigation items\n * @param {Object} options User options and settings\n */\n var Constructor = function (selector, options) {\n //\n // Variables\n //\n\n var publicAPIs = {};\n var navItems, contents, current, timeout, settings;\n\n //\n // Methods\n //\n\n /**\n * Set variables from DOM elements\n */\n publicAPIs.setup = function () {\n // Get all nav items\n navItems = document.querySelectorAll(selector);\n\n // Create contents array\n contents = [];\n\n // Loop through each item, get it's matching content, and push to the array\n Array.prototype.forEach.call(navItems, function (item) {\n // Get the content for the nav item\n var content = document.getElementById(\n decodeURIComponent(item.hash.substr(1)),\n );\n if (!content) return;\n\n // Push to the contents array\n contents.push({\n nav: item,\n content: content,\n });\n });\n\n // Sort contents by the order they appear in the DOM\n sortContents(contents);\n };\n\n /**\n * Detect which content is currently active\n */\n publicAPIs.detect = function () {\n // Get the active content\n var active = getActive(contents, settings);\n\n // if there's no active content, deactivate and bail\n if (!active) {\n if (current) {\n deactivate(current, settings);\n current = null;\n }\n return;\n }\n\n // If the active content is the one currently active, do nothing\n if (current && active.content === current.content) return;\n\n // Deactivate the current content and activate the new content\n deactivate(current, settings);\n activate(active, settings);\n\n // Update the currently active content\n current = active;\n };\n\n /**\n * Detect the active content on scroll\n * Debounced for performance\n */\n var scrollHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(publicAPIs.detect);\n };\n\n /**\n * Update content sorting on resize\n * Debounced for performance\n */\n var resizeHandler = function (event) {\n // If there's a timer, cancel it\n if (timeout) {\n window.cancelAnimationFrame(timeout);\n }\n\n // Setup debounce callback\n timeout = window.requestAnimationFrame(function () {\n sortContents(contents);\n publicAPIs.detect();\n });\n };\n\n /**\n * Destroy the current instantiation\n */\n publicAPIs.destroy = function () {\n // Undo DOM changes\n if (current) {\n deactivate(current, settings);\n }\n\n // Remove event listeners\n window.removeEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.removeEventListener(\"resize\", resizeHandler, false);\n }\n\n // Reset variables\n contents = null;\n navItems = null;\n current = null;\n timeout = null;\n settings = null;\n };\n\n /**\n * Initialize the current instantiation\n */\n var init = function () {\n // Merge user options into defaults\n settings = extend(defaults, options || {});\n\n // Setup variables based on the current DOM\n publicAPIs.setup();\n\n // Find the currently active content\n publicAPIs.detect();\n\n // Setup event listeners\n window.addEventListener(\"scroll\", scrollHandler, false);\n if (settings.reflow) {\n window.addEventListener(\"resize\", resizeHandler, false);\n }\n };\n\n //\n // Initialize and return the public APIs\n //\n\n init();\n return publicAPIs;\n };\n\n //\n // Return the Constructor\n //\n\n return Constructor;\n },\n);\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// getDefaultExport function for compatibility with non-harmony modules\n__webpack_require__.n = (module) => {\n\tvar getter = module && module.__esModule ?\n\t\t() => (module['default']) :\n\t\t() => (module);\n\t__webpack_require__.d(getter, { a: getter });\n\treturn getter;\n};","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.g = (function() {\n\tif (typeof globalThis === 'object') return globalThis;\n\ttry {\n\t\treturn this || new Function('return this')();\n\t} catch (e) {\n\t\tif (typeof window === 'object') return window;\n\t}\n})();","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","import Gumshoe from \"./gumshoe-patched.js\";\n\n////////////////////////////////////////////////////////////////////////////////\n// Scroll Handling\n////////////////////////////////////////////////////////////////////////////////\nvar tocScroll = null;\nvar header = null;\nvar lastScrollTop = document.documentElement.scrollTop;\nconst GO_TO_TOP_OFFSET = 64;\n\nfunction scrollHandlerForHeader(positionY) {\n const headerTop = Math.floor(header.getBoundingClientRect().top);\n\n console.log(`headerTop: ${headerTop}`);\n if (headerTop == 0 && positionY != headerTop) {\n header.classList.add(\"scrolled\");\n } else {\n header.classList.remove(\"scrolled\");\n }\n}\n\nfunction scrollHandlerForBackToTop(positionY) {\n if (positionY < GO_TO_TOP_OFFSET) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n } else {\n if (positionY < lastScrollTop) {\n document.documentElement.classList.add(\"show-back-to-top\");\n } else if (positionY > lastScrollTop) {\n document.documentElement.classList.remove(\"show-back-to-top\");\n }\n }\n lastScrollTop = positionY;\n}\n\nfunction scrollHandlerForTOC(positionY) {\n if (tocScroll === null) {\n return;\n }\n\n // top of page.\n if (positionY == 0) {\n tocScroll.scrollTo(0, 0);\n } else if (\n // bottom of page.\n Math.ceil(positionY) >=\n Math.floor(document.documentElement.scrollHeight - window.innerHeight)\n ) {\n tocScroll.scrollTo(0, tocScroll.scrollHeight);\n } else {\n // somewhere in the middle.\n const current = document.querySelector(\".scroll-current\");\n if (current == null) {\n return;\n }\n\n // https://github.com/pypa/pip/issues/9159 This breaks scroll behaviours.\n // // scroll the currently \"active\" heading in toc, into view.\n // const rect = current.getBoundingClientRect();\n // if (0 > rect.top) {\n // current.scrollIntoView(true); // the argument is \"alignTop\"\n // } else if (rect.bottom > window.innerHeight) {\n // current.scrollIntoView(false);\n // }\n }\n}\n\nfunction scrollHandler(positionY) {\n scrollHandlerForHeader(positionY);\n scrollHandlerForBackToTop(positionY);\n scrollHandlerForTOC(positionY);\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Theme Toggle\n////////////////////////////////////////////////////////////////////////////////\nfunction setTheme(mode) {\n if (mode !== \"light\" && mode !== \"dark\" && mode !== \"auto\") {\n console.error(`Got invalid theme mode: ${mode}. Resetting to auto.`);\n mode = \"auto\";\n }\n\n document.body.dataset.theme = mode;\n localStorage.setItem(\"theme\", mode);\n console.log(`Changed to ${mode} mode.`);\n}\n\nfunction cycleThemeOnce() {\n const currentTheme = localStorage.getItem(\"theme\") || \"auto\";\n const prefersDark = window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n\n if (prefersDark) {\n // Auto (dark) -> Light -> Dark\n if (currentTheme === \"auto\") {\n setTheme(\"light\");\n } else if (currentTheme == \"light\") {\n setTheme(\"dark\");\n } else {\n setTheme(\"auto\");\n }\n } else {\n // Auto (light) -> Dark -> Light\n if (currentTheme === \"auto\") {\n setTheme(\"dark\");\n } else if (currentTheme == \"dark\") {\n setTheme(\"light\");\n } else {\n setTheme(\"auto\");\n }\n }\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Setup\n////////////////////////////////////////////////////////////////////////////////\nfunction setupScrollHandler() {\n // Taken from https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event\n let last_known_scroll_position = 0;\n let ticking = false;\n\n window.addEventListener(\"scroll\", function (e) {\n last_known_scroll_position = window.scrollY;\n\n if (!ticking) {\n window.requestAnimationFrame(function () {\n scrollHandler(last_known_scroll_position);\n ticking = false;\n });\n\n ticking = true;\n }\n });\n window.scroll();\n}\n\nfunction setupScrollSpy() {\n if (tocScroll === null) {\n return;\n }\n\n // Scrollspy -- highlight table on contents, based on scroll\n new Gumshoe(\".toc-tree a\", {\n reflow: true,\n recursive: true,\n navClass: \"scroll-current\",\n offset: () => {\n let rem = parseFloat(getComputedStyle(document.documentElement).fontSize);\n return header.getBoundingClientRect().height + 2.5 * rem + 1;\n },\n });\n}\n\nfunction setupTheme() {\n // Attach event handlers for toggling themes\n const buttons = document.getElementsByClassName(\"theme-toggle\");\n Array.from(buttons).forEach((btn) => {\n btn.addEventListener(\"click\", cycleThemeOnce);\n });\n}\n\nfunction setup() {\n setupTheme();\n setupScrollHandler();\n setupScrollSpy();\n}\n\n////////////////////////////////////////////////////////////////////////////////\n// Main entrypoint\n////////////////////////////////////////////////////////////////////////////////\nfunction main() {\n document.body.parentNode.classList.remove(\"no-js\");\n\n header = document.querySelector(\"header\");\n tocScroll = document.querySelector(\".toc-scroll\");\n\n setup();\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", main);\n"],"names":["root","g","window","this","defaults","navClass","contentClass","nested","nestedClass","offset","reflow","events","emitEvent","type","elem","detail","settings","event","CustomEvent","bubbles","cancelable","dispatchEvent","getOffsetTop","location","offsetParent","offsetTop","sortContents","contents","sort","item1","item2","content","isInView","bottom","bounds","getBoundingClientRect","parseFloat","getOffset","parseInt","innerHeight","document","documentElement","clientHeight","top","isAtBottom","Math","ceil","pageYOffset","max","body","scrollHeight","offsetHeight","getActive","last","length","item","useLastItem","i","deactivateNested","nav","parentNode","li","closest","classList","remove","deactivate","items","link","activateNested","add","selector","options","navItems","current","timeout","publicAPIs","querySelectorAll","Array","prototype","forEach","call","getElementById","decodeURIComponent","hash","substr","push","active","activate","scrollHandler","cancelAnimationFrame","requestAnimationFrame","detect","resizeHandler","destroy","removeEventListener","merged","arguments","obj","key","hasOwnProperty","extend","setup","addEventListener","factory","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","undefined","exports","module","__webpack_modules__","n","getter","__esModule","d","a","definition","o","Object","defineProperty","enumerable","get","globalThis","Function","e","prop","tocScroll","header","lastScrollTop","scrollTop","GO_TO_TOP_OFFSET","cycleThemeOnce","currentTheme","localStorage","getItem","mode","matchMedia","matches","console","error","dataset","theme","setItem","log","buttons","getElementsByClassName","from","btn","setupTheme","last_known_scroll_position","ticking","scrollY","positionY","headerTop","floor","scrollHandlerForHeader","scrollHandlerForBackToTop","scrollTo","querySelector","scrollHandlerForTOC","scroll","setupScrollHandler","recursive","rem","getComputedStyle","fontSize","height"],"sourceRoot":""} \ No newline at end of file diff --git a/docs/html/_static/searchtools.js b/docs/html/_static/searchtools.js index 97d56a74..2c774d17 100644 --- a/docs/html/_static/searchtools.js +++ b/docs/html/_static/searchtools.js @@ -1,12 +1,5 @@ /* - * searchtools.js - * ~~~~~~~~~~~~~~~~ - * * Sphinx JavaScript utilities for the full-text search. - * - * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * */ "use strict"; @@ -20,7 +13,7 @@ if (typeof Scorer === "undefined") { // and returns the new score. /* score: result => { - const [docname, title, anchor, descr, score, filename] = result + const [docname, title, anchor, descr, score, filename, kind] = result return score }, */ @@ -47,6 +40,14 @@ if (typeof Scorer === "undefined") { }; } +// Global search result kind enum, used by themes to style search results. +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + const _removeChildren = (element) => { while (element && element.lastChild) element.removeChild(element.lastChild); }; @@ -57,16 +58,20 @@ const _removeChildren = (element) => { const _escapeRegExp = (string) => string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string -const _displayItem = (item, searchTerms) => { +const _displayItem = (item, searchTerms, highlightTerms) => { const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; - const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; - const [docName, title, anchor, descr, score, _filename] = item; + const [docName, title, anchor, descr, score, _filename, kind] = item; let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); let requestUrl; let linkUrl; if (docBuilder === "dirhtml") { @@ -75,28 +80,35 @@ const _displayItem = (item, searchTerms) => { if (dirname.match(/\/index\/$/)) dirname = dirname.substring(0, dirname.length - 6); else if (dirname === "index/") dirname = ""; - requestUrl = docUrlRoot + dirname; + requestUrl = contentRoot + dirname; linkUrl = requestUrl; } else { // normal html builders - requestUrl = docUrlRoot + docName + docFileSuffix; + requestUrl = contentRoot + docName + docFileSuffix; linkUrl = docName + docLinkSuffix; } let linkEl = listItem.appendChild(document.createElement("a")); linkEl.href = linkUrl + anchor; linkEl.dataset.score = score; linkEl.innerHTML = title; - if (descr) + if (descr) { listItem.appendChild(document.createElement("span")).innerHTML = " (" + descr + ")"; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); + } else if (showSearchSummary) fetch(requestUrl) .then((responseData) => responseData.text()) .then((data) => { if (data) listItem.appendChild( - Search.makeSearchSummary(data, searchTerms) + Search.makeSearchSummary(data, searchTerms, anchor) ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js + highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted")); }); Search.output.appendChild(listItem); }; @@ -108,27 +120,46 @@ const _finishSearch = (resultCount) => { "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." ); else - Search.status.innerText = _( - `Search finished, found ${resultCount} page(s) matching the search query.` - ); + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace('${resultCount}', resultCount); }; const _displayNextItem = ( results, resultCount, - searchTerms + searchTerms, + highlightTerms, ) => { // results left, load the summary and display it // this is intended to be dynamic (don't sub resultsCount) if (results.length) { - _displayItem(results.pop(), searchTerms); + _displayItem(results.pop(), searchTerms, highlightTerms); setTimeout( - () => _displayNextItem(results, resultCount, searchTerms), + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), 5 ); } // search finished, update title and status message else _finishSearch(resultCount); }; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; /** * Default splitQuery function. Can be overridden in ``sphinx.search`` with a @@ -152,13 +183,26 @@ const Search = { _queued_query: null, _pulse_status: -1, - htmlToText: (htmlString) => { + htmlToText: (htmlString, anchor) => { const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); - htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.` + ); + } + + // if anchor not specified or not found, fall back to main content const docContent = htmlElement.querySelector('[role="main"]'); - if (docContent !== undefined) return docContent.textContent; + if (docContent) return docContent.textContent; + console.warn( - "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template." ); return ""; }, @@ -211,6 +255,7 @@ const Search = { searchSummary.classList.add("search-summary"); searchSummary.innerText = ""; const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); searchList.classList.add("search"); const out = document.getElementById("search-results"); @@ -231,16 +276,7 @@ const Search = { else Search.deferQuery(query); }, - /** - * execute search (requires search index to be loaded) - */ - query: (query) => { - const filenames = Search._index.filenames; - const docNames = Search._index.docnames; - const titles = Search._index.titles; - const allTitles = Search._index.alltitles; - const indexEntries = Search._index.indexentries; - + _parseQuery: (query) => { // stem the search terms and add them to the correct list const stemmer = new Stemmer(); const searchTerms = new Set(); @@ -276,22 +312,40 @@ const Search = { // console.info("required: ", [...searchTerms]); // console.info("excluded: ", [...excludedTerms]); - // array of [docname, title, anchor, descr, score, filename] - let results = []; + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + _removeChildren(document.getElementById("search-progress")); - const queryLower = query.toLowerCase(); + const queryLower = query.toLowerCase().trim(); for (const [title, foundTitles] of Object.entries(allTitles)) { - if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) { for (const [file, id] of foundTitles) { - let score = Math.round(100 * queryLower.length / title.length) - results.push([ + const score = Math.round(Scorer.title * queryLower.length / title.length); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ docNames[file], titles[file] !== title ? `${titles[file]} > ${title}` : title, id !== null ? "#" + id : "", null, - score, + score + boost, filenames[file], + SearchResultKind.title, ]); } } @@ -300,46 +354,48 @@ const Search = { // search for explicit entries in index directives for (const [entry, foundEntries] of Object.entries(indexEntries)) { if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { - for (const [file, id] of foundEntries) { - let score = Math.round(100 * queryLower.length / entry.length) - results.push([ + for (const [file, id, isMain] of foundEntries) { + const score = Math.round(100 * queryLower.length / entry.length); + const result = [ docNames[file], titles[file], id ? "#" + id : "", null, score, filenames[file], - ]); + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } } } } // lookup as object objectTerms.forEach((term) => - results.push(...Search.performObjectSearch(term, objectTerms)) + normalResults.push(...Search.performObjectSearch(term, objectTerms)) ); // lookup as search terms in fulltext - results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms)); // let the scorer override scores with a custom scoring function - if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); - - // now sort the results by score (in opposite order of appearance, since the - // display function below uses pop() to retrieve items) and then - // alphabetically - results.sort((a, b) => { - const leftScore = a[4]; - const rightScore = b[4]; - if (leftScore === rightScore) { - // same score: sort alphabetically - const leftTitle = a[1].toLowerCase(); - const rightTitle = b[1].toLowerCase(); - if (leftTitle === rightTitle) return 0; - return leftTitle > rightTitle ? -1 : 1; // inverted is intentional - } - return leftScore > rightScore ? 1 : -1; - }); + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; // remove duplicate search results // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept @@ -353,14 +409,19 @@ const Search = { return acc; }, []); - results = results.reverse(); + return results.reverse(); + }, + + query: (query) => { + const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query); + const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms); // for debugging //Search.lastresults = results.slice(); // a copy // console.info("search results:", Search.lastresults); // print the results - _displayNextItem(results, results.length, searchTerms); + _displayNextItem(results, results.length, searchTerms, highlightTerms); }, /** @@ -424,6 +485,7 @@ const Search = { descr, score, filenames[match[0]], + SearchResultKind.object, ]); }; Object.keys(objects).forEach((prefix) => @@ -458,14 +520,18 @@ const Search = { // add support for partial matches if (word.length > 2) { const escapedWord = _escapeRegExp(word); - Object.keys(terms).forEach((term) => { - if (term.match(escapedWord) && !terms[word]) - arr.push({ files: terms[term], score: Scorer.partialTerm }); - }); - Object.keys(titleTerms).forEach((term) => { - if (term.match(escapedWord) && !titleTerms[word]) - arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); - }); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } } // no match but word was a required one @@ -488,9 +554,8 @@ const Search = { // create the mapping files.forEach((file) => { - if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) - fileMap.get(file).push(word); - else fileMap.set(file, [word]); + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word); }); }); @@ -531,6 +596,7 @@ const Search = { null, score, filenames[file], + SearchResultKind.text, ]); } return results; @@ -541,8 +607,8 @@ const Search = { * search summary for a given text. keywords is a list * of stemmed words. */ - makeSearchSummary: (htmlText, keywords) => { - const text = Search.htmlToText(htmlText); + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); if (text === "") return null; const textLower = text.toLowerCase(); diff --git a/docs/html/_static/skeleton.css b/docs/html/_static/skeleton.css new file mode 100644 index 00000000..467c878c --- /dev/null +++ b/docs/html/_static/skeleton.css @@ -0,0 +1,296 @@ +/* Some sane resets. */ +html { + height: 100%; +} + +body { + margin: 0; + min-height: 100%; +} + +/* All the flexbox magic! */ +body, +.sb-announcement, +.sb-content, +.sb-main, +.sb-container, +.sb-container__inner, +.sb-article-container, +.sb-footer-content, +.sb-header, +.sb-header-secondary, +.sb-footer { + display: flex; +} + +/* These order things vertically */ +body, +.sb-main, +.sb-article-container { + flex-direction: column; +} + +/* Put elements in the center */ +.sb-header, +.sb-header-secondary, +.sb-container, +.sb-content, +.sb-footer, +.sb-footer-content { + justify-content: center; +} +/* Put elements at the ends */ +.sb-article-container { + justify-content: space-between; +} + +/* These elements grow. */ +.sb-main, +.sb-content, +.sb-container, +article { + flex-grow: 1; +} + +/* Because padding making this wider is not fun */ +article { + box-sizing: border-box; +} + +/* The announcements element should never be wider than the page. */ +.sb-announcement { + max-width: 100%; +} + +.sb-sidebar-primary, +.sb-sidebar-secondary { + flex-shrink: 0; + width: 17rem; +} + +.sb-announcement__inner { + justify-content: center; + + box-sizing: border-box; + height: 3rem; + + overflow-x: auto; + white-space: nowrap; +} + +/* Sidebars, with checkbox-based toggle */ +.sb-sidebar-primary, +.sb-sidebar-secondary { + position: fixed; + height: 100%; + top: 0; +} + +.sb-sidebar-primary { + left: -17rem; + transition: left 250ms ease-in-out; +} +.sb-sidebar-secondary { + right: -17rem; + transition: right 250ms ease-in-out; +} + +.sb-sidebar-toggle { + display: none; +} +.sb-sidebar-overlay { + position: fixed; + top: 0; + width: 0; + height: 0; + + transition: width 0ms ease 250ms, height 0ms ease 250ms, opacity 250ms ease; + + opacity: 0; + background-color: rgba(0, 0, 0, 0.54); +} + +#sb-sidebar-toggle--primary:checked + ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--primary"], +#sb-sidebar-toggle--secondary:checked + ~ .sb-sidebar-overlay[for="sb-sidebar-toggle--secondary"] { + width: 100%; + height: 100%; + opacity: 1; + transition: width 0ms ease, height 0ms ease, opacity 250ms ease; +} + +#sb-sidebar-toggle--primary:checked ~ .sb-container .sb-sidebar-primary { + left: 0; +} +#sb-sidebar-toggle--secondary:checked ~ .sb-container .sb-sidebar-secondary { + right: 0; +} + +/* Full-width mode */ +.drop-secondary-sidebar-for-full-width-content + .hide-when-secondary-sidebar-shown { + display: none !important; +} +.drop-secondary-sidebar-for-full-width-content .sb-sidebar-secondary { + display: none !important; +} + +/* Mobile views */ +.sb-page-width { + width: 100%; +} + +.sb-article-container, +.sb-footer-content__inner, +.drop-secondary-sidebar-for-full-width-content .sb-article, +.drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 100vw; +} + +.sb-article, +.match-content-width { + padding: 0 1rem; + box-sizing: border-box; +} + +@media (min-width: 32rem) { + .sb-article, + .match-content-width { + padding: 0 2rem; + } +} + +/* Tablet views */ +@media (min-width: 42rem) { + .sb-article-container { + width: auto; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 42rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} +@media (min-width: 46rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 46rem; + } + .sb-article, + .match-content-width { + width: 46rem; + } +} +@media (min-width: 50rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 50rem; + } + .sb-article, + .match-content-width { + width: 50rem; + } +} + +/* Tablet views */ +@media (min-width: 59rem) { + .sb-sidebar-secondary { + position: static; + } + .hide-when-secondary-sidebar-shown { + display: none !important; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 59rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} +@media (min-width: 63rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 63rem; + } + .sb-article, + .match-content-width { + width: 46rem; + } +} +@media (min-width: 67rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } + .sb-article, + .match-content-width { + width: 50rem; + } +} + +/* Desktop views */ +@media (min-width: 76rem) { + .sb-sidebar-primary { + position: static; + } + .hide-when-primary-sidebar-shown { + display: none !important; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 59rem; + } + .sb-article, + .match-content-width { + width: 42rem; + } +} + +/* Full desktop views */ +@media (min-width: 80rem) { + .sb-article, + .match-content-width { + width: 46rem; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 63rem; + } +} + +@media (min-width: 84rem) { + .sb-article, + .match-content-width { + width: 50rem; + } + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } +} + +@media (min-width: 88rem) { + .sb-footer-content__inner, + .drop-secondary-sidebar-for-full-width-content .sb-article, + .drop-secondary-sidebar-for-full-width-content .match-content-width { + width: 67rem; + } + .sb-page-width { + width: 88rem; + } +} diff --git a/docs/html/_static/sphinx_highlight.js b/docs/html/_static/sphinx_highlight.js index aae669d7..8a96c69a 100644 --- a/docs/html/_static/sphinx_highlight.js +++ b/docs/html/_static/sphinx_highlight.js @@ -29,14 +29,19 @@ const _highlight = (node, addItems, text, className) => { } span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); parent.insertBefore( span, parent.insertBefore( - document.createTextNode(val.substr(pos + text.length)), + rest, node.nextSibling ) ); node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); if (isInSVG) { const rect = document.createElementNS( @@ -140,5 +145,10 @@ const SphinxHighlight = { }, }; -_ready(SphinxHighlight.highlightSearchWords); -_ready(SphinxHighlight.initEscapeListener); +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/docs/html/_static/styles/furo-extensions.css b/docs/html/_static/styles/furo-extensions.css new file mode 100644 index 00000000..82295876 --- /dev/null +++ b/docs/html/_static/styles/furo-extensions.css @@ -0,0 +1,2 @@ +#furo-sidebar-ad-placement{padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)}#furo-sidebar-ad-placement .ethical-sidebar{background:var(--color-background-secondary);border:none;box-shadow:none}#furo-sidebar-ad-placement .ethical-sidebar:hover{background:var(--color-background-hover)}#furo-sidebar-ad-placement .ethical-sidebar a{color:var(--color-foreground-primary)}#furo-sidebar-ad-placement .ethical-callout a{color:var(--color-foreground-secondary)!important}#furo-readthedocs-versions{background:transparent;display:block;position:static;width:100%}#furo-readthedocs-versions .rst-versions{background:#1a1c1e}#furo-readthedocs-versions .rst-current-version{background:var(--color-sidebar-item-background);cursor:unset}#furo-readthedocs-versions .rst-current-version:hover{background:var(--color-sidebar-item-background)}#furo-readthedocs-versions .rst-current-version .fa-book{color:var(--color-foreground-primary)}#furo-readthedocs-versions>.rst-other-versions{padding:0}#furo-readthedocs-versions>.rst-other-versions small{opacity:1}#furo-readthedocs-versions .injected .rst-versions{position:unset}#furo-readthedocs-versions:focus-within,#furo-readthedocs-versions:hover{box-shadow:0 0 0 1px var(--color-sidebar-background-border)}#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:hover .rst-current-version{background:#1a1c1e;font-size:inherit;height:auto;line-height:inherit;padding:12px;text-align:right}#furo-readthedocs-versions:focus-within .rst-current-version .fa-book,#furo-readthedocs-versions:hover .rst-current-version .fa-book{color:#fff;float:left}#furo-readthedocs-versions:focus-within .fa-caret-down,#furo-readthedocs-versions:hover .fa-caret-down{display:none}#furo-readthedocs-versions:focus-within .injected,#furo-readthedocs-versions:focus-within .rst-current-version,#furo-readthedocs-versions:focus-within .rst-other-versions,#furo-readthedocs-versions:hover .injected,#furo-readthedocs-versions:hover .rst-current-version,#furo-readthedocs-versions:hover .rst-other-versions{display:block}#furo-readthedocs-versions:focus-within>.rst-current-version,#furo-readthedocs-versions:hover>.rst-current-version{display:none}.highlight:hover button.copybtn{color:var(--color-code-foreground)}.highlight button.copybtn{align-items:center;background-color:var(--color-code-background);border:none;color:var(--color-background-item);cursor:pointer;height:1.25em;right:.5rem;top:.625rem;transition:color .3s,opacity .3s;width:1.25em}.highlight button.copybtn:hover{background-color:var(--color-code-background);color:var(--color-brand-content)}.highlight button.copybtn:after{background-color:transparent;color:var(--color-code-foreground);display:none}.highlight button.copybtn.success{color:#22863a;transition:color 0ms}.highlight button.copybtn.success:after{display:block}.highlight button.copybtn svg{padding:0}body{--sd-color-primary:var(--color-brand-primary);--sd-color-primary-highlight:var(--color-brand-content);--sd-color-primary-text:var(--color-background-primary);--sd-color-shadow:rgba(0,0,0,.05);--sd-color-card-border:var(--color-card-border);--sd-color-card-border-hover:var(--color-brand-content);--sd-color-card-background:var(--color-card-background);--sd-color-card-text:var(--color-foreground-primary);--sd-color-card-header:var(--color-card-marginals-background);--sd-color-card-footer:var(--color-card-marginals-background);--sd-color-tabs-label-active:var(--color-brand-content);--sd-color-tabs-label-hover:var(--color-foreground-muted);--sd-color-tabs-label-inactive:var(--color-foreground-muted);--sd-color-tabs-underline-active:var(--color-brand-content);--sd-color-tabs-underline-hover:var(--color-foreground-border);--sd-color-tabs-underline-inactive:var(--color-background-border);--sd-color-tabs-overline:var(--color-background-border);--sd-color-tabs-underline:var(--color-background-border)}.sd-tab-content{box-shadow:0 -2px var(--sd-color-tabs-overline),0 1px var(--sd-color-tabs-underline)}.sd-card{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)}.sd-shadow-sm{box-shadow:0 .1rem .25rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-md{box-shadow:0 .3rem .75rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-shadow-lg{box-shadow:0 .6rem 1.5rem var(--sd-color-shadow),0 0 .0625rem rgba(0,0,0,.1)!important}.sd-card-hover:hover{transform:none}.sd-cards-carousel{gap:.25rem;padding:.25rem}body{--tabs--label-text:var(--color-foreground-muted);--tabs--label-text--hover:var(--color-foreground-muted);--tabs--label-text--active:var(--color-brand-content);--tabs--label-text--active--hover:var(--color-brand-content);--tabs--label-background:transparent;--tabs--label-background--hover:transparent;--tabs--label-background--active:transparent;--tabs--label-background--active--hover:transparent;--tabs--padding-x:0.25em;--tabs--margin-x:1em;--tabs--border:var(--color-background-border);--tabs--label-border:transparent;--tabs--label-border--hover:var(--color-foreground-muted);--tabs--label-border--active:var(--color-brand-content);--tabs--label-border--active--hover:var(--color-brand-content)}[role=main] .container{max-width:none;padding-left:0;padding-right:0}.shadow.docutils{border:none;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)!important}.sphinx-bs .card{background-color:var(--color-background-secondary);color:var(--color-foreground)} +/*# sourceMappingURL=furo-extensions.css.map*/ \ No newline at end of file diff --git a/docs/html/_static/styles/furo-extensions.css.map b/docs/html/_static/styles/furo-extensions.css.map new file mode 100644 index 00000000..c26eac7f --- /dev/null +++ b/docs/html/_static/styles/furo-extensions.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo-extensions.css","mappings":"AAGA,2BACE,oFACA,4CAKE,6CAHA,YACA,eAEA,CACA,kDACE,yCAEF,8CACE,sCAEJ,8CACE,kDAEJ,2BAGE,uBACA,cAHA,gBACA,UAEA,CAGA,yCACE,mBAEF,gDAEE,gDADA,YACA,CACA,sDACE,gDACF,yDACE,sCAEJ,+CACE,UACA,qDACE,UAGF,mDACE,eAEJ,yEAEE,4DAEA,mHASE,mBAPA,kBAEA,YADA,oBAGA,aADA,gBAIA,CAEA,qIAEE,WADA,UACA,CAEJ,uGACE,aAEF,iUAGE,cAEF,mHACE,aC1EJ,gCACE,mCAEF,0BAEE,mBAUA,8CACA,YAFA,mCAKA,eAZA,cAIA,YADA,YAYA,iCAdA,YAcA,CAEA,gCAEE,8CADA,gCACA,CAEF,gCAGE,6BADA,mCADA,YAEA,CAEF,kCAEE,cADA,oBACA,CACA,wCACE,cAEJ,8BACE,UCzCN,KAEE,6CAA8C,CAC9C,uDAAwD,CACxD,uDAAwD,CAGxD,iCAAsC,CAGtC,+CAAgD,CAChD,uDAAwD,CACxD,uDAAwD,CACxD,oDAAqD,CACrD,6DAA8D,CAC9D,6DAA8D,CAG9D,uDAAwD,CACxD,yDAA0D,CAC1D,4DAA6D,CAC7D,2DAA4D,CAC5D,8DAA+D,CAC/D,iEAAkE,CAClE,uDAAwD,CACxD,wDAAyD,CAG3D,gBACE,qFAGF,SACE,6EAEF,cACE,uFAEF,cACE,uFAEF,cACE,uFAGF,qBACE,eAEF,mBACE,WACA,eChDF,KACE,gDAAiD,CACjD,uDAAwD,CACxD,qDAAsD,CACtD,4DAA6D,CAC7D,oCAAqC,CACrC,2CAA4C,CAC5C,4CAA6C,CAC7C,mDAAoD,CACpD,wBAAyB,CACzB,oBAAqB,CACrB,6CAA8C,CAC9C,gCAAiC,CACjC,yDAA0D,CAC1D,uDAAwD,CACxD,8DAA+D,CCbjE,uBACE,eACA,eACA,gBAGF,iBACE,YACA,+EAGF,iBACE,mDACA","sources":["webpack:///./src/furo/assets/styles/extensions/_readthedocs.sass","webpack:///./src/furo/assets/styles/extensions/_copybutton.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-design.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-inline-tabs.sass","webpack:///./src/furo/assets/styles/extensions/_sphinx-panels.sass"],"sourcesContent":["// This file contains the styles used for tweaking how ReadTheDoc's embedded\n// contents would show up inside the theme.\n\n#furo-sidebar-ad-placement\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n .ethical-sidebar\n // Remove the border and box-shadow.\n border: none\n box-shadow: none\n // Manage the background colors.\n background: var(--color-background-secondary)\n &:hover\n background: var(--color-background-hover)\n // Ensure the text is legible.\n a\n color: var(--color-foreground-primary)\n\n .ethical-callout a\n color: var(--color-foreground-secondary) !important\n\n#furo-readthedocs-versions\n position: static\n width: 100%\n background: transparent\n display: block\n\n // Make the background color fit with the theme's aesthetic.\n .rst-versions\n background: rgb(26, 28, 30)\n\n .rst-current-version\n cursor: unset\n background: var(--color-sidebar-item-background)\n &:hover\n background: var(--color-sidebar-item-background)\n .fa-book\n color: var(--color-foreground-primary)\n\n > .rst-other-versions\n padding: 0\n small\n opacity: 1\n\n .injected\n .rst-versions\n position: unset\n\n &:hover,\n &:focus-within\n box-shadow: 0 0 0 1px var(--color-sidebar-background-border)\n\n .rst-current-version\n // Undo the tweaks done in RTD's CSS\n font-size: inherit\n line-height: inherit\n height: auto\n text-align: right\n padding: 12px\n\n // Match the rest of the body\n background: #1a1c1e\n\n .fa-book\n float: left\n color: white\n\n .fa-caret-down\n display: none\n\n .rst-current-version,\n .rst-other-versions,\n .injected\n display: block\n\n > .rst-current-version\n display: none\n",".highlight\n &:hover button.copybtn\n color: var(--color-code-foreground)\n\n button.copybtn\n // Align things correctly\n align-items: center\n\n height: 1.25em\n width: 1.25em\n\n top: 0.625rem // $code-spacing-vertical\n right: 0.5rem\n\n // Make it look better\n color: var(--color-background-item)\n background-color: var(--color-code-background)\n border: none\n\n // Change to cursor to make it obvious that you can click on it\n cursor: pointer\n\n // Transition smoothly, for aesthetics\n transition: color 300ms, opacity 300ms\n\n &:hover\n color: var(--color-brand-content)\n background-color: var(--color-code-background)\n\n &::after\n display: none\n color: var(--color-code-foreground)\n background-color: transparent\n\n &.success\n transition: color 0ms\n color: #22863a\n &::after\n display: block\n\n svg\n padding: 0\n","body\n // Colors\n --sd-color-primary: var(--color-brand-primary)\n --sd-color-primary-highlight: var(--color-brand-content)\n --sd-color-primary-text: var(--color-background-primary)\n\n // Shadows\n --sd-color-shadow: rgba(0, 0, 0, 0.05)\n\n // Cards\n --sd-color-card-border: var(--color-card-border)\n --sd-color-card-border-hover: var(--color-brand-content)\n --sd-color-card-background: var(--color-card-background)\n --sd-color-card-text: var(--color-foreground-primary)\n --sd-color-card-header: var(--color-card-marginals-background)\n --sd-color-card-footer: var(--color-card-marginals-background)\n\n // Tabs\n --sd-color-tabs-label-active: var(--color-brand-content)\n --sd-color-tabs-label-hover: var(--color-foreground-muted)\n --sd-color-tabs-label-inactive: var(--color-foreground-muted)\n --sd-color-tabs-underline-active: var(--color-brand-content)\n --sd-color-tabs-underline-hover: var(--color-foreground-border)\n --sd-color-tabs-underline-inactive: var(--color-background-border)\n --sd-color-tabs-overline: var(--color-background-border)\n --sd-color-tabs-underline: var(--color-background-border)\n\n// Tabs\n.sd-tab-content\n box-shadow: 0 -2px var(--sd-color-tabs-overline), 0 1px var(--sd-color-tabs-underline)\n\n// Shadows\n.sd-card // Have a shadow by default\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n.sd-shadow-sm\n box-shadow: 0 0.1rem 0.25rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-md\n box-shadow: 0 0.3rem 0.75rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n.sd-shadow-lg\n box-shadow: 0 0.6rem 1.5rem var(--sd-color-shadow), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Cards\n.sd-card-hover:hover // Don't change scale on hover\n transform: none\n\n.sd-cards-carousel // Have a bit of gap in the carousel by default\n gap: 0.25rem\n padding: 0.25rem\n","// This file contains styles to tweak sphinx-inline-tabs to work well with Furo.\n\nbody\n --tabs--label-text: var(--color-foreground-muted)\n --tabs--label-text--hover: var(--color-foreground-muted)\n --tabs--label-text--active: var(--color-brand-content)\n --tabs--label-text--active--hover: var(--color-brand-content)\n --tabs--label-background: transparent\n --tabs--label-background--hover: transparent\n --tabs--label-background--active: transparent\n --tabs--label-background--active--hover: transparent\n --tabs--padding-x: 0.25em\n --tabs--margin-x: 1em\n --tabs--border: var(--color-background-border)\n --tabs--label-border: transparent\n --tabs--label-border--hover: var(--color-foreground-muted)\n --tabs--label-border--active: var(--color-brand-content)\n --tabs--label-border--active--hover: var(--color-brand-content)\n","// This file contains styles to tweak sphinx-panels to work well with Furo.\n\n// sphinx-panels includes Bootstrap 4, which uses .container which can conflict\n// with docutils' `.. container::` directive.\n[role=\"main\"] .container\n max-width: initial\n padding-left: initial\n padding-right: initial\n\n// Make the panels look nicer!\n.shadow.docutils\n border: none\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1) !important\n\n// Make panel colors respond to dark mode\n.sphinx-bs .card\n background-color: var(--color-background-secondary)\n color: var(--color-foreground)\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/docs/html/_static/styles/furo.css b/docs/html/_static/styles/furo.css new file mode 100644 index 00000000..05a56b17 --- /dev/null +++ b/docs/html/_static/styles/furo.css @@ -0,0 +1,2 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}@media print{.content-icon-container,.headerlink,.mobile-header,.related-pages{display:none!important}.highlight{border:.1pt solid var(--color-foreground-border)}a,blockquote,dl,ol,p,pre,table,ul{page-break-inside:avoid}caption,figure,h1,h2,h3,h4,h5,h6,img{page-break-after:avoid;page-break-inside:avoid}dl,ol,ul{page-break-before:avoid}}.visually-hidden{height:1px!important;margin:-1px!important;overflow:hidden!important;padding:0!important;position:absolute!important;width:1px!important;clip:rect(0,0,0,0)!important;background:var(--color-background-primary);border:0!important;color:var(--color-foreground-primary);white-space:nowrap!important}:-moz-focusring{outline:auto}body{--font-stack:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji;--font-stack--monospace:"SFMono-Regular",Menlo,Consolas,Monaco,Liberation Mono,Lucida Console,monospace;--font-stack--headings:var(--font-stack);--font-size--normal:100%;--font-size--small:87.5%;--font-size--small--2:81.25%;--font-size--small--3:75%;--font-size--small--4:62.5%;--sidebar-caption-font-size:var(--font-size--small--2);--sidebar-item-font-size:var(--font-size--small);--sidebar-search-input-font-size:var(--font-size--small);--toc-font-size:var(--font-size--small--3);--toc-font-size--mobile:var(--font-size--normal);--toc-title-font-size:var(--font-size--small--4);--admonition-font-size:0.8125rem;--admonition-title-font-size:0.8125rem;--code-font-size:var(--font-size--small--2);--api-font-size:var(--font-size--small);--header-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*4);--header-padding:0.5rem;--sidebar-tree-space-above:1.5rem;--sidebar-caption-space-above:1rem;--sidebar-item-line-height:1rem;--sidebar-item-spacing-vertical:0.5rem;--sidebar-item-spacing-horizontal:1rem;--sidebar-item-height:calc(var(--sidebar-item-line-height) + var(--sidebar-item-spacing-vertical)*2);--sidebar-expander-width:var(--sidebar-item-height);--sidebar-search-space-above:0.5rem;--sidebar-search-input-spacing-vertical:0.5rem;--sidebar-search-input-spacing-horizontal:0.5rem;--sidebar-search-input-height:1rem;--sidebar-search-icon-size:var(--sidebar-search-input-height);--toc-title-padding:0.25rem 0;--toc-spacing-vertical:1.5rem;--toc-spacing-horizontal:1.5rem;--toc-item-spacing-vertical:0.4rem;--toc-item-spacing-horizontal:1rem;--icon-search:url('data:image/svg+xml;charset=utf-8,');--icon-pencil:url('data:image/svg+xml;charset=utf-8,');--icon-abstract:url('data:image/svg+xml;charset=utf-8,');--icon-info:url('data:image/svg+xml;charset=utf-8,');--icon-flame:url('data:image/svg+xml;charset=utf-8,');--icon-question:url('data:image/svg+xml;charset=utf-8,');--icon-warning:url('data:image/svg+xml;charset=utf-8,');--icon-failure:url('data:image/svg+xml;charset=utf-8,');--icon-spark:url('data:image/svg+xml;charset=utf-8,');--color-admonition-title--caution:#ff9100;--color-admonition-title-background--caution:rgba(255,145,0,.2);--color-admonition-title--warning:#ff9100;--color-admonition-title-background--warning:rgba(255,145,0,.2);--color-admonition-title--danger:#ff5252;--color-admonition-title-background--danger:rgba(255,82,82,.2);--color-admonition-title--attention:#ff5252;--color-admonition-title-background--attention:rgba(255,82,82,.2);--color-admonition-title--error:#ff5252;--color-admonition-title-background--error:rgba(255,82,82,.2);--color-admonition-title--hint:#00c852;--color-admonition-title-background--hint:rgba(0,200,82,.2);--color-admonition-title--tip:#00c852;--color-admonition-title-background--tip:rgba(0,200,82,.2);--color-admonition-title--important:#00bfa5;--color-admonition-title-background--important:rgba(0,191,165,.2);--color-admonition-title--note:#00b0ff;--color-admonition-title-background--note:rgba(0,176,255,.2);--color-admonition-title--seealso:#448aff;--color-admonition-title-background--seealso:rgba(68,138,255,.2);--color-admonition-title--admonition-todo:grey;--color-admonition-title-background--admonition-todo:hsla(0,0%,50%,.2);--color-admonition-title:#651fff;--color-admonition-title-background:rgba(101,31,255,.2);--icon-admonition-default:var(--icon-abstract);--color-topic-title:#14b8a6;--color-topic-title-background:rgba(20,184,166,.2);--icon-topic-default:var(--icon-pencil);--color-problematic:#b30000;--color-foreground-primary:#000;--color-foreground-secondary:#5a5c63;--color-foreground-muted:#6b6f76;--color-foreground-border:#878787;--color-background-primary:#fff;--color-background-secondary:#f8f9fb;--color-background-hover:#efeff4;--color-background-hover--transparent:#efeff400;--color-background-border:#eeebee;--color-background-item:#ccc;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#0a4bff;--color-brand-content:#2757dd;--color-brand-visited:#872ee0;--color-api-background:var(--color-background-hover--transparent);--color-api-background-hover:var(--color-background-hover);--color-api-overall:var(--color-foreground-secondary);--color-api-name:var(--color-problematic);--color-api-pre-name:var(--color-problematic);--color-api-paren:var(--color-foreground-secondary);--color-api-keyword:var(--color-foreground-primary);--color-api-added:#21632c;--color-api-added-border:#38a84d;--color-api-changed:#046172;--color-api-changed-border:#06a1bc;--color-api-deprecated:#605706;--color-api-deprecated-border:#f0d90f;--color-api-removed:#b30000;--color-api-removed-border:#ff5c5c;--color-highlight-on-target:#ffc;--color-inline-code-background:var(--color-background-secondary);--color-highlighted-background:#def;--color-highlighted-text:var(--color-foreground-primary);--color-guilabel-background:#ddeeff80;--color-guilabel-border:#bedaf580;--color-guilabel-text:var(--color-foreground-primary);--color-admonition-background:transparent;--color-table-header-background:var(--color-background-secondary);--color-table-border:var(--color-background-border);--color-card-border:var(--color-background-secondary);--color-card-background:transparent;--color-card-marginals-background:var(--color-background-secondary);--color-header-background:var(--color-background-primary);--color-header-border:var(--color-background-border);--color-header-text:var(--color-foreground-primary);--color-sidebar-background:var(--color-background-secondary);--color-sidebar-background-border:var(--color-background-border);--color-sidebar-brand-text:var(--color-foreground-primary);--color-sidebar-caption-text:var(--color-foreground-muted);--color-sidebar-link-text:var(--color-foreground-secondary);--color-sidebar-link-text--top-level:var(--color-brand-primary);--color-sidebar-item-background:var(--color-sidebar-background);--color-sidebar-item-background--current:var( --color-sidebar-item-background );--color-sidebar-item-background--hover:linear-gradient(90deg,var(--color-background-hover--transparent) 0%,var(--color-background-hover) var(--sidebar-item-spacing-horizontal),var(--color-background-hover) 100%);--color-sidebar-item-expander-background:transparent;--color-sidebar-item-expander-background--hover:var( --color-background-hover );--color-sidebar-search-text:var(--color-foreground-primary);--color-sidebar-search-background:var(--color-background-secondary);--color-sidebar-search-background--focus:var(--color-background-primary);--color-sidebar-search-border:var(--color-background-border);--color-sidebar-search-icon:var(--color-foreground-muted);--color-toc-background:var(--color-background-primary);--color-toc-title-text:var(--color-foreground-muted);--color-toc-item-text:var(--color-foreground-secondary);--color-toc-item-text--hover:var(--color-foreground-primary);--color-toc-item-text--active:var(--color-brand-primary);--color-content-foreground:var(--color-foreground-primary);--color-content-background:transparent;--color-link:var(--color-brand-content);--color-link-underline:var(--color-background-border);--color-link--hover:var(--color-brand-content);--color-link-underline--hover:var(--color-foreground-border);--color-link--visited:var(--color-brand-visited);--color-link-underline--visited:var(--color-background-border);--color-link--visited--hover:var(--color-brand-visited);--color-link-underline--visited--hover:var(--color-foreground-border)}.only-light{display:block!important}html body .only-dark{display:none!important}@media not print{body[data-theme=dark]{--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body[data-theme=dark] .only-light{display:none!important}body[data-theme=dark] .only-dark{display:block!important}@media(prefers-color-scheme:dark){body:not([data-theme=light]){--color-problematic:#ee5151;--color-foreground-primary:#cfd0d0;--color-foreground-secondary:#9ca0a5;--color-foreground-muted:#81868d;--color-foreground-border:#666;--color-background-primary:#131416;--color-background-secondary:#1a1c1e;--color-background-hover:#1e2124;--color-background-hover--transparent:#1e212400;--color-background-border:#303335;--color-background-item:#444;--color-announcement-background:#000000dd;--color-announcement-text:#eeebee;--color-brand-primary:#3d94ff;--color-brand-content:#5ca5ff;--color-brand-visited:#b27aeb;--color-highlighted-background:#083563;--color-guilabel-background:#08356380;--color-guilabel-border:#13395f80;--color-api-keyword:var(--color-foreground-secondary);--color-highlight-on-target:#330;--color-api-added:#3db854;--color-api-added-border:#267334;--color-api-changed:#09b0ce;--color-api-changed-border:#056d80;--color-api-deprecated:#b1a10b;--color-api-deprecated-border:#6e6407;--color-api-removed:#ff7575;--color-api-removed-border:#b03b3b;--color-admonition-background:#18181a;--color-card-border:var(--color-background-secondary);--color-card-background:#18181a;--color-card-marginals-background:var(--color-background-hover)}html body:not([data-theme=light]) .only-light{display:none!important}body:not([data-theme=light]) .only-dark{display:block!important}}}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:block}@media(prefers-color-scheme:dark){body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-dark{display:block}body[data-theme=auto] .theme-toggle svg.theme-icon-when-auto-light{display:none}}body[data-theme=dark] .theme-toggle svg.theme-icon-when-dark,body[data-theme=light] .theme-toggle svg.theme-icon-when-light{display:block}body{font-family:var(--font-stack)}code,kbd,pre,samp{font-family:var(--font-stack--monospace)}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}article{line-height:1.5}h1,h2,h3,h4,h5,h6{border-radius:.5rem;font-family:var(--font-stack--headings);font-weight:700;line-height:1.25;margin:.5rem -.5rem;padding-left:.5rem;padding-right:.5rem}h1+p,h2+p,h3+p,h4+p,h5+p,h6+p{margin-top:0}h1{font-size:2.5em;margin-bottom:1rem}h1,h2{margin-top:1.75rem}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.25em}h5{font-size:1.125em}h6{font-size:1em}small{font-size:80%;opacity:75%}p{margin-bottom:.75rem;margin-top:.5rem}hr.docutils{background-color:var(--color-background-border);border:0;height:1px;margin:2rem 0;padding:0}.centered{text-align:center}a{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}a:visited{color:var(--color-link--visited);text-decoration-color:var(--color-link-underline--visited)}a:visited:hover{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}a:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link{color:inherit}a.muted-link:hover{color:var(--color-link--hover);text-decoration-color:var(--color-link-underline--hover)}a.muted-link:hover:visited{color:var(--color-link--visited--hover);text-decoration-color:var(--color-link-underline--visited--hover)}html{overflow-x:hidden;overflow-y:scroll;scroll-behavior:smooth}.sidebar-scroll,.toc-scroll,article[role=main] *{scrollbar-color:var(--color-foreground-border) transparent;scrollbar-width:thin}.sidebar-scroll::-webkit-scrollbar,.toc-scroll::-webkit-scrollbar,article[role=main] ::-webkit-scrollbar{height:.25rem;width:.25rem}.sidebar-scroll::-webkit-scrollbar-thumb,.toc-scroll::-webkit-scrollbar-thumb,article[role=main] ::-webkit-scrollbar-thumb{background-color:var(--color-foreground-border);border-radius:.125rem}body,html{height:100%}.skip-to-content,body,html{background:var(--color-background-primary);color:var(--color-foreground-primary)}.skip-to-content{border-radius:1rem;left:.25rem;padding:1rem;position:fixed;top:.25rem;transform:translateY(-200%);transition:transform .3s ease-in-out;z-index:40}.skip-to-content:focus-within{transform:translateY(0)}article{background:var(--color-content-background);color:var(--color-content-foreground);overflow-wrap:break-word}.page{display:flex;min-height:100%}.mobile-header{background-color:var(--color-header-background);border-bottom:1px solid var(--color-header-border);color:var(--color-header-text);display:none;height:var(--header-height);width:100%;z-index:10}.mobile-header.scrolled{border-bottom:none;box-shadow:0 0 .2rem rgba(0,0,0,.1),0 .2rem .4rem rgba(0,0,0,.2)}.mobile-header .header-center a{color:var(--color-header-text);text-decoration:none}.main{display:flex;flex:1}.sidebar-drawer{background:var(--color-sidebar-background);border-right:1px solid var(--color-sidebar-background-border);box-sizing:border-box;display:flex;justify-content:flex-end;min-width:15em;width:calc(50% - 26em)}.sidebar-container,.toc-drawer{box-sizing:border-box;width:15em}.toc-drawer{background:var(--color-toc-background);padding-right:1rem}.sidebar-sticky,.toc-sticky{display:flex;flex-direction:column;height:min(100%,100vh);height:100vh;position:sticky;top:0}.sidebar-scroll,.toc-scroll{flex-grow:1;flex-shrink:1;overflow:auto;scroll-behavior:smooth}.content{display:flex;flex-direction:column;justify-content:space-between;padding:0 3em;width:46em}.icon{display:inline-block;height:1rem;width:1rem}.icon svg{height:100%;width:100%}.announcement{align-items:center;background-color:var(--color-announcement-background);color:var(--color-announcement-text);display:flex;height:var(--header-height);overflow-x:auto}.announcement+.page{min-height:calc(100% - var(--header-height))}.announcement-content{box-sizing:border-box;min-width:100%;padding:.5rem;text-align:center;white-space:nowrap}.announcement-content a{color:var(--color-announcement-text);text-decoration-color:var(--color-announcement-text)}.announcement-content a:hover{color:var(--color-announcement-text);text-decoration-color:var(--color-link--hover)}.no-js .theme-toggle-container{display:none}.theme-toggle-container{display:flex}.theme-toggle{background:transparent;border:none;cursor:pointer;display:flex;padding:0}.theme-toggle svg{color:var(--color-foreground-primary);display:none;height:1.25rem;width:1.25rem}.theme-toggle-header{align-items:center;display:flex;justify-content:center}.nav-overlay-icon,.toc-overlay-icon{cursor:pointer;display:none}.nav-overlay-icon .icon,.toc-overlay-icon .icon{color:var(--color-foreground-secondary);height:1.5rem;width:1.5rem}.nav-overlay-icon,.toc-header-icon{align-items:center;justify-content:center}.toc-content-icon{height:1.5rem;width:1.5rem}.content-icon-container{display:flex;float:right;gap:.5rem;margin-bottom:1rem;margin-left:1rem;margin-top:1.5rem}.content-icon-container .edit-this-page svg,.content-icon-container .view-this-page svg{color:inherit;height:1.25rem;width:1.25rem}.sidebar-toggle{display:none;position:absolute}.sidebar-toggle[name=__toc]{left:20px}.sidebar-toggle:checked{left:40px}.overlay{background-color:rgba(0,0,0,.54);height:0;opacity:0;position:fixed;top:0;transition:width 0ms,height 0ms,opacity .25s ease-out;width:0}.sidebar-overlay{z-index:20}.toc-overlay{z-index:40}.sidebar-drawer{transition:left .25s ease-in-out;z-index:30}.toc-drawer{transition:right .25s ease-in-out;z-index:50}#__navigation:checked~.sidebar-overlay{height:100%;opacity:1;width:100%}#__navigation:checked~.page .sidebar-drawer{left:0;top:0}#__toc:checked~.toc-overlay{height:100%;opacity:1;width:100%}#__toc:checked~.page .toc-drawer{right:0;top:0}.back-to-top{background:var(--color-background-primary);border-radius:1rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 1px 0 hsla(220,9%,46%,.502);display:none;font-size:.8125rem;left:0;margin-left:50%;padding:.5rem .75rem .5rem .5rem;position:fixed;text-decoration:none;top:1rem;transform:translateX(-50%);z-index:10}.back-to-top svg{height:1rem;width:1rem;fill:currentColor;display:inline-block}.back-to-top span{margin-left:.25rem}.show-back-to-top .back-to-top{align-items:center;display:flex}@media(min-width:97em){html{font-size:110%}}@media(max-width:82em){.toc-content-icon{display:flex}.toc-drawer{border-left:1px solid var(--color-background-muted);height:100vh;position:fixed;right:-15em;top:0}.toc-tree{border-left:none;font-size:var(--toc-font-size--mobile)}.sidebar-drawer{width:calc(50% - 18.5em)}}@media(max-width:67em){.content{margin-left:auto;margin-right:auto;padding:0 1em}}@media(max-width:63em){.nav-overlay-icon{display:flex}.sidebar-drawer{height:100vh;left:-15em;position:fixed;top:0;width:15em}.theme-toggle-header,.toc-header-icon{display:flex}.theme-toggle-content,.toc-content-icon{display:none}.mobile-header{align-items:center;display:flex;justify-content:space-between;position:sticky;top:0}.mobile-header .header-left,.mobile-header .header-right{display:flex;height:var(--header-height);padding:0 var(--header-padding)}.mobile-header .header-left label,.mobile-header .header-right label{height:100%;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.nav-overlay-icon .icon,.theme-toggle svg{height:1.5rem;width:1.5rem}:target{scroll-margin-top:calc(var(--header-height) + 2.5rem)}.back-to-top{top:calc(var(--header-height) + .5rem)}.page{flex-direction:column;justify-content:center}}@media(max-width:48em){.content{overflow-x:auto;width:100%}}@media(max-width:46em){article[role=main] aside.sidebar{float:none;margin:1rem 0;width:100%}}.admonition,.topic{background:var(--color-admonition-background);border-radius:.2rem;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1);font-size:var(--admonition-font-size);margin:1rem auto;overflow:hidden;padding:0 .5rem .5rem;page-break-inside:avoid}.admonition>:nth-child(2),.topic>:nth-child(2){margin-top:0}.admonition>:last-child,.topic>:last-child{margin-bottom:0}.admonition p.admonition-title,p.topic-title{font-size:var(--admonition-title-font-size);font-weight:500;line-height:1.3;margin:0 -.5rem .5rem;padding:.4rem .5rem .4rem 2rem;position:relative}.admonition p.admonition-title:before,p.topic-title:before{content:"";height:1rem;left:.5rem;position:absolute;width:1rem}p.admonition-title{background-color:var(--color-admonition-title-background)}p.admonition-title:before{background-color:var(--color-admonition-title);-webkit-mask-image:var(--icon-admonition-default);mask-image:var(--icon-admonition-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}p.topic-title{background-color:var(--color-topic-title-background)}p.topic-title:before{background-color:var(--color-topic-title);-webkit-mask-image:var(--icon-topic-default);mask-image:var(--icon-topic-default);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.admonition{border-left:.2rem solid var(--color-admonition-title)}.admonition.caution{border-left-color:var(--color-admonition-title--caution)}.admonition.caution>.admonition-title{background-color:var(--color-admonition-title-background--caution)}.admonition.caution>.admonition-title:before{background-color:var(--color-admonition-title--caution);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.warning{border-left-color:var(--color-admonition-title--warning)}.admonition.warning>.admonition-title{background-color:var(--color-admonition-title-background--warning)}.admonition.warning>.admonition-title:before{background-color:var(--color-admonition-title--warning);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.danger{border-left-color:var(--color-admonition-title--danger)}.admonition.danger>.admonition-title{background-color:var(--color-admonition-title-background--danger)}.admonition.danger>.admonition-title:before{background-color:var(--color-admonition-title--danger);-webkit-mask-image:var(--icon-spark);mask-image:var(--icon-spark)}.admonition.attention{border-left-color:var(--color-admonition-title--attention)}.admonition.attention>.admonition-title{background-color:var(--color-admonition-title-background--attention)}.admonition.attention>.admonition-title:before{background-color:var(--color-admonition-title--attention);-webkit-mask-image:var(--icon-warning);mask-image:var(--icon-warning)}.admonition.error{border-left-color:var(--color-admonition-title--error)}.admonition.error>.admonition-title{background-color:var(--color-admonition-title-background--error)}.admonition.error>.admonition-title:before{background-color:var(--color-admonition-title--error);-webkit-mask-image:var(--icon-failure);mask-image:var(--icon-failure)}.admonition.hint{border-left-color:var(--color-admonition-title--hint)}.admonition.hint>.admonition-title{background-color:var(--color-admonition-title-background--hint)}.admonition.hint>.admonition-title:before{background-color:var(--color-admonition-title--hint);-webkit-mask-image:var(--icon-question);mask-image:var(--icon-question)}.admonition.tip{border-left-color:var(--color-admonition-title--tip)}.admonition.tip>.admonition-title{background-color:var(--color-admonition-title-background--tip)}.admonition.tip>.admonition-title:before{background-color:var(--color-admonition-title--tip);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.important{border-left-color:var(--color-admonition-title--important)}.admonition.important>.admonition-title{background-color:var(--color-admonition-title-background--important)}.admonition.important>.admonition-title:before{background-color:var(--color-admonition-title--important);-webkit-mask-image:var(--icon-flame);mask-image:var(--icon-flame)}.admonition.note{border-left-color:var(--color-admonition-title--note)}.admonition.note>.admonition-title{background-color:var(--color-admonition-title-background--note)}.admonition.note>.admonition-title:before{background-color:var(--color-admonition-title--note);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition.seealso{border-left-color:var(--color-admonition-title--seealso)}.admonition.seealso>.admonition-title{background-color:var(--color-admonition-title-background--seealso)}.admonition.seealso>.admonition-title:before{background-color:var(--color-admonition-title--seealso);-webkit-mask-image:var(--icon-info);mask-image:var(--icon-info)}.admonition.admonition-todo{border-left-color:var(--color-admonition-title--admonition-todo)}.admonition.admonition-todo>.admonition-title{background-color:var(--color-admonition-title-background--admonition-todo)}.admonition.admonition-todo>.admonition-title:before{background-color:var(--color-admonition-title--admonition-todo);-webkit-mask-image:var(--icon-pencil);mask-image:var(--icon-pencil)}.admonition-todo>.admonition-title{text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd{margin-left:2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:first-child{margin-top:.125rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list,dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) dd>:last-child{margin-bottom:.75rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list>dt{font-size:var(--font-size--small);text-transform:uppercase}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd:empty{margin-bottom:.5rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul{margin-left:-1.2rem}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p:nth-child(2){margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple) .field-list dd>ul>li>p+p:last-child:empty{margin-bottom:0;margin-top:0}dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)>dt{color:var(--color-api-overall)}.sig:not(.sig-inline){background:var(--color-api-background);border-radius:.25rem;font-family:var(--font-stack--monospace);font-size:var(--api-font-size);font-weight:700;margin-left:-.25rem;margin-right:-.25rem;padding:.25rem .5rem .25rem 3em;text-indent:-2.5em;transition:background .1s ease-out}.sig:not(.sig-inline):hover{background:var(--color-api-background-hover)}.sig:not(.sig-inline) a.reference .viewcode-link{font-weight:400;width:4.25rem}em.property{font-style:normal}em.property:first-child{color:var(--color-api-keyword)}.sig-name{color:var(--color-api-name)}.sig-prename{color:var(--color-api-pre-name);font-weight:400}.sig-paren{color:var(--color-api-paren)}.sig-param{font-style:normal}div.deprecated,div.versionadded,div.versionchanged,div.versionremoved{border-left:.1875rem solid;border-radius:.125rem;padding-left:.75rem}div.deprecated p,div.versionadded p,div.versionchanged p,div.versionremoved p{margin-bottom:.125rem;margin-top:.125rem}div.versionadded{border-color:var(--color-api-added-border)}div.versionadded .versionmodified{color:var(--color-api-added)}div.versionchanged{border-color:var(--color-api-changed-border)}div.versionchanged .versionmodified{color:var(--color-api-changed)}div.deprecated{border-color:var(--color-api-deprecated-border)}div.deprecated .versionmodified{color:var(--color-api-deprecated)}div.versionremoved{border-color:var(--color-api-removed-border)}div.versionremoved .versionmodified{color:var(--color-api-removed)}.viewcode-back,.viewcode-link{float:right;text-align:right}.line-block{margin-bottom:.75rem;margin-top:.5rem}.line-block .line-block{margin-bottom:0;margin-top:0;padding-left:1rem}.code-block-caption,article p.caption,table>caption{font-size:var(--font-size--small);text-align:center}.toctree-wrapper.compound .caption,.toctree-wrapper.compound :not(.caption)>.caption-text{font-size:var(--font-size--small);margin-bottom:0;text-align:initial;text-transform:uppercase}.toctree-wrapper.compound>ul{margin-bottom:0;margin-top:0}.sig-inline,code.literal{background:var(--color-inline-code-background);border-radius:.2em;font-size:var(--font-size--small--2);padding:.1em .2em}pre.literal-block .sig-inline,pre.literal-block code.literal{font-size:inherit;padding:0}p .sig-inline,p code.literal{border:1px solid var(--color-background-border)}.sig-inline{font-family:var(--font-stack--monospace)}div[class*=" highlight-"],div[class^=highlight-]{display:flex;margin:1em 0}div[class*=" highlight-"] .table-wrapper,div[class^=highlight-] .table-wrapper,pre{margin:0;padding:0}pre{overflow:auto}article[role=main] .highlight pre{line-height:1.5}.highlight pre,pre.literal-block{font-size:var(--code-font-size);padding:.625rem .875rem}pre.literal-block{background-color:var(--color-code-background);border-radius:.2rem;color:var(--color-code-foreground);margin-bottom:1rem;margin-top:1rem}.highlight{border-radius:.2rem;width:100%}.highlight .gp,.highlight span.linenos{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.highlight .hll{display:block;margin-left:-.875rem;margin-right:-.875rem;padding-left:.875rem;padding-right:.875rem}.code-block-caption{background-color:var(--color-code-background);border-bottom:1px solid;border-radius:.25rem;border-bottom-left-radius:0;border-bottom-right-radius:0;border-color:var(--color-background-border);color:var(--color-code-foreground);display:flex;font-weight:300;padding:.625rem .875rem}.code-block-caption+div[class]{margin-top:0}.code-block-caption+div[class] pre{border-top-left-radius:0;border-top-right-radius:0}.highlighttable{display:block;width:100%}.highlighttable tbody{display:block}.highlighttable tr{display:flex}.highlighttable td.linenos{background-color:var(--color-code-background);border-bottom-left-radius:.2rem;border-top-left-radius:.2rem;color:var(--color-code-foreground);padding:.625rem 0 .625rem .875rem}.highlighttable .linenodiv{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;font-size:var(--code-font-size);padding-right:.875rem}.highlighttable td.code{display:block;flex:1;overflow:hidden;padding:0}.highlighttable td.code .highlight{border-bottom-left-radius:0;border-top-left-radius:0}.highlight span.linenos{box-shadow:-.0625rem 0 var(--color-foreground-border) inset;display:inline-block;margin-right:.875rem;padding-left:0;padding-right:.875rem}.footnote-reference{font-size:var(--font-size--small--4);vertical-align:super}dl.footnote.brackets{color:var(--color-foreground-secondary);display:grid;font-size:var(--font-size--small);grid-template-columns:max-content auto}dl.footnote.brackets dt{margin:0}dl.footnote.brackets dt>.fn-backref{margin-left:.25rem}dl.footnote.brackets dt:after{content:":"}dl.footnote.brackets dt .brackets:before{content:"["}dl.footnote.brackets dt .brackets:after{content:"]"}dl.footnote.brackets dd{margin:0;padding:0 1rem}aside.footnote{color:var(--color-foreground-secondary);font-size:var(--font-size--small)}aside.footnote>span,div.citation>span{float:left;font-weight:500;padding-right:.25rem}aside.footnote>:not(span),div.citation>p{margin-left:2rem}img{box-sizing:border-box;height:auto;max-width:100%}article .figure,article figure{border-radius:.2rem;margin:0}article .figure :last-child,article figure :last-child{margin-bottom:0}article .align-left{clear:left;float:left;margin:0 1rem 1rem}article .align-right{clear:right;float:right;margin:0 1rem 1rem}article .align-center,article .align-default{display:block;margin-left:auto;margin-right:auto;text-align:center}article table.align-default{display:table;text-align:initial}.domainindex-jumpbox,.genindex-jumpbox{border-bottom:1px solid var(--color-background-border);border-top:1px solid var(--color-background-border);padding:.25rem}.domainindex-section h2,.genindex-section h2{margin-bottom:.5rem;margin-top:.75rem}.domainindex-section ul,.genindex-section ul{margin-bottom:0;margin-top:0}ol,ul{margin-bottom:1rem;margin-top:1rem;padding-left:1.2rem}ol li>p:first-child,ul li>p:first-child{margin-bottom:.25rem;margin-top:.25rem}ol li>p:last-child,ul li>p:last-child{margin-top:.25rem}ol li>ol,ol li>ul,ul li>ol,ul li>ul{margin-bottom:.5rem;margin-top:.5rem}ol.arabic{list-style:decimal}ol.loweralpha{list-style:lower-alpha}ol.upperalpha{list-style:upper-alpha}ol.lowerroman{list-style:lower-roman}ol.upperroman{list-style:upper-roman}.simple li>ol,.simple li>ul,.toctree-wrapper li>ol,.toctree-wrapper li>ul{margin-bottom:0;margin-top:0}.field-list dt,.option-list dt,dl.footnote dt,dl.glossary dt,dl.simple dt,dl:not([class]) dt{font-weight:500;margin-top:.25rem}.field-list dt+dt,.option-list dt+dt,dl.footnote dt+dt,dl.glossary dt+dt,dl.simple dt+dt,dl:not([class]) dt+dt{margin-top:0}.field-list dt .classifier:before,.option-list dt .classifier:before,dl.footnote dt .classifier:before,dl.glossary dt .classifier:before,dl.simple dt .classifier:before,dl:not([class]) dt .classifier:before{content:":";margin-left:.2rem;margin-right:.2rem}.field-list dd ul,.field-list dd>p:first-child,.option-list dd ul,.option-list dd>p:first-child,dl.footnote dd ul,dl.footnote dd>p:first-child,dl.glossary dd ul,dl.glossary dd>p:first-child,dl.simple dd ul,dl.simple dd>p:first-child,dl:not([class]) dd ul,dl:not([class]) dd>p:first-child{margin-top:.125rem}.field-list dd ul,.option-list dd ul,dl.footnote dd ul,dl.glossary dd ul,dl.simple dd ul,dl:not([class]) dd ul{margin-bottom:.125rem}.math-wrapper{overflow-x:auto;width:100%}div.math{position:relative;text-align:center}div.math .headerlink,div.math:focus .headerlink{display:none}div.math:hover .headerlink{display:inline-block}div.math span.eqno{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);z-index:1}abbr[title]{cursor:help}.problematic{color:var(--color-problematic)}kbd:not(.compound){background-color:var(--color-background-secondary);border:1px solid var(--color-foreground-border);border-radius:.2rem;box-shadow:0 .0625rem 0 rgba(0,0,0,.2),inset 0 0 0 .125rem var(--color-background-primary);color:var(--color-foreground-primary);display:inline-block;font-size:var(--font-size--small--3);margin:0 .2rem;padding:0 .2rem;vertical-align:text-bottom}blockquote{background:var(--color-background-secondary);border-left:4px solid var(--color-background-border);margin-left:0;margin-right:0;padding:.5rem 1rem}blockquote .attribution{font-weight:600;text-align:right}blockquote.highlights,blockquote.pull-quote{font-size:1.25em}blockquote.epigraph,blockquote.pull-quote{border-left-width:0;border-radius:.5rem}blockquote.highlights{background:transparent;border-left-width:0}p .reference img{vertical-align:middle}p.rubric{font-size:1.125em;font-weight:700;line-height:1.25}dd p.rubric{font-size:var(--font-size--small);font-weight:inherit;line-height:inherit;text-transform:uppercase}article .sidebar{background-color:var(--color-background-secondary);border:1px solid var(--color-background-border);border-radius:.2rem;clear:right;float:right;margin-left:1rem;margin-right:0;width:30%}article .sidebar>*{padding-left:1rem;padding-right:1rem}article .sidebar>ol,article .sidebar>ul{padding-left:2.2rem}article .sidebar .sidebar-title{border-bottom:1px solid var(--color-background-border);font-weight:500;margin:0;padding:.5rem 1rem}[role=main] .table-wrapper.container{margin-bottom:.5rem;margin-top:1rem;overflow-x:auto;padding:.2rem .2rem .75rem;width:100%}table.docutils{border-collapse:collapse;border-radius:.2rem;border-spacing:0;box-shadow:0 .2rem .5rem rgba(0,0,0,.05),0 0 .0625rem rgba(0,0,0,.1)}table.docutils th{background:var(--color-table-header-background)}table.docutils td,table.docutils th{border-bottom:1px solid var(--color-table-border);border-left:1px solid var(--color-table-border);border-right:1px solid var(--color-table-border);padding:0 .25rem}table.docutils td p,table.docutils th p{margin:.25rem}table.docutils td:first-child,table.docutils th:first-child{border-left:none}table.docutils td:last-child,table.docutils th:last-child{border-right:none}table.docutils td.text-left,table.docutils th.text-left{text-align:left}table.docutils td.text-right,table.docutils th.text-right{text-align:right}table.docutils td.text-center,table.docutils th.text-center{text-align:center}:target{scroll-margin-top:2.5rem}@media(max-width:67em){:target{scroll-margin-top:calc(2.5rem + var(--header-height))}section>span:target{scroll-margin-top:calc(2.8rem + var(--header-height))}}.headerlink{font-weight:100;-webkit-user-select:none;-moz-user-select:none;user-select:none}.code-block-caption>.headerlink,dl dt>.headerlink,figcaption p>.headerlink,h1>.headerlink,h2>.headerlink,h3>.headerlink,h4>.headerlink,h5>.headerlink,h6>.headerlink,p.caption>.headerlink,table>caption>.headerlink{margin-left:.5rem;visibility:hidden}.code-block-caption:hover>.headerlink,dl dt:hover>.headerlink,figcaption p:hover>.headerlink,h1:hover>.headerlink,h2:hover>.headerlink,h3:hover>.headerlink,h4:hover>.headerlink,h5:hover>.headerlink,h6:hover>.headerlink,p.caption:hover>.headerlink,table>caption:hover>.headerlink{visibility:visible}.code-block-caption>.toc-backref,dl dt>.toc-backref,figcaption p>.toc-backref,h1>.toc-backref,h2>.toc-backref,h3>.toc-backref,h4>.toc-backref,h5>.toc-backref,h6>.toc-backref,p.caption>.toc-backref,table>caption>.toc-backref{color:inherit;text-decoration-line:none}figure:hover>figcaption>p>.headerlink,table:hover>caption>.headerlink{visibility:visible}:target>h1:first-of-type,:target>h2:first-of-type,:target>h3:first-of-type,:target>h4:first-of-type,:target>h5:first-of-type,:target>h6:first-of-type,span:target~h1:first-of-type,span:target~h2:first-of-type,span:target~h3:first-of-type,span:target~h4:first-of-type,span:target~h5:first-of-type,span:target~h6:first-of-type{background-color:var(--color-highlight-on-target)}:target>h1:first-of-type code.literal,:target>h2:first-of-type code.literal,:target>h3:first-of-type code.literal,:target>h4:first-of-type code.literal,:target>h5:first-of-type code.literal,:target>h6:first-of-type code.literal,span:target~h1:first-of-type code.literal,span:target~h2:first-of-type code.literal,span:target~h3:first-of-type code.literal,span:target~h4:first-of-type code.literal,span:target~h5:first-of-type code.literal,span:target~h6:first-of-type code.literal{background-color:transparent}.literal-block-wrapper:target .code-block-caption,.this-will-duplicate-information-and-it-is-still-useful-here li :target,figure:target,table:target>caption{background-color:var(--color-highlight-on-target)}dt:target{background-color:var(--color-highlight-on-target)!important}.footnote-reference:target,.footnote>dt:target+dd{background-color:var(--color-highlight-on-target)}.guilabel{background-color:var(--color-guilabel-background);border:1px solid var(--color-guilabel-border);border-radius:.5em;color:var(--color-guilabel-text);font-size:.9em;padding:0 .3em}footer{display:flex;flex-direction:column;font-size:var(--font-size--small);margin-top:2rem}.bottom-of-page{align-items:center;border-top:1px solid var(--color-background-border);color:var(--color-foreground-secondary);display:flex;justify-content:space-between;line-height:1.5;margin-top:1rem;padding-bottom:1rem;padding-top:1rem}@media(max-width:46em){.bottom-of-page{flex-direction:column-reverse;gap:.25rem;text-align:center}}.bottom-of-page .left-details{font-size:var(--font-size--small)}.bottom-of-page .right-details{display:flex;flex-direction:column;gap:.25rem;text-align:right}.bottom-of-page .icons{display:flex;font-size:1rem;gap:.25rem;justify-content:flex-end}.bottom-of-page .icons a{text-decoration:none}.bottom-of-page .icons img,.bottom-of-page .icons svg{font-size:1.125rem;height:1em;width:1em}.related-pages a{align-items:center;display:flex;text-decoration:none}.related-pages a:hover .page-info .title{color:var(--color-link);text-decoration:underline;text-decoration-color:var(--color-link-underline)}.related-pages a svg.furo-related-icon,.related-pages a svg.furo-related-icon>use{color:var(--color-foreground-border);flex-shrink:0;height:.75rem;margin:0 .5rem;width:.75rem}.related-pages a.next-page{clear:right;float:right;max-width:50%;text-align:right}.related-pages a.prev-page{clear:left;float:left;max-width:50%}.related-pages a.prev-page svg{transform:rotate(180deg)}.page-info{display:flex;flex-direction:column;overflow-wrap:anywhere}.next-page .page-info{align-items:flex-end}.page-info .context{align-items:center;color:var(--color-foreground-muted);display:flex;font-size:var(--font-size--small);padding-bottom:.1rem;text-decoration:none}ul.search{list-style:none;padding-left:0}ul.search li{border-bottom:1px solid var(--color-background-border);padding:1rem 0}[role=main] .highlighted{background-color:var(--color-highlighted-background);color:var(--color-highlighted-text)}.sidebar-brand{display:flex;flex-direction:column;flex-shrink:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none}.sidebar-brand-text{color:var(--color-sidebar-brand-text);font-size:1.5rem;overflow-wrap:break-word}.sidebar-brand-text,.sidebar-logo-container{margin:var(--sidebar-item-spacing-vertical) 0}.sidebar-logo{display:block;margin:0 auto;max-width:100%}.sidebar-search-container{align-items:center;background:var(--color-sidebar-search-background);display:flex;margin-top:var(--sidebar-search-space-above);position:relative}.sidebar-search-container:focus-within,.sidebar-search-container:hover{background:var(--color-sidebar-search-background--focus)}.sidebar-search-container:before{background-color:var(--color-sidebar-search-icon);content:"";height:var(--sidebar-search-icon-size);left:var(--sidebar-item-spacing-horizontal);-webkit-mask-image:var(--icon-search);mask-image:var(--icon-search);position:absolute;width:var(--sidebar-search-icon-size)}.sidebar-search{background:transparent;border:none;border-bottom:1px solid var(--color-sidebar-search-border);border-top:1px solid var(--color-sidebar-search-border);box-sizing:border-box;color:var(--color-sidebar-search-foreground);padding:var(--sidebar-search-input-spacing-vertical) var(--sidebar-search-input-spacing-horizontal) var(--sidebar-search-input-spacing-vertical) calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size));width:100%;z-index:10}.sidebar-search:focus{outline:none}.sidebar-search::-moz-placeholder{font-size:var(--sidebar-search-input-font-size)}.sidebar-search::placeholder{font-size:var(--sidebar-search-input-font-size)}#searchbox .highlight-link{margin:0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0;text-align:center}#searchbox .highlight-link a{color:var(--color-sidebar-search-icon);font-size:var(--font-size--small--2)}.sidebar-tree{font-size:var(--sidebar-item-font-size);margin-bottom:var(--sidebar-item-spacing-vertical);margin-top:var(--sidebar-tree-space-above)}.sidebar-tree ul{display:flex;flex-direction:column;list-style:none;margin-bottom:0;margin-top:0;padding:0}.sidebar-tree li{margin:0;position:relative}.sidebar-tree li>ul{margin-left:var(--sidebar-item-spacing-horizontal)}.sidebar-tree .icon,.sidebar-tree .reference{color:var(--color-sidebar-link-text)}.sidebar-tree .reference{box-sizing:border-box;display:inline-block;height:100%;line-height:var(--sidebar-item-line-height);overflow-wrap:anywhere;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-decoration:none;width:100%}.sidebar-tree .reference:hover{background:var(--color-sidebar-item-background--hover);color:var(--color-sidebar-link-text)}.sidebar-tree .reference.external:after{color:var(--color-sidebar-link-text);content:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23607D8B' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' viewBox='0 0 24 24'%3E%3Cpath stroke='none' d='M0 0h24v24H0z'/%3E%3Cpath d='M11 7H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-5M10 14 20 4M15 4h5v5'/%3E%3C/svg%3E");margin:0 .25rem;vertical-align:middle}.sidebar-tree .current-page>.reference{font-weight:700}.sidebar-tree label{align-items:center;cursor:pointer;display:flex;height:var(--sidebar-item-height);justify-content:center;position:absolute;right:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:var(--sidebar-expander-width)}.sidebar-tree .caption,.sidebar-tree :not(.caption)>.caption-text{color:var(--color-sidebar-caption-text);font-size:var(--sidebar-caption-font-size);font-weight:700;margin:var(--sidebar-caption-space-above) 0 0 0;padding:var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal);text-transform:uppercase}.sidebar-tree li.has-children>.reference{padding-right:var(--sidebar-expander-width)}.sidebar-tree .toctree-l1>.reference,.sidebar-tree .toctree-l1>label .icon{color:var(--color-sidebar-link-text--top-level)}.sidebar-tree label{background:var(--color-sidebar-item-expander-background)}.sidebar-tree label:hover{background:var(--color-sidebar-item-expander-background--hover)}.sidebar-tree .current>.reference{background:var(--color-sidebar-item-background--current)}.sidebar-tree .current>.reference:hover{background:var(--color-sidebar-item-background--hover)}.toctree-checkbox{display:none;position:absolute}.toctree-checkbox~ul{display:none}.toctree-checkbox~label .icon svg{transform:rotate(90deg)}.toctree-checkbox:checked~ul{display:block}.toctree-checkbox:checked~label .icon svg{transform:rotate(-90deg)}.toc-title-container{padding:var(--toc-title-padding);padding-top:var(--toc-spacing-vertical)}.toc-title{color:var(--color-toc-title-text);font-size:var(--toc-title-font-size);padding-left:var(--toc-spacing-horizontal);text-transform:uppercase}.no-toc{display:none}.toc-tree-container{padding-bottom:var(--toc-spacing-vertical)}.toc-tree{border-left:1px solid var(--color-background-border);font-size:var(--toc-font-size);line-height:1.3;padding-left:calc(var(--toc-spacing-horizontal) - var(--toc-item-spacing-horizontal))}.toc-tree>ul>li:first-child{padding-top:0}.toc-tree>ul>li:first-child>ul{padding-left:0}.toc-tree>ul>li:first-child>a{display:none}.toc-tree ul{list-style-type:none;margin-bottom:0;margin-top:0;padding-left:var(--toc-item-spacing-horizontal)}.toc-tree li{padding-top:var(--toc-item-spacing-vertical)}.toc-tree li.scroll-current>.reference{color:var(--color-toc-item-text--active);font-weight:700}.toc-tree a.reference{color:var(--color-toc-item-text);overflow-wrap:anywhere;text-decoration:none}.toc-scroll{max-height:100vh;overflow-y:scroll}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here){background:rgba(255,0,0,.25);color:var(--color-problematic)}.contents:not(.this-will-duplicate-information-and-it-is-still-useful-here):before{content:"ERROR: Adding a table of contents in Furo-based documentation is unnecessary, and does not work well with existing styling. Add a 'this-will-duplicate-information-and-it-is-still-useful-here' class, if you want an escape hatch."}.text-align\:left>p{text-align:left}.text-align\:center>p{text-align:center}.text-align\:right>p{text-align:right} +/*# sourceMappingURL=furo.css.map*/ \ No newline at end of file diff --git a/docs/html/_static/styles/furo.css.map b/docs/html/_static/styles/furo.css.map new file mode 100644 index 00000000..3ecc3715 --- /dev/null +++ b/docs/html/_static/styles/furo.css.map @@ -0,0 +1 @@ +{"version":3,"file":"styles/furo.css","mappings":"AAAA,2EAA2E,CAU3E,KACE,gBAAiB,CACjB,6BACF,CASA,KACE,QACF,CAMA,KACE,aACF,CAOA,GACE,aAAc,CACd,cACF,CAUA,GACE,sBAAuB,CACvB,QAAS,CACT,gBACF,CAOA,IACE,+BAAiC,CACjC,aACF,CASA,EACE,4BACF,CAOA,YACE,kBAAmB,CACnB,yBAA0B,CAC1B,gCACF,CAMA,SAEE,kBACF,CAOA,cAGE,+BAAiC,CACjC,aACF,CAeA,QAEE,aAAc,CACd,aAAc,CACd,iBAAkB,CAClB,uBACF,CAEA,IACE,aACF,CAEA,IACE,SACF,CASA,IACE,iBACF,CAUA,sCAKE,mBAAoB,CACpB,cAAe,CACf,gBAAiB,CACjB,QACF,CAOA,aAEE,gBACF,CAOA,cAEE,mBACF,CAMA,gDAIE,yBACF,CAMA,wHAIE,iBAAkB,CAClB,SACF,CAMA,4GAIE,6BACF,CAMA,SACE,0BACF,CASA,OACE,qBAAsB,CACtB,aAAc,CACd,aAAc,CACd,cAAe,CACf,SAAU,CACV,kBACF,CAMA,SACE,uBACF,CAMA,SACE,aACF,CAOA,6BAEE,qBAAsB,CACtB,SACF,CAMA,kFAEE,WACF,CAOA,cACE,4BAA6B,CAC7B,mBACF,CAMA,yCACE,uBACF,CAOA,6BACE,yBAA0B,CAC1B,YACF,CASA,QACE,aACF,CAMA,QACE,iBACF,CAiBA,kBACE,YACF,CCvVA,aAcE,kEACE,uBAOF,WACE,iDAMF,kCACE,wBAEF,qCAEE,uBADA,uBACA,CAEF,SACE,wBAtBA,CCpBJ,iBAGE,qBAEA,sBACA,0BAFA,oBAHA,4BACA,oBAKA,6BAIA,2CAFA,mBACA,sCAFA,4BAGA,CAEF,gBACE,aCTF,KCGE,mHAEA,wGAEA,wCAAyC,CAEzC,wBAAyB,CACzB,wBAAyB,CACzB,4BAA6B,CAC7B,yBAA0B,CAC1B,2BAA4B,CAG5B,sDAAuD,CACvD,gDAAiD,CACjD,wDAAyD,CAGzD,0CAA2C,CAC3C,gDAAiD,CACjD,gDAAiD,CAKjD,gCAAiC,CACjC,sCAAuC,CAGvC,2CAA4C,CAG5C,uCAAwC,CCjCxC,+FAGA,uBAAwB,CAGxB,iCAAkC,CAClC,kCAAmC,CAEnC,+BAAgC,CAChC,sCAAuC,CACvC,sCAAuC,CACvC,qGAIA,mDAAoD,CAEpD,mCAAoC,CACpC,8CAA+C,CAC/C,gDAAiD,CACjD,kCAAmC,CACnC,6DAA8D,CAG9D,6BAA8B,CAC9B,6BAA8B,CAC9B,+BAAgC,CAChC,kCAAmC,CACnC,kCAAmC,CCPjC,+jBCYA,iqCAZF,iaCVA,8KAOA,4SAWA,4SAUA,0CACA,gEAGA,0CAGA,gEAGA,yCACA,+DAIA,4CACA,kEAGA,wCAUA,8DACA,uCAGA,4DACA,sCACA,2DAGA,4CACA,kEACA,uCAGA,6DACA,2GAGA,sHAEA,yFAEA,+CACA,+EAGA,4MAOA,gCACA,sHAIA,kCACA,uEACA,gEACA,4DACA,kEAGA,2DACA,sDACA,0CACA,8CACA,wGAGA,0BACA,iCAGA,+DACA,+BACA,sCACA,+DAEA,kGACA,oCACA,yDACA,sCL7HF,kCAEA,sDAIA,0CK2HE,kEAIA,oDACA,sDAGA,oCACA,oEAEA,0DACA,qDAIA,oDACA,6DAIA,iEAIA,2DAIA,2DAGA,4DACA,gEAIA,gEAEA,gFAEA,oNASA,qDLxKE,gFAGE,4DAIF,oEKkHF,yEAEA,6DAGA,0DAEA,uDACA,qDACA,wDAIA,6DAIA,yDACA,2DAIA,uCAGA,wCACA,sDAGA,+CAGA,6DAEA,iDACA,+DAEA,wDAEA,sEAMA,0DACA,sBACA,mEL9JI,wEAEA,iCACE,+BAMN,wEAGA,iCACE,kFAEA,uEAIF,gEACE,8BAGF,qEMvDA,sCAKA,wFAKA,iCAIA,0BAWA,iCACA,4BACA,mCAGA,+BAEA,sCACA,4BAEA,mCAEA,sCAKA,sDAIA,gCAEA,gEAQF,wCAME,sBACA,kCAKA,uBAEA,gEAIA,2BAIA,mCAEA,qCACA,iCAGE,+BACA,wEAEE,iCACA,kFAGF,6BACA,0CACF,kCAEE,8BACE,8BACA,qEAEE,sCACA,wFCnFN,iCAGF,2DAEE,4BACA,oCAGA,mIAGA,4HACE,gEAMJ,+CAGE,sBACA,yCAEF,uBAEE,sEAKA,gDACA,kEAGA,iFAGE,YAGF,EACA,4HAQF,mBACE,6BACA,mBACA,wCACA,wCACA,2CAIA,eAGA,mBAKE,mBAGA,CAJA,uCACA,iBAFF,gBACE,CAKE,mBACA,mBAGJ,oBAIF,+BAGE,kDACA,OADA,kBAGA,CAFA,gBAEA,mBACA,oBAEA,sCACA,OAGF,cAHE,WAGF,GAEE,oBACA,CAHF,gBAGE,CC9Gc,YDiHd,+CAIF,SAEE,CAPF,UACE,wBAMA,4BAEA,GAGA,uBACA,CAJA,yBAGA,CACA,iDAKA,2CAGA,2DAQA,iBACA,uCAGA,kEAKE,SAKJ,8BACE,yDACA,2BAEA,oBACA,8BAEA,yDAEE,4BAEJ,uCACE,CACA,iEAGA,CAEA,wCACE,uBACA,kDAEA,0DAEE,CAJF,oBAIE,0GAWN,aACE,CAHA,YAGA,4HASA,+CAGF,sBACE,WACA,WAQA,4BAFF,0CAEE,CARA,qCAsBA,CAdA,iBAEA,kBACE,aADF,4BACE,WAMF,2BAGF,qCAEE,CAXE,UAWF,+BAGA,uBAEA,SAEA,0CAIE,CANF,qCAEA,CAIE,2DACE,gBAIN,+CAIA,CAEA,kDAKE,CAPF,8BAEA,CAOE,YACA,CAjBI,2BAGN,CAHM,WAcJ,UAGA,CAEA,2GAIF,iCAGE,8BAIA,qBACA,oBACF,uBAOI,0CAIA,CATF,6DAKE,CALF,sBASE,qCAKF,CACE,cACA,CAFF,sBAEE,CACA,+BAEA,qBAEE,WAKN,aACE,sCAGA,mBAEA,6BAMA,kCACA,CAJA,sBACA,aAEA,CAJA,eACA,MAIA,2FAEA,UAGA,YACA,sBACE,8BAEA,CALF,aACA,WAIE,OACA,oBAEF,uBACE,WAEF,YAFE,UAEF,eAgBA,kBACE,CAhBA,qDAQF,qCAGF,CAGI,YACF,CAJF,2BAGI,CAEA,eACA,qBAGA,mEAEA,qBACA,8BAIA,kBADF,kBACE,yBAEJ,oCAGI,qDAIJ,+BAGI,oCAEA,+CAQF,4CACE,yBACF,2BAOE,sBACA,CAHA,WACA,CAFF,cACE,CAJA,YAGF,CAEE,SAEA,mBAGA,kDAEE,CAJF,cAEA,cAEE,sBAEA,mBADA,YACA,uBACA,mDACE,CADF,YACE,iDAEA,uCAEN,+DAOE,mBADF,sBACE,mBAGF,aACE,sCAIA,aADF,WACE,CAKF,SACE,CAHJ,kBAEE,CAJE,gBAEJ,CAHI,iBAMA,yFAKA,aACA,eACA,cElbJ,iBAEE,aADA,iBACA,6BAEA,kCAEA,SACA,UAIA,gCACA,CALA,SAEA,SAEA,CAJA,0EAEA,CAFA,OAKA,CAGA,mDACE,iBAGF,gCACE,CADF,UACE,aAEJ,iCAEE,CAFF,UAEE,wCAEA,WACA,WADA,UACA,CACA,4CAGA,MACA,CADA,KACA,wCACA,UAGA,CAJA,UAIA,6DAUA,0CACE,CAFF,mBAEE,wEACA,CAVA,YACA,CAMF,mBAJE,OAOA,gBAJJ,gCACE,CANE,cACA,CAHA,oBACA,CAGA,QAGJ,CAII,0BACA,CADA,UACA,wCAEJ,kBACE,0DACA,gCACE,kBACA,CADA,YACA,oEACA,2CAMF,mDAII,CALN,YACE,CANE,cAKJ,CACE,iBAII,kEACA,yCACE,kDACA,yDACE,+CACA,uBANN,CAMM,+BANN,uCACE,qDACA,4BAEE,mBADA,0CACA,CADA,qBACA,0DACE,wCACA,sGALJ,oCACA,sBACE,kBAFF,UAEE,2CACA,wFACE,cACA,kEANN,uBACE,iDACA,CADA,UACA,0DACE,wDAEE,iEACA,qEANN,sCACE,CAGE,iBAHF,gBAGE,qBACE,CAJJ,uBACA,gDACE,wDACA,6DAHF,2CACA,CADA,gBACA,eACE,CAGE,sBANN,8BACE,CAII,iBAFF,4DACA,WACE,YADF,uCACE,6EACA,2BANN,8CACE,kDACA,0CACE,8BACA,yFACE,sBACA,sFALJ,mEACA,sBACE,kEACA,6EACE,uCACA,kEALJ,qGAEE,kEACA,6EACE,uCACA,kEALJ,8CACA,uDACE,sEACA,2EACE,sCACA,iEALJ,mGACA,qCACE,oDACA,0DACE,6GACA,gDAGR,yDCrEA,sEACE,CACA,6GACE,gEACF,iGAIF,wFACE,qDAGA,mGAEE,2CAEF,4FACE,gCACF,wGACE,8DAEE,6FAIA,iJAKN,6GACE,gDAKF,yDACA,qCAGA,6BACA,kBACA,qDAKA,oCAEA,+DAGA,2CAGE,oDAIA,oEAEE,qBAGJ,wDAEE,uCAEF,kEAGA,8CAEA,uDAIF,gEAIE,6BACA,gEAIA,+CACE,0EAIF,sDAEE,+DAGF,sCACA,8BACE,oCAEJ,wBACE,4FAEE,gBAEJ,yGAGI,kBAGJ,CCnHE,2MCFF,oBAGE,wGAKA,iCACE,CADF,wBACE,8GAQA,mBCjBJ,2GAIE,mBACA,6HAMA,YACE,mIAYF,eACA,CAHF,YAGE,4FAGE,8BAKF,uBAkBE,sCACA,CADA,qBAbA,wCAIA,CALF,8BACE,CADF,gBAKE,wCACA,CAOA,kDACA,CACA,kCAKF,6BAGA,4CACE,kDACA,eAGF,cACE,aACA,iBACA,yBACA,8BACA,WAGJ,2BACE,cAGA,+BACA,CAHA,eAGA,wCACA,YACA,iBACA,uEAGA,0BACA,2CAEA,8EAGI,qBACA,CAFF,kBAEE,kBAGN,0CAGE,mCAGA,4BAIA,gEACE,qCACA,8BAEA,gBACA,+CACA,iCAEF,iCAEE,gEACA,qCAGF,8BAEE,+BAIA,yCAEE,qBADA,gBACA,yBAKF,eACA,CAFF,YACE,CACA,iBACA,qDAEA,mDCvIJ,2FAOE,iCACA,CAEA,eACA,CAHA,kBAEA,CAFA,wBAGA,8BACA,eACE,CAFF,YAEE,0BACA,8CAGA,oBACE,oCAGA,kBACE,8DAEA,iBAEN,UACE,8BAIJ,+CAEE,qDAEF,kDAIE,YAEF,CAFE,YAEF,CCpCE,mFADA,kBAKE,CAJF,IAGA,aACE,mCAGA,iDACE,+BAEJ,wBAEE,mBAMA,6CAEF,CAJE,mBAEA,CAEF,kCAGE,CARF,kBACE,CAHA,eAUA,YACA,mBACA,CADA,UACA,wCC9BF,oBDkCE,wBCnCJ,uCACE,+BACA,+DACA,sBAGA,qBCDA,6CAIE,CAPF,uBAGA,CDGE,oBACF,yDAEE,CCDE,2CAGF,CAJA,kCACE,CDJJ,YACE,CAIA,eCTF,CDKE,uBCMA,gCACE,YAEF,oCAEE,wBACA,0BAIF,iBAEA,cADF,UACE,uBAEA,iCAEA,wCAEA,6CAMA,CAYF,gCATI,4BASJ,CAZE,mCAEE,iCAUJ,4BAGE,4DADA,+BACA,CAHF,qBAGE,sCACE,OAEF,iBAHA,SAGA,iHACE,2DAKF,CANA,8EAMA,uSAEE,kBAEF,+FACE,yCCjEJ,WACA,yBAGA,uBACA,gBAEA,uCAIA,CAJA,iCAIA,uCAGA,UACE,gBACA,qBAEA,0CClBJ,gBACE,KAGF,qBACE,YAGF,CAHE,cAGF,gCAEE,mBACA,iEAEA,oCACA,wCAEA,sBACA,WAEA,CAFA,YAEA,8EAEA,mCAFA,iBAEA,6BAIA,wEAKA,sDAIE,CARF,mDAIA,CAIE,cAEF,8CAIA,oBAFE,iBAEF,8CAGE,eAEF,CAFE,YAEF,OAEE,kBAGJ,CAJI,eACA,CAFF,mBAKF,yCCjDE,oBACA,CAFA,iBAEA,uCAKE,iBACA,qCAGA,mBCZJ,CDWI,gBCXJ,6BAEE,eACA,sBAGA,eAEA,sBACA,oDACA,iGAMA,gBAFE,YAEF,8FAME,iJCnBF,YACA,gNAWE,gDAEF,iSAaE,kBACE,gHAKF,oCACE,eACF,CADE,UACF,8CACE,gDACF,wCACE,oBCxCJ,oBAEF,6BACE,QACE,kDAGF,yBACE,kDAmBA,kDAEF,CAhBA,+CAaA,CAbA,oBAaA,0FACE,CADF,gGAfF,cACE,gBACA,CAaA,0BAGA,mQACE,gBAGF,oMACE,iBACA,CAFF,eACE,CADF,gBAEE,aAGJ,iCAEE,CAFF,wCAEE,wBAUE,+VAIE,uEAHA,2BAGA,wXAKJ,iDAGF,CARM,+CACE,iDAIN,CALI,gBAQN,mHACE,gBAGF,2DACE,0EAOA,0EAGF,gBAEE,6DC/EA,kDACA,gCACA,qDAGA,qBACA,qDCFA,cACA,eAEA,yBAGF,sBAEE,iBACA,sNAWA,iBACE,kBACA,wRAgBA,kBAEA,iOAgBA,uCACE,uEAEA,kBAEF,qUAuBE,iDAIJ,CACA,geCxFF,4BAEE,CAQA,6JACA,iDAIA,sEAGA,mDAOF,iDAGE,4DAIA,8CACA,qDAEE,eAFF,cAEE,oBAEF,uBAFE,kCAGA,eACA,iBACA,mBAIA,mDACA,CAHA,uCAEA,CAJA,0CACA,CAIA,gBAJA,gBACA,oBADA,gBAIA,wBAEJ,gBAGE,6BACA,YAHA,iBAGA,gCACA,iEAEA,6CACA,sDACA,0BADA,wBACA,0BACA,oIAIA,mBAFA,YAEA,qBACA,0CAIE,uBAEF,CAHA,yBACE,CAEF,iDACE,mFAKJ,oCACE,CANE,aAKJ,CACE,qEAIA,YAFA,WAEA,CAHA,aACA,CAEA,gBACE,4BACA,sBADA,aACA,gCAMF,oCACA,yDACA,2CAEA,qBAGE,kBAEA,CACA,mCAIF,CARE,YACA,CAOF,iCAEE,CAPA,oBACA,CAQA,oBACE,uDAEJ,sDAGA,CAHA,cAGA,0BACE,oDAIA,oCACA,4BACA,sBAGA,cAEA,oFAGA,sBAEA,yDACE,CAIF,iBAJE,wBAIF,6CAHE,6CAKA,eACA,aACA,CADA,cACA,yCAGJ,kBACE,CAKA,iDAEA,CARF,aACE,4CAGA,kBAIA,wEAGA,wDAGA,kCAOA,iDAGA,CAPF,WAEE,sCAEA,CAJF,2CACE,CAMA,qCACA,+BARF,kBACE,qCAOA,iBAsBA,sBACE,CAvBF,WAKA,CACE,0DAIF,CALA,uDACE,CANF,sBAqBA,4CACA,CALA,gRAIA,YAEE,6CAEN,mCAEE,+CASA,6EAIA,4BChNA,SDmNA,qFCnNA,gDACA,sCAGA,qCACA,sDACA,CAKA,kDAGA,CARA,0CAQA,kBAGA,YACA,sBACA,iBAFA,gBADF,YACE,CAHA,SAKA,kBAEA,SAFA,iBAEA,uEAGA,CAEE,6CAFF,oCAgBI,CAdF,yBACE,qBACF,CAGF,oBACE,CAIF,WACE,CALA,2CAGA,uBACF,CACE,mFAGE,CALF,qBAEA,UAGE,gCAIF,sDAEA,CALE,oCAKF,yCC7CJ,oCACE,CD+CA,yXAQE,sCCrDJ,wCAGA,oCACE","sources":["webpack:///./node_modules/normalize.css/normalize.css","webpack:///./src/furo/assets/styles/base/_print.sass","webpack:///./src/furo/assets/styles/base/_screen-readers.sass","webpack:///./src/furo/assets/styles/base/_theme.sass","webpack:///./src/furo/assets/styles/variables/_fonts.scss","webpack:///./src/furo/assets/styles/variables/_spacing.scss","webpack:///./src/furo/assets/styles/variables/_icons.scss","webpack:///./src/furo/assets/styles/variables/_admonitions.scss","webpack:///./src/furo/assets/styles/variables/_colors.scss","webpack:///./src/furo/assets/styles/base/_typography.sass","webpack:///./src/furo/assets/styles/_scaffold.sass","webpack:///./src/furo/assets/styles/variables/_layout.scss","webpack:///./src/furo/assets/styles/content/_admonitions.sass","webpack:///./src/furo/assets/styles/content/_api.sass","webpack:///./src/furo/assets/styles/content/_blocks.sass","webpack:///./src/furo/assets/styles/content/_captions.sass","webpack:///./src/furo/assets/styles/content/_code.sass","webpack:///./src/furo/assets/styles/content/_footnotes.sass","webpack:///./src/furo/assets/styles/content/_images.sass","webpack:///./src/furo/assets/styles/content/_indexes.sass","webpack:///./src/furo/assets/styles/content/_lists.sass","webpack:///./src/furo/assets/styles/content/_math.sass","webpack:///./src/furo/assets/styles/content/_misc.sass","webpack:///./src/furo/assets/styles/content/_rubrics.sass","webpack:///./src/furo/assets/styles/content/_sidebar.sass","webpack:///./src/furo/assets/styles/content/_tables.sass","webpack:///./src/furo/assets/styles/content/_target.sass","webpack:///./src/furo/assets/styles/content/_gui-labels.sass","webpack:///./src/furo/assets/styles/components/_footer.sass","webpack:///./src/furo/assets/styles/components/_sidebar.sass","webpack:///./src/furo/assets/styles/components/_table_of_contents.sass","webpack:///./src/furo/assets/styles/_shame.sass"],"sourcesContent":["/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n line-height: 1.15; /* 1 */\n -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n ========================================================================== */\n\n/**\n * Remove the margin in all browsers.\n */\n\nbody {\n margin: 0;\n}\n\n/**\n * Render the `main` element consistently in IE.\n */\n\nmain {\n display: block;\n}\n\n/**\n * Correct the font size and margin on `h1` elements within `section` and\n * `article` contexts in Chrome, Firefox, and Safari.\n */\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n/* Grouping content\n ========================================================================== */\n\n/**\n * 1. Add the correct box sizing in Firefox.\n * 2. Show the overflow in Edge and IE.\n */\n\nhr {\n box-sizing: content-box; /* 1 */\n height: 0; /* 1 */\n overflow: visible; /* 2 */\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\npre {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n ========================================================================== */\n\n/**\n * Remove the gray background on active links in IE 10.\n */\n\na {\n background-color: transparent;\n}\n\n/**\n * 1. Remove the bottom border in Chrome 57-\n * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n */\n\nabbr[title] {\n border-bottom: none; /* 1 */\n text-decoration: underline; /* 2 */\n text-decoration: underline dotted; /* 2 */\n}\n\n/**\n * Add the correct font weight in Chrome, Edge, and Safari.\n */\n\nb,\nstrong {\n font-weight: bolder;\n}\n\n/**\n * 1. Correct the inheritance and scaling of font size in all browsers.\n * 2. Correct the odd `em` font sizing in all browsers.\n */\n\ncode,\nkbd,\nsamp {\n font-family: monospace, monospace; /* 1 */\n font-size: 1em; /* 2 */\n}\n\n/**\n * Add the correct font size in all browsers.\n */\n\nsmall {\n font-size: 80%;\n}\n\n/**\n * Prevent `sub` and `sup` elements from affecting the line height in\n * all browsers.\n */\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -0.25em;\n}\n\nsup {\n top: -0.5em;\n}\n\n/* Embedded content\n ========================================================================== */\n\n/**\n * Remove the border on images inside links in IE 10.\n */\n\nimg {\n border-style: none;\n}\n\n/* Forms\n ========================================================================== */\n\n/**\n * 1. Change the font styles in all browsers.\n * 2. Remove the margin in Firefox and Safari.\n */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n font-family: inherit; /* 1 */\n font-size: 100%; /* 1 */\n line-height: 1.15; /* 1 */\n margin: 0; /* 2 */\n}\n\n/**\n * Show the overflow in IE.\n * 1. Show the overflow in Edge.\n */\n\nbutton,\ninput { /* 1 */\n overflow: visible;\n}\n\n/**\n * Remove the inheritance of text transform in Edge, Firefox, and IE.\n * 1. Remove the inheritance of text transform in Firefox.\n */\n\nbutton,\nselect { /* 1 */\n text-transform: none;\n}\n\n/**\n * Correct the inability to style clickable types in iOS and Safari.\n */\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\n/**\n * Remove the inner border and padding in Firefox.\n */\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n border-style: none;\n padding: 0;\n}\n\n/**\n * Restore the focus styles unset by the previous rule.\n */\n\nbutton:-moz-focusring,\n[type=\"button\"]:-moz-focusring,\n[type=\"reset\"]:-moz-focusring,\n[type=\"submit\"]:-moz-focusring {\n outline: 1px dotted ButtonText;\n}\n\n/**\n * Correct the padding in Firefox.\n */\n\nfieldset {\n padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n * 1. Correct the text wrapping in Edge and IE.\n * 2. Correct the color inheritance from `fieldset` elements in IE.\n * 3. Remove the padding so developers are not caught out when they zero out\n * `fieldset` elements in all browsers.\n */\n\nlegend {\n box-sizing: border-box; /* 1 */\n color: inherit; /* 2 */\n display: table; /* 1 */\n max-width: 100%; /* 1 */\n padding: 0; /* 3 */\n white-space: normal; /* 1 */\n}\n\n/**\n * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n */\n\nprogress {\n vertical-align: baseline;\n}\n\n/**\n * Remove the default vertical scrollbar in IE 10+.\n */\n\ntextarea {\n overflow: auto;\n}\n\n/**\n * 1. Add the correct box sizing in IE 10.\n * 2. Remove the padding in IE 10.\n */\n\n[type=\"checkbox\"],\n[type=\"radio\"] {\n box-sizing: border-box; /* 1 */\n padding: 0; /* 2 */\n}\n\n/**\n * Correct the cursor style of increment and decrement buttons in Chrome.\n */\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n/**\n * 1. Correct the odd appearance in Chrome and Safari.\n * 2. Correct the outline style in Safari.\n */\n\n[type=\"search\"] {\n -webkit-appearance: textfield; /* 1 */\n outline-offset: -2px; /* 2 */\n}\n\n/**\n * Remove the inner padding in Chrome and Safari on macOS.\n */\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n/**\n * 1. Correct the inability to style clickable types in iOS and Safari.\n * 2. Change font properties to `inherit` in Safari.\n */\n\n::-webkit-file-upload-button {\n -webkit-appearance: button; /* 1 */\n font: inherit; /* 2 */\n}\n\n/* Interactive\n ========================================================================== */\n\n/*\n * Add the correct display in Edge, IE 10+, and Firefox.\n */\n\ndetails {\n display: block;\n}\n\n/*\n * Add the correct display in all browsers.\n */\n\nsummary {\n display: list-item;\n}\n\n/* Misc\n ========================================================================== */\n\n/**\n * Add the correct display in IE 10+.\n */\n\ntemplate {\n display: none;\n}\n\n/**\n * Add the correct display in IE 10.\n */\n\n[hidden] {\n display: none;\n}\n","// This file contains styles for managing print media.\n\n////////////////////////////////////////////////////////////////////////////////\n// Hide elements not relevant to print media.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Hide icon container.\n .content-icon-container\n display: none !important\n\n // Hide showing header links if hovering over when printing.\n .headerlink\n display: none !important\n\n // Hide mobile header.\n .mobile-header\n display: none !important\n\n // Hide navigation links.\n .related-pages\n display: none !important\n\n////////////////////////////////////////////////////////////////////////////////\n// Tweaks related to decolorization.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n // Apply a border around code which no longer have a color background.\n .highlight\n border: 0.1pt solid var(--color-foreground-border)\n\n////////////////////////////////////////////////////////////////////////////////\n// Avoid page break in some relevant cases.\n////////////////////////////////////////////////////////////////////////////////\n@media print\n ul, ol, dl, a, table, pre, blockquote, p\n page-break-inside: avoid\n\n h1, h2, h3, h4, h5, h6, img, figure, caption\n page-break-inside: avoid\n page-break-after: avoid\n\n ul, ol, dl\n page-break-before: avoid\n",".visually-hidden\n position: absolute !important\n width: 1px !important\n height: 1px !important\n padding: 0 !important\n margin: -1px !important\n overflow: hidden !important\n clip: rect(0,0,0,0) !important\n white-space: nowrap !important\n border: 0 !important\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n:-moz-focusring\n outline: auto\n","// This file serves as the \"skeleton\" of the theming logic.\n//\n// This contains the bulk of the logic for handling dark mode, color scheme\n// toggling and the handling of color-scheme-specific hiding of elements.\n\nbody\n @include fonts\n @include spacing\n @include icons\n @include admonitions\n @include default-admonition(#651fff, \"abstract\")\n @include default-topic(#14B8A6, \"pencil\")\n\n @include colors\n\n.only-light\n display: block !important\nhtml body .only-dark\n display: none !important\n\n// Ignore dark-mode hints if print media.\n@media not print\n // Enable dark-mode, if requested.\n body[data-theme=\"dark\"]\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n // Enable dark mode, unless explicitly told to avoid.\n @media (prefers-color-scheme: dark)\n body:not([data-theme=\"light\"])\n @include colors-dark\n\n html & .only-light\n display: none !important\n .only-dark\n display: block !important\n\n//\n// Theme toggle presentation\n//\nbody[data-theme=\"auto\"]\n .theme-toggle svg.theme-icon-when-auto-light\n display: block\n\n @media (prefers-color-scheme: dark)\n .theme-toggle svg.theme-icon-when-auto-dark\n display: block\n .theme-toggle svg.theme-icon-when-auto-light\n display: none\n\nbody[data-theme=\"dark\"]\n .theme-toggle svg.theme-icon-when-dark\n display: block\n\nbody[data-theme=\"light\"]\n .theme-toggle svg.theme-icon-when-light\n display: block\n","// Fonts used by this theme.\n//\n// There are basically two things here -- using the system font stack and\n// defining sizes for various elements in %ages. We could have also used `em`\n// but %age is easier to reason about for me.\n\n@mixin fonts {\n // These are adapted from https://systemfontstack.com/\n --font-stack: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --font-stack--monospace: \"SFMono-Regular\", Menlo, Consolas, Monaco,\n Liberation Mono, Lucida Console, monospace;\n --font-stack--headings: var(--font-stack);\n\n --font-size--normal: 100%;\n --font-size--small: 87.5%;\n --font-size--small--2: 81.25%;\n --font-size--small--3: 75%;\n --font-size--small--4: 62.5%;\n\n // Sidebar\n --sidebar-caption-font-size: var(--font-size--small--2);\n --sidebar-item-font-size: var(--font-size--small);\n --sidebar-search-input-font-size: var(--font-size--small);\n\n // Table of Contents\n --toc-font-size: var(--font-size--small--3);\n --toc-font-size--mobile: var(--font-size--normal);\n --toc-title-font-size: var(--font-size--small--4);\n\n // Admonitions\n //\n // These aren't defined in terms of %ages, since nesting these is permitted.\n --admonition-font-size: 0.8125rem;\n --admonition-title-font-size: 0.8125rem;\n\n // Code\n --code-font-size: var(--font-size--small--2);\n\n // API\n --api-font-size: var(--font-size--small);\n}\n","// Spacing for various elements on the page\n//\n// If the user wants to tweak things in a certain way, they are permitted to.\n// They also have to deal with the consequences though!\n\n@mixin spacing {\n // Header!\n --header-height: calc(\n var(--sidebar-item-line-height) + 4 * #{var(--sidebar-item-spacing-vertical)}\n );\n --header-padding: 0.5rem;\n\n // Sidebar\n --sidebar-tree-space-above: 1.5rem;\n --sidebar-caption-space-above: 1rem;\n\n --sidebar-item-line-height: 1rem;\n --sidebar-item-spacing-vertical: 0.5rem;\n --sidebar-item-spacing-horizontal: 1rem;\n --sidebar-item-height: calc(\n var(--sidebar-item-line-height) + 2 *#{var(--sidebar-item-spacing-vertical)}\n );\n\n --sidebar-expander-width: var(--sidebar-item-height); // be square\n\n --sidebar-search-space-above: 0.5rem;\n --sidebar-search-input-spacing-vertical: 0.5rem;\n --sidebar-search-input-spacing-horizontal: 0.5rem;\n --sidebar-search-input-height: 1rem;\n --sidebar-search-icon-size: var(--sidebar-search-input-height);\n\n // Table of Contents\n --toc-title-padding: 0.25rem 0;\n --toc-spacing-vertical: 1.5rem;\n --toc-spacing-horizontal: 1.5rem;\n --toc-item-spacing-vertical: 0.4rem;\n --toc-item-spacing-horizontal: 1rem;\n}\n","// Expose theme icons as CSS variables.\n\n$icons: (\n // Adapted from tabler-icons\n // url: https://tablericons.com/\n \"search\":\n url('data:image/svg+xml;charset=utf-8,'),\n // Factored out from mkdocs-material on 24-Aug-2020.\n // url: https://squidfunk.github.io/mkdocs-material/reference/admonitions/\n \"pencil\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"abstract\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"info\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"flame\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"question\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"warning\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"failure\":\n url('data:image/svg+xml;charset=utf-8,'),\n \"spark\":\n url('data:image/svg+xml;charset=utf-8,')\n);\n\n@mixin icons {\n @each $name, $glyph in $icons {\n --icon-#{$name}: #{$glyph};\n }\n}\n","// Admonitions\n\n// Structure of these is:\n// admonition-class: color \"icon-name\";\n//\n// The colors are translated into CSS variables below. The icons are\n// used directly in the main declarations to set the `mask-image` in\n// the title.\n\n// prettier-ignore\n$admonitions: (\n // Each of these has an reST directives for it.\n \"caution\": #ff9100 \"spark\",\n \"warning\": #ff9100 \"warning\",\n \"danger\": #ff5252 \"spark\",\n \"attention\": #ff5252 \"warning\",\n \"error\": #ff5252 \"failure\",\n \"hint\": #00c852 \"question\",\n \"tip\": #00c852 \"info\",\n \"important\": #00bfa5 \"flame\",\n \"note\": #00b0ff \"pencil\",\n \"seealso\": #448aff \"info\",\n \"admonition-todo\": #808080 \"pencil\"\n);\n\n@mixin default-admonition($color, $icon-name) {\n --color-admonition-title: #{$color};\n --color-admonition-title-background: #{rgba($color, 0.2)};\n\n --icon-admonition-default: var(--icon-#{$icon-name});\n}\n\n@mixin default-topic($color, $icon-name) {\n --color-topic-title: #{$color};\n --color-topic-title-background: #{rgba($color, 0.2)};\n\n --icon-topic-default: var(--icon-#{$icon-name});\n}\n\n@mixin admonitions {\n @each $name, $values in $admonitions {\n --color-admonition-title--#{$name}: #{nth($values, 1)};\n --color-admonition-title-background--#{$name}: #{rgba(\n nth($values, 1),\n 0.2\n )};\n }\n}\n","// Colors used throughout this theme.\n//\n// The aim is to give the user more control. Thus, instead of hard-coding colors\n// in various parts of the stylesheet, the approach taken is to define all\n// colors as CSS variables and reusing them in all the places.\n//\n// `colors-dark` depends on `colors` being included at a lower specificity.\n\n@mixin colors {\n --color-problematic: #b30000;\n\n // Base Colors\n --color-foreground-primary: black; // for main text and headings\n --color-foreground-secondary: #5a5c63; // for secondary text\n --color-foreground-muted: #6b6f76; // for muted text\n --color-foreground-border: #878787; // for content borders\n\n --color-background-primary: white; // for content\n --color-background-secondary: #f8f9fb; // for navigation + ToC\n --color-background-hover: #efeff4ff; // for navigation-item hover\n --color-background-hover--transparent: #efeff400;\n --color-background-border: #eeebee; // for UI borders\n --color-background-item: #ccc; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #0a4bff;\n --color-brand-content: #2757dd;\n --color-brand-visited: #872ee0;\n\n // API documentation\n --color-api-background: var(--color-background-hover--transparent);\n --color-api-background-hover: var(--color-background-hover);\n --color-api-overall: var(--color-foreground-secondary);\n --color-api-name: var(--color-problematic);\n --color-api-pre-name: var(--color-problematic);\n --color-api-paren: var(--color-foreground-secondary);\n --color-api-keyword: var(--color-foreground-primary);\n\n --color-api-added: #21632c;\n --color-api-added-border: #38a84d;\n --color-api-changed: #046172;\n --color-api-changed-border: #06a1bc;\n --color-api-deprecated: #605706;\n --color-api-deprecated-border: #f0d90f;\n --color-api-removed: #b30000;\n --color-api-removed-border: #ff5c5c;\n\n --color-highlight-on-target: #ffffcc;\n\n // Inline code background\n --color-inline-code-background: var(--color-background-secondary);\n\n // Highlighted text (search)\n --color-highlighted-background: #ddeeff;\n --color-highlighted-text: var(--color-foreground-primary);\n\n // GUI Labels\n --color-guilabel-background: #ddeeff80;\n --color-guilabel-border: #bedaf580;\n --color-guilabel-text: var(--color-foreground-primary);\n\n // Admonitions!\n --color-admonition-background: transparent;\n\n //////////////////////////////////////////////////////////////////////////////\n // Everything below this should be one of:\n // - var(...)\n // - *-gradient(...)\n // - special literal values (eg: transparent, none)\n //////////////////////////////////////////////////////////////////////////////\n\n // Tables\n --color-table-header-background: var(--color-background-secondary);\n --color-table-border: var(--color-background-border);\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: transparent;\n --color-card-marginals-background: var(--color-background-secondary);\n\n // Header\n --color-header-background: var(--color-background-primary);\n --color-header-border: var(--color-background-border);\n --color-header-text: var(--color-foreground-primary);\n\n // Sidebar (left)\n --color-sidebar-background: var(--color-background-secondary);\n --color-sidebar-background-border: var(--color-background-border);\n\n --color-sidebar-brand-text: var(--color-foreground-primary);\n --color-sidebar-caption-text: var(--color-foreground-muted);\n --color-sidebar-link-text: var(--color-foreground-secondary);\n --color-sidebar-link-text--top-level: var(--color-brand-primary);\n\n --color-sidebar-item-background: var(--color-sidebar-background);\n --color-sidebar-item-background--current: var(\n --color-sidebar-item-background\n );\n --color-sidebar-item-background--hover: linear-gradient(\n 90deg,\n var(--color-background-hover--transparent) 0%,\n var(--color-background-hover) var(--sidebar-item-spacing-horizontal),\n var(--color-background-hover) 100%\n );\n\n --color-sidebar-item-expander-background: transparent;\n --color-sidebar-item-expander-background--hover: var(\n --color-background-hover\n );\n\n --color-sidebar-search-text: var(--color-foreground-primary);\n --color-sidebar-search-background: var(--color-background-secondary);\n --color-sidebar-search-background--focus: var(--color-background-primary);\n --color-sidebar-search-border: var(--color-background-border);\n --color-sidebar-search-icon: var(--color-foreground-muted);\n\n // Table of Contents (right)\n --color-toc-background: var(--color-background-primary);\n --color-toc-title-text: var(--color-foreground-muted);\n --color-toc-item-text: var(--color-foreground-secondary);\n --color-toc-item-text--hover: var(--color-foreground-primary);\n --color-toc-item-text--active: var(--color-brand-primary);\n\n // Actual page contents\n --color-content-foreground: var(--color-foreground-primary);\n --color-content-background: transparent;\n\n // Links\n --color-link: var(--color-brand-content);\n --color-link-underline: var(--color-background-border);\n --color-link--hover: var(--color-brand-content);\n --color-link-underline--hover: var(--color-foreground-border);\n\n --color-link--visited: var(--color-brand-visited);\n --color-link-underline--visited: var(--color-background-border);\n --color-link--visited--hover: var(--color-brand-visited);\n --color-link-underline--visited--hover: var(--color-foreground-border);\n}\n\n@mixin colors-dark {\n --color-problematic: #ee5151;\n\n // Base Colors\n --color-foreground-primary: #cfd0d0; // for main text and headings\n --color-foreground-secondary: #9ca0a5; // for secondary text\n --color-foreground-muted: #81868d; // for muted text\n --color-foreground-border: #666666; // for content borders\n\n --color-background-primary: #131416; // for content\n --color-background-secondary: #1a1c1e; // for navigation + ToC\n --color-background-hover: #1e2124ff; // for navigation-item hover\n --color-background-hover--transparent: #1e212400;\n --color-background-border: #303335; // for UI borders\n --color-background-item: #444; // for \"background\" items (eg: copybutton)\n\n // Announcements\n --color-announcement-background: #000000dd;\n --color-announcement-text: #eeebee;\n\n // Brand colors\n --color-brand-primary: #3d94ff;\n --color-brand-content: #5ca5ff;\n --color-brand-visited: #b27aeb;\n\n // Highlighted text (search)\n --color-highlighted-background: #083563;\n\n // GUI Labels\n --color-guilabel-background: #08356380;\n --color-guilabel-border: #13395f80;\n\n // API documentation\n --color-api-keyword: var(--color-foreground-secondary);\n --color-highlight-on-target: #333300;\n\n --color-api-added: #3db854;\n --color-api-added-border: #267334;\n --color-api-changed: #09b0ce;\n --color-api-changed-border: #056d80;\n --color-api-deprecated: #b1a10b;\n --color-api-deprecated-border: #6e6407;\n --color-api-removed: #ff7575;\n --color-api-removed-border: #b03b3b;\n\n // Admonitions\n --color-admonition-background: #18181a;\n\n // Cards\n --color-card-border: var(--color-background-secondary);\n --color-card-background: #18181a;\n --color-card-marginals-background: var(--color-background-hover);\n}\n","// This file contains the styling for making the content throughout the page,\n// including fonts, paragraphs, headings and spacing among these elements.\n\nbody\n font-family: var(--font-stack)\npre,\ncode,\nkbd,\nsamp\n font-family: var(--font-stack--monospace)\n\n// Make fonts look slightly nicer.\nbody\n -webkit-font-smoothing: antialiased\n -moz-osx-font-smoothing: grayscale\n\n// Line height from Bootstrap 4.1\narticle\n line-height: 1.5\n\n//\n// Headings\n//\nh1,\nh2,\nh3,\nh4,\nh5,\nh6\n line-height: 1.25\n font-family: var(--font-stack--headings)\n font-weight: bold\n\n border-radius: 0.5rem\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n margin-left: -0.5rem\n margin-right: -0.5rem\n padding-left: 0.5rem\n padding-right: 0.5rem\n\n + p\n margin-top: 0\n\nh1\n font-size: 2.5em\n margin-top: 1.75rem\n margin-bottom: 1rem\nh2\n font-size: 2em\n margin-top: 1.75rem\nh3\n font-size: 1.5em\nh4\n font-size: 1.25em\nh5\n font-size: 1.125em\nh6\n font-size: 1em\n\nsmall\n opacity: 75%\n font-size: 80%\n\n// Paragraph\np\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n\n// Horizontal rules\nhr.docutils\n height: 1px\n padding: 0\n margin: 2rem 0\n background-color: var(--color-background-border)\n border: 0\n\n.centered\n text-align: center\n\n// Links\na\n text-decoration: underline\n\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n &:visited\n color: var(--color-link--visited)\n text-decoration-color: var(--color-link-underline--visited)\n &:hover\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &.muted-link\n color: inherit\n &:hover\n color: var(--color-link--hover)\n text-decoration-color: var(--color-link-underline--hover)\n &:visited\n color: var(--color-link--visited--hover)\n text-decoration-color: var(--color-link-underline--visited--hover)\n","// This file contains the styles for the overall layouting of the documentation\n// skeleton, including the responsive changes as well as sidebar toggles.\n//\n// This is implemented as a mobile-last design, which isn't ideal, but it is\n// reasonably good-enough and I got pretty tired by the time I'd finished this\n// to move the rules around to fix this. Shouldn't take more than 3-4 hours,\n// if you know what you're doing tho.\n\n// HACK: Not all browsers account for the scrollbar width in media queries.\n// This results in horizontal scrollbars in the breakpoint where we go\n// from displaying everything to hiding the ToC. We accomodate for this by\n// adding a bit of padding to the TOC drawer, disabling the horizontal\n// scrollbar and allowing the scrollbars to cover the padding.\n// https://www.456bereastreet.com/archive/201301/media_query_width_and_vertical_scrollbars/\n\n// HACK: Always having the scrollbar visible, prevents certain browsers from\n// causing the content to stutter horizontally between taller-than-viewport and\n// not-taller-than-viewport pages.\n\nhtml\n overflow-x: hidden\n overflow-y: scroll\n scroll-behavior: smooth\n\n.sidebar-scroll, .toc-scroll, article[role=main] *\n // Override Firefox scrollbar style\n scrollbar-width: thin\n scrollbar-color: var(--color-foreground-border) transparent\n\n // Override Chrome scrollbar styles\n &::-webkit-scrollbar\n width: 0.25rem\n height: 0.25rem\n &::-webkit-scrollbar-thumb\n background-color: var(--color-foreground-border)\n border-radius: 0.125rem\n\n//\n// Overalls\n//\nhtml,\nbody\n height: 100%\n color: var(--color-foreground-primary)\n background: var(--color-background-primary)\n\n.skip-to-content\n position: fixed\n padding: 1rem\n border-radius: 1rem\n left: 0.25rem\n top: 0.25rem\n z-index: 40\n background: var(--color-background-primary)\n color: var(--color-foreground-primary)\n\n transform: translateY(-200%)\n transition: transform 300ms ease-in-out\n\n &:focus-within\n transform: translateY(0%)\n\narticle\n color: var(--color-content-foreground)\n background: var(--color-content-background)\n overflow-wrap: break-word\n\n.page\n display: flex\n // fill the viewport for pages with little content.\n min-height: 100%\n\n.mobile-header\n width: 100%\n height: var(--header-height)\n background-color: var(--color-header-background)\n color: var(--color-header-text)\n border-bottom: 1px solid var(--color-header-border)\n\n // Looks like sub-script/super-script have this, and we need this to\n // be \"on top\" of those.\n z-index: 10\n\n // We don't show the header on large screens.\n display: none\n\n // Add shadow when scrolled\n &.scrolled\n border-bottom: none\n box-shadow: 0 0 0.2rem rgba(0, 0, 0, 0.1), 0 0.2rem 0.4rem rgba(0, 0, 0, 0.2)\n\n .header-center\n a\n color: var(--color-header-text)\n text-decoration: none\n\n.main\n display: flex\n flex: 1\n\n// Sidebar (left) also covers the entire left portion of screen.\n.sidebar-drawer\n box-sizing: border-box\n\n border-right: 1px solid var(--color-sidebar-background-border)\n background: var(--color-sidebar-background)\n\n display: flex\n justify-content: flex-end\n // These next two lines took me two days to figure out.\n width: calc((100% - #{$full-width}) / 2 + #{$sidebar-width})\n min-width: $sidebar-width\n\n// Scroll-along sidebars\n.sidebar-container,\n.toc-drawer\n box-sizing: border-box\n width: $sidebar-width\n\n.toc-drawer\n background: var(--color-toc-background)\n // See HACK described on top of this document\n padding-right: 1rem\n\n.sidebar-sticky,\n.toc-sticky\n position: sticky\n top: 0\n height: min(100%, 100vh)\n height: 100vh\n\n display: flex\n flex-direction: column\n\n.sidebar-scroll,\n.toc-scroll\n flex-grow: 1\n flex-shrink: 1\n\n overflow: auto\n scroll-behavior: smooth\n\n// Central items.\n.content\n padding: 0 $content-padding\n width: $content-width\n\n display: flex\n flex-direction: column\n justify-content: space-between\n\n.icon\n display: inline-block\n height: 1rem\n width: 1rem\n svg\n width: 100%\n height: 100%\n\n//\n// Accommodate announcement banner\n//\n.announcement\n background-color: var(--color-announcement-background)\n color: var(--color-announcement-text)\n\n height: var(--header-height)\n display: flex\n align-items: center\n overflow-x: auto\n & + .page\n min-height: calc(100% - var(--header-height))\n\n.announcement-content\n box-sizing: border-box\n padding: 0.5rem\n min-width: 100%\n white-space: nowrap\n text-align: center\n\n a\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-announcement-text)\n\n &:hover\n color: var(--color-announcement-text)\n text-decoration-color: var(--color-link--hover)\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for theme\n////////////////////////////////////////////////////////////////////////////////\n.no-js .theme-toggle-container // don't show theme toggle if there's no JS\n display: none\n\n.theme-toggle-container\n display: flex\n\n.theme-toggle\n display: flex\n cursor: pointer\n border: none\n padding: 0\n background: transparent\n\n.theme-toggle svg\n height: 1.25rem\n width: 1.25rem\n color: var(--color-foreground-primary)\n display: none\n\n.theme-toggle-header\n display: flex\n align-items: center\n justify-content: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Toggles for elements\n////////////////////////////////////////////////////////////////////////////////\n.toc-overlay-icon, .nav-overlay-icon\n display: none\n cursor: pointer\n\n .icon\n color: var(--color-foreground-secondary)\n height: 1.5rem\n width: 1.5rem\n\n.toc-header-icon, .nav-overlay-icon\n // for when we set display: flex\n justify-content: center\n align-items: center\n\n.toc-content-icon\n height: 1.5rem\n width: 1.5rem\n\n.content-icon-container\n float: right\n display: flex\n margin-top: 1.5rem\n margin-left: 1rem\n margin-bottom: 1rem\n gap: 0.5rem\n\n .edit-this-page, .view-this-page\n svg\n color: inherit\n height: 1.25rem\n width: 1.25rem\n\n.sidebar-toggle\n position: absolute\n display: none\n// \n.sidebar-toggle[name=\"__toc\"]\n left: 20px\n.sidebar-toggle:checked\n left: 40px\n// \n\n.overlay\n position: fixed\n top: 0\n width: 0\n height: 0\n\n transition: width 0ms, height 0ms, opacity 250ms ease-out\n\n opacity: 0\n background-color: rgba(0, 0, 0, 0.54)\n.sidebar-overlay\n z-index: 20\n.toc-overlay\n z-index: 40\n\n// Keep things on top and smooth.\n.sidebar-drawer\n z-index: 30\n transition: left 250ms ease-in-out\n.toc-drawer\n z-index: 50\n transition: right 250ms ease-in-out\n\n// Show the Sidebar\n#__navigation:checked\n & ~ .sidebar-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .sidebar-drawer\n top: 0\n left: 0\n // Show the toc sidebar\n#__toc:checked\n & ~ .toc-overlay\n width: 100%\n height: 100%\n opacity: 1\n & ~ .page\n .toc-drawer\n top: 0\n right: 0\n\n////////////////////////////////////////////////////////////////////////////////\n// Back to top\n////////////////////////////////////////////////////////////////////////////////\n.back-to-top\n text-decoration: none\n\n display: none\n position: fixed\n left: 0\n top: 1rem\n padding: 0.5rem\n padding-right: 0.75rem\n border-radius: 1rem\n font-size: 0.8125rem\n\n background: var(--color-background-primary)\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), #6b728080 0px 0px 1px 0px\n\n z-index: 10\n\n margin-left: 50%\n transform: translateX(-50%)\n svg\n height: 1rem\n width: 1rem\n fill: currentColor\n display: inline-block\n\n span\n margin-left: 0.25rem\n\n .show-back-to-top &\n display: flex\n align-items: center\n\n////////////////////////////////////////////////////////////////////////////////\n// Responsive layouting\n////////////////////////////////////////////////////////////////////////////////\n// Make things a bit bigger on bigger screens.\n@media (min-width: $full-width + $sidebar-width)\n html\n font-size: 110%\n\n@media (max-width: $full-width)\n // Collapse \"toc\" into the icon.\n .toc-content-icon\n display: flex\n .toc-drawer\n position: fixed\n height: 100vh\n top: 0\n right: -$sidebar-width\n border-left: 1px solid var(--color-background-muted)\n .toc-tree\n border-left: none\n font-size: var(--toc-font-size--mobile)\n\n // Accomodate for a changed content width.\n .sidebar-drawer\n width: calc((100% - #{$full-width - $sidebar-width}) / 2 + #{$sidebar-width})\n\n@media (max-width: $content-padded-width + $sidebar-width)\n // Center the page\n .content\n margin-left: auto\n margin-right: auto\n padding: 0 $content-padding--small\n\n@media (max-width: $content-padded-width--small + $sidebar-width)\n // Collapse \"navigation\".\n .nav-overlay-icon\n display: flex\n .sidebar-drawer\n position: fixed\n height: 100vh\n width: $sidebar-width\n\n top: 0\n left: -$sidebar-width\n\n // Swap which icon is visible.\n .toc-header-icon, .theme-toggle-header\n display: flex\n .toc-content-icon, .theme-toggle-content\n display: none\n\n // Show the header.\n .mobile-header\n position: sticky\n top: 0\n display: flex\n justify-content: space-between\n align-items: center\n\n .header-left,\n .header-right\n display: flex\n height: var(--header-height)\n padding: 0 var(--header-padding)\n label\n height: 100%\n width: 100%\n user-select: none\n\n .nav-overlay-icon .icon,\n .theme-toggle svg\n height: 1.5rem\n width: 1.5rem\n\n // Add a scroll margin for the content\n :target\n scroll-margin-top: calc(var(--header-height) + 2.5rem)\n\n // Show back-to-top below the header\n .back-to-top\n top: calc(var(--header-height) + 0.5rem)\n\n // Accommodate for the header.\n .page\n flex-direction: column\n justify-content: center\n\n@media (max-width: $content-width + 2* $content-padding--small)\n // Content should respect window limits.\n .content\n width: 100%\n overflow-x: auto\n\n@media (max-width: $content-width)\n article[role=main] aside.sidebar\n float: none\n width: 100%\n margin: 1rem 0\n","// Overall Layout Variables\n//\n// Because CSS variables can't be used in media queries. The fact that this\n// makes the layout non-user-configurable is a good thing.\n$content-padding: 3em;\n$content-padding--small: 1em;\n$content-width: 46em;\n$sidebar-width: 15em;\n$content-padded-width: $content-width + 2 * $content-padding;\n$content-padded-width--small: $content-width + 2 * $content-padding--small;\n$full-width: $content-padded-width + 2 * $sidebar-width;\n","//\n// The design here is strongly inspired by mkdocs-material.\n.admonition, .topic\n margin: 1rem auto\n padding: 0 0.5rem 0.5rem 0.5rem\n\n background: var(--color-admonition-background)\n\n border-radius: 0.2rem\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n font-size: var(--admonition-font-size)\n\n overflow: hidden\n page-break-inside: avoid\n\n // First element should have no margin, since the title has it.\n > :nth-child(2)\n margin-top: 0\n\n // Last item should have no margin, since we'll control that w/ padding\n > :last-child\n margin-bottom: 0\n\n.admonition p.admonition-title,\np.topic-title\n position: relative\n margin: 0 -0.5rem 0.5rem\n padding-left: 2rem\n padding-right: .5rem\n padding-top: .4rem\n padding-bottom: .4rem\n\n font-weight: 500\n font-size: var(--admonition-title-font-size)\n line-height: 1.3\n\n // Our fancy icon\n &::before\n content: \"\"\n position: absolute\n left: 0.5rem\n width: 1rem\n height: 1rem\n\n// Default styles\np.admonition-title\n background-color: var(--color-admonition-title-background)\n &::before\n background-color: var(--color-admonition-title)\n mask-image: var(--icon-admonition-default)\n mask-repeat: no-repeat\n\np.topic-title\n background-color: var(--color-topic-title-background)\n &::before\n background-color: var(--color-topic-title)\n mask-image: var(--icon-topic-default)\n mask-repeat: no-repeat\n\n//\n// Variants\n//\n.admonition\n border-left: 0.2rem solid var(--color-admonition-title)\n\n @each $type, $value in $admonitions\n &.#{$type}\n border-left-color: var(--color-admonition-title--#{$type})\n > .admonition-title\n background-color: var(--color-admonition-title-background--#{$type})\n &::before\n background-color: var(--color-admonition-title--#{$type})\n mask-image: var(--icon-#{nth($value, 2)})\n\n.admonition-todo > .admonition-title\n text-transform: uppercase\n","// This file stylizes the API documentation (stuff generated by autodoc). It's\n// deeply nested due to how autodoc structures the HTML without enough classes\n// to select the relevant items.\n\n// API docs!\ndl[class]:not(.option-list):not(.field-list):not(.footnote):not(.glossary):not(.simple)\n // Tweak the spacing of all the things!\n dd\n margin-left: 2rem\n > :first-child\n margin-top: 0.125rem\n > :last-child\n margin-bottom: 0.75rem\n\n // This is used for the arguments\n .field-list\n margin-bottom: 0.75rem\n\n // \"Headings\" (like \"Parameters\" and \"Return\")\n > dt\n text-transform: uppercase\n font-size: var(--font-size--small)\n\n dd:empty\n margin-bottom: 0.5rem\n dd > ul\n margin-left: -1.2rem\n > li\n > p:nth-child(2)\n margin-top: 0\n // When the last-empty-paragraph follows a paragraph, it doesn't need\n // to augument the existing spacing.\n > p + p:last-child:empty\n margin-top: 0\n margin-bottom: 0\n\n // Colorize the elements\n > dt\n color: var(--color-api-overall)\n\n.sig:not(.sig-inline)\n font-weight: bold\n\n font-size: var(--api-font-size)\n font-family: var(--font-stack--monospace)\n\n margin-left: -0.25rem\n margin-right: -0.25rem\n padding-top: 0.25rem\n padding-bottom: 0.25rem\n padding-right: 0.5rem\n\n // These are intentionally em, to properly match the font size.\n padding-left: 3em\n text-indent: -2.5em\n\n border-radius: 0.25rem\n\n background: var(--color-api-background)\n transition: background 100ms ease-out\n\n &:hover\n background: var(--color-api-background-hover)\n\n // adjust the size of the [source] link on the right.\n a.reference\n .viewcode-link\n font-weight: normal\n width: 4.25rem\n\nem.property\n font-style: normal\n &:first-child\n color: var(--color-api-keyword)\n.sig-name\n color: var(--color-api-name)\n.sig-prename\n font-weight: normal\n color: var(--color-api-pre-name)\n.sig-paren\n color: var(--color-api-paren)\n.sig-param\n font-style: normal\n\ndiv.versionadded,\ndiv.versionchanged,\ndiv.deprecated,\ndiv.versionremoved\n border-left: 0.1875rem solid\n border-radius: 0.125rem\n\n padding-left: 0.75rem\n\n p\n margin-top: 0.125rem\n margin-bottom: 0.125rem\n\ndiv.versionadded\n border-color: var(--color-api-added-border)\n .versionmodified\n color: var(--color-api-added)\n\ndiv.versionchanged\n border-color: var(--color-api-changed-border)\n .versionmodified\n color: var(--color-api-changed)\n\ndiv.deprecated\n border-color: var(--color-api-deprecated-border)\n .versionmodified\n color: var(--color-api-deprecated)\n\ndiv.versionremoved\n border-color: var(--color-api-removed-border)\n .versionmodified\n color: var(--color-api-removed)\n\n// Align the [docs] and [source] to the right.\n.viewcode-link, .viewcode-back\n float: right\n text-align: right\n",".line-block\n margin-top: 0.5rem\n margin-bottom: 0.75rem\n .line-block\n margin-top: 0rem\n margin-bottom: 0rem\n padding-left: 1rem\n","// Captions\narticle p.caption,\ntable > caption,\n.code-block-caption\n font-size: var(--font-size--small)\n text-align: center\n\n// Caption above a TOCTree\n.toctree-wrapper.compound\n .caption, :not(.caption) > .caption-text\n font-size: var(--font-size--small)\n text-transform: uppercase\n\n text-align: initial\n margin-bottom: 0\n\n > ul\n margin-top: 0\n margin-bottom: 0\n","// Inline code\ncode.literal, .sig-inline\n background: var(--color-inline-code-background)\n border-radius: 0.2em\n // Make the font smaller, and use padding to recover.\n font-size: var(--font-size--small--2)\n padding: 0.1em 0.2em\n\n pre.literal-block &\n font-size: inherit\n padding: 0\n\n p &\n border: 1px solid var(--color-background-border)\n\n.sig-inline\n font-family: var(--font-stack--monospace)\n\n// Code and Literal Blocks\n$code-spacing-vertical: 0.625rem\n$code-spacing-horizontal: 0.875rem\n\n// Wraps every literal block + line numbers.\ndiv[class*=\" highlight-\"],\ndiv[class^=\"highlight-\"]\n margin: 1em 0\n display: flex\n\n .table-wrapper\n margin: 0\n padding: 0\n\npre\n margin: 0\n padding: 0\n overflow: auto\n\n // Needed to have more specificity than pygments' \"pre\" selector. :(\n article[role=\"main\"] .highlight &\n line-height: 1.5\n\n &.literal-block,\n .highlight &\n font-size: var(--code-font-size)\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n // Make it look like all the other blocks.\n &.literal-block\n margin-top: 1rem\n margin-bottom: 1rem\n\n border-radius: 0.2rem\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n\n// All code is always contained in this.\n.highlight\n width: 100%\n border-radius: 0.2rem\n\n // Make line numbers and prompts un-selectable.\n .gp, span.linenos\n user-select: none\n pointer-events: none\n\n // Expand the line-highlighting.\n .hll\n display: block\n margin-left: -$code-spacing-horizontal\n margin-right: -$code-spacing-horizontal\n padding-left: $code-spacing-horizontal\n padding-right: $code-spacing-horizontal\n\n/* Make code block captions be nicely integrated */\n.code-block-caption\n display: flex\n padding: $code-spacing-vertical $code-spacing-horizontal\n\n border-radius: 0.25rem\n border-bottom-left-radius: 0\n border-bottom-right-radius: 0\n font-weight: 300\n border-bottom: 1px solid\n\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n border-color: var(--color-background-border)\n\n + div[class]\n margin-top: 0\n pre\n border-top-left-radius: 0\n border-top-right-radius: 0\n\n// When `html_codeblock_linenos_style` is table.\n.highlighttable\n width: 100%\n display: block\n tbody\n display: block\n\n tr\n display: flex\n\n // Line numbers\n td.linenos\n background-color: var(--color-code-background)\n color: var(--color-code-foreground)\n padding: $code-spacing-vertical $code-spacing-horizontal\n padding-right: 0\n border-top-left-radius: 0.2rem\n border-bottom-left-radius: 0.2rem\n\n .linenodiv\n padding-right: $code-spacing-horizontal\n font-size: var(--code-font-size)\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n\n // Actual code\n td.code\n padding: 0\n display: block\n flex: 1\n overflow: hidden\n\n .highlight\n border-top-left-radius: 0\n border-bottom-left-radius: 0\n\n// When `html_codeblock_linenos_style` is inline.\n.highlight\n span.linenos\n display: inline-block\n padding-left: 0\n padding-right: $code-spacing-horizontal\n margin-right: $code-spacing-horizontal\n box-shadow: -0.0625rem 0 var(--color-foreground-border) inset\n","// Inline Footnote Reference\n.footnote-reference\n font-size: var(--font-size--small--4)\n vertical-align: super\n\n// Definition list, listing the content of each note.\n// docutils <= 0.17\ndl.footnote.brackets\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\n display: grid\n grid-template-columns: max-content auto\n dt\n margin: 0\n > .fn-backref\n margin-left: 0.25rem\n\n &:after\n content: \":\"\n\n .brackets\n &:before\n content: \"[\"\n &:after\n content: \"]\"\n\n dd\n margin: 0\n padding: 0 1rem\n\n// docutils >= 0.18\naside.footnote\n font-size: var(--font-size--small)\n color: var(--color-foreground-secondary)\n\naside.footnote > span,\ndiv.citation > span\n float: left\n font-weight: 500\n padding-right: 0.25rem\n\naside.footnote > *:not(span),\ndiv.citation > p\n margin-left: 2rem\n","//\n// Figures\n//\nimg\n box-sizing: border-box\n max-width: 100%\n height: auto\n\narticle\n figure, .figure\n border-radius: 0.2rem\n\n margin: 0\n :last-child\n margin-bottom: 0\n\n .align-left\n float: left\n clear: left\n margin: 0 1rem 1rem\n\n .align-right\n float: right\n clear: right\n margin: 0 1rem 1rem\n\n .align-default,\n .align-center\n display: block\n text-align: center\n margin-left: auto\n margin-right: auto\n\n // WELL, table needs to be stylised like a table.\n table.align-default\n display: table\n text-align: initial\n",".genindex-jumpbox, .domainindex-jumpbox\n border-top: 1px solid var(--color-background-border)\n border-bottom: 1px solid var(--color-background-border)\n padding: 0.25rem\n\n.genindex-section, .domainindex-section\n h2\n margin-top: 0.75rem\n margin-bottom: 0.5rem\n ul\n margin-top: 0\n margin-bottom: 0\n","ul,\nol\n padding-left: 1.2rem\n\n // Space lists out like paragraphs\n margin-top: 1rem\n margin-bottom: 1rem\n // reduce margins within li.\n li\n > p:first-child\n margin-top: 0.25rem\n margin-bottom: 0.25rem\n\n > p:last-child\n margin-top: 0.25rem\n\n > ul,\n > ol\n margin-top: 0.5rem\n margin-bottom: 0.5rem\n\nol\n &.arabic\n list-style: decimal\n &.loweralpha\n list-style: lower-alpha\n &.upperalpha\n list-style: upper-alpha\n &.lowerroman\n list-style: lower-roman\n &.upperroman\n list-style: upper-roman\n\n// Don't space lists out when they're \"simple\" or in a `.. toctree::`\n.simple,\n.toctree-wrapper\n li\n > ul,\n > ol\n margin-top: 0\n margin-bottom: 0\n\n// Definition Lists\n.field-list,\n.option-list,\ndl:not([class]),\ndl.simple,\ndl.footnote,\ndl.glossary\n dt\n font-weight: 500\n margin-top: 0.25rem\n + dt\n margin-top: 0\n\n .classifier::before\n content: \":\"\n margin-left: 0.2rem\n margin-right: 0.2rem\n\n dd\n > p:first-child,\n ul\n margin-top: 0.125rem\n\n ul\n margin-bottom: 0.125rem\n",".math-wrapper\n width: 100%\n overflow-x: auto\n\ndiv.math\n position: relative\n text-align: center\n\n .headerlink,\n &:focus .headerlink\n display: none\n\n &:hover .headerlink\n display: inline-block\n\n span.eqno\n position: absolute\n right: 0.5rem\n top: 50%\n transform: translate(0, -50%)\n z-index: 1\n","// Abbreviations\nabbr[title]\n cursor: help\n\n// \"Problematic\" content, as identified by Sphinx\n.problematic\n color: var(--color-problematic)\n\n// Keyboard / Mouse \"instructions\"\nkbd:not(.compound)\n margin: 0 0.2rem\n padding: 0 0.2rem\n border-radius: 0.2rem\n border: 1px solid var(--color-foreground-border)\n color: var(--color-foreground-primary)\n vertical-align: text-bottom\n\n font-size: var(--font-size--small--3)\n display: inline-block\n\n box-shadow: 0 0.0625rem 0 rgba(0, 0, 0, 0.2), inset 0 0 0 0.125rem var(--color-background-primary)\n\n background-color: var(--color-background-secondary)\n\n// Blockquote\nblockquote\n border-left: 4px solid var(--color-background-border)\n background: var(--color-background-secondary)\n\n margin-left: 0\n margin-right: 0\n padding: 0.5rem 1rem\n\n .attribution\n font-weight: 600\n text-align: right\n\n &.pull-quote,\n &.highlights\n font-size: 1.25em\n\n &.epigraph,\n &.pull-quote\n border-left-width: 0\n border-radius: 0.5rem\n\n &.highlights\n border-left-width: 0\n background: transparent\n\n// Center align embedded-in-text images\np .reference img\n vertical-align: middle\n","p.rubric\n line-height: 1.25\n font-weight: bold\n font-size: 1.125em\n\n // For Numpy-style documentation that's got rubrics within it.\n // https://github.com/pradyunsg/furo/discussions/505\n dd &\n line-height: inherit\n font-weight: inherit\n\n font-size: var(--font-size--small)\n text-transform: uppercase\n","article .sidebar\n float: right\n clear: right\n width: 30%\n\n margin-left: 1rem\n margin-right: 0\n\n border-radius: 0.2rem\n background-color: var(--color-background-secondary)\n border: var(--color-background-border) 1px solid\n\n > *\n padding-left: 1rem\n padding-right: 1rem\n\n > ul, > ol // lists need additional padding, because bullets.\n padding-left: 2.2rem\n\n .sidebar-title\n margin: 0\n padding: 0.5rem 1rem\n border-bottom: var(--color-background-border) 1px solid\n\n font-weight: 500\n\n// TODO: subtitle\n// TODO: dedicated variables?\n","[role=main] .table-wrapper.container\n width: 100%\n overflow-x: auto\n margin-top: 1rem\n margin-bottom: 0.5rem\n padding: 0.2rem 0.2rem 0.75rem\n\ntable.docutils\n border-radius: 0.2rem\n border-spacing: 0\n border-collapse: collapse\n\n box-shadow: 0 0.2rem 0.5rem rgba(0, 0, 0, 0.05), 0 0 0.0625rem rgba(0, 0, 0, 0.1)\n\n th\n background: var(--color-table-header-background)\n\n td,\n th\n // Space things out properly\n padding: 0 0.25rem\n\n // Get the borders looking just-right.\n border-left: 1px solid var(--color-table-border)\n border-right: 1px solid var(--color-table-border)\n border-bottom: 1px solid var(--color-table-border)\n\n p\n margin: 0.25rem\n\n &:first-child\n border-left: none\n &:last-child\n border-right: none\n\n // MyST-parser tables set these classes for control of column alignment\n &.text-left\n text-align: left\n &.text-right\n text-align: right\n &.text-center\n text-align: center\n",":target\n scroll-margin-top: 2.5rem\n\n@media (max-width: $full-width - $sidebar-width)\n :target\n scroll-margin-top: calc(2.5rem + var(--header-height))\n\n // When a heading is selected\n section > span:target\n scroll-margin-top: calc(2.8rem + var(--header-height))\n\n// Permalinks\n.headerlink\n font-weight: 100\n user-select: none\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\ndl dt,\np.caption,\nfigcaption p,\ntable > caption,\n.code-block-caption\n > .headerlink\n margin-left: 0.5rem\n visibility: hidden\n &:hover > .headerlink\n visibility: visible\n\n // Don't change to link-like, if someone adds the contents directive.\n > .toc-backref\n color: inherit\n text-decoration-line: none\n\n// Figure and table captions are special.\nfigure:hover > figcaption > p > .headerlink,\ntable:hover > caption > .headerlink\n visibility: visible\n\n:target >, // Regular section[id] style anchors\nspan:target ~ // Non-regular span[id] style \"extra\" anchors\n h1,\n h2,\n h3,\n h4,\n h5,\n h6\n &:nth-of-type(1)\n background-color: var(--color-highlight-on-target)\n // .headerlink\n // visibility: visible\n code.literal\n background-color: transparent\n\ntable:target > caption,\nfigure:target\n background-color: var(--color-highlight-on-target)\n\n// Inline page contents\n.this-will-duplicate-information-and-it-is-still-useful-here li :target\n background-color: var(--color-highlight-on-target)\n\n// Code block permalinks\n.literal-block-wrapper:target .code-block-caption\n background-color: var(--color-highlight-on-target)\n\n// When a definition list item is selected\n//\n// There isn't really an alternative to !important here, due to the\n// high-specificity of API documentation's selector.\ndt:target\n background-color: var(--color-highlight-on-target) !important\n\n// When a footnote reference is selected\n.footnote > dt:target + dd,\n.footnote-reference:target\n background-color: var(--color-highlight-on-target)\n",".guilabel\n background-color: var(--color-guilabel-background)\n border: 1px solid var(--color-guilabel-border)\n color: var(--color-guilabel-text)\n\n padding: 0 0.3em\n border-radius: 0.5em\n font-size: 0.9em\n","// This file contains the styles used for stylizing the footer that's shown\n// below the content.\n\nfooter\n font-size: var(--font-size--small)\n display: flex\n flex-direction: column\n\n margin-top: 2rem\n\n// Bottom of page information\n.bottom-of-page\n display: flex\n align-items: center\n justify-content: space-between\n\n margin-top: 1rem\n padding-top: 1rem\n padding-bottom: 1rem\n\n color: var(--color-foreground-secondary)\n border-top: 1px solid var(--color-background-border)\n\n line-height: 1.5\n\n @media (max-width: $content-width)\n text-align: center\n flex-direction: column-reverse\n gap: 0.25rem\n\n .left-details\n font-size: var(--font-size--small)\n\n .right-details\n display: flex\n flex-direction: column\n gap: 0.25rem\n text-align: right\n\n .icons\n display: flex\n justify-content: flex-end\n gap: 0.25rem\n font-size: 1rem\n\n a\n text-decoration: none\n\n svg,\n img\n font-size: 1.125rem\n height: 1em\n width: 1em\n\n// Next/Prev page information\n.related-pages\n a\n display: flex\n align-items: center\n\n text-decoration: none\n &:hover .page-info .title\n text-decoration: underline\n color: var(--color-link)\n text-decoration-color: var(--color-link-underline)\n\n svg.furo-related-icon,\n svg.furo-related-icon > use\n flex-shrink: 0\n\n color: var(--color-foreground-border)\n\n width: 0.75rem\n height: 0.75rem\n margin: 0 0.5rem\n\n &.next-page\n max-width: 50%\n\n float: right\n clear: right\n text-align: right\n\n &.prev-page\n max-width: 50%\n\n float: left\n clear: left\n\n svg\n transform: rotate(180deg)\n\n.page-info\n display: flex\n flex-direction: column\n overflow-wrap: anywhere\n\n .next-page &\n align-items: flex-end\n\n .context\n display: flex\n align-items: center\n\n padding-bottom: 0.1rem\n\n color: var(--color-foreground-muted)\n font-size: var(--font-size--small)\n text-decoration: none\n","// This file contains the styles for the contents of the left sidebar, which\n// contains the navigation tree, logo, search etc.\n\n////////////////////////////////////////////////////////////////////////////////\n// Brand on top of the scrollable tree.\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-brand\n display: flex\n flex-direction: column\n flex-shrink: 0\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n text-decoration: none\n\n.sidebar-brand-text\n color: var(--color-sidebar-brand-text)\n overflow-wrap: break-word\n margin: var(--sidebar-item-spacing-vertical) 0\n font-size: 1.5rem\n\n.sidebar-logo-container\n margin: var(--sidebar-item-spacing-vertical) 0\n\n.sidebar-logo\n margin: 0 auto\n display: block\n max-width: 100%\n\n////////////////////////////////////////////////////////////////////////////////\n// Search\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-search-container\n display: flex\n align-items: center\n margin-top: var(--sidebar-search-space-above)\n\n position: relative\n\n background: var(--color-sidebar-search-background)\n &:hover,\n &:focus-within\n background: var(--color-sidebar-search-background--focus)\n\n &::before\n content: \"\"\n position: absolute\n left: var(--sidebar-item-spacing-horizontal)\n width: var(--sidebar-search-icon-size)\n height: var(--sidebar-search-icon-size)\n\n background-color: var(--color-sidebar-search-icon)\n mask-image: var(--icon-search)\n\n.sidebar-search\n box-sizing: border-box\n\n border: none\n border-top: 1px solid var(--color-sidebar-search-border)\n border-bottom: 1px solid var(--color-sidebar-search-border)\n\n padding-top: var(--sidebar-search-input-spacing-vertical)\n padding-bottom: var(--sidebar-search-input-spacing-vertical)\n padding-right: var(--sidebar-search-input-spacing-horizontal)\n padding-left: calc(var(--sidebar-item-spacing-horizontal) + var(--sidebar-search-input-spacing-horizontal) + var(--sidebar-search-icon-size))\n\n width: 100%\n\n color: var(--color-sidebar-search-foreground)\n background: transparent\n z-index: 10\n\n &:focus\n outline: none\n\n &::placeholder\n font-size: var(--sidebar-search-input-font-size)\n\n//\n// Hide Search Matches link\n//\n#searchbox .highlight-link\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal) 0\n margin: 0\n text-align: center\n\n a\n color: var(--color-sidebar-search-icon)\n font-size: var(--font-size--small--2)\n\n////////////////////////////////////////////////////////////////////////////////\n// Structure/Skeleton of the navigation tree (left)\n////////////////////////////////////////////////////////////////////////////////\n.sidebar-tree\n font-size: var(--sidebar-item-font-size)\n margin-top: var(--sidebar-tree-space-above)\n margin-bottom: var(--sidebar-item-spacing-vertical)\n\n ul\n padding: 0\n margin-top: 0\n margin-bottom: 0\n\n display: flex\n flex-direction: column\n\n list-style: none\n\n li\n position: relative\n margin: 0\n\n > ul\n margin-left: var(--sidebar-item-spacing-horizontal)\n\n .icon\n color: var(--color-sidebar-link-text)\n\n .reference\n box-sizing: border-box\n color: var(--color-sidebar-link-text)\n\n // Fill the parent.\n display: inline-block\n line-height: var(--sidebar-item-line-height)\n text-decoration: none\n\n // Don't allow long words to cause wrapping.\n overflow-wrap: anywhere\n\n height: 100%\n width: 100%\n\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n &:hover\n color: var(--color-sidebar-link-text)\n background: var(--color-sidebar-item-background--hover)\n\n // Add a nice little \"external-link\" arrow here.\n &.external::after\n content: url('data:image/svg+xml,')\n margin: 0 0.25rem\n vertical-align: middle\n color: var(--color-sidebar-link-text)\n\n // Make the current page reference bold.\n .current-page > .reference\n font-weight: bold\n\n label\n position: absolute\n top: 0\n right: 0\n height: var(--sidebar-item-height)\n width: var(--sidebar-expander-width)\n\n cursor: pointer\n user-select: none\n\n display: flex\n justify-content: center\n align-items: center\n\n .caption, :not(.caption) > .caption-text\n font-size: var(--sidebar-caption-font-size)\n color: var(--color-sidebar-caption-text)\n\n font-weight: bold\n text-transform: uppercase\n\n margin: var(--sidebar-caption-space-above) 0 0 0\n padding: var(--sidebar-item-spacing-vertical) var(--sidebar-item-spacing-horizontal)\n\n // If it has children, add a bit more padding to wrap the content to avoid\n // overlapping with the