genlayer-test 0.1.0b1__tar.gz → 0.1.0b3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/PKG-INFO +1 -1
  2. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/genlayer_test.egg-info/PKG-INFO +1 -1
  3. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/genlayer_test.egg-info/SOURCES.txt +5 -2
  4. genlayer_test-0.1.0b3/genlayer_test.egg-info/entry_points.txt +5 -0
  5. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/genlayer_test.egg-info/top_level.txt +1 -0
  6. genlayer_test-0.1.0b3/gltest/artifacts/contract.py +86 -0
  7. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/glchain/contract.py +45 -21
  8. genlayer_test-0.1.0b3/gltest/plugin_config.py +12 -0
  9. genlayer_test-0.1.0b3/gltest/plugin_hooks.py +16 -0
  10. genlayer_test-0.1.0b3/gltest/types.py +7 -0
  11. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/pyproject.toml +5 -2
  12. genlayer_test-0.1.0b1/genlayer_test.egg-info/entry_points.txt +0 -2
  13. genlayer_test-0.1.0b1/gltest/artifacts/contract.py +0 -52
  14. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/README.md +0 -0
  15. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/genlayer_test.egg-info/dependency_links.txt +0 -0
  16. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/genlayer_test.egg-info/requires.txt +0 -0
  17. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/__init__.py +0 -0
  18. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/artifacts/__init__.py +0 -0
  19. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/assertions.py +0 -0
  20. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/exceptions.py +0 -0
  21. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/glchain/__init__.py +0 -0
  22. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/glchain/account.py +0 -0
  23. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/gltest/glchain/client.py +0 -0
  24. /genlayer_test-0.1.0b1/gltest/cli.py → /genlayer_test-0.1.0b3/gltest_cli/main.py +0 -0
  25. {genlayer_test-0.1.0b1 → genlayer_test-0.1.0b3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: genlayer-test
3
- Version: 0.1.0b1
3
+ Version: 0.1.0b3
4
4
  Summary: GenLayer Testing Suite
5
5
  Author: GenLayer
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: genlayer-test
3
- Version: 0.1.0b1
3
+ Version: 0.1.0b3
4
4
  Summary: GenLayer Testing Suite
5
5
  Author: GenLayer
6
6
  License-Expression: MIT
@@ -8,11 +8,14 @@ genlayer_test.egg-info/requires.txt
8
8
  genlayer_test.egg-info/top_level.txt
9
9
  gltest/__init__.py
10
10
  gltest/assertions.py
11
- gltest/cli.py
12
11
  gltest/exceptions.py
12
+ gltest/plugin_config.py
13
+ gltest/plugin_hooks.py
14
+ gltest/types.py
13
15
  gltest/artifacts/__init__.py
14
16
  gltest/artifacts/contract.py
15
17
  gltest/glchain/__init__.py
16
18
  gltest/glchain/account.py
17
19
  gltest/glchain/client.py
18
- gltest/glchain/contract.py
20
+ gltest/glchain/contract.py
21
+ gltest_cli/main.py
@@ -0,0 +1,5 @@
1
+ [console_scripts]
2
+ gltest = gltest_cli.main:main
3
+
4
+ [pytest11]
5
+ gltest = gltest.plugin_hooks
@@ -1,4 +1,5 @@
1
1
  build
2
2
  dist
3
3
  gltest
4
+ gltest_cli
4
5
  tests
@@ -0,0 +1,86 @@
1
+ import ast
2
+ from typing import Optional
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from gltest.plugin_config import get_contracts_dir
6
+ import io
7
+ import zipfile
8
+ from typing import Union
9
+
10
+
11
+ @dataclass
12
+ class ContractDefinition:
13
+ """Class that represents a contract definition from a .gpy file."""
14
+
15
+ contract_name: str
16
+ contract_code: Union[str, bytes]
17
+ main_file_path: Path
18
+ runner_file_path: Optional[Path]
19
+
20
+
21
+ def search_path_by_class_name(contracts_dir: Path, contract_name: str) -> Path:
22
+ """Search for a file by class name in the contracts directory."""
23
+ for file_path in contracts_dir.rglob("*.gpy"):
24
+ try:
25
+ # Read the file content
26
+ with open(file_path, "r") as f:
27
+ content = f.read()
28
+ # Parse the content into an AST
29
+ tree = ast.parse(content)
30
+ # Search for class definitions
31
+ for node in ast.walk(tree):
32
+ if isinstance(node, ast.ClassDef) and node.name == contract_name:
33
+ # Found the contract class
34
+ return file_path
35
+ except Exception as e:
36
+ raise ValueError(f"Error reading file {file_path}: {e}")
37
+ raise FileNotFoundError(f"Contract {contract_name} not found at: {contracts_dir}")
38
+
39
+
40
+ def compute_contract_code(
41
+ main_file_path: Path,
42
+ runner_file_path: Optional[Path] = None,
43
+ ) -> str:
44
+ """Compute the contract code."""
45
+ # Single file contract
46
+ if runner_file_path is None:
47
+ return main_file_path.read_text()
48
+
49
+ # Multifile contract
50
+ main_file_dir = main_file_path.parent
51
+ buffer = io.BytesIO()
52
+
53
+ with zipfile.ZipFile(buffer, mode="w") as zip:
54
+ zip.write(main_file_path, "contract/__init__.py")
55
+ for file_path in main_file_dir.rglob("*"):
56
+ if file_path.name in ["runner.json", "__init__.gpy"]:
57
+ continue
58
+ rel_path = file_path.relative_to(main_file_dir)
59
+ zip.write(file_path, f"contract/{rel_path}")
60
+ zip.write(runner_file_path, "runner.json")
61
+ buffer.flush()
62
+ return buffer.getvalue()
63
+
64
+
65
+ def find_contract_definition(contract_name: str) -> Optional[ContractDefinition]:
66
+ """
67
+ Search in the contracts directory for a contract definition.
68
+ """
69
+ contracts_dir = get_contracts_dir()
70
+ if not contracts_dir.exists():
71
+ raise FileNotFoundError(f"Contracts directory not found at: {contracts_dir}")
72
+ main_file_path = search_path_by_class_name(contracts_dir, contract_name)
73
+ main_file_dir = main_file_path.parent
74
+ runner_file_path = None
75
+ if main_file_path.name == "__init__.gpy":
76
+ # Likely a multifile contract
77
+ runner_file_path = main_file_dir.joinpath("runner.json")
78
+ if not runner_file_path.exists():
79
+ # No runner file, so it's a single file contract
80
+ runner_file_path = None
81
+ return ContractDefinition(
82
+ contract_name=contract_name,
83
+ contract_code=compute_contract_code(main_file_path, runner_file_path),
84
+ main_file_path=main_file_path,
85
+ runner_file_path=runner_file_path,
86
+ )
@@ -3,7 +3,7 @@ from gltest.artifacts import find_contract_definition
3
3
  from gltest.assertions import tx_execution_failed
4
4
  from gltest.exceptions import DeploymentError
5
5
  from .client import get_gl_client
6
- from genlayer_py.types import CalldataEncodable, GenLayerTransaction
6
+ from gltest.types import CalldataEncodable, GenLayerTransaction, TransactionStatus
7
7
  from eth_account.signers.local import LocalAccount
8
8
  from typing import List, Any, Type, Optional, Dict, Callable
9
9
  import types
@@ -18,36 +18,47 @@ class Contract:
18
18
 
19
19
  address: str
20
20
  account: Optional[LocalAccount] = None
21
+ _schema: Optional[Dict[str, Any]] = None
21
22
 
22
23
  @classmethod
23
- def from_address_and_schema(
24
- cls, address: str, schema: Dict[str, Any]
24
+ def new(
25
+ cls,
26
+ address: str,
27
+ schema: Dict[str, Any],
28
+ account: Optional[LocalAccount] = None,
25
29
  ) -> "Contract":
26
30
  """
27
31
  Build the methods from the schema.
28
32
  """
29
33
  if not isinstance(schema, dict) or "methods" not in schema:
30
34
  raise ValueError("Invalid schema: must contain 'methods' field")
35
+ instance = cls(address=address, _schema=schema, account=account)
36
+ instance._build_methods_from_schema()
37
+ return instance
31
38
 
32
- instance = cls(address=address)
33
- for method_name, method_info in schema["methods"].items():
39
+ def _build_methods_from_schema(self):
40
+ if self._schema is None:
41
+ raise ValueError("No schema provided")
42
+ for method_name, method_info in self._schema["methods"].items():
34
43
  if not isinstance(method_info, dict) or "readonly" not in method_info:
35
44
  raise ValueError(
36
- f"Invalid method info for {method_name}: must contain 'readonly' field"
45
+ f"Invalid method info for '{method_name}': must contain 'readonly' field"
37
46
  )
38
- setattr(
39
- instance,
40
- method_name,
41
- types.MethodType(
42
- cls.contract_method_factory(method_name, method_info["readonly"]),
43
- instance,
44
- ),
47
+ method_func = self.contract_method_factory(
48
+ method_name, method_info["readonly"]
45
49
  )
46
- return instance
50
+ bound_method = types.MethodType(method_func, self)
51
+ setattr(self, method_name, bound_method)
47
52
 
48
53
  def connect(self, account: LocalAccount) -> "Contract":
49
- self.account = account
50
- return self
54
+ """
55
+ Create a new instance of the contract with the same methods and a different account.
56
+ """
57
+ new_contract = self.__class__(
58
+ address=self.address, account=account, _schema=self._schema
59
+ )
60
+ new_contract._build_methods_from_schema()
61
+ return new_contract
51
62
 
52
63
  @staticmethod
53
64
  def contract_method_factory(method_name: str, read_only: bool) -> Callable:
@@ -78,6 +89,9 @@ class Contract:
78
89
  leader_only: bool = False,
79
90
  wait_interval: Optional[int] = None,
80
91
  wait_retries: Optional[int] = None,
92
+ wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
93
+ wait_triggered_transactions: bool = True,
94
+ wait_triggered_transactions_status: TransactionStatus = TransactionStatus.ACCEPTED,
81
95
  ) -> GenLayerTransaction:
82
96
  """
83
97
  Wrapper to the contract write method.
@@ -98,8 +112,18 @@ class Contract:
98
112
  if wait_retries is not None:
99
113
  extra_args["retries"] = wait_retries
100
114
  receipt = client.wait_for_transaction_receipt(
101
- transaction_hash=tx_hash, status="FINALIZED", **extra_args
115
+ transaction_hash=tx_hash,
116
+ status=wait_transaction_status,
117
+ **extra_args,
102
118
  )
119
+ if wait_triggered_transactions:
120
+ triggered_transactions = receipt["triggered_transactions"]
121
+ for triggered_transaction in triggered_transactions:
122
+ client.wait_for_transaction_receipt(
123
+ transaction_hash=triggered_transaction,
124
+ status=wait_triggered_transactions_status,
125
+ **extra_args,
126
+ )
103
127
  return receipt
104
128
 
105
129
  return read_contract_wrapper if read_only else write_contract_wrapper
@@ -138,6 +162,7 @@ class ContractFactory:
138
162
  leader_only: bool = False,
139
163
  wait_interval: Optional[int] = None,
140
164
  wait_retries: Optional[int] = None,
165
+ wait_transaction_status: TransactionStatus = TransactionStatus.FINALIZED,
141
166
  ) -> Contract:
142
167
  """
143
168
  Deploy the contract
@@ -157,9 +182,8 @@ class ContractFactory:
157
182
  if wait_retries is not None:
158
183
  extra_args["retries"] = wait_retries
159
184
  tx_receipt = client.wait_for_transaction_receipt(
160
- transaction_hash=tx_hash, status="FINALIZED", **extra_args
185
+ transaction_hash=tx_hash, status=wait_transaction_status, **extra_args
161
186
  )
162
-
163
187
  if (
164
188
  not tx_receipt
165
189
  or "data" not in tx_receipt
@@ -176,8 +200,8 @@ class ContractFactory:
176
200
 
177
201
  contract_address = tx_receipt["data"]["contract_address"]
178
202
  schema = client.get_contract_schema(address=contract_address)
179
- return Contract.from_address_and_schema(
180
- address=contract_address, schema=schema
203
+ return Contract.new(
204
+ address=contract_address, schema=schema, account=account
181
205
  )
182
206
  except Exception as e:
183
207
  raise DeploymentError(
@@ -0,0 +1,12 @@
1
+ from pathlib import Path
2
+
3
+ _contracts_dir = None
4
+
5
+
6
+ def set_contracts_dir(path: Path):
7
+ global _contracts_dir
8
+ _contracts_dir = path
9
+
10
+
11
+ def get_contracts_dir() -> Path:
12
+ return Path(_contracts_dir)
@@ -0,0 +1,16 @@
1
+ from gltest.plugin_config import set_contracts_dir
2
+ from pathlib import Path
3
+
4
+
5
+ def pytest_addoption(parser):
6
+ parser.addoption(
7
+ "--contracts-dir",
8
+ action="store",
9
+ default="contracts",
10
+ help="Directory containing contract files",
11
+ )
12
+
13
+
14
+ def pytest_configure(config):
15
+ contracts_dir = config.getoption("--contracts-dir")
16
+ set_contracts_dir(Path(contracts_dir))
@@ -0,0 +1,7 @@
1
+ # Re-export genlayer-py types
2
+ from genlayer_py.types import (
3
+ CalldataAddress,
4
+ GenLayerTransaction,
5
+ TransactionStatus,
6
+ CalldataEncodable,
7
+ )
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "genlayer-test"
7
- version = "0.1.0b1"
7
+ version = "0.1.0b3"
8
8
  description = "GenLayer Testing Suite"
9
9
  authors = [
10
10
  { name = "GenLayer" }
@@ -30,5 +30,8 @@ classifiers = [
30
30
  [tool.setuptools.packages.find]
31
31
  where = ["."]
32
32
 
33
+ [project.entry-points.pytest11]
34
+ gltest = "gltest.plugin_hooks"
35
+
33
36
  [project.scripts]
34
- gltest = "gltest.cli:main"
37
+ gltest = "gltest_cli.main:main"
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- gltest = gltest.cli:main
@@ -1,52 +0,0 @@
1
- import ast
2
- from pathlib import Path
3
- from typing import Optional
4
- from dataclasses import dataclass
5
-
6
-
7
- @dataclass
8
- class ContractDefinition:
9
- """Class that represents a contract definition from a .gpy file."""
10
-
11
- contract_name: str
12
- contract_code: str
13
- source_file: str
14
- ast_node: ast.ClassDef
15
-
16
-
17
- def find_contract_definition(
18
- contract_name: str, relative_contracts_dir: str = "contracts"
19
- ) -> Optional[ContractDefinition]:
20
- """
21
- Search in the contracts directory for a contract definition.
22
- TODO: Make this more robust to handle imports and other files.
23
- """
24
- # Use provided directory or default to 'contracts' in current working directory
25
- contracts_dir = Path.cwd() / relative_contracts_dir
26
-
27
- if not contracts_dir.exists():
28
- raise FileNotFoundError(f"Contracts directory not found at: {contracts_dir}")
29
-
30
- # Search through all .gpy files in the contracts directory
31
- for file_path in contracts_dir.glob("*.gpy"):
32
- try:
33
- # Read the file content
34
- with open(file_path, "r") as f:
35
- content = f.read()
36
-
37
- # Parse the content into an AST
38
- tree = ast.parse(content)
39
-
40
- # Search for class definitions
41
- for node in ast.walk(tree):
42
- if isinstance(node, ast.ClassDef) and node.name == contract_name:
43
- # Found the contract class
44
- return ContractDefinition(
45
- contract_name=contract_name,
46
- source_file=str(file_path),
47
- contract_code=content,
48
- ast_node=node,
49
- )
50
- except Exception as e:
51
- raise ValueError(f"Error reading file {file_path}: {e}")
52
- return None