Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,8 @@ gen_stubs.script = "scripts.generate_stubs:main()"
gen_avm_ops.script = "scripts.generate_avm_ops:main()"
gen_assemble_ops.script = "scripts.generate_assemble_op_spec:main()"
gen_puya_lib.script = "scripts.generate_puya_lib:main()"
gen = ["gen_lang_spec", "gen_stubs", "gen_avm_ops", "gen_assemble_ops", "gen_puya_lib"]
gen_stub_symtables.script = "scripts.generate_stub_symtables:main()"
gen = ["gen_lang_spec", "gen_stubs", "gen_avm_ops", "gen_assemble_ops", "gen_puya_lib", "gen_stub_symtables"]
compile_all.script = "scripts.compile_all_examples:main()"
bump_stubs.script = "scripts.bump_stubs_version:main()"
check_stubs.script = "scripts.check_stubs_version:app()"
Expand Down
76 changes: 76 additions & 0 deletions scripts/generate_stub_symtables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env python3
import subprocess
import sys
import typing
from pathlib import Path

import mypy.build
import mypy.find_sources
import mypy.nodes

from puya.log import configure_stdio
from puyapy.parse import _get_mypy_options
from scripts.script_utils import autogenerated_header_for

THIS_PATH = Path(__file__)
SCRIPTS_DIR = THIS_PATH.parent
VCS_ROOT = SCRIPTS_DIR.parent
SRC_DIR = VCS_ROOT / "src"
STUBS_DIR = VCS_ROOT / "stubs" / "algopy-stubs"


class SymbolData(typing.NamedTuple):
fullname: str
module_public: bool


_ALLOWED_STUB_PKGS = ("algopy",)


def main() -> int:
configure_stdio()
mypy_opts = _get_mypy_options()
mypy_opts.python_executable = sys.executable
source_list = mypy.find_sources.create_source_list([str(STUBS_DIR)], mypy_opts)
result = mypy.build.build(source_list, options=mypy_opts)
symbol_tables = dict[str, dict[str, SymbolData]]()
stub_dependencies = dict[str, list[str]]()
for module_name, mypy_file in result.files.items():
if module_name.partition(".")[0] in _ALLOWED_STUB_PKGS:
assert module_name not in symbol_tables
state = result.graph[module_name]
stub_dependencies[module_name] = sorted(
d for d in state.dependencies_set if d.partition(".")[0] in _ALLOWED_STUB_PKGS
)
module_symtable = dict[str, SymbolData]()
symbol_tables[module_name] = module_symtable
for symbol_name, symbol in mypy_file.names.items():
if not (
symbol.module_hidden
or (symbol_name.startswith("__") and symbol_name.endswith("__"))
):
assert not (symbol.module_public and symbol.module_hidden)
assert symbol.kind == mypy.nodes.GDEF
assert symbol.fullname
module_symtable[symbol_name] = SymbolData(
symbol.fullname, symbol.module_public
)
output_path = SRC_DIR / "puyapy" / "_stub_symtables.py"
output_lines = [
autogenerated_header_for(__file__),
"import typing",
"from collections.abc import Mapping, Sequence",
'SymbolData = typing.NamedTuple("SymbolData", [("fullname", str), ("module_public", bool)])', # noqa: E501
f"STUB_SYMTABLES: typing.Final[Mapping[str, Mapping[str, SymbolData]]] = {symbol_tables!r}", # noqa: E501
"",
f"STUB_DEPENDENCIES: typing.Final[Mapping[str, Sequence[str]]] = {stub_dependencies!r}",
]
output_path.write_text("\n".join(output_lines), encoding="utf-8")
subprocess.run(["ruff", "format", str(output_path)], check=True, cwd=VCS_ROOT)
subprocess.run(["ruff", "check", "--fix", str(output_path)], check=True, cwd=VCS_ROOT)
subprocess.run(["ruff", "format", str(output_path)], check=True, cwd=VCS_ROOT)
return 0


if __name__ == "__main__":
sys.exit(main())
521 changes: 521 additions & 0 deletions src/puyapy/_stub_symtables.py

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion src/puyapy/awst_build/eb/transaction/itxn_args.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import typing
from collections.abc import Mapping

import attrs
Expand Down Expand Up @@ -99,7 +100,7 @@ def _validate_and_convert_item(self, item: InstanceBuilder) -> InstanceBuilder:
)


PYTHON_ITXN_ARGUMENTS = {
PYTHON_ITXN_ARGUMENTS: typing.Final[Mapping[str, PythonITxnArgument]] = {
# type(:) TransactionType = ...,
"type": PythonITxnArgument(
field=TxnField.TypeEnum,
Expand Down
5 changes: 4 additions & 1 deletion src/puyapy/awst_build/eb/transaction/txn_fields.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import typing
from collections.abc import Mapping

import attrs

from puya import log
Expand All @@ -13,7 +16,7 @@ class PythonTxnField:
type: pytypes.RuntimeType


PYTHON_TXN_FIELDS = {
PYTHON_TXN_FIELDS: typing.Final[Mapping[str, PythonTxnField]] = {
"sender": PythonTxnField(TxnField.Sender, pytypes.AccountType),
"fee": PythonTxnField(TxnField.Fee, pytypes.UInt64Type),
"first_valid": PythonTxnField(TxnField.FirstValid, pytypes.UInt64Type),
Expand Down
1 change: 1 addition & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
TEST_CASES_DIR = VCS_ROOT / "test_cases"
FROM_AWST_DIR = VCS_ROOT / "tests" / "from_awst"
NO_INIT_DIR = VCS_ROOT / "tests" / "no-init"
STUBS_DIR = VCS_ROOT / "stubs" / "algopy-stubs"
84 changes: 43 additions & 41 deletions tests/test_pytypes.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,50 @@
import sys
import ast
from collections.abc import Mapping
from pathlib import Path

import pytest

from puyapy.awst_build import pytypes
from tests import VCS_ROOT

_STUB_SUFFIX = ".pyi"


def stub_class_names_and_predefined_aliases() -> list[str]:
from mypy import build, find_sources, fscache, nodes

from puyapy.parse import _get_mypy_options

stubs_dir = (VCS_ROOT / "stubs" / "algopy-stubs").resolve()
mypy_options = _get_mypy_options()
mypy_options.python_executable = sys.executable
fs_cache = fscache.FileSystemCache()
mypy_build_sources = find_sources.create_source_list(
paths=[str(stubs_dir)], options=mypy_options, fscache=fs_cache
)
build_result = build.build(sources=mypy_build_sources, options=mypy_options, fscache=fs_cache)
result = set()

algopy_module = build_result.files["algopy"]
modules_to_visit = [algopy_module]
seen_modules = set()
while modules_to_visit:
module = modules_to_visit.pop()
if module in seen_modules:
continue
seen_modules.add(module)
for name, symbol in module.names.items():
if name.startswith("_") or symbol.module_hidden or symbol.kind != nodes.GDEF:
continue
match symbol.node:
case nodes.MypyFile() as new_module:
modules_to_visit.append(new_module)
case nodes.TypeAlias(fullname=alias_name):
result.add(alias_name)
case nodes.TypeInfo(fullname=class_name):
result.add(class_name)
return sorted(result)
from tests import STUBS_DIR


def _symbols_from_file(file_path: Path) -> list[str]:
"""
Parse a single .pyi file and return class names,
and module level assignments which are (probably) type annotations.
"""
text = file_path.read_text(encoding="utf8")
tree = ast.parse(text, filename=str(file_path))
symbols = []
for stmt in tree.body:
match stmt:
case ast.ClassDef(name=class_name):
symbols.append(class_name)
case (
ast.AnnAssign(target=ast.Name(id=alias_name))
| ast.Assign(targets=[ast.Name(id=alias_name)])
) if stmt.value is None or not _is_ellipsis(stmt.value):
symbols.append(alias_name)
return symbols


def _is_ellipsis(expr: ast.expr) -> bool:
match expr:
case ast.Constant(value=value):
return value is Ellipsis
case _:
return False


def _stub_class_names_and_predefined_aliases() -> list[str]:
stubs_root = STUBS_DIR.resolve()
results = []
for path in stubs_root.glob("*.pyi"):
module = f"algopy.{path.stem}"
symbols = _symbols_from_file(path)
qualified_symbols = [f"{module}.{name}" for name in symbols if not name.startswith("_")]
results.extend(qualified_symbols)
return results


@pytest.fixture(scope="session")
Expand All @@ -52,7 +54,7 @@ def builtins_registry() -> Mapping[str, pytypes.PyType]:

@pytest.mark.parametrize(
"fullname",
stub_class_names_and_predefined_aliases(),
_stub_class_names_and_predefined_aliases(),
ids=str,
)
def test_stub_class_names_lookup(
Expand Down
Loading
Loading