Skip to content

Commit 72807c3

Browse files
authored
feat: add support for ARC56 typed client generation (#595)
* feat: add support for ARC56 typed client generation
1 parent 84d7690 commit 72807c3

14 files changed

+67
-20
lines changed

docs/cli/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ algokit generate [OPTIONS] COMMAND [ARGS]...
541541

542542
### client
543543

544-
Create a typed ApplicationClient from an ARC-32 application.json
544+
Create a typed ApplicationClient from an ARC-32/56 application.json
545545

546546
Supply the path to an application specification file or a directory to recursively search
547547
for "application.json" files

docs/features/generate.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ The `algokit generate` [command](../cli/index.md#generate) is used to generate c
44

55
## 1. Typed clients
66

7-
The `algokit generate client` [command](../cli/index.md#client) can be used to generate a typed client from an [ARC-0032](https://arc.algorand.foundation/ARCs/arc-0032) application specification with both Python and TypeScript available as target languages.
7+
The `algokit generate client` [command](../cli/index.md#client) can be used to generate a typed client from an [ARC-0032](https://arc.algorand.foundation/ARCs/arc-0032) or [ARC-0056](https://github.com/algorandfoundation/ARCs/pull/258) application specification with both Python and TypeScript available as target languages.
88

99
### Prerequisites
1010

@@ -15,7 +15,7 @@ Each generated client will also have a dependency on `algokit-utils` libraries f
1515

1616
### Input file / directory
1717

18-
You can either specify a path to a ARC-0032 JSON file, or to a directory that is recursively scanned for `application.json` or `*.arc32.json` file(s).
18+
You can either specify a path to an ARC-0032 JSON file, an ARC-0056 JSON file or to a directory that is recursively scanned for `application.json`, `*.arc32.json`, `*.arc56.json` file(s).
1919

2020
### Output tokens
2121

@@ -24,8 +24,8 @@ The output path is interpreted as relative to the current working directory, how
2424

2525
There are two tokens available for use with the `-o`, `--output` [option](../cli/index.md#-o---output-):
2626

27-
- `{contract_name}`: This will resolve to a name based on the ARC-0032 contract name, formatted appropriately for the target language.
28-
- `{app_spec_dir}`: This will resolve to the parent directory of the `application.json` or `*.arc32.json` file which can be useful to output a client relative to its source file.
27+
- `{contract_name}`: This will resolve to a name based on the ARC-0032/ARC-0056 contract name, formatted appropriately for the target language.
28+
- `{app_spec_dir}`: This will resolve to the parent directory of the `application.json`, `*.arc32.json`, `*.arc56.json` file which can be useful to output a client relative to its source file.
2929

3030
### Version Pinning
3131

src/algokit/cli/generate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def generate_group() -> None:
141141
def generate_client(
142142
output_path_pattern: str | None, app_spec_path_or_dir: Path, language: str | None, version: str | None
143143
) -> None:
144-
"""Create a typed ApplicationClient from an ARC-32 application.json
144+
"""Create a typed ApplicationClient from an ARC-32/56 application.json
145145
146146
Supply the path to an application specification file or a directory to recursively search
147147
for "application.json" files"""
@@ -164,7 +164,7 @@ def generate_client(
164164
if not app_spec_path_or_dir.is_dir():
165165
app_specs = [app_spec_path_or_dir]
166166
else:
167-
patterns = ["application.json", "*.arc32.json"]
167+
patterns = ["application.json", "*.arc32.json", "*.arc56.json"]
168168

169169
app_specs = []
170170
for pattern in patterns:

src/algokit/cli/project/link.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import logging
22
import typing
33
from dataclasses import dataclass
4+
from itertools import chain
45
from pathlib import Path
56

67
import click
@@ -85,12 +86,12 @@ def _link_projects(
8586
"""
8687
output_path_pattern = f"{frontend_clients_path}/{{contract_name}}.{'ts' if language == 'typescript' else 'py'}"
8788
generator = ClientGenerator.create_for_language(language, version=version)
88-
app_specs = list(contract_project_root.rglob("application.json")) + list(
89-
contract_project_root.rglob("*.arc32.json")
90-
)
89+
file_patterns = ["application.json", "*.arc32.json", "*.arc56.json"]
90+
app_specs = list(chain.from_iterable(contract_project_root.rglob(pattern) for pattern in file_patterns))
9191
if not app_specs:
9292
click.secho(
93-
f"WARNING: No application.json | *.arc32.json files found in {contract_project_root}. Skipping...",
93+
f"WARNING: No application.json | *.arc32.json | *.arc56.json files found in {contract_project_root}. "
94+
"Skipping...",
9495
fg="yellow",
9596
)
9697
return

src/algokit/core/typed_client_generation.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,10 @@ def create_for_extension(cls, extension: str, version: str | None) -> "ClientGen
5858
def resolve_output_path(self, app_spec: Path, output_path_pattern: str | None) -> Path | None:
5959
try:
6060
application_json = json.loads(app_spec.read_text())
61-
contract_name: str = application_json["contract"]["name"]
61+
try:
62+
contract_name: str = application_json["name"] # ARC-56
63+
except KeyError:
64+
contract_name = application_json["contract"]["name"] # ARC-32
6265
except Exception:
6366
logger.error(f"Couldn't parse contract name from {app_spec}", exc_info=True)
6467
return None

tests/generate/test_generate_client.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ def arc32_json(cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory) -> P
7676
return dir_with_app_spec_factory(cwd, "app.arc32.json")
7777

7878

79+
@pytest.fixture()
80+
def arc56_json(cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory) -> Path:
81+
return dir_with_app_spec_factory(cwd, "app.arc56.json")
82+
83+
7984
@pytest.fixture(autouse=True)
8085
def which_mock(mocker: MockerFixture) -> WhichMock:
8186
which_mock = WhichMock()
@@ -211,6 +216,26 @@ def test_generate_client_python_arc32_filename(
211216
assert proc_mock.called[3].command == _get_python_generate_command(None, arc32_json, expected_output_path).split()
212217

213218

219+
@pytest.mark.parametrize(
220+
("options", "expected_output_path"),
221+
[
222+
("-o client.py", "client.py"),
223+
],
224+
)
225+
def test_generate_client_python_arc56_filename(
226+
proc_mock: ProcMock, arc56_json: Path, options: str, expected_output_path: Path
227+
) -> None:
228+
proc_mock.should_bad_exit_on(["poetry", "show", PYTHON_PYPI_PACKAGE, "--tree"])
229+
proc_mock.should_bad_exit_on(["pipx", "list", "--short"])
230+
231+
result = invoke(f"generate client {options} {arc56_json.name}", cwd=arc56_json.parent)
232+
233+
assert result.exit_code == 0
234+
verify(_normalize_output(result.output), options=NamerFactory.with_parameters(*options.split()))
235+
assert len(proc_mock.called) == 4 # noqa: PLR2004
236+
assert proc_mock.called[3].command == _get_python_generate_command(None, arc56_json, expected_output_path).split()
237+
238+
214239
@pytest.mark.usefixtures("mock_platform_system")
215240
@pytest.mark.parametrize(
216241
("options", "expected_output_path"),
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
DEBUG: Searching for project installed client generator
2+
DEBUG: Running 'poetry show algokit-client-generator --tree' in '{current_working_directory}'
3+
DEBUG: poetry: STDOUT
4+
DEBUG: poetry: STDERR
5+
DEBUG: Running 'pipx --version' in '{current_working_directory}'
6+
DEBUG: pipx: STDOUT
7+
DEBUG: pipx: STDERR
8+
DEBUG: Searching for globally installed client generator
9+
DEBUG: Running 'pipx list --short' in '{current_working_directory}'
10+
DEBUG: pipx: STDOUT
11+
DEBUG: pipx: STDERR
12+
DEBUG: No matching installed client generator found, run client generator via pipx
13+
Generating Python client code for application specified in {current_working_directory}/app.arc56.json and writing to client.py
14+
DEBUG: Running 'pipx run --spec=algokit-client-generator algokitgen-py -a {current_working_directory}/app.arc56.json -o client.py' in '{current_working_directory}'
15+
DEBUG: pipx: STDOUT
16+
DEBUG: pipx: STDERR
17+
STDOUT
18+
STDERR

tests/generate/test_generate_client.test_generate_help.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Options:
88
-h, --help Show this message and exit.
99

1010
Commands:
11-
client Create a typed ApplicationClient from an ARC-32 application.json
11+
client Create a typed ApplicationClient from an ARC-32/56 application.json

tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_invalid_generic_generator.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ Options:
99
-h, --help Show this message and exit.
1010

1111
Commands:
12-
client Create a typed ApplicationClient from an ARC-32 application.json
12+
client Create a typed ApplicationClient from an ARC-32/56 application.json

tests/generate/test_generate_custom_generate_commands.test_generate_custom_generate_commands_no_toml.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ Options:
88
-h, --help Show this message and exit.
99

1010
Commands:
11-
client Create a typed ApplicationClient from an ARC-32 application.json
11+
client Create a typed ApplicationClient from an ARC-32/56 application.json

0 commit comments

Comments
 (0)