Skip to content

Commit

Permalink
Merge pull request #58 from singnet/development
Browse files Browse the repository at this point in the history
SDK release v3.4.0
  • Loading branch information
kiruxaspb authored Jul 30, 2024
2 parents 75a599b + bc6c272 commit 91bff3f
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 67 deletions.
57 changes: 36 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,7 @@ config = {
"private_key": 'YOUR_PRIVATE_WALLET_KEY',
"eth_rpc_endpoint": f"https://sepolia.infura.io/v3/YOUR_INFURA_KEY",
"email": "[email protected]",
"free_call_auth_token-bin":"f5533eb0f01f0d45239c11b411bdfd4221fd3b125e4250db1f7bc044466108bc10ce95ab62ae224b6578b68d0ce337b4ec36e4b9dfbe6653e04973107813cbc01c",
"free-call-token-expiry-block":19690819,
"concurrency": False,
"org_id": "organization_id",
"service_id": "id_of_the_service",
"group_name": "default_group",
"identity_name": "local_name_for_that_identity",
"identity_type": "key",
"network": "sepolia",
Expand All @@ -55,46 +50,66 @@ See [test_sdk_client.py](https://github.com/singnet/snet-sdk-python/blob/master/
private_key: Your wallet's private key that will be used to pay for calls. Is **required** to make a call;
eth_rpc_endpoint: RPC endpoint that is used to access the Ethereum network. Is **required** to make a call;
email: Your email;
"free_call_auth_token-bin" and "free-call-token-expiry-block": Are used to make free calls. See more on that below;
org_id: ID of the organization that owns the service you want to call. Is **required** to make a call;
service_id: ID of the service you want to call. Is **required** to make a call;
identity_name: Name that will be used locally to save your wallet settings. You can check your identities in the `~/.snet/config` file;
identity_type: Type of your wallet authentication. Note that snet-sdk currently supports only "key" identity_type;
network: You can set the Ethereum network that will be used to make a call;
force_update: If set to False, will reuse the existing gRPC stubs (if any) instead of downloading proto and regenerating them every time.

##### List organizations and their services

After executing this code, you should have client libraries created for this service. They are located at the following path: `~/.snet/org_id/service_id/python/`
You can use the sdk client instance`s methods get_organization_list() to list all organizations and get_services_list("org_id") to list all services of a given organization.

Note: Currently you can only save files to `~/.snet/`. We will fix this in the future.
```python
print(snet_sdk.get_organization_list())
print(snet_sdk.get_services_list("26072b8b6a0e448180f8c0e702ab6d2f"))
```

##### Free call configuration

If you want to use a free call you need to add these attributes to the config dictionary:
If you want to use the free calls you will need to pass these arguments to the create_service_client() method:

```
"free_call_auth_token-bin":"f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765",
"free-call-token-expiry-block":172800,
"email":"[email protected]"
```
You can receive these for a given service from the [Dapp](https://beta.singularitynet.io/)
#### Calling the service
Now, the instance of the sdk can be used to create the service client instances.
Continuing from the previous code this is an example using `Exampleservice` from the `26072b8b6a0e448180f8c0e702ab6d2f` organization:
Continuing from the previous code here is an example using `Exampleservice` from the `26072b8b6a0e448180f8c0e702ab6d2f` organization:

```python
service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f",
service_id="Exampleservice",
group_name="default_group")
```
Creating a service client with free calls included would look like this:
```python
service_client = snet_sdk.create_service_client(org_id="26072b8b6a0e448180f8c0e702ab6d2f",
service_id="Exampleservice",
group_name="default_group",
free_call_auth_token_bin="f2548d27ffd319b9c05918eeac15ebab934e5cfcd68e1ec3db2b92765",
free_call_token_expiry_block=172800)
```

After executing this code, you should have client libraries created for this service. They are located at the following path: `~/.snet/org_id/service_id/python/`

Note: Currently you can only save files to `~/.snet/`. We will fix this in the future.
```python
service_client = snet_sdk.create_service_client()
service_client.deposit_and_open_channel(123456, 33333)
```
`deposit_and_open_channel()` function deposits the specified amount of AGIX tokens in cogs into an MPE smart contract and opens a payment channel.
The instance of service_client that has been generated can be utilized to invoke the methods that the service offers.
To do this, use the the call_rpc method. This method needs the names of the method and data object, along with the data itself, to be passed into it.
The specific data that needs to be passed can be found in the .proto file. Building upon the previously written code, here’s an example that uses the *Exampleservice* from the *26072b8b6a0e448180f8c0e702ab6d2f* organization:
`deposit_and_open_channel(amount, expiration)` function deposits the specified amount of AGIX tokens in cogs into an MPE smart contract and opens a payment channel. Expiration is payment channel's TTL in blocks.
The instance of service_client that has been generated can be utilized to invoke the methods that the service offers. You can list these using the get_services_and_messages_info_as_pretty_string() method:
```python
result = service_client.call_rpc("mul", "Numbers", a=20, b=3)
print(f"Performing 20 * 3: {result}") # Performing 20 * 3: value: 60.0
print(service_client.get_services_and_messages_info_as_pretty_string())
```

You can get this code example at [https://github.com/singnet/snet-code-examples/tree/python_client/python/client](https://github.com/singnet/snet-code-examples/tree/python_client/python/client)
To invoke the service`s methods, you can use the the call_rpc() method. This method requires the names of the method and data object, along with the data itself, to be passed into it.
To continue with our example, here’s a call to the *mul* method of the *Exampleservice* from the *26072b8b6a0e448180f8c0e702ab6d2f* organization:

```python
result = service_client.call_rpc("mul", "Numbers", a=20, b=3)
print(f"Calculating 20 * 3: {result}") # Calculating 20 * 3: 60.0
```

For more information about gRPC and how to use it with Python, please see:
- [gRPC Basics - Python](https://grpc.io/docs/tutorials/basic/python.html)
Expand Down
5 changes: 3 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ argcomplete==3.1.2
grpcio-health-checking==1.59.0
jsonschema==4.0.0
eth-account==0.9.0
snet-cli==2.1.3
snet.contracts==0.1.1
snet.cli==2.1.4
snet.contracts==0.1.1
urllib3>=2.2.2
110 changes: 75 additions & 35 deletions snet/sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
from pathlib import Path
import sys
from typing import Any, NewType
import warnings
import copy

import google.protobuf.internal.api_implementation

with warnings.catch_warnings():
# Suppress the eth-typing package`s warnings related to some new networks
warnings.filterwarnings("ignore", "Network .* does not have a valid ChainId. eth-typing should be "
"updated with the latest networks.", UserWarning)

from snet.sdk.metadata_provider.ipfs_metadata_provider import IPFSMetadataProvider
from snet.sdk.payment_strategies.default_payment_strategy import DefaultPaymentStrategy
from snet.cli.commands.sdk_command import SDKCommand
from snet.cli.commands.commands import BlockchainCommand
from snet.cli.config import Config
from snet.cli.utils.utils import bytes32_to_str, type_converter

google.protobuf.internal.api_implementation.Type = lambda: 'python'

Expand Down Expand Up @@ -40,7 +50,7 @@


class Arguments:
def __init__(self, org_id, service_id):
def __init__(self, org_id=None, service_id=None):
self.org_id = org_id
self.service_id = service_id
self.language = "python"
Expand All @@ -50,56 +60,46 @@ def __init__(self, org_id, service_id):
class SnetSDK:
"""Base Snet SDK"""

def __init__(self, config, metadata_provider=None):
self._config = config
def __init__(self, sdk_config, metadata_provider=None):
self._sdk_config = sdk_config
self._metadata_provider = metadata_provider

# Instantiate Ethereum client
eth_rpc_endpoint = self._config.get("eth_rpc_endpoint", "https://mainnet.infura.io/v3/e7732e1f679e461b9bb4da5653ac3fc2")
eth_rpc_request_kwargs = self._config.get("eth_rpc_request_kwargs")
eth_rpc_endpoint = self._sdk_config.get("eth_rpc_endpoint",
"https://mainnet.infura.io/v3/e7732e1f679e461b9bb4da5653ac3fc2")
eth_rpc_request_kwargs = self._sdk_config.get("eth_rpc_request_kwargs")

provider = web3.HTTPProvider(endpoint_uri=eth_rpc_endpoint, request_kwargs=eth_rpc_request_kwargs)

self.web3 = web3.Web3(provider)

# Get MPE contract address from config if specified; mostly for local testing
_mpe_contract_address = self._config.get("mpe_contract_address", None)
_mpe_contract_address = self._sdk_config.get("mpe_contract_address", None)
if _mpe_contract_address is None:
self.mpe_contract = MPEContract(self.web3)
else:
self.mpe_contract = MPEContract(self.web3, _mpe_contract_address)

# Instantiate IPFS client
ipfs_endpoint = self._config.get("default_ipfs_endpoint", "/dns/ipfs.singularitynet.io/tcp/80/")
ipfs_endpoint = self._sdk_config.get("default_ipfs_endpoint", "/dns/ipfs.singularitynet.io/tcp/80/")
self.ipfs_client = ipfshttpclient.connect(ipfs_endpoint)

# Get Registry contract address from config if specified; mostly for local testing
_registry_contract_address = self._config.get("registry_contract_address", None)
_registry_contract_address = self._sdk_config.get("registry_contract_address", None)
if _registry_contract_address is None:
self.registry_contract = get_contract_object(self.web3, "Registry")
else:
self.registry_contract = get_contract_object(self.web3, "Registry", _registry_contract_address)

self.account = Account(self.web3, config, self.mpe_contract)
self.account = Account(self.web3, sdk_config, self.mpe_contract)

global_config = Config(sdk_config=config)
global_config = Config(sdk_config=sdk_config)
self.setup_config(global_config)
sdk = SDKCommand(global_config, args=Arguments(config['org_id'], config['service_id']))
force_update = config.get('force_update', False)

if force_update:
sdk.generate_client_library()
else:
path_to_pb_files = self.get_path_to_pb_files(config['org_id'], config['service_id'])
pb_2_file_name = find_file_by_keyword(path_to_pb_files, keyword="pb2.py")
pb_2_grpc_file_name = find_file_by_keyword(path_to_pb_files, keyword="pb2_grpc.py")
if not pb_2_file_name or not pb_2_grpc_file_name:
sdk.generate_client_library()

def setup_config(self, config: Config) -> None:
out_f = sys.stdout
network = self._config.get("network", None)
identity_name = self._config.get("identity_name", None)
network = self._sdk_config.get("network", None)
identity_name = self._sdk_config.get("identity_name", None)
# Checking for an empty network
if network and config["session"]["network"] != network:
config.set_session_network(network, out_f)
Expand All @@ -117,21 +117,39 @@ def set_session_identity(self, identity_name: str, config: Config, out_f):
elif config["session"]["identity"] != identity_name:
config.set_session_identity(identity_name, out_f)

def create_service_client(self, payment_channel_management_strategy=None,
options=None, concurrent_calls=1):
org_id = self._config.get("org_id")
service_id = self._config.get("service_id")
group_name = self._config.get("group_name", "default_group")
def create_service_client(self, org_id: str, service_id: str, group_name=None,
payment_channel_management_strategy=None,
free_call_auth_token_bin=None,
free_call_token_expiry_block=None,
options=None,
concurrent_calls=1):

# Create and instance of the Config object, so we can create an instance of SDKCommand
sdk_config_object = Config(sdk_config=self._sdk_config)
sdk = SDKCommand(sdk_config_object, args=Arguments(org_id, service_id))

# Download the proto file and generate stubs if needed
force_update = self._sdk_config.get('force_update', False)
if force_update:
sdk.generate_client_library()
else:
path_to_pb_files = self.get_path_to_pb_files(org_id, service_id)
pb_2_file_name = find_file_by_keyword(path_to_pb_files, keyword="pb2.py")
pb_2_grpc_file_name = find_file_by_keyword(path_to_pb_files, keyword="pb2_grpc.py")
if not pb_2_file_name or not pb_2_grpc_file_name:
sdk.generate_client_library()

if payment_channel_management_strategy is None:
payment_channel_management_strategy = DefaultPaymentStrategy(concurrent_calls)

if options is None:
options = dict()

options['free_call_auth_token-bin'] = bytes.fromhex(self._config.get("free_call_auth_token-bin", ""))
options['free-call-token-expiry-block'] = self._config.get("free-call-token-expiry-block", 0)
options['email'] = self._config.get("email", "")
options['concurrency'] = self._config.get("concurrency", True)
options['free_call_auth_token-bin'] = bytes.fromhex(free_call_auth_token_bin) if\
free_call_token_expiry_block else ""
options['free-call-token-expiry-block'] = free_call_token_expiry_block if\
free_call_token_expiry_block else 0
options['email'] = self._sdk_config.get("email", "")
options['concurrency'] = self._sdk_config.get("concurrency", True)

if self._metadata_provider is None:
self._metadata_provider = IPFSMetadataProvider(self.ipfs_client, self.registry_contract)
Expand All @@ -144,8 +162,8 @@ def create_service_client(self, payment_channel_management_strategy=None,

pb2_module = self.get_module_by_keyword(org_id, service_id, keyword="pb2.py")

service_client = ServiceClient(org_id, service_id, service_metadata, group, service_stub, strategy, options,
self.mpe_contract, self.account, self.web3, pb2_module)
service_client = ServiceClient(org_id, service_id, service_metadata, group, service_stub, strategy,
options, self.mpe_contract, self.account, self.web3, pb2_module)
return service_client

def get_service_stub(self, org_id: str, service_id: str) -> ServiceStub:
Expand Down Expand Up @@ -204,3 +222,25 @@ def _get_service_group_details(self, service_metadata, group_name):
return self._get_first_group(service_metadata)

return self._get_group_by_group_name(service_metadata, group_name)

def get_organization_list(self) -> list:
global_config = Config(sdk_config=self._sdk_config)
blockchain_command = BlockchainCommand(config=global_config, args=Arguments())
org_list = blockchain_command.call_contract_command(
"Registry", "listOrganizations", [])
organization_list = []
for idx, org_id in enumerate(org_list):
organization_list.append(bytes32_to_str(org_id))
return organization_list

def get_services_list(self, org_id: str) -> list:
global_config = Config(sdk_config=self._sdk_config)
blockchain_command = BlockchainCommand(config=global_config, args=Arguments())
(found, org_service_list) = blockchain_command.call_contract_command("Registry",
"listServicesForOrganization",
[type_converter("bytes32")(org_id)])
if not found:
raise Exception(f"Organization with id={org_id} doesn't exist!")
org_service_list = list(map(bytes32_to_str, org_service_list))
return org_service_list

Loading

0 comments on commit 91bff3f

Please sign in to comment.