Skip to content

Commit

Permalink
Monorepo first commit
Browse files Browse the repository at this point in the history
Reorganized sdk and cli to be two different packages in the same
repository

Moved sdk to cli repo

Rewrote SDK to match JS SDK implementation

CircleCI fix
  • Loading branch information
vforvalerio87 committed May 23, 2019
1 parent f749b34 commit 92335ac
Show file tree
Hide file tree
Showing 113 changed files with 1,026 additions and 19 deletions.
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ jobs:
- run:
name: Install snet-cli
command: |
./scripts/blockchain install
sudo pip3 install -e .
./snet_cli/scripts/blockchain install
sudo pip3 install -e ./snet_cli
- run:
name: Install platform-contracts from master
command: |
# Install platform-contracts (from master)
# we will deploy contracts from test/utils/reset_enviroment.sh
# we will deploy contracts from snet_cli/test/utils/reset_enviroment.sh
cd ..
git clone https://github.com/singnet/platform-contracts
cd platform-contracts
Expand All @@ -42,7 +42,7 @@ jobs:
- run:
name: Functional tests
command: |
bash -ex test/utils/run_all_functional.sh
bash -ex snet_cli/test/utils/run_all_functional.sh
- run:
name: Trigger platform-pipeline build
command: |
Expand Down
26 changes: 17 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
venv/
.idea/
snet_cli.egg-info/
blockchain/node_modules/
__pycache__/
snet_cli/resources/contracts/abi
snet_cli/resources/contracts/networks
snet_cli/resources/proto/*.py
build/
dist/
client_libraries/

snet_cli/venv/
.idea/
snet_cli/snet_cli.egg-info/
snet_cli/snet_cli/resources/contracts/abi
snet_cli/snet_cli/resources/contracts/networks
snet_cli/snet_cli/resources/proto/*.py
snet_cli/build/
snet_cli/dist/

snet_sdk/venv/
snet_sdk/snet_sdk.egg-info/
__pycache__
snet_sdk/snet_sdk/resources/contracts/abi
snet_sdk/snet_sdk/resources/contracts/networks
snet_sdk/build/
snet_sdk/dist/
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes
File renamed without changes.
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion scripts/blockchain → snet_cli/scripts/blockchain
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import sys
def main():
assert len(sys.argv) > 1, "please select a target from 'install', 'uninstall'"
target = sys.argv[1]
blockchain_dir = pathlib.Path(__file__).absolute().parent.parent.joinpath("blockchain")
blockchain_dir = pathlib.Path(__file__).absolute().parent.parent.parent.joinpath("blockchain")
node_modules_dir = blockchain_dir.joinpath("node_modules")
platform_json_src_dir = node_modules_dir.joinpath("singularitynet-platform-contracts")
token_json_src_dir = node_modules_dir.joinpath("singularitynet-token-contracts")
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions snet_cli/test/utils/run_all_functional.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
for f in snet_cli/test/functional_tests/*.sh
do
bash -ex snet_cli/test/utils/reset_environment.sh --i-no-what-i-am-doing
bash -ex -c "cd snet_cli/test/functional_tests; bash -ex `basename $f`"
done
21 changes: 21 additions & 0 deletions snet_sdk/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2018 SingularityNET

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3 changes: 3 additions & 0 deletions snet_sdk/MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
include snet_sdk/resources/proto/*
include snet_sdk/resources/contracts/abi/*
include snet_sdk/resources/contracts/networks/*
101 changes: 101 additions & 0 deletions snet_sdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# snet-sdk-python

SingularityNET SDK for Python

## Getting Started

These instructions are for the development and use of the SingularityNET SDK for Python.

### Usage

The SingularityNET SDK allows you to import compiled client libraries for your service or services or choice and make calls to those services programmatically from your application by setting up state channels with the providers of those services and making gRPC calls to the SingularityNET daemons for those services by selecting a channel with sufficient funding and supplying the appropriate metadata for authentication.

Once you have installed the snet-sdk in your current environment and it's in your PYTHONPATH, you should import it and create an instance of the base sdk class:

```python
from snet_sdk import Snet
import config
snet = Snet(private_key=config.private_key)
```

Now, the instance of the sdk can be used to instantiate clients for SingularityNET services. To interact with those services, the sdk needs to be supplied the compiled client libraries and a reference to their path on your file system.

To generate the client libraries, you need the SingularityNET Command Line Interface, or CLI, which you can download from PyPi, see [https://github.com/singnet/snet-cli#installing-with-pip](https://github.com/singnet/snet-cli#installing-with-pip)

Once you have the CLI installed, run the following command:
```bash
snet sdk generate-client-library python <org_id> <service_id>
```

Optionally, you can specify an output path; otherwise it's going to be `./client_libraries/python/<org_id>/<service_id>`

Now, by default the sdk is going to look for those client libraries in the `./grpc/<org_id>/<service_id>` in the directory of the main process of the module it's being imported by.

Once you have the generated client libraries in place, you can create an instance of a SingularityNET service client:
```python
client = snet.client("<org_id>", "<service_id>")
```

The client exposes the following properties and methods:
- All of the modules from the generated client library as `client.grpc.<module_name`. These are temporarily added to your PYTHONPATH and imported at runtime
- Functions to open, fund and extend state channels
- Functions to retrieve the list of state channels between you and the service provider from the blockchain
- Functions to get the updated state for a specific channel from the service provider, signed by yourself
- Functions to generate and sign the required metadata to make gRPC calls to the service daemon

This is an example of how to make a call to a SingularityNET service in the `snet` organization with the `example-service` service_id using the base SDK and client instances created as shown before:
```python
stub = client.grpc.example_service_pb2_grpc.CalculatorStub(client.grpc_channel)
request = calculator.grpc.example_service_pb2.Numbers(a=10, b=12)
result = stub.add(request)
print(result)
```
If you have no open state channels with the service provider, you can create one by calling the following function:
```python
client.open_channel()
```
By default, this will create a channel with the shortest possible expiration date and the necessary amount to make 100 service calls.
Once an open channel is created and funded, the sdk will automatically find and use a funded, non-expired channel.

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)
- [gRPC Python’s documentation](https://grpc.io/grpc/python/)

---

## Development

### Installing

#### Prerequisites

* [Python 3.6.5](https://www.python.org/downloads/release/python-365/)
* [Node 8+ w/npm](https://nodejs.org/en/download/)

---

* Clone the git repository
```bash
$ git clone [email protected]:singnet/snet-sdk-python.git
$ cd snet-sdk-python
```

* Install development/test blockchain dependencies
```bash
$ ./scripts/blockchain install
```

* Install the package in development/editable mode
```bash
$ pip install -e .
```

### Versioning

We use [SemVer](http://semver.org/) for versioning. For the versions available, see the
[tags on this repository](https://github.com/singnet/snet-sdk-python/tags).

## License

This project is licensed under the MIT License - see the
[LICENSE](https://github.com/singnet/snet-sdk-python/blob/master/LICENSE) file for details.
6 changes: 6 additions & 0 deletions snet_sdk/blockchain/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"dependencies": {
"singularitynet-platform-contracts": "0.2.6",
"singularitynet-token-contracts": "2.0.1"
}
}
55 changes: 55 additions & 0 deletions snet_sdk/scripts/blockchain
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python3

import os
import pathlib
import shutil
import subprocess
import sys


def main():
assert len(sys.argv) > 1, "please select a target from 'install', 'uninstall'"
target = sys.argv[1]
blockchain_dir = pathlib.Path(__file__).absolute().parent.parent.parent.joinpath("blockchain")
node_modules_dir = blockchain_dir.joinpath("node_modules")
platform_json_src_dir = node_modules_dir.joinpath("singularitynet-platform-contracts")
token_json_src_dir = node_modules_dir.joinpath("singularitynet-token-contracts")
token_contract_name = "SingularityNetToken"
contract_json_dest_dir = pathlib.Path(__file__).absolute().parent.parent.joinpath("snet_sdk", "resources", "contracts")
abi_contract_names = ["Registry", "MultiPartyEscrow"]
networks_contract_names = ["Registry", "MultiPartyEscrow"]

npm_location = shutil.which('npm')
if not npm_location:
raise Exception("This script requires 'npm' to be installed and in your PATH")

if target == "install":
shutil.rmtree(contract_json_dest_dir)

subprocess.call([npm_location, "install"], cwd=blockchain_dir)

os.makedirs(contract_json_dest_dir.joinpath("abi"), exist_ok=True)
os.makedirs(contract_json_dest_dir.joinpath("networks"), exist_ok=True)

for contract_name in abi_contract_names:
shutil.copy(platform_json_src_dir.joinpath("abi", "{}.json".format(contract_name)),
contract_json_dest_dir.joinpath("abi", "{}.json".format(contract_name)))
for contract_name in networks_contract_names:
shutil.copy(platform_json_src_dir.joinpath("networks", "{}.json".format(contract_name)),
contract_json_dest_dir.joinpath("networks", "{}.json".format(contract_name)))

shutil.copy(token_json_src_dir.joinpath("abi", "{}.json".format(token_contract_name)),
contract_json_dest_dir.joinpath("abi", "{}.json".format(token_contract_name)))
shutil.copy(token_json_src_dir.joinpath("networks", "{}.json".format(token_contract_name)),
contract_json_dest_dir.joinpath("networks", "{}.json".format(token_contract_name)))
elif target == "uninstall":
try:
shutil.rmtree(node_modules_dir)
shutil.rmtree(contract_json_dest_dir.joinpath("abi"))
shutil.rmtree(contract_json_dest_dir.joinpath("networks"))
except FileNotFoundError:
pass


if __name__ == "__main__":
main()
3 changes: 3 additions & 0 deletions snet_sdk/scripts/package-pip
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

python3.6 setup.py sdist bdist_wheel
23 changes: 23 additions & 0 deletions snet_sdk/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from setuptools import setup, find_packages
import re

with open('snet_sdk/__init__.py', 'rt', encoding='utf8') as f:
version = re.search(r'__version__ = "(.*?)"', f.read()).group(1)

setup(
name='snet_sdk',
version=version,
packages=find_packages(),
url='https://github.com/singnet/snet-sdk-python',
license='MIT',
author='SingularityNET Foundation',
author_email='[email protected]',
description='SingularityNET Python SDK',
install_requires=[
'grpcio-tools==1.17.1',
'ecdsa==0.13',
'web3==4.2.1',
'ipfsapi==0.4.2.post1',
'rfc3986==1.1.0'
]
)
71 changes: 71 additions & 0 deletions snet_sdk/snet_sdk/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import operator
from functools import reduce
import sys
import os
import importlib
import json
import base64
from pathlib import PurePath, Path
from urllib.parse import urljoin

import hashlib
import collections
import web3
from web3.gas_strategies.rpc import rpc_gas_price_strategy
from eth_account.messages import defunct_hash_message
from rfc3986 import urlparse
import ipfsapi
from web3.utils.datastructures import AttributeDict, MutableAttributeDict

import snet_sdk.generic_client_interceptor as generic_client_interceptor

from snet_sdk.utils import get_contract_object
from snet_sdk.service_client import ServiceClient
from snet_sdk.account import Account
from snet_sdk.mpe_contract import MPEContract
from snet_sdk.payment_channel_management_strategies.default import PaymentChannelManagementStrategy

__version__ = "0.0.1"


class SnetSDK:
"""Base Snet SDK"""
def __init__(
self,
config
):
self._config = config

# Instantiate Ethereum client
eth_rpc_endpoint = self._config.get("eth_rpc_endpoint", "https://mainnet.infura.io")
provider = web3.HTTPProvider(eth_rpc_endpoint)
self.web3 = web3.Web3(provider)
self.web3.eth.setGasPriceStrategy(rpc_gas_price_strategy)

self.mpe_contract = MPEContract(self.web3)

# Instantiate IPFS client
ipfs_rpc_endpoint = self._config.get("ipfs_rpc_endpoint", "https://ipfs.singularitynet.io:80")
ipfs_rpc_endpoint = urlparse(ipfs_rpc_endpoint)
ipfs_scheme = ipfs_rpc_endpoint.scheme if ipfs_rpc_endpoint.scheme else "http"
ipfs_port = ipfs_rpc_endpoint.port if ipfs_rpc_endpoint.port else 5001
self.ipfs_client = ipfsapi.connect(urljoin(ipfs_scheme, ipfs_rpc_endpoint.hostname), ipfs_port)

self.registry_contract = get_contract_object(self.web3, "Registry.json")
self.account = Account(self.web3, config, self.mpe_contract)

self.block_offset = self._config.get("block_offset", 604800)

def create_service_client(self, org_id, service_id, service_stub, group_name="default_group", payment_channel_management_strategy=PaymentChannelManagementStrategy, options={}):
service_metadata = self.service_metadata(org_id, service_id)
try:
group = next(filter(lambda group: group["group_name"] == group_name, service_metadata.groups))
except StopIteration:
raise ValueError("Group[name: {}] not found for orgId: {} and serviceId: {}".format(group_name, org_id, service_id))
service_client = ServiceClient(self, service_metadata, group, service_stub, payment_channel_management_strategy(self), options)
return service_client

def service_metadata(self, org_id, service_id):
(found, registration_id, metadata_uri, tags) = self.registry_contract.functions.getServiceRegistrationById(bytes(org_id, "utf-8"), bytes(service_id, "utf-8")).call()
metadata = AttributeDict(json.loads(self.ipfs_client.cat(metadata_uri.rstrip(b"\0").decode('ascii')[7:])))
return metadata
Loading

0 comments on commit 92335ac

Please sign in to comment.