diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 673b40d65..19fe6be69 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,13 +13,11 @@ jobs: container: python:${{ matrix.python }} strategy: matrix: - python: ["3.10", "3.11"] + python: ["3.10", "3.11", "3.12"] steps: - run: python3 --version - name: Check out code uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Install Python dependencies, Build, and Test run: make setup-build-test @@ -27,12 +25,10 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: ["3.10", "3.11"] + python: ["3.10", "3.11", "3.12"] steps: - name: Check out code uses: actions/checkout@v3 - with: - fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: "${{ matrix.python }}" @@ -53,8 +49,6 @@ jobs: steps: - name: Check out code uses: actions/checkout@v3 - with: - fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: "${{ matrix.python }}" @@ -77,8 +71,6 @@ jobs: steps: - name: Check out code uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Install dependencies run: make setup-wheel - name: Build package diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 1fe1be0f7..9ba66f666 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -10,13 +10,11 @@ jobs: container: python:${{ matrix.python }} strategy: matrix: - python: ["3.10", "3.11"] + python: ["3.10", "3.11", "3.12"] steps: - run: python3 --version - name: Check out code uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Check for code changes id: check run: make check-code-changes @@ -32,12 +30,10 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python: ["3.10", "3.11"] + python: ["3.10", "3.11", "3.12"] steps: - name: Check out code uses: actions/checkout@v3 - with: - fetch-depth: 0 - name: Check for code changes id: check run: make check-code-changes diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eeaf4a58..1125a414c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Added +* Support for Python 3.12. ([#713](https://github.com/algorand/pyteal/pull/713)) + ## Fixed ## Changed diff --git a/docs/requirements.txt b/docs/requirements.txt index 117f5b9d5..8f0d34023 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,7 +2,7 @@ sphinx==5.1.1 sphinx-rtd-theme==1.0.0 # dependencies from setup.py docstring-parser==0.14.1 -executing==1.2.0 +executing==2.0.1 py-algorand-sdk>=2.0.0,<3.0.0 semantic-version>=2.9.0,<3.0.0 tabulate>=0.9.0,<0.10.0 diff --git a/examples/application/sourcemap.py b/examples/application/sourcemap.py index 25f9e0265..9126b68af 100644 --- a/examples/application/sourcemap.py +++ b/examples/application/sourcemap.py @@ -10,7 +10,7 @@ from feature_gates import FeatureGates -FeatureGates.set_sourcemap_enabled(True) +FeatureGates.set("sourcemap_enabled", True) # INTENTIONALLY importing pyteal and objects _AFTER_ enabling the sourcemap feature import pyteal as pt # noqa: E402 @@ -56,6 +56,8 @@ def is_even(i): f.write(results.teal) with open(annotated, "w") as f: + assert isinstance(results.sourcemap, pt.PyTealSourceMap) + assert isinstance(results.sourcemap.annotated_teal, str) f.write(results.sourcemap.annotated_teal) print(f"SUCCESS!!! Please check out {teal} and {annotated}") diff --git a/examples/signature/dutch_auction.py b/examples/signature/dutch_auction.py index ff60ad243..be2adbb61 100644 --- a/examples/signature/dutch_auction.py +++ b/examples/signature/dutch_auction.py @@ -3,7 +3,7 @@ from feature_gates import FeatureGates -FeatureGates.set_sourcemap_enabled(True) +FeatureGates.set("sourcemap_enabled", True) from pyteal import * # noqa: E402 @@ -157,4 +157,6 @@ def dutch_auction( f.write(results.teal) with open(EXAMPLES / "dutch_auction_annotated.teal", "w") as f: + assert isinstance(results.sourcemap, PyTealSourceMap) + assert isinstance(results.sourcemap.annotated_teal, str) f.write(results.sourcemap.annotated_teal) diff --git a/pyteal/ast/abi/util_test.py b/pyteal/ast/abi/util_test.py index 4be987a21..c80f418cc 100644 --- a/pyteal/ast/abi/util_test.py +++ b/pyteal/ast/abi/util_test.py @@ -870,7 +870,7 @@ def test_type_spec_is_assignable_safe_bidirectional_full_coverage(ts: type): def exists_in_safe_bidirectional(_ts: type): for safe_bidirectional in SAFE_BIDIRECTIONAL_TEST_CASES: for t in safe_bidirectional.xs: - if type(t) == _ts: + if type(t) is _ts: return True return False @@ -943,10 +943,10 @@ def test_type_spec_is_assignable_safe_assignment(tc: SafeAssignment): def test_type_spec_is_assignable_safe_assignment_full_coverage(ts: type): def exists_in_safe_assignment(_ts: type): for safe_assignment in SAFE_ASSIGNMENT_TEST_CASES: - if type(safe_assignment.a) == _ts: + if type(safe_assignment.a) is _ts: return True for t in safe_assignment.bs: - if type(t) == _ts: + if type(t) is _ts: return True return False @@ -1049,7 +1049,7 @@ def test_type_spec_is_assignable_unsafe_bidirectional_full_coverage(ts: type): def exists_in_unsafe_bidirectional(_ts: type): for unsafe_bidirectional in UNSAFE_BIDIRECTIONAL_TEST_CASES: for t in unsafe_bidirectional.xs: - if type(t) == _ts: + if type(t) is _ts: return True return False diff --git a/pyteal/ast/opup.py b/pyteal/ast/opup.py index 6fe1cc05f..fe4dc5755 100644 --- a/pyteal/ast/opup.py +++ b/pyteal/ast/opup.py @@ -146,7 +146,7 @@ def ensure_budget( should be sufficient for most use cases. If lack of budget is an issue then consider moving the call to ensure_budget() earlier in the pyteal program.""" require_type(required_budget, TealType.uint64) - if not type(fee_source) == OpUpFeeSource: + if type(fee_source) is not OpUpFeeSource: raise TealTypeError(type(fee_source), OpUpFeeSource) # A budget buffer is necessary to deal with an edge case of ensure_budget(): @@ -179,7 +179,7 @@ def maximize_budget( sufficient for most use cases. If lack of budget is an issue then consider moving the call to maximize_budget() earlier in the pyteal program.""" require_type(fee, TealType.uint64) - if not type(fee_source) == OpUpFeeSource: + if type(fee_source) is not OpUpFeeSource: raise TealTypeError(type(fee_source), OpUpFeeSource) i = ScratchVar(TealType.uint64) diff --git a/pyteal/compiler/constants.py b/pyteal/compiler/constants.py index 415b7d19e..4050d1166 100644 --- a/pyteal/compiler/constants.py +++ b/pyteal/compiler/constants.py @@ -81,7 +81,7 @@ def extractAddrValue(op: TealOp) -> Union[str, bytes]: If the op is loading a template variable, returns the name of the variable as a string. Otherwise, returns the bytes of the public key of the address that the op is loading. """ - if len(op.args) != 1 or type(op.args[0]) != str: + if len(op.args) != 1 or type(op.args[0]) is not str: raise TealInternalError("Unexpected args in addr opcode: {}".format(op.args)) value = cast(str, op.args[0]) @@ -96,7 +96,7 @@ def extractMethodSigValue(op: TealOp) -> bytes: Returns: The bytes of method selector computed from the method signature that the op is loading. """ - if len(op.args) != 1 or type(op.args[0]) != str: + if len(op.args) != 1 or type(op.args[0]) is not str: raise TealInternalError("Unexpected args in method opcode: {}".format(op.args)) methodSignature = cast(str, op.args[0]) diff --git a/pyteal/compiler/optimizer/optimizer.py b/pyteal/compiler/optimizer/optimizer.py index ae52b4234..a9ecd98b4 100644 --- a/pyteal/compiler/optimizer/optimizer.py +++ b/pyteal/compiler/optimizer/optimizer.py @@ -64,7 +64,7 @@ def use_frame_pointers(self, version: int) -> bool: def _remove_extraneous_slot_access(start: TealBlock, remove: Set[ScratchSlot]): def keep_op(op: TealOp) -> bool: - if type(op) != TealOp or (op.op != Op.store and op.op != Op.load): + if type(op) is not TealOp or (op.op != Op.store and op.op != Op.load): return True return not set(op.getSlots()).issubset(remove) @@ -84,7 +84,7 @@ def _has_load_dependencies( if block == cur_block and i == pos: continue - if type(op) == TealOp and op.op == Op.load and slot in set(op.getSlots()): + if type(op) is TealOp and op.op == Op.load and slot in set(op.getSlots()): return True return False @@ -96,14 +96,14 @@ def _apply_slot_to_stack( slots_to_remove = set() # surprisingly, this slicing is totally safe - even if the list is empty. for i, op in enumerate(cur_block.ops[:-1]): - if type(op) != TealOp or op.op != Op.store: + if type(op) is not TealOp or op.op != Op.store: continue if set(op.getSlots()).issubset(skip_slots): continue next_op = cur_block.ops[i + 1] - if type(next_op) != TealOp or next_op.op != Op.load: + if type(next_op) is not TealOp or next_op.op != Op.load: continue cur_slots, next_slots = op.getSlots(), next_op.getSlots() diff --git a/pyteal/compiler/sourcemap.py b/pyteal/compiler/sourcemap.py index c745c2a46..975978eb4 100644 --- a/pyteal/compiler/sourcemap.py +++ b/pyteal/compiler/sourcemap.py @@ -203,7 +203,7 @@ def __post_init__(self): if entry >= entries[i + 1]: raise TypeError( - f"Invalid source map as entries aren't properly ordered: entries[{i}] = {entry} >= entries[{i+1}] = {entries[i + 1]}" + f"Invalid source map as entries aren't properly ordered: entries[{i}] = {entry} >= entries[{i + 1}] = {entries[i + 1]}" ) def __repr__(self) -> str: @@ -1329,12 +1329,12 @@ def _validate_annotated( ): if not annotated_line.startswith(teal_line): raise cls._unexpected_error( - f"annotated teal ought to begin exactly with the teal line but line {i+1} [{annotated_line}] doesn't start with [{teal_line}]", + f"annotated teal ought to begin exactly with the teal line but line {i + 1} [{annotated_line}] doesn't start with [{teal_line}]", ) pattern = r"^\s*($|//.*)" if not re.match(pattern, annotated_line[len(teal_line) :]): raise cls._unexpected_error( - f"annotated teal ought to begin exactly with the teal line followed by annotation in comments but line {i+1} [{annotated_line}] has non-commented out annotations" + f"annotated teal ought to begin exactly with the teal line followed by annotation in comments but line {i + 1} [{annotated_line}] has non-commented out annotations" ) @classmethod diff --git a/pyteal/errors.py b/pyteal/errors.py index b2e9e0433..dd74ecbf6 100644 --- a/pyteal/errors.py +++ b/pyteal/errors.py @@ -98,7 +98,9 @@ def __str__(self): class SourceMapDisabledError(RuntimeError): - msg = value = """ + msg = ( + value + ) = """ Cannot calculate Teal to PyTeal source map because stack frame discovery is turned off. To enable source maps: import `from feature_gates import FeatureGates` and call `FeatureGates.set_sourcemap_enabled(True)`. diff --git a/pyteal/pragma/pragma.py b/pyteal/pragma/pragma.py index 745b28e28..73786a8e0 100644 --- a/pyteal/pragma/pragma.py +++ b/pyteal/pragma/pragma.py @@ -1,5 +1,5 @@ import re -import pkg_resources +from importlib import metadata from typing import Any from semantic_version import Version, NpmSpec @@ -116,7 +116,7 @@ def pragma( # version constraint pragma(compiler_version="^0.14.0") """ - pkg_version = pkg_resources.require("pyteal")[0].version + pkg_version = metadata.version("pyteal") pyteal_version = Version(__convert_pep440_compiler_version(pkg_version)) if pyteal_version not in NpmSpec( __convert_pep440_compiler_version(compiler_version) diff --git a/pyteal/pragma/pragma_test.py b/pyteal/pragma/pragma_test.py index 65b4a399b..374505041 100644 --- a/pyteal/pragma/pragma_test.py +++ b/pyteal/pragma/pragma_test.py @@ -1,5 +1,5 @@ import pytest -import pkg_resources +from importlib import metadata from tests.mock_version import ( # noqa: F401 mock_version, ) @@ -54,8 +54,8 @@ def test_convert_pep440_compiler_version(compiler_version, expected): False, ), # Ignores local version (consistent with PEP 440) ( - pkg_resources.require("pyteal")[0].version, - pkg_resources.require("pyteal")[0].version, + metadata.version("pyteal"), + metadata.version("pyteal"), False, ), # invalid diff --git a/pyteal/stack_frame.py b/pyteal/stack_frame.py index c43b7594d..c34a1a291 100644 --- a/pyteal/stack_frame.py +++ b/pyteal/stack_frame.py @@ -9,7 +9,7 @@ import os import re -from executing import Source # type: ignore +from executing import Source class SourceMapStackFramesError(RuntimeError): diff --git a/requirements.txt b/requirements.txt index 5f3bba770..102ea54fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,11 +1,12 @@ -black==23.1.0 -flake8==5.0.4 -flake8-tidy-imports==4.6.0 +black==23.12.0 +flake8==6.1.0 +flake8-tidy-imports==4.10.0 graviton@git+https://github.com/algorand/graviton@v0.9.0 -mypy==1.1.1 -pytest==7.2.0 +mypy==1.7.1 +pytest==7.4.3 pytest-cov==3.0.0 pytest-custom-exit-code==0.3.0 pytest-timeout==2.1.0 pytest-xdist==3.0.2 -types-setuptools==57.4.18 +setuptools==69.0.2 +types-setuptools==69.0.0.0 diff --git a/setup.py b/setup.py index 4167deeb4..3a5c4f743 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ install_requires=[ # when changing this list, also update docs/requirements.txt "docstring-parser==0.14.1", - "executing==1.2.0", + "executing==2.0.1", "py-algorand-sdk>=2.0.0,<3.0.0", "semantic-version>=2.9.0,<3.0.0", "tabulate>=0.9.0,<0.10.0", diff --git a/tests/integration/graviton_test.py b/tests/integration/graviton_test.py index 922525880..988db713a 100644 --- a/tests/integration/graviton_test.py +++ b/tests/integration/graviton_test.py @@ -513,7 +513,7 @@ def blackbox_test_runner( assert mode_has_property(exec_mode, dr_prop) invariant = Invariant(predicate, name=f"{case_name}[{i}]@{mode}-{dr_prop}") - print(f"{i+1}. Assertion for {case_name}-{mode}: {dr_prop} <<{predicate}>>") + print(f"{i + 1}. Assertion for {case_name}-{mode}: {dr_prop} <<{predicate}>>") invariant.validates(dr_prop, inspectors) return inspectors diff --git a/tests/integration/pure_logicsig_test.py b/tests/integration/pure_logicsig_test.py index 510f70f28..422c66468 100644 --- a/tests/integration/pure_logicsig_test.py +++ b/tests/integration/pure_logicsig_test.py @@ -95,7 +95,7 @@ def payment_amount(x, y): ) print( - f"generating a report for (a,p,q) = {a,p,q} with {M, N} dry-run calls and spreadsheet rows" + f"generating a report for (a,p,q) = {a, p, q} with {M, N} dry-run calls and spreadsheet rows" ) filebase = f"factorizer_game_{a}_{p}_{q}" @@ -105,7 +105,9 @@ def payment_amount(x, y): with open(csvpath, "w") as f: f.write(Inspector.csv_report(inputs, inspectors, txns=txns)) - print(f"validating passing_invariant for (a,p,q) = {a,p,q} over {N} dry-run calls") + print( + f"validating passing_invariant for (a,p,q) = {a, p, q} over {N} dry-run calls" + ) passing_invariant = Invariant( lambda args: bool(payment_amount(*args)), name=f"passing invariant for coeffs {a, p, q}", @@ -113,7 +115,7 @@ def payment_amount(x, y): passing_invariant.validates(DRProp.passed, inspectors) print( - f"validate procedurally that payment amount as expected for (a,p,q) = {a,p,q} over {M, N} dry-rundry-run calls" + f"validate procedurally that payment amount as expected for (a,p,q) = {a, p, q} over {M, N} dry-rundry-run calls" ) for args, inspector in zip(inputs, inspectors): diff --git a/tests/integration/sourcemap_monkey_integ_test.py b/tests/integration/sourcemap_monkey_integ_test.py index 6ab0054d3..617faacd5 100644 --- a/tests/integration/sourcemap_monkey_integ_test.py +++ b/tests/integration/sourcemap_monkey_integ_test.py @@ -118,10 +118,10 @@ def first_diff(expected, actual): elines = expected.splitlines() for i, e in enumerate(elines): if i >= len(alines): - return f"""LINE[{i+1}] missing from actual: + return f"""LINE[{i + 1}] missing from actual: {e}""" if e != (a := alines[i]): - return f"""LINE[{i+1}] + return f"""LINE[{i + 1}] {e} VS. {a} diff --git a/tests/mock_version.py b/tests/mock_version.py index d7473fcb6..5bf4f7491 100644 --- a/tests/mock_version.py +++ b/tests/mock_version.py @@ -1,20 +1,16 @@ import pytest -import pkg_resources +from importlib import metadata @pytest.fixture def mock_version(version: str, monkeypatch: pytest.MonkeyPatch): - def mocked_require(name: str): + def mocked_version(name: str): if ( name == "pyteal" and version is not None # don't mock if no version is specified ): - return [ - pkg_resources.Distribution( - version=version, - ) - ] + return version else: - return pkg_resources.require(name)[0] + return metadata.version(name) - monkeypatch.setattr(pkg_resources, "require", mocked_require) + monkeypatch.setattr(metadata, "version", mocked_version) diff --git a/tests/unit/sourcemap_test.py b/tests/unit/sourcemap_test.py index 52e7347cf..2d2bcea39 100644 --- a/tests/unit/sourcemap_test.py +++ b/tests/unit/sourcemap_test.py @@ -89,7 +89,7 @@ def test_TealMapItem_source_mapping(sourcemap_enabled): expr_line_offset, expr_str = 50, "expr = pt.Int(0) + pt.Int(1)" def mock_teal(ops): - return [f"{i+1}. {op}" for i, op in enumerate(ops)] + return [f"{i + 1}. {op}" for i, op in enumerate(ops)] components = [] b = expr.__teal__(pt.CompileOptions())[0]