Skip to content

Commit

Permalink
Support multiple patterns when generating app client, including arc32…
Browse files Browse the repository at this point in the history
… suffix (#345)

* feat: Update generate.py to support multiple patterns when generating app client including arc32 suffix

I created a list called `patterns` to add different patterns `rglob` should pick up. This design will allow more patterns to be added in the future with ease.

* chore: Adding test coverage for new arc32 suffix client generate feature

* docs: Updated documentation with arc32 suffix

---------

Co-authored-by: Chris Kim (Hyunggun) <[email protected]>
  • Loading branch information
robdmoore and iskysun96 authored Nov 7, 2023
1 parent f64ffd0 commit b11ec18
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 17 deletions.
25 changes: 14 additions & 11 deletions docs/features/generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ The `algokit generate` [command](../cli/index.md#generate) is used to generate c

## 1. Typed clients

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.
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.

### Prerequisites

Expand All @@ -17,6 +16,10 @@ Each generated client will also have a dependency on `algokit-utils` libraries f
- Python clients require: `algokit-utils@^1.2`
- TypeScript clients require: `@algorandfoundation/algokit-utils@^2.0`

### Input file / directory

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).

### Output tokens

The output path is interpreted as relative to the current working directory, however an absolute path may also be specified e.g.
Expand All @@ -25,7 +28,7 @@ The output path is interpreted as relative to the current working directory, how
There are two tokens available for use with the `-o`, `--output` [option](../cli/index.md#-o---output-):

- `{contract_name}`: This will resolve to a name based on the ARC-0032 contract name, formatted appropriately for the target language.
- `{app_spec_dir}`: This will resolve to the parent directory of an `application.json` which can be useful to output a client relative to its source application.json.
- `{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.

### Usage

Expand Down Expand Up @@ -59,24 +62,24 @@ response = app_client.hello(name="World")

```typescript
// A similar working example can be seen in the beaker_production template, when using TypeScript deployment
import { HelloWorldAppClient } from './artifacts/HelloWorldApp/client'
import { HelloWorldAppClient } from "./artifacts/HelloWorldApp/client";

const appClient = new HelloWorldAppClient(
{
resolveBy: 'creatorAndName',
resolveBy: "creatorAndName",
findExistingUsing: indexer,
sender: deployer,
creatorAddress: deployer.addr,
},
algod,
)
algod
);
const app = await appClient.deploy({
allowDelete: isLocal,
allowUpdate: isLocal,
onSchemaBreak: isLocal ? 'replace' : 'fail',
onUpdate: isLocal ? 'update' : 'fail',
})
const response = await appClient.hello({ name: 'world' })
onSchemaBreak: isLocal ? "replace" : "fail",
onUpdate: isLocal ? "update" : "fail",
});
const response = await appClient.hello({ name: "world" });
```

### Examples
Expand Down
9 changes: 8 additions & 1 deletion src/algokit/cli/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,14 @@ def generate_client(output_path_pattern: str | None, app_spec_path_or_dir: Path,
if not app_spec_path_or_dir.is_dir():
app_specs = [app_spec_path_or_dir]
else:
app_specs = sorted(app_spec_path_or_dir.rglob("application.json"))
patterns = ["application.json", "*.arc32.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:
Expand Down
30 changes: 25 additions & 5 deletions tests/generate/test_generate_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from tests.utils.proc_mock import ProcMock
from tests.utils.which_mock import WhichMock

DirWithAppSpecFactory = Callable[[Path], Path]
DirWithAppSpecFactory = Callable[[Path, str], Path]


def _normalize_output(output: str) -> str:
Expand All @@ -29,9 +29,9 @@ def cwd(tmp_path_factory: TempPathFactory) -> Path:
def dir_with_app_spec_factory() -> DirWithAppSpecFactory:
app_spec_example_path = Path(__file__).parent / "application.json"

def factory(app_spec_dir: Path) -> Path:
def factory(app_spec_dir: Path, app_spec_file_name: str) -> Path:
app_spec_dir.mkdir(exist_ok=True, parents=True)
app_spec_path = app_spec_dir / "application.json"
app_spec_path = app_spec_dir / app_spec_file_name
shutil.copy(app_spec_example_path, app_spec_path)
return app_spec_path

Expand All @@ -40,7 +40,12 @@ def factory(app_spec_dir: Path) -> Path:

@pytest.fixture()
def application_json(cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory) -> Path:
return dir_with_app_spec_factory(cwd)
return dir_with_app_spec_factory(cwd, "application.json")


@pytest.fixture()
def arc32_json(cwd: Path, dir_with_app_spec_factory: DirWithAppSpecFactory) -> Path:
return dir_with_app_spec_factory(cwd, "app.arc32.json")


@pytest.fixture(autouse=True)
Expand Down Expand Up @@ -82,6 +87,21 @@ def test_generate_client_python(application_json: Path, options: str, expected_o
assert (application_json.parent / expected_output_path).read_text()


@pytest.mark.parametrize(
("options", "expected_output_path"),
[
("-o client.py", "client.py"),
],
)
def test_generate_client_python_arc32_filename(arc32_json: Path, options: str, expected_output_path: Path) -> None:
result = invoke(f"generate client {options} {arc32_json.name}", cwd=arc32_json.parent)

assert result.exit_code == 0
verify(_normalize_output(result.output), options=NamerFactory.with_parameters(*options.split()))
assert (arc32_json.parent / expected_output_path).exists()
assert (arc32_json.parent / expected_output_path).read_text()


@pytest.mark.parametrize(
("options", "expected_output_path"),
[
Expand Down Expand Up @@ -133,7 +153,7 @@ def test_generate_client_recursive(cwd: Path, dir_with_app_spec_factory: DirWith
cwd / "dir2" / "sub_dir",
]
for dir_path in dir_paths:
dir_with_app_spec_factory(dir_path)
dir_with_app_spec_factory(dir_path, "application.json")

result = invoke("generate client -o {app_spec_dir}/output.py .", cwd=cwd)
assert result.exit_code == 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Generating Python client code for application specified in {current_working_directory}/app.arc32.json and writing to client.py
Output typed client for HelloWorldApp to client.py

1 comment on commit b11ec18

@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__.py220%1–3
src/algokit/cli
   completions.py108298%83, 98
   deploy.py72790%44, 46, 89–91, 155, 179
   dispenser.py118199%75
   doctor.py48394%142–144
   generate.py62198%116
   goal.py39197%57
   init.py1901692%272–273, 323, 326–328, 339, 383, 409, 449, 458–460, 463–468, 481
   localnet.py93397%162, 183–184
src/algokit/cli/common
   utils.py26292%120, 123
src/algokit/cli/tasks
   assets.py821384%65–66, 72, 74–75, 105, 119, 125–126, 132, 134, 136–137
   ipfs.py541180%54, 84, 89–91, 96, 98–99, 109–111
   mint.py66494%48, 70, 91, 247
   send_transaction.py651085%52–53, 57, 89, 158, 170–174
   sign_transaction.py59886%21, 28–30, 71–72, 109, 123
   transfer.py37392%25, 89, 116
   utils.py994555%26–34, 40–43, 75–76, 100–101, 125–133, 152–162, 209, 258–259, 279–290, 297–299
   vanity_address.py561082%41, 45–48, 112, 114, 121–123
   wallet.py79495%21, 66, 136, 162
src/algokit/core
   bootstrap.py1612485%103–104, 126, 149, 214, 217, 223–237, 246–251
   conf.py54885%10, 24, 28, 36, 38, 71–73
   deploy.py691184%61–64, 73–75, 79, 84, 91–93
   dispenser.py2022687%91, 123–124, 141–149, 191–192, 198–200, 218–219, 259–260, 318, 332–334, 345–346, 356, 369, 384
   doctor.py65789%67–69, 92–94, 134
   generate.py41295%68, 86
   goal.py56395%27–28, 38
   log_handlers.py68790%50–51, 63, 112–116, 125
   proc.py45198%98
   sandbox.py1831592%100–107, 118, 285, 301, 316–318, 334
   typed_client_generation.py80594%55–57, 70, 75
   utils.py18289%27–28
   version_prompt.py73889%27–28, 40, 59–62, 80, 109
src/algokit/core/tasks
   ipfs.py57493%133, 137, 139, 145
   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.py781087%123–133, 187
   models.py901188%50, 52, 57, 71–74, 85–88
TOTAL314334989% 

Tests Skipped Failures Errors Time
374 0 💤 0 ❌ 0 🔥 23.914s ⏱️

Please sign in to comment.