From 3f05730cdce772a85cbed37a7c9ea6e1ad6b2422 Mon Sep 17 00:00:00 2001 From: "Frank V. Castellucci" Date: Thu, 18 Jul 2024 04:27:37 -0400 Subject: [PATCH] Release 0.65.0 --- .vscode/extensions.json | 1 - CHANGELOG.md | 5 +- doc/source/graphql.rst | 27 +++-- doc/source/graphql_prog_txn.rst | 5 +- doc/source/graphql_pyconfig.rst | 75 +++++++++++- pgql_s_example.py | 6 +- pysui/sui/sui_pgql/config/confgroup.py | 133 ++++++++++++++++++---- pysui/sui/sui_pgql/config/confmodel.py | 49 +++++--- pysui/sui/sui_pgql/config/pysui_config.py | 83 ++------------ pysui/sui/sui_pgql/pgql_txn_base.py | 5 +- pysui/sui/sui_utils.py | 48 ++++++++ samples/cmdsg.py | 9 +- 12 files changed, 306 insertions(+), 140 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d844b43..a36659d 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -7,7 +7,6 @@ "rogalmic.bash-debug", "ms-python.black-formatter", "vadimcn.vscode-lldb", - "serayuzgur.crates", "ms-vscode.makefile-tools", "mikestead.dotenv", "graphql.vscode-graphql-syntax", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dabb51..4694d30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- SyncGqlClient and AsyncGqlClient as aliases in `pysui.__init__` - ### Fixed ### Changed +- *BREAKING* SuiConfig is no longer used to instantiate a GraphQL client, instead + use PysuiConfiguration +- PysuiConfiguration, SyncGqlClient and AsyncGqlClient as aliases in `pysui.__init__` - [change](https://github.com/FrankC01/pysui/issues/218) GraphQL remove SuiTransaction legacy dependency - Deprecated Event and Transaction subscriptions and subscription Client - Deprecate all Builders diff --git a/doc/source/graphql.rst b/doc/source/graphql.rst index d40270f..d5c4d42 100644 --- a/doc/source/graphql.rst +++ b/doc/source/graphql.rst @@ -50,7 +50,7 @@ Simple dev example # """Development example.""" - from pysui import SuiConfig, handle_result, SyncGqlClient + from pysui import PysuiConfiguration, handle_result, SyncGqlClient import pysui.sui.sui_pgql.pgql_query as qn @@ -70,7 +70,8 @@ Simple dev example if __name__ == "__main__": # Initialize synchronous client - client_init = SyncGqlClient(config=SuiConfig.default_config(),write_schema=False) + cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP ) + client_init = SyncGqlClient(pysui_config=cfg,write_schema=False) main(client_init) ------ @@ -83,13 +84,14 @@ copy of a Sui GraphQL RPC schema in use, you can instruct pysui to write a copy .. code-block:: python :linenos: - from pysui import SuiConfig, SyncGqlClient + from pysui import PysuiConfiguration, SyncGqlClient def main(): """Dump Sui GraphQL Schema.""" # Initialize synchronous client - client_init = SyncGqlClient(config=SuiConfig.default_config(),write_schema=True) - print(f"Schema dumped to: {client_init.schema_version}.graqhql`") + cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP ) + client_init = SyncGqlClient(pysui_config=cfg,write_schema=True) + print(f"Schema dumped to: {client_init.base_schema_version}.graqhql`") if __name__ == "__main__": main() @@ -106,13 +108,14 @@ If not provided at construction, it defaults to ``{"headers":None}`` .. code-block:: python :emphasize-lines: 8,15 - from pysui import SuiConfig, SyncGqlClient + from pysui import PysuiConfiguration, SyncGqlClient import pysui.sui.sui_pgql.pgql_query as qn def main(): """Set global headers to include in the RPC calls.""" # Initialize synchronous client with default headers - client = SyncGqlClient(config=SuiConfig.default_config(),default_header={"headers": {"from": "youremail@acme.org"}}) + cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP ) + client_init = SyncGqlClient(pysui_config=cfg,default_header={"headers": {"from": "youremail@acme.org"}}) print(client.client_headers) # Use different 'from' in headers for this one call qres = client.execute_query_node( @@ -182,7 +185,7 @@ convert the sting to a ``DocumentNode``, execute the query and either return the # """String query example.""" - from pysui import SuiConfig, SyncGqlClient + from pysui import PysuiConfiguration, SyncGqlClient def main(client: SyncGqlClient): """Execute a static string query.""" @@ -202,7 +205,8 @@ convert the sting to a ``DocumentNode``, execute the query and either return the if __name__ == "__main__": # Initialize synchronous client - client_init = SyncGqlClient(config=SuiConfig.default_config(),write_schema=False) + cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP ) + client_init = SyncGqlClient(pysui_config=cfg) main(client_init) ----------------------- @@ -218,7 +222,7 @@ using ``gql`` functions. """DocumentNode query example.""" from gql import gql - from pysui import SuiConfig, SyncGqlClient + from pysui import PysuiConfiguration, SyncGqlClient def main(client: SyncGqlClient): """Execute a DocumentNode as result of `gql` compilation.""" @@ -228,7 +232,8 @@ using ``gql`` functions. if __name__ == "__main__": # Initialize synchronous client - client_init = SyncGqlClient(config=SuiConfig.default_config(),write_schema=False) + cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP ) + client_init = SyncGqlClient(pysui_config=cfg) main(client_init) ----------------------- diff --git a/doc/source/graphql_prog_txn.rst b/doc/source/graphql_prog_txn.rst index d3ce162..0033634 100644 --- a/doc/source/graphql_prog_txn.rst +++ b/doc/source/graphql_prog_txn.rst @@ -33,10 +33,11 @@ New .. code-block:: python :linenos: - from pysui import SuiConfig, SyncGqlClient + from pysui import PysuiConfiguration, SyncGqlClient from pysui.sui.sui_pgql.pgql_sync_txn import SuiTransaction - client = SyncGqlClient(config=SuiConfig.default_config()) # Keyword argument + cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP ) + client = SyncGqlClient(pysui_config=cfg) txn = SuiTransaction(client=client) =============== diff --git a/doc/source/graphql_pyconfig.rst b/doc/source/graphql_pyconfig.rst index c6dfcb6..8180250 100644 --- a/doc/source/graphql_pyconfig.rst +++ b/doc/source/graphql_pyconfig.rst @@ -1,8 +1,79 @@ GraphQL PysuiConfiguration -"""""""""""""""""""""""""""""""" +"""""""""""""""""""""""""" PysuiConfiguration is the replacement for the legacy SuiConfig and this new class should be -used with GraphQL clients. +used when creating a new GraphQL clients. This new configuration scheme is also leveraged +by the GraphQL SuiTransaction. + + +General +======= +Up until now, pysui relied on the presence of ``~/.sui`` and it's constituent configuration elements including +``client.yaml``, ``sui.keystore``, and ``sui.aliases`` which were all encapsulated by SuiConfig. While SuiConfig +supported some maniplations (i.e. adding new keys, alias management, etc.) it fell short of a more robust management +strategy. In addition, the code itself did not gracefully age with the advent of Sui GraphQL RPC. Until Mysten +eliminates JSON RPC, SuiConfig may continue to be used with the JSON RPC clients. + +PysuiConfiguration persists its own configuratoin (default to ``~/.pysui``) and offers more flexibility when it +comes to configuration management. Amongst other things: + +#. It does not presume it's configuration is persisted to a fixed location (configurable) +#. It supports programmatic switching between it's primary components (see Anatomy below) +#. It has a smaller code base that, when legacy JSON RPC support is removed, has a smaller memory footprint +#. And more... Anatomy of PysuiConfiguration ============================= +The primary data model for PysuiConfiguration is a series of related ``dataclasses`` objects: + +* The root data model is ``PysuiConfigModel`` which is a member of PysuiConfiguration. + It manages one or more... + + * ``ProfileGroup``, or group for short, encapsulates unique environment configurations. + Example configuration may include "sui_json_config" (reserved), "sui_gql_config" (reserved) + or "user" (reserved). New groups may be created by the developer. Its construct includes, + all of which are configurable: + + * One or more ``Profile``, or profile, a named object containing individual urls for communicatinhg to Sui with. + * Associated keys, aliases and addresses + * Identifies an active profile + * Identifies an active address + +PysuiConfiguration +================== + +PysuiConfiguration is the primary object to interact with when managing or using the underlying groups and +profiles. However; it is import to understand the initial setup before using it as part of the GraphQL Clients +and the arguments you can pass to the constructor rely on that understanding. + +First time instantiation +------------------------ +The first time PysuiConfiguration is used it will look for the ``PysuiConfig.json`` file in the +default configuration folder ``~/.pysui`` unless that folder path is overriden with +the argument ``from_cfg_path``. If the configuration file is found it is loaded, otherwise it initializes +a new configuration file with initial groups and their contents. + +If Sui binaries installed +~~~~~~~~~~~~~~~~~~~~~~~~~ +Three groups will be created and initially populated: + + **sui_json_config** - This group and it's profiles will be populated from the contents of ``~/.sui/sui_config`` from the + files ``client.yaml``, ``sui.keystore`` and ``sui.aliases``. + + **sui_gql_config** - This group and will create four profiles, each one with a ``url`` pointing to Mysten's free + GraphQL nodes. It will also copy the keys, aliases and addresses from the ``sui_json_config`` group. + + **user** - Even though you may create your own groups this group is initially created as a convenience and begins + empty. + +The location of the ``sui`` binary will be captured, if present, enabling Move project compiling. + +If Sui binaries not installed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +**sui_gql_config** and **user** will be created however; **sui_gql_config** will be populated with a new +active address and keypair (ed25519), an alias of 'Primary', and will make 'testnet' profile active. + +The location of the ``sui`` binary will be captured, if present, enabling Move project compiling. + +PysuiConfiguration Method Summaries +=================================== diff --git a/pgql_s_example.py b/pgql_s_example.py index 35a907c..38a1105 100644 --- a/pgql_s_example.py +++ b/pgql_s_example.py @@ -708,7 +708,9 @@ def do_unstake(client: SyncGqlClient): if __name__ == "__main__": # - cfg = PysuiConfiguration(group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP) + cfg = PysuiConfiguration( + group_name=PysuiConfiguration.SUI_GQL_RPC_GROUP # , profile_name="testnet" + ) client_init = SyncGqlClient( write_schema=False, pysui_config=cfg, @@ -721,7 +723,7 @@ def do_unstake(client: SyncGqlClient): ## QueryNodes (fetch) # do_coin_meta(client_init) # do_coins_for_type(client_init) - do_gas(client_init) + # do_gas(client_init) # do_all_gas(client_init) # do_gas_ids(client_init) # do_sysstate(client_init) diff --git a/pysui/sui/sui_pgql/config/confgroup.py b/pysui/sui/sui_pgql/config/confgroup.py index 2aacdec..e3bafae 100644 --- a/pysui/sui/sui_pgql/config/confgroup.py +++ b/pysui/sui/sui_pgql/config/confgroup.py @@ -10,7 +10,10 @@ import dataclasses from typing import Optional, Union import dataclasses_json +from pysui.abstracts.client_keypair import SignatureScheme import pysui.sui.sui_crypto as crypto +import pysui.sui.sui_utils as utils +from pysui.sui.sui_constants import SUI_MAX_ALIAS_LEN, SUI_MIN_ALIAS_LEN @dataclasses.dataclass @@ -164,43 +167,123 @@ def address_keypair(self, *, address: str) -> crypto.SuiKeyPair: self.key_list[self.address_list.index(_res)].private_key_base64 ) + @staticmethod + def _alias_check_or_gen( + *, + aliases: Optional[list[str]] = None, + word_counts: Optional[int] = 12, + alias: Optional[str] = None, + current_iter: Optional[int] = 0, + ) -> str: + """_alias_check_or_gen If alias is provided, checks if unique otherwise creates one or more. + + :param aliases: List of existing aliases, defaults to None + :type aliases: list[str], optional + :param word_counts: Words count used for mnemonic phrase, defaults to 12 + :type word_counts: Optional[int], optional + :param alias: An inbound alias, defaults to None + :type alias: Optional[str], optional + :param current_iter: Internal recursion count, defaults to 0 + :type current_iter: Optional[int], optional + :return: An aliases + :rtype: str + """ + if not alias: + parts = list( + utils.partition( + crypto.gen_mnemonic_phrase(word_counts).split(" "), + int(word_counts / 2), + ) + ) + alias_names = [k + "-" + v for k, v in zip(*parts)] + + # alias_names = self._alias_gen_batch(word_counts=word_counts) + # Find a unique part if just_one + if not aliases: + alias = alias_names[0] + else: + for alias_name in alias_names: + if alias_name not in aliases: + # Found one + alias = alias_name + break + # If all match (unlikely), try unless threshold + if not alias: + if current_iter > 2: + raise ValueError("Unable to find unique alias") + else: + alias = ProfileGroup._alias_check_or_gen( + aliases=aliases, + word_counts=word_counts, + current_iter=current_iter + 1, + ) + else: + if alias in aliases: + raise ValueError(f"Alias {alias} already exists.") + if not (SUI_MIN_ALIAS_LEN <= len(alias) <= SUI_MAX_ALIAS_LEN): + raise ValueError( + f"Invalid alias string length, must be betwee {SUI_MIN_ALIAS_LEN} and {SUI_MAX_ALIAS_LEN} characters." + ) + return alias + + @staticmethod + def new_keypair_parts( + *, + of_keytype: Optional[SignatureScheme] = SignatureScheme.ED25519, + word_counts: Optional[int] = 12, + derivation_path: Optional[str] = None, + alias: Optional[str] = None, + alias_list: list[ProfileAlias], + ) -> tuple[str, str, ProfileKey, ProfileAlias]: + """.""" + mnem, keypair = crypto.create_new_keypair( + scheme=of_keytype, + word_counts=word_counts, + derv_path=derivation_path, + ) + _new_keystr = keypair.serialize() + _new_prf_key = ProfileKey(_new_keystr) + # Generate artifacts + _pkey_bytes = keypair.to_bytes() + _digest = _pkey_bytes[0:33] if _pkey_bytes[0] == 0 else _pkey_bytes[0:34] + # ProfileAlias Entry + if not alias: + alias = ProfileGroup._alias_check_or_gen( + aliases=alias_list, alias=alias, word_counts=word_counts + ) + + _new_alias = ProfileAlias( + alias, + base64.b64encode(keypair.public_key.scheme_and_key()).decode(), + ) + _new_addy = format(f"0x{hashlib.blake2b(_digest, digest_size=32).hexdigest()}") + return mnem, _new_addy, _new_prf_key, _new_alias + def add_keypair_and_parts( self, *, - new_alias: str, - new_keypair: crypto.SuiKeyPair, + new_address: str, + new_alias: ProfileAlias, + new_key: ProfileKey, make_active: Optional[bool] = False, ) -> str: """Add a new keypair with associated address and alias.""" - _new_keystr = new_keypair.serialize() - if not self._key_exists(key_string=_new_keystr): - if self._alias_exists(alias_name=new_alias): + + if not self._key_exists(key_string=new_key.private_key_base64): + if self._alias_exists(alias_name=new_alias.alias): raise ValueError( - f"Alias {new_alias} already exist attempting new key and address." + f"Alias {new_alias.alias} already exist attempting new key and address." ) - # ProfileKey Entry - _prvk_entry = ProfileKey(_new_keystr) - # Generate artifacts - _pkey_bytes = new_keypair.to_bytes() - _digest = _pkey_bytes[0:33] if _pkey_bytes[0] == 0 else _pkey_bytes[0:34] - # ProfileAlias Entry - _alias_entry = ProfileAlias( - new_alias, - base64.b64encode(new_keypair.public_key.scheme_and_key()).decode(), - ) - _new_addy = format( - f"0x{hashlib.blake2b(_digest, digest_size=32).hexdigest()}" - ) # Populate group - self.address_list.append(_new_addy) - self.key_list.append(_prvk_entry) - self.alias_list.append(_alias_entry) + self.address_list.append(new_address) + self.key_list.append(new_key) + self.alias_list.append(new_alias) if make_active: - self.using_address = _new_addy - return _new_addy + self.using_address = new_address + raise ValueError( - f"Private keystring {_new_keystr} already exists attempting new key and address.." + f"Private keystring {new_key.private_key_base64} already exists attempting new key and address.." ) def add_profile(self, *, new_prf: Profile, make_active: bool = False): diff --git a/pysui/sui/sui_pgql/config/confmodel.py b/pysui/sui/sui_pgql/config/confmodel.py index f712b19..44033d9 100644 --- a/pysui/sui/sui_pgql/config/confmodel.py +++ b/pysui/sui/sui_pgql/config/confmodel.py @@ -10,6 +10,7 @@ from pathlib import Path import dataclasses_json + import pysui.sui.sui_pgql.config.confgroup as prfgrp from pysui.sui.sui_pgql.config.conflegacy import load_client_yaml @@ -118,23 +119,41 @@ def initialize_gql_rpc( if not _res: # Get keys, aliases and addresses from rpc _suigrp = self._group_exists(group_name=json_rpc_group_name) - if not _suigrp: - raise ValueError(f"{json_rpc_group_name} group does not exist.") - if _suigrp.using_profile in list(_GQL_DEFAULTS.keys()): - _using_profile = _suigrp.using_profile + if _suigrp: + if _suigrp.using_profile in list(_GQL_DEFAULTS.keys()): + _using_profile = _suigrp.using_profile + else: + _using_profile = "testnet" + self.groups.append( + prfgrp.ProfileGroup( + gql_rpc_group_name, + _using_profile, + _suigrp.using_address, + _suigrp.alias_list.copy(), + _suigrp.key_list.copy(), + _suigrp.address_list.copy(), + [prfgrp.Profile(k, v) for k, v in _GQL_DEFAULTS.items()], + ) + ) else: - _using_profile = "testnet" - self.groups.append( - prfgrp.ProfileGroup( - gql_rpc_group_name, - _using_profile, - _suigrp.using_address, - _suigrp.alias_list.copy(), - _suigrp.key_list.copy(), - _suigrp.address_list.copy(), - [prfgrp.Profile(k, v) for k, v in _GQL_DEFAULTS.items()], + _mnem, new_addy, prf_key, prf_alias = ( + prfgrp.ProfileGroup.new_keypair_parts( + alias="Primary", + alias_list=[], + ) ) - ) + self.groups.append( + prfgrp.ProfileGroup( + gql_rpc_group_name, + "testnet", + new_addy, + [prf_alias], + [prf_key], + [new_addy], + [prfgrp.Profile(k, v) for k, v in _GQL_DEFAULTS.items()], + ) + ) + _updated = True return _updated diff --git a/pysui/sui/sui_pgql/config/pysui_config.py b/pysui/sui/sui_pgql/config/pysui_config.py index e6493e5..e22d318 100644 --- a/pysui/sui/sui_pgql/config/pysui_config.py +++ b/pysui/sui/sui_pgql/config/pysui_config.py @@ -10,12 +10,7 @@ from pathlib import Path from typing import Optional -import pysui.sui.sui_crypto as crypto -import pysui.sui.sui_utils as utils - from pysui.abstracts.client_keypair import SignatureScheme -from pysui.sui.sui_constants import SUI_MAX_ALIAS_LEN, SUI_MIN_ALIAS_LEN - from pysui.sui.sui_pgql.config.confgroup import ProfileGroup from pysui.sui.sui_pgql.config.confmodel import PysuiConfigModel @@ -223,65 +218,6 @@ def make_active( if _changes and persist: self._write_model() - def _alias_check_or_gen( - self, - *, - aliases: Optional[list[str]] = None, - word_counts: Optional[int] = 12, - alias: Optional[str] = None, - current_iter: Optional[int] = 0, - ) -> str: - """_alias_check_or_gen If alias is provided, checks if unique otherwise creates one or more. - - :param aliases: List of existing aliases, defaults to None - :type aliases: list[str], optional - :param word_counts: Words count used for mnemonic phrase, defaults to 12 - :type word_counts: Optional[int], optional - :param alias: An inbound alias, defaults to None - :type alias: Optional[str], optional - :param current_iter: Internal recursion count, defaults to 0 - :type current_iter: Optional[int], optional - :return: An aliases - :rtype: str - """ - if not alias: - parts = list( - utils.partition( - crypto.gen_mnemonic_phrase(word_counts).split(" "), - int(word_counts / 2), - ) - ) - alias_names = [k + "-" + v for k, v in zip(*parts)] - - # alias_names = self._alias_gen_batch(word_counts=word_counts) - # Find a unique part if just_one - if not aliases: - alias = alias_names[0] - else: - for alias_name in alias_names: - if alias_name not in aliases: - # Found one - alias = alias_name - break - # If all match (unlikely), try unless threshold - if not alias: - if current_iter > 2: - raise ValueError("Unable to find unique alias") - else: - alias = self._alias_check_or_gen( - aliases=aliases, - word_counts=word_counts, - current_iter=current_iter + 1, - ) - else: - if alias in aliases: - raise ValueError(f"Alias {alias} already exists.") - if not (SUI_MIN_ALIAS_LEN <= len(alias) <= SUI_MAX_ALIAS_LEN): - raise ValueError( - f"Invalid alias string length, must be betwee {SUI_MIN_ALIAS_LEN} and {SUI_MAX_ALIAS_LEN} characters." - ) - return alias - def new_keypair( self, *, @@ -305,19 +241,18 @@ def new_keypair( | SignatureScheme.SECP256K1 | SignatureScheme.SECP256R1 ): - # First, early check alias given or create new one - _aliases = [x.alias for x in _group.alias_list] - alias = self._alias_check_or_gen( - aliases=_aliases, alias=alias, word_counts=word_counts - ) - # Generate the new key and address - mnem, keypair = crypto.create_new_keypair( - scheme=of_keytype, + mnem, new_addy, prf_key, prf_alias = ProfileGroup.new_keypair_parts( + of_keytype=of_keytype, word_counts=word_counts, - derv_path=derivation_path, + derivation_path=derivation_path, + alias=alias, + alias_list=_group.alias_list, ) new_addy = _group.add_keypair_and_parts( - new_alias=alias, new_keypair=keypair, make_active=make_active + new_address=new_addy, + new_alias=prf_alias, + new_key=prf_key, + make_active=make_active, ) if make_active and _group.group_name != self.active_group.group_name: self._model.active_group = _group.group_name diff --git a/pysui/sui/sui_pgql/pgql_txn_base.py b/pysui/sui/sui_pgql/pgql_txn_base.py index 323af55..583f428 100644 --- a/pysui/sui/sui_pgql/pgql_txn_base.py +++ b/pysui/sui/sui_pgql/pgql_txn_base.py @@ -24,7 +24,7 @@ import pysui.sui.sui_txn.transaction_builder as tx_builder from pysui.sui.sui_types import bcs from pysui.sui.sui_types.scalars import SuiString -from pysui.sui.sui_utils import publish_build +from pysui.sui.sui_utils import publish_buildg # Standard library logging setup logger = logging.getLogger("pysui.gql_transaction") @@ -230,7 +230,8 @@ def _compile_source( """ src_path = Path(os.path.expanduser(project_path)) args_list = args_list if args_list else [] - compiled_package = publish_build( + compiled_package = publish_buildg( + self.client.config.model.sui_binary, src_path, args_list, ) diff --git a/pysui/sui/sui_utils.py b/pysui/sui/sui_utils.py index 1b53d69..8672157 100644 --- a/pysui/sui/sui_utils.py +++ b/pysui/sui/sui_utils.py @@ -116,6 +116,22 @@ def _compile_project( raise SuiPackageBuildFail(result.stdout) +def _compile_projectg( + sui_bin_str: str, path_to_package: Path, args_list: list[str] +) -> Union[Path, SuiException]: + """_compile_projectg Compiles a sui move project in GraohQL environment.""" + mbs = _SUI_BUILD.copy() + mbs.extend(args_list) + mbs.append("-p") + mbs.append(path_to_package) + + mbs.insert(0, sui_bin_str) + result = subprocess.run(mbs, capture_output=True, text=True) + if result.returncode == 0: + return path_to_package + raise SuiPackageBuildFail(result.stdout) + + def _module_bytes(module: Path) -> Union[ModuleReader, OSError]: """Fetch the module reader for this module.""" return deser.reader_from_file(str(module)) @@ -219,6 +235,38 @@ def publish_build( return cpackage +def publish_buildg( + sui_bin_path_str: str, + path_to_package: Path, + args_list: list[str], +) -> Union[CompiledPackage, Exception]: + """Build and collect module base64 strings and dependencies ObjectIDs.""" + # Compile the package + path_to_package = _compile_projectg(sui_bin_path_str, path_to_package, args_list) + # Find the build folder + build_path = path_to_package.joinpath("build") + if not build_path.exists(): + raise SuiMiisingBuildFolder(f"No build folder found in {path_to_package}") + # Get the project folder + build_subdir = [ + x for x in os.scandir(build_path) if x.is_dir() and x.name != "locks" + ] + if len(build_subdir) > 1: + raise SuiMiisingBuildFolder(f"No build folder found in {path_to_package}") + # Finally, get the module(s) bytecode folder + byte_modules = Path(build_subdir[0]).joinpath("bytecode_modules") + if not byte_modules.exists(): + raise SuiMiisingBuildFolder( + f"No bytecode_modules folder found for {path_to_package}/build" + ) + + # Construct initial package + cpackage = _build_dep_info(build_subdir[0].path) + # Set module bytes as base64 strings and generate package digest + _package_digest(cpackage, _modules_bytes(byte_modules)) + return cpackage + + @versionchanged(version="0.41.0", reason="Sui aliases configuration feature added") def sui_base_get_config() -> tuple[Path, Path, Union[Path, None]]: """sui_base_get_config Load a sui-base configuration. diff --git a/samples/cmdsg.py b/samples/cmdsg.py index dd60d78..f365f65 100644 --- a/samples/cmdsg.py +++ b/samples/cmdsg.py @@ -582,10 +582,11 @@ def txn_txn(client: SuiGQLClient, args: argparse.Namespace) -> None: def alias_list(client: SuiGQLClient, args: argparse.Namespace) -> None: """List address aliases.""" print() - agroup = client.config.active_group - for alias_obj in agroup.alias_list: + for alias_obj in client.config.active_group.alias_list: print(f"Alias: {alias_obj.alias}") - print(f"Address: {agroup.address_for_alias(alias_obj.alias)}") + print( + f"Address: {client.config.address_for_alias(alias_name=alias_obj.alias)}" + ) print(f"PublicKey: {alias_obj.public_key_base64}\n") @@ -593,7 +594,7 @@ def alias_rename(client: SuiGQLClient, args: argparse.Namespace) -> None: """List address aliases.""" print() try: - client.config.address_for_alias(args.existing) + client.config.address_for_alias(alias_name=args.existing) except ValueError as ve: print(ve.args) return