Skip to content

Commit 8d43492

Browse files
authored
chore: improving codebase; adding semantic releases; fixing scripts & tests post migration (#2)
* fix: patching helper scripts; adding pre-commit; bumping compiler version * ci: adding semantic releases * chore: patching pipeline * chore: improving cd * chore: patching ci * chore: refining ci * chore: refine ci
1 parent a488ac3 commit 8d43492

21 files changed

+377
-112
lines changed

.github/workflows/cd.yaml

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
name: Continuous Delivery of Algorand Python Testing package
22

33
on:
4+
push:
5+
branches:
6+
- main
7+
paths-ignore:
8+
- "**.md"
9+
- "docs/**"
10+
- "scripts/**"
11+
- "examples/**"
12+
- "tests/**"
13+
414
workflow_dispatch:
515
inputs:
6-
publish_pypi:
7-
description: "Publish to PyPi?"
8-
type: boolean
9-
required: true
10-
default: false
11-
run_checks:
12-
description: "Run checks?"
16+
prerelease:
17+
description: "Prerelease?"
1318
type: boolean
1419
required: true
1520
default: true
@@ -35,10 +40,12 @@ jobs:
3540
contents: write
3641
packages: read
3742
env:
38-
DRY_RUN: ${{ inputs.dry_run && '--noop' || '' }}
43+
DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.dry_run && '--noop' || '') || '' }}
44+
PRERELEASE: ${{ github.event_name == 'workflow_dispatch' && (github.event.inputs.prerelease && 'true' || '') || 'true' }}
3945
steps:
4046
- uses: actions/checkout@v4
4147
with:
48+
fetch-depth: 0
4249
token: ${{ secrets.RELEASE_GITHUB_TOKEN }}
4350

4451
- name: Install hatch
@@ -63,20 +70,25 @@ jobs:
6370
run: hatch build
6471

6572
- name: Run tests (codebase)
66-
run: hatch run test:codebase
73+
run: hatch run tests
6774

6875
- name: Run tests (examples)
6976
run: hatch run examples:tests
7077

71-
- name: Create Unit Testing Wheel
72-
run: hatch build
73-
7478
- uses: actions/upload-artifact@v4 # upload artifacts so they are retained on the job
7579
with:
7680
path: dist
7781

78-
- name: Publish to PyPI - Unit Testing
79-
if: ${{ !inputs.dry_run && inputs.publish_pypi }}
82+
- name: Python Semantic Release
83+
if: ${{ github.ref == 'refs/heads/main' }}
84+
uses: python-semantic-release/python-semantic-release@master
85+
with:
86+
github_token: ${{ secrets.RELEASE_GITHUB_TOKEN }}
87+
prerelease: ${{ env.PRERELEASE == 'true' }}
88+
root_options: $DRY_RUN
89+
90+
- name: Publish to PyPI
91+
if: ${{ !inputs.dry_run }}
8092
uses: pypa/gh-action-pypi-publish@release/v1
8193
with:
8294
packages-dir: dist

.github/workflows/ci.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
run: hatch build
3434

3535
- name: Run tests (codebase)
36-
run: hatch run test:codebase
36+
run: hatch run tests
3737

3838
- name: Run tests (examples)
3939
run: hatch run examples:tests

.pre-commit-config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
repos:
2+
- repo: local
3+
hooks:
4+
- id: pre-commit
5+
name: pre-commit
6+
description: "Run pre-commit task via hatch"
7+
entry: hatch run pre_commit
8+
language: system
9+
additional_dependencies: []
10+
minimum_pre_commit_version: "0"
11+
12+
- id: examples-pre-commit
13+
name: examples-pre-commit
14+
description: "Run examples venv pre-commit task via hatch"
15+
entry: hatch run examples:pre_commit
16+
language: system
17+
additional_dependencies: []
18+
minimum_pre_commit_version: "0"

CONTRIBUTING.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
# Development dependencies
1+
# Contributing to `algorand-python-testing`
2+
3+
Welcome to the Algorand Python Testing library! This guide will help you get started with the project and contribute to its development.
24

35
# Development Dependencies
46

57
Our development environment relies on the following tools:
68

79
- **Python 3.12**
810
- **[Hatch](https://hatch.pypa.io/1.9/install/)**: A modern, extensible Python project manager.
11+
- **[pre-commit](https://pre-commit.com/)**: A framework for managing and maintaining code quality.
912

1013
## Common Commands
1114

@@ -22,6 +25,18 @@ Here are some common commands you will use with Hatch:
2225
- **Regenerate typed clients for example contracts:** `hatch run refresh_test_artifacts`
2326
- **Coverage check of existing progress on implementing AlgoPy Stubs:** `hatch run check_stubs_cov`
2427

28+
# Using `pre-commit`
29+
30+
Execute `pre-commit install` to ensure auto run of hatch against `src` and `examples` folders
31+
2532
# Examples folder
2633

2734
Examples folder uses a dedicated 'venv.examples' virtual environment managed by Hatch that simulates a user environment with both algorand-python and algorand-python-testing installed explicitly. This is useful for testing new features or bug fixes in the testing library.
35+
36+
- **Pre-commit checks against examples:** `hatch run examples:pre-commit`
37+
38+
# Release automation
39+
40+
Project relies on [python-semantic-release](https://python-semantic-release.readthedocs.io/en/latest/) for release automation.
41+
42+
Releases are triggered by a `workflow_dispatch` event on the `main` branch with `prerelease` set to accordingly using the `.github/workflows/cd.yaml` file.

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,15 @@
1-
# Algorand Python Testing
1+
<div align="center">
2+
<a href="https://github.com/algorandfoundation/algorand-python-testing"><img src="https://bafybeiaibjaf6zy6hvef2rrysaacsfsyb3hw4qqtgn657gw7k5tdzqdxzi.ipfs.nftstorage.link/" width=60%></a>
3+
</div>
4+
5+
<p align="center">
6+
<a target="_blank" href="https://algorandfoundation.github.io/algorand-python-testing/"><img src="https://img.shields.io/badge/docs-repository-74dfdc?logo=github&style=flat.svg" /></a>
7+
<a target="_blank" href="https://developer.algorand.org/algokit/"><img src="https://img.shields.io/badge/learn-AlgoKit-74dfdc?logo=algorand&mac=flat.svg" /></a>
8+
<a target="_blank" href="https://github.com/algorandfoundation/algorand-python-testing"><img src="https://img.shields.io/github/stars/algorandfoundation/algorand-python-testing?color=74dfdc&logo=star&style=flat" /></a>
9+
<a target="_blank" href="https://developer.algorand.org/algokit/"><img src="https://api.visitorbadge.io/api/visitors?path=https%3A%2F%2Fgithub.com%2Falgorandfoundation%2Falgorand-python-testing&countColor=%2374dfdc&style=flat" /></a>
10+
</p>
11+
12+
---
213

314
Algorand Python Testing is a companion package to [Algorand Python](https://github.com/algorandfoundation/puya) that enables efficient unit testing of Algorand Python smart contracts in an offline environment. It emulates key AVM behaviors without requiring a network connection, offering fast and reliable testing capabilities with a familiar Pythonic interface.
415

examples/zk_whitelist/contract.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Txn,
1212
UInt64,
1313
itxn,
14+
op,
1415
subroutine,
1516
)
1617
from algopy.arc4 import (
@@ -62,7 +63,9 @@ def add_address_to_whitelist(self, address: Bytes32, proof: DynamicArray[Bytes32
6263
# The verifier expects public inputs to be in the curve field, but an
6364
# Algorand address might represent a number larger than the field
6465
# modulus, so to be safe we take the address modulo the field modulus
65-
address_mod = Bytes32.from_bytes((py.BigUInt.from_bytes(address.bytes) % curve_mod).bytes)
66+
address_mod = Bytes32.from_bytes(
67+
(py.BigUInt.from_bytes(address.bytes) % curve_mod).bytes or op.bzero(32)
68+
)
6669

6770
# Verify the proof by calling the deposit verifier app
6871
verified = verify_proof(

pyproject.toml

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "algorand-python-testing"
7-
version = "0.2.1"
7+
version = "0.0.2-beta.1"
88
description = 'Algorand Python testing library'
99
readme = "README.md"
1010
requires-python = ">=3.12"
@@ -52,7 +52,7 @@ type = "virtual"
5252
path = ".venv"
5353
python = "3.12"
5454
dependencies = [
55-
"puyapy>=2.1",
55+
"puyapy>=2.1.2",
5656
"pytest>=7.4",
5757
"pytest-mock>=3.10.0",
5858
"pytest-xdist[psutil]>=3.3",
@@ -81,6 +81,7 @@ check = [
8181
]
8282
# type checks algorand-python-testing code
8383
mypy_testing = "mypy . --exclude examples"
84+
tests = "pytest --cov --cov-report xml"
8485

8586
# isolated environments for dev tooling
8687
[tool.hatch.envs.lint]
@@ -109,6 +110,7 @@ fix_examples = [ 'black examples', 'ruff check --fix examples']
109110
path = ".venv.cicd"
110111
dependencies = [
111112
"algokit",
113+
"python-semantic-release>=9.8.5",
112114
]
113115
[tool.hatch.envs.cicd.scripts]
114116
localnet_start = [
@@ -158,17 +160,6 @@ check = [
158160
"hatch run mypy examples",
159161
]
160162

161-
[tool.hatch.envs.test]
162-
dependencies = [
163-
"pytest>=7.4",
164-
"pytest-cov>=4.1.0",
165-
]
166-
[tool.hatch.envs.test.scripts]
167-
codebase = "pytest --cov --cov-report xml"
168-
169-
[tool.hatch.envs.test.env-vars]
170-
PYTHONPATH = "./src"
171-
172163
# tool configurations
173164
[tool.black]
174165
line-length = 99
@@ -277,3 +268,30 @@ follow_imports = "skip"
277268

278269
[tool.pytest.ini_options]
279270
addopts = "-n auto --cov-config=.coveragerc"
271+
pythonpath = ['src']
272+
273+
# === Semantic releases config ===
274+
275+
[tool.semantic_release]
276+
version_toml = ["pyproject.toml:project.version"]
277+
build_command = "hatch build"
278+
commit_message = "{version}\n\nAutomatically generated by python-semantic-release"
279+
tag_format = "v{version}"
280+
major_on_zero = true
281+
282+
[tool.semantic_release.branches.main]
283+
match = "main"
284+
prerelease_token = "beta"
285+
prerelease = false
286+
287+
[tool.semantic_release.commit_parser_options]
288+
allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "style", "refactor", "test"]
289+
minor_tags = ["feat"]
290+
patch_tags = ["fix", "perf", "docs"]
291+
292+
[tool.semantic_release.publish]
293+
dist_glob_patterns = ["dist/*.whl"] # order here is important to ensure compiler wheel is published first
294+
upload_to_vcs_release = true
295+
296+
[tool.semantic_release.remote.token]
297+
env = "GITHUB_TOKEN"

scripts/check_stubs_cov.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import ast
22
import inspect
3+
import site
34
import sys
45
from collections.abc import Iterable
56
from pathlib import Path
67
from typing import NamedTuple
78

89
from prettytable import PrettyTable
910

10-
# TODO: update to follow the new project structure
11-
12-
PROJECT_ROOT = (Path(__file__).parent / "..").resolve()
13-
VCS_ROOT = (PROJECT_ROOT / "..").resolve()
14-
STUBS = VCS_ROOT / "stubs" / "algopy-stubs"
11+
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
12+
SITE_PACKAGES = Path(site.getsitepackages()[0])
13+
STUBS_ROOT = SITE_PACKAGES / "algopy-stubs"
1514
IMPL = PROJECT_ROOT / "src"
1615
ROOT_MODULE = "algopy"
1716

@@ -49,13 +48,20 @@ def coverage(self) -> float:
4948

5049

5150
def main() -> None:
51+
clear_algopy_content()
5252
stubs = collect_public_stubs()
5353
coverage = collect_coverage(stubs)
5454
print_results(coverage)
5555

5656

57+
def clear_algopy_content() -> None:
58+
algopy_path = SITE_PACKAGES / "algopy.py"
59+
if algopy_path.exists():
60+
algopy_path.write_text("")
61+
62+
5763
def collect_public_stubs() -> dict[str, ASTNodeDefinition]:
58-
stubs_root = STUBS / "__init__.pyi"
64+
stubs_root = STUBS_ROOT / "__init__.pyi"
5965
stubs_ast = _parse_python_file(stubs_root)
6066
result = dict[str, ASTNodeDefinition]()
6167
for stmt in stubs_ast.body:
@@ -80,15 +86,15 @@ def collect_imports(
8086
dest = alias.asname
8187
# from module import *
8288
if src == "*" and dest is None:
83-
for defn_name, defn in collect_stubs(STUBS, relative_module).items():
89+
for defn_name, defn in collect_stubs(STUBS_ROOT, relative_module).items():
8490
yield f"{ROOT_MODULE}.{defn_name}", defn
8591
# from root import src as src
8692
elif stmt.module == ROOT_MODULE and dest == src:
87-
for defn_name, defn in collect_stubs(STUBS, src).items():
93+
for defn_name, defn in collect_stubs(STUBS_ROOT, src).items():
8894
yield f"{ROOT_MODULE}.{dest}.{defn_name}", defn
8995
# from foo.bar import src as src
9096
elif dest == src:
91-
stubs = collect_stubs(STUBS, relative_module)
97+
stubs = collect_stubs(STUBS_ROOT, relative_module)
9298
yield f"{ROOT_MODULE}.{src}", stubs[src]
9399
else:
94100
raise NotImplementedError
@@ -138,7 +144,7 @@ def collect_coverage(stubs: dict[str, ASTNodeDefinition]) -> list[CoverageResult
138144
result.append(
139145
CoverageResult(
140146
full_name=full_name,
141-
stub_file=str(stub.path.relative_to(STUBS)),
147+
stub_file=str(stub.path.relative_to(STUBS_ROOT)),
142148
impl_file=impl_file,
143149
coverage=coverage.coverage if coverage else 0,
144150
missing=", ".join(coverage.missing if coverage else []),
@@ -185,15 +191,25 @@ def _parse_python_file(filepath: Path) -> ast.Module:
185191

186192
def _get_impl_coverage(symbol: str, stub: ASTNodeDefinition) -> ImplCoverage | None:
187193
import importlib
194+
import sys
195+
from pathlib import Path
196+
197+
# Add the src directory to the Python path
198+
src_path = Path(__file__).parent.parent / "src"
199+
sys.path.insert(0, str(src_path))
188200

189201
module, name = symbol.rsplit(".", maxsplit=1)
190202
try:
203+
# Use importlib.import_module for both algopy and non-algopy modules
191204
mod = importlib.import_module(module)
192-
except ImportError:
205+
except ImportError as ex:
206+
print(f"Error importing {module}: {ex}")
193207
return None
208+
194209
try:
195210
impl = getattr(mod, name)
196211
except AttributeError:
212+
print(f"Attribute {name} not found in module {module}")
197213
return None
198214

199215
try:

scripts/refresh_test_artifacts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def compile_contract(folder: Path) -> None:
2727
"hatch",
2828
"run",
2929
"puyapy",
30+
"--log-level",
31+
"debug",
3032
str(contract_path),
3133
"--out-dir",
3234
"data",

0 commit comments

Comments
 (0)