Skip to content

Commit

Permalink
fix: handle client generation in a dir containing multiple app spec t…
Browse files Browse the repository at this point in the history
…ypes (#599)

* fix: handle client generation in a dir containing multiple app spec types
  • Loading branch information
neilcampbell authored Jan 6, 2025
1 parent 1e0e39c commit 1a479a1
Show file tree
Hide file tree
Showing 10 changed files with 382 additions and 141 deletions.
6 changes: 3 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 9 additions & 18 deletions src/algokit/cli/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import click

from algokit.core.generate import load_generators, run_generator
from algokit.core.typed_client_generation import ClientGenerator
from algokit.core.typed_client_generation import AppSpecsNotFoundError, ClientGenerator

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -161,20 +161,11 @@ def generate_client(
"One of --language or --output is required to determine the client language to generate"
)

if not app_spec_path_or_dir.is_dir():
app_specs = [app_spec_path_or_dir]
else:
patterns = ["application.json", "*.arc32.json", "*.arc56.json"]

app_specs = []
for pattern in patterns:
app_specs.extend(app_spec_path_or_dir.rglob(pattern))

app_specs = list(set(app_specs))
app_specs.sort()
if not app_specs:
raise click.ClickException("No app specs found")
for app_spec in app_specs:
output_path = generator.resolve_output_path(app_spec, output_path_pattern)
if output_path is not None:
generator.generate(app_spec, output_path)
try:
generator.generate_all(
app_spec_path_or_dir,
output_path_pattern,
raise_on_path_resolution_failure=False,
)
except AppSpecsNotFoundError as ex:
raise click.ClickException("No app specs found") from ex
25 changes: 9 additions & 16 deletions src/algokit/cli/project/link.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import logging
import typing
from dataclasses import dataclass
from itertools import chain
from pathlib import Path

import click
Expand All @@ -11,7 +10,7 @@
from algokit.core import questionary_extensions
from algokit.core.conf import get_algokit_config
from algokit.core.project import ProjectType, get_project_configs
from algokit.core.typed_client_generation import ClientGenerator
from algokit.core.typed_client_generation import AppSpecsNotFoundError, ClientGenerator

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -86,25 +85,19 @@ def _link_projects(
"""
output_path_pattern = f"{frontend_clients_path}/{{contract_name}}.{'ts' if language == 'typescript' else 'py'}"
generator = ClientGenerator.create_for_language(language, version=version)
file_patterns = ["application.json", "*.arc32.json", "*.arc56.json"]
app_specs = list(chain.from_iterable(contract_project_root.rglob(pattern) for pattern in file_patterns))
if not app_specs:

try:
generator.generate_all(
contract_project_root,
output_path_pattern,
raise_on_path_resolution_failure=fail_fast,
)
except AppSpecsNotFoundError:
click.secho(
f"WARNING: No application.json | *.arc32.json | *.arc56.json files found in {contract_project_root}. "
"Skipping...",
fg="yellow",
)
return

for app_spec in app_specs:
output_path = generator.resolve_output_path(app_spec, output_path_pattern)
if output_path is None:
if fail_fast:
raise click.ClickException(f"Error generating client for {app_spec}")

logger.warning(f"Error generating client for {app_spec}")
continue
generator.generate(app_spec, output_path)


def _prompt_contract_project() -> ContractArtifacts | None:
Expand Down
55 changes: 53 additions & 2 deletions src/algokit/core/typed_client_generation.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import abc
import enum
import json
import logging
import re
import shutil # noqa: F401
from functools import reduce
from itertools import chain
from pathlib import Path
from typing import ClassVar

Expand All @@ -26,6 +29,15 @@ def _snake_case(s: str) -> str:
return re.sub(r"[-\s]", "_", s).lower()


class AppSpecType(enum.Enum):
ARC32 = "arc32"
ARC56 = "arc56"


class AppSpecsNotFoundError(Exception):
pass


class ClientGenerator(abc.ABC):
language: ClassVar[str]
extension: ClassVar[str]
Expand Down Expand Up @@ -55,13 +67,15 @@ def create_for_language(cls, language: str, version: str | None) -> "ClientGener
def create_for_extension(cls, extension: str, version: str | None) -> "ClientGenerator":
return cls._by_extension[extension](version)

def resolve_output_path(self, app_spec: Path, output_path_pattern: str | None) -> Path | None:
def resolve_output_path(self, app_spec: Path, output_path_pattern: str | None) -> tuple[Path, AppSpecType] | None:
try:
application_json = json.loads(app_spec.read_text())
try:
contract_name: str = application_json["name"] # ARC-56
app_spec_type: AppSpecType = AppSpecType.ARC56
except KeyError:
contract_name = application_json["contract"]["name"] # ARC-32
app_spec_type = AppSpecType.ARC32
except Exception:
logger.error(f"Couldn't parse contract name from {app_spec}", exc_info=True)
return None
Expand All @@ -73,7 +87,7 @@ def resolve_output_path(self, app_spec: Path, output_path_pattern: str | None) -
if output_path.exists() and not output_path.is_file():
logger.error(f"Could not output to {output_path} as it already exists and is a directory")
return None
return output_path
return (output_path, app_spec_type)

@abc.abstractmethod
def generate(self, app_spec: Path, output: Path) -> None: ...
Expand All @@ -88,6 +102,43 @@ def find_generate_command(self, version: str | None) -> list[str]: ...
def format_contract_name(self, contract_name: str) -> str:
return contract_name

def generate_all(
self,
app_spec_path_or_dir: Path,
output_path_pattern: str | None,
*,
raise_on_path_resolution_failure: bool,
) -> None:
if not app_spec_path_or_dir.is_dir():
app_specs = [app_spec_path_or_dir]
else:
file_patterns = ["application.json", "*.arc32.json", "*.arc56.json"]
app_specs = list(set(chain.from_iterable(app_spec_path_or_dir.rglob(pattern) for pattern in file_patterns)))
app_specs.sort()
if not app_specs:
raise AppSpecsNotFoundError

def accumulate_items_to_generate(
acc: dict[Path, tuple[Path, AppSpecType]], app_spec: Path
) -> dict[Path, tuple[Path, AppSpecType]]:
output_path_result = self.resolve_output_path(app_spec, output_path_pattern)
if output_path_result is None:
if raise_on_path_resolution_failure:
raise click.ClickException(f"Error generating client for {app_spec}")
return acc
(output_path, app_spec_type) = output_path_result
if output_path in acc:
# ARC-56 app specs take precedence over ARC-32 app specs
if acc[output_path][1] == AppSpecType.ARC32 and app_spec_type == AppSpecType.ARC56:
acc[output_path] = (app_spec, app_spec_type)
else:
acc[output_path] = (app_spec, app_spec_type)
return acc

items_to_generate: dict[Path, tuple[Path, AppSpecType]] = reduce(accumulate_items_to_generate, app_specs, {})
for output_path, (app_spec, _) in items_to_generate.items():
self.generate(app_spec, output_path)


class PythonClientGenerator(ClientGenerator, language="python", extension=".py"):
def generate(self, app_spec: Path, output: Path) -> None:
Expand Down
75 changes: 75 additions & 0 deletions tests/generate/app.arc32.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
{
"hints": {
"hello(string)string": {
"call_config": {
"no_op": "CALL"
}
},
"hello_world_check(string)void": {
"call_config": {
"no_op": "CALL"
}
}
},
"source": {
"approval": "I3ByYWdtYSB2ZXJzaW9uIDgKaW50Y2Jsb2NrIDAgMQp0eG4gTnVtQXBwQXJncwppbnRjXzAgLy8gMAo9PQpibnogbWFpbl9sNgp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweDAyYmVjZTExIC8vICJoZWxsbyhzdHJpbmcpc3RyaW5nIgo9PQpibnogbWFpbl9sNQp0eG5hIEFwcGxpY2F0aW9uQXJncyAwCnB1c2hieXRlcyAweGJmOWMxZWRmIC8vICJoZWxsb193b3JsZF9jaGVjayhzdHJpbmcpdm9pZCIKPT0KYm56IG1haW5fbDQKZXJyCm1haW5fbDQ6CnR4biBPbkNvbXBsZXRpb24KaW50Y18wIC8vIE5vT3AKPT0KdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KJiYKYXNzZXJ0CnR4bmEgQXBwbGljYXRpb25BcmdzIDEKY2FsbHN1YiBoZWxsb3dvcmxkY2hlY2tfMwppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sNToKdHhuIE9uQ29tcGxldGlvbgppbnRjXzAgLy8gTm9PcAo9PQp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQomJgphc3NlcnQKdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMQpjYWxsc3ViIGhlbGxvXzIKc3RvcmUgMApwdXNoYnl0ZXMgMHgxNTFmN2M3NSAvLyAweDE1MWY3Yzc1CmxvYWQgMApjb25jYXQKbG9nCmludGNfMSAvLyAxCnJldHVybgptYWluX2w2Ogp0eG4gT25Db21wbGV0aW9uCmludGNfMCAvLyBOb09wCj09CmJueiBtYWluX2wxMgp0eG4gT25Db21wbGV0aW9uCnB1c2hpbnQgNCAvLyBVcGRhdGVBcHBsaWNhdGlvbgo9PQpibnogbWFpbl9sMTEKdHhuIE9uQ29tcGxldGlvbgpwdXNoaW50IDUgLy8gRGVsZXRlQXBwbGljYXRpb24KPT0KYm56IG1haW5fbDEwCmVycgptYWluX2wxMDoKdHhuIEFwcGxpY2F0aW9uSUQKaW50Y18wIC8vIDAKIT0KYXNzZXJ0CmNhbGxzdWIgZGVsZXRlXzEKaW50Y18xIC8vIDEKcmV0dXJuCm1haW5fbDExOgp0eG4gQXBwbGljYXRpb25JRAppbnRjXzAgLy8gMAohPQphc3NlcnQKY2FsbHN1YiB1cGRhdGVfMAppbnRjXzEgLy8gMQpyZXR1cm4KbWFpbl9sMTI6CnR4biBBcHBsaWNhdGlvbklECmludGNfMCAvLyAwCj09CmFzc2VydAppbnRjXzEgLy8gMQpyZXR1cm4KCi8vIHVwZGF0ZQp1cGRhdGVfMDoKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX1VQREFUQUJMRSAvLyBUTVBMX1VQREFUQUJMRQovLyBDaGVjayBhcHAgaXMgdXBkYXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGRlbGV0ZQpkZWxldGVfMToKcHJvdG8gMCAwCnR4biBTZW5kZXIKZ2xvYmFsIENyZWF0b3JBZGRyZXNzCj09Ci8vIHVuYXV0aG9yaXplZAphc3NlcnQKcHVzaGludCBUTVBMX0RFTEVUQUJMRSAvLyBUTVBMX0RFTEVUQUJMRQovLyBDaGVjayBhcHAgaXMgZGVsZXRhYmxlCmFzc2VydApyZXRzdWIKCi8vIGhlbGxvCmhlbGxvXzI6CnByb3RvIDEgMQpwdXNoYnl0ZXMgMHggLy8gIiIKcHVzaGJ5dGVzIDB4NDg2NTZjNmM2ZjJjMjAgLy8gIkhlbGxvLCAiCmZyYW1lX2RpZyAtMQpleHRyYWN0IDIgMApjb25jYXQKZnJhbWVfYnVyeSAwCmZyYW1lX2RpZyAwCmxlbgppdG9iCmV4dHJhY3QgNiAwCmZyYW1lX2RpZyAwCmNvbmNhdApmcmFtZV9idXJ5IDAKcmV0c3ViCgovLyBoZWxsb193b3JsZF9jaGVjawpoZWxsb3dvcmxkY2hlY2tfMzoKcHJvdG8gMSAwCmZyYW1lX2RpZyAtMQpleHRyYWN0IDIgMApwdXNoYnl0ZXMgMHg1NzZmNzI2YzY0IC8vICJXb3JsZCIKPT0KYXNzZXJ0CnJldHN1Yg==",
"clear": "I3ByYWdtYSB2ZXJzaW9uIDgKcHVzaGludCAwIC8vIDAKcmV0dXJu"
},
"state": {
"global": {
"num_byte_slices": 0,
"num_uints": 0
},
"local": {
"num_byte_slices": 0,
"num_uints": 0
}
},
"schema": {
"global": {
"declared": {},
"reserved": {}
},
"local": {
"declared": {},
"reserved": {}
}
},
"contract": {
"name": "HelloWorldApp",
"methods": [
{
"name": "hello",
"args": [
{
"type": "string",
"name": "name"
}
],
"returns": {
"type": "string"
},
"desc": "Returns Hello, {name}"
},
{
"name": "hello_world_check",
"args": [
{
"type": "string",
"name": "name"
}
],
"returns": {
"type": "void"
},
"desc": "Asserts {name} is \"World\""
}
],
"networks": {}
},
"bare_call_config": {
"delete_application": "CALL",
"no_op": "CREATE",
"update_application": "CALL"
}
}
95 changes: 95 additions & 0 deletions tests/generate/app.arc56.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"name": "HelloWorldApp",
"structs": {},
"methods": [
{
"name": "hello",
"args": [
{
"type": "string",
"name": "name"
}
],
"returns": {
"type": "string"
},
"actions": {
"create": [],
"call": [
"NoOp"
]
},
"readonly": false,
"events": [],
"recommendations": {}
}
],
"arcs": [
22,
28
],
"networks": {},
"state": {
"schema": {
"global": {
"ints": 0,
"bytes": 0
},
"local": {
"ints": 0,
"bytes": 0
}
},
"keys": {
"global": {},
"local": {},
"box": {}
},
"maps": {
"global": {},
"local": {},
"box": {}
}
},
"bareActions": {
"create": [
"NoOp"
],
"call": []
},
"sourceInfo": {
"approval": {
"sourceInfo": [
{
"pc": [
35
],
"errorMessage": "OnCompletion is not NoOp"
},
{
"pc": [
75
],
"errorMessage": "can only call when creating"
},
{
"pc": [
38
],
"errorMessage": "can only call when not creating"
}
],
"pcOffsetMethod": "none"
},
"clear": {
"sourceInfo": [],
"pcOffsetMethod": "none"
}
},
"source": {
"approval": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5hcHByb3ZhbF9wcm9ncmFtOgogICAgaW50Y2Jsb2NrIDAgMQogICAgY2FsbHN1YiBfX3B1eWFfYXJjNF9yb3V0ZXJfXwogICAgcmV0dXJuCgoKLy8gc21hcnRfY29udHJhY3RzLmhlbGxvX3dvcmxkLmNvbnRyYWN0LkhlbGxvV29ybGQuX19wdXlhX2FyYzRfcm91dGVyX18oKSAtPiB1aW50NjQ6Cl9fcHV5YV9hcmM0X3JvdXRlcl9fOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHByb3RvIDAgMQogICAgdHhuIE51bUFwcEFyZ3MKICAgIGJ6IF9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1CiAgICBwdXNoYnl0ZXMgMHgwMmJlY2UxMSAvLyBtZXRob2QgImhlbGxvKHN0cmluZylzdHJpbmciCiAgICB0eG5hIEFwcGxpY2F0aW9uQXJncyAwCiAgICBtYXRjaCBfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyCiAgICBpbnRjXzAgLy8gMAogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19oZWxsb19yb3V0ZUAyOgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjYKICAgIC8vIEBhYmltZXRob2QoKQogICAgdHhuIE9uQ29tcGxldGlvbgogICAgIQogICAgYXNzZXJ0IC8vIE9uQ29tcGxldGlvbiBpcyBub3QgTm9PcAogICAgdHhuIEFwcGxpY2F0aW9uSUQKICAgIGFzc2VydCAvLyBjYW4gb25seSBjYWxsIHdoZW4gbm90IGNyZWF0aW5nCiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NQogICAgLy8gY2xhc3MgSGVsbG9Xb3JsZChBUkM0Q29udHJhY3QpOgogICAgdHhuYSBBcHBsaWNhdGlvbkFyZ3MgMQogICAgZXh0cmFjdCAyIDAKICAgIC8vIHNtYXJ0X2NvbnRyYWN0cy9oZWxsb193b3JsZC9jb250cmFjdC5weTo2CiAgICAvLyBAYWJpbWV0aG9kKCkKICAgIGNhbGxzdWIgaGVsbG8KICAgIGR1cAogICAgbGVuCiAgICBpdG9iCiAgICBleHRyYWN0IDYgMgogICAgc3dhcAogICAgY29uY2F0CiAgICBwdXNoYnl0ZXMgMHgxNTFmN2M3NQogICAgc3dhcAogICAgY29uY2F0CiAgICBsb2cKICAgIGludGNfMSAvLyAxCiAgICByZXRzdWIKCl9fcHV5YV9hcmM0X3JvdXRlcl9fX2JhcmVfcm91dGluZ0A1OgogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjUKICAgIC8vIGNsYXNzIEhlbGxvV29ybGQoQVJDNENvbnRyYWN0KToKICAgIHR4biBPbkNvbXBsZXRpb24KICAgIGJueiBfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDkKICAgIHR4biBBcHBsaWNhdGlvbklECiAgICAhCiAgICBhc3NlcnQgLy8gY2FuIG9ubHkgY2FsbCB3aGVuIGNyZWF0aW5nCiAgICBpbnRjXzEgLy8gMQogICAgcmV0c3ViCgpfX3B1eWFfYXJjNF9yb3V0ZXJfX19hZnRlcl9pZl9lbHNlQDk6CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6NQogICAgLy8gY2xhc3MgSGVsbG9Xb3JsZChBUkM0Q29udHJhY3QpOgogICAgaW50Y18wIC8vIDAKICAgIHJldHN1YgoKCi8vIHNtYXJ0X2NvbnRyYWN0cy5oZWxsb193b3JsZC5jb250cmFjdC5IZWxsb1dvcmxkLmhlbGxvKG5hbWU6IGJ5dGVzKSAtPiBieXRlczoKaGVsbG86CiAgICAvLyBzbWFydF9jb250cmFjdHMvaGVsbG9fd29ybGQvY29udHJhY3QucHk6Ni03CiAgICAvLyBAYWJpbWV0aG9kKCkKICAgIC8vIGRlZiBoZWxsbyhzZWxmLCBuYW1lOiBTdHJpbmcpIC0+IFN0cmluZzoKICAgIHByb3RvIDEgMQogICAgLy8gc21hcnRfY29udHJhY3RzL2hlbGxvX3dvcmxkL2NvbnRyYWN0LnB5OjgKICAgIC8vIHJldHVybiAiSGVsbG8sICIgKyBuYW1lCiAgICBwdXNoYnl0ZXMgIkhlbGxvLCAiCiAgICBmcmFtZV9kaWcgLTEKICAgIGNvbmNhdAogICAgcmV0c3ViCg==",
"clear": "I3ByYWdtYSB2ZXJzaW9uIDEwCgpzbWFydF9jb250cmFjdHMuaGVsbG9fd29ybGQuY29udHJhY3QuSGVsbG9Xb3JsZC5jbGVhcl9zdGF0ZV9wcm9ncmFtOgogICAgcHVzaGludCAxIC8vIDEKICAgIHJldHVybgo="
},
"events": [],
"templateVariables": {}
}
Loading

1 comment on commit 1a479a1

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
src/algokit
   __init__.py15753%6–13, 17–24, 32–34
   __main__.py440%1–7
src/algokit/cli
   __init__.py47394%31–34
   codespace.py50982%28, 114, 137, 150–155
   completions.py108992%63–64, 83, 93–99
   dispenser.py121199%77
   doctor.py53394%146–148
   explore.py631576%35–40, 42–47, 85–92, 113
   generate.py60395%76–77, 155
   goal.py54591%85, 96, 107–109
   init.py3102492%491–492, 497–498, 501, 522, 525–527, 538, 542, 600, 626, 655, 688, 697–699, 702–707, 720, 739, 751–752
   localnet.py1643280%67, 88–115, 164–168, 212, 233, 248–258, 271, 322, 343–344
   task.py34391%25–28
src/algokit/cli/common
   utils.py37295%137, 139
src/algokit/cli/project
   bootstrap.py32197%33
   deploy.py1172083%47, 49, 102, 125, 147–149, 270, 277, 291–299, 302–311
   link.py811285%60, 65–66, 109–114, 142–143, 212–213, 217
   list.py33585%21–23, 51–56
   run.py46296%38, 174
src/algokit/cli/tasks
   analyze.py81199%81
   assets.py821384%65–66, 72, 74–75, 105, 119, 125–126, 132, 134, 136–137
   ipfs.py51884%52, 80, 92, 94–95, 105–107
   mint.py1061586%51, 73, 100–103, 108, 113, 131–132, 158, 335–339
   send_transaction.py651085%52–53, 57, 89, 158, 170–174
   sign_transaction.py59886%21, 28–30, 71–72, 109, 123
   transfer.py39392%26, 90, 117
   utils.py1144660%29–37, 43–46, 78–79, 103–104, 128–136, 155–165, 212, 261–262, 282–293, 300–302, 324
   vanity_address.py561082%41, 45–48, 112, 114, 121–123
   wallet.py79495%21, 66, 136, 162
src/algokit/core
   codespace.py1756861%34–37, 41–44, 48–71, 111–112, 125–133, 191, 200–202, 210, 216–217, 229–236, 251–298, 311–313, 338–344, 348, 395
   conf.py57984%12, 24, 28, 36, 38, 73–75, 80
   dispenser.py2022687%92, 124–125, 142–150, 192–193, 199–201, 219–220, 260–261, 319, 333–335, 346–347, 357, 370, 385
   doctor.py65789%67–69, 92–94, 134
   generate.py50394%44, 85, 103
   goal.py65494%21, 36–37, 47
   init.py721086%53, 57–62, 70, 81, 88, 114–115
   log_handlers.py68790%50–51, 63, 112–116, 125
   proc.py45198%99
   sandbox.py2762392%32, 89–92, 97, 101–103, 174, 222–229, 240, 611, 627, 652, 660
   typed_client_generation.py2062190%79–81, 127, 157–162, 186, 189–192, 210, 213–216, 283, 286–289
   utils.py1504073%50–51, 57–69, 125–131, 155, 158, 164–177, 206–208, 237–240, 262
src/algokit/core/_vendor/auth0/authentication
   token_verifier.py15711129%16, 45, 58, 73–85, 98–107, 119–124, 136–137, 140, 170, 178–180, 190–199, 206–213, 227–236, 258, 280–287, 314–323, 333–444
src/algokit/core/compilers
   python.py28582%19–20, 25, 49–50
src/algokit/core/config_commands
   container_engine.py412149%24, 29–31, 47–76
   version_prompt.py921485%37–38, 68, 87–90, 108, 118–125, 148
src/algokit/core/project
   __init__.py53394%50, 86, 145
   bootstrap.py120893%47, 126–127, 149, 176, 207–209
   deploy.py69987%108–111, 120–122, 126, 131
   run.py1321390%83, 88, 97–98, 133–134, 138–139, 143, 147, 277–278, 293
src/algokit/core/tasks
   analyze.py93397%105–112, 187
   ipfs.py63789%58–64, 140, 144, 146, 152
   nfd.py491373%25, 31, 34–41, 70–72, 99–101
   vanity_address.py903462%49–50, 54, 59–75, 92–108, 128–131
   wallet.py71593%37, 129, 155–157
src/algokit/core/tasks/mint
   mint.py74988%123–133
   models.py921782%50, 52, 57, 71–74, 81–90
TOTAL496876985% 

Tests Skipped Failures Errors Time
510 0 💤 0 ❌ 0 🔥 28.214s ⏱️

Please sign in to comment.