Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:Remmeauth/remme-core-cli into in…
Browse files Browse the repository at this point in the history
…tegrate-config-file-reading-to-commands
  • Loading branch information
anastasiia-bilova committed Apr 23, 2019
2 parents 5536c83 + fcf353a commit f51d2b5
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 79 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

### Installation

Install the package from the [PypI](https://pypi.org/project/remme-core-cli) through [pip](https://github.com/pypa/pip):
Install the package from the [PyPi](https://pypi.org/project/remme-core-cli) through [pip](https://github.com/pypa/pip):

```bash
$ pip3 install remme-core-cli
Expand Down Expand Up @@ -109,7 +109,11 @@ Get balance of the account by its address — ``remme account get-balance``:
$ remme account get-balance \
--address=1120076ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf \
--node-url=node-genesis-testnet.remme.io
368440.0
{
"result": {
"balance": 368440.0
}
}
```

## Development
Expand Down
18 changes: 9 additions & 9 deletions cli/account/cli.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""
Provide implementation of the command line interface's account commands.
"""
import asyncio
import sys

import click
from remme import Remme

from cli.account.forms import GetAccountBalanceForm
from cli.account.help import GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE
from cli.account.help import ADDRESS_ARGUMENT_HELP_MESSAGE
from cli.account.service import Account
from cli.constants import (
FAILED_EXIT_FROM_COMMAND_CODE,
Expand All @@ -20,8 +19,6 @@
print_result,
)

loop = asyncio.get_event_loop()


@click.group('account', chain=True)
def account_commands():
Expand All @@ -31,7 +28,7 @@ def account_commands():
pass


@click.option('--address', type=str, required=True, help=GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE)
@click.option('--address', type=str, required=True, help=ADDRESS_ARGUMENT_HELP_MESSAGE)
@click.option('--node-url', type=str, required=False, help=NODE_URL_ARGUMENT_HELP_MESSAGE, default=default_node_url())
@account_commands.command('get-balance')
def get_balance(address, node_url):
Expand All @@ -44,7 +41,7 @@ def get_balance(address, node_url):
})

if errors:
print_errors(errors)
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

address = arguments.get('address')
Expand All @@ -54,7 +51,10 @@ def get_balance(address, node_url):
'node_address': str(node_url) + ':8080',
})

account_service = Account(service=remme)
balance = loop.run_until_complete(account_service.get_balance(address=address))
result, errors = Account(service=remme).get_balance(address=address)

if errors is not None:
print_errors(errors=errors)
sys.exit(FAILED_EXIT_FROM_COMMAND_CODE)

print_result(balance)
print_result(result=result)
43 changes: 6 additions & 37 deletions cli/account/forms.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
"""
Provide forms for command line interface's account commands.
"""
import re
from marshmallow import Schema

from marshmallow import (
Schema,
ValidationError,
fields,
validates,
)

from cli.constants import (
ADDRESS_REGEXP,
DOMAIN_NAME_REGEXP,
from cli.generic.forms.fields import (
AccountAddressField,
NodeURLField,
)


Expand All @@ -21,29 +14,5 @@ class GetAccountBalanceForm(Schema):
Get balance of the account form.
"""

address = fields.String(required=True)
node_url = fields.String(allow_none=True, required=False)

@validates('address')
def validate_address(self, address):
"""
Validate account address.
"""
if re.match(pattern=ADDRESS_REGEXP, string=address) is None:
raise ValidationError(f'The following address `{address}` is invalid.')

@validates('node_url')
def validate_node_url(self, node_url):
"""
Validate node URL.
If node URL is localhost, it means client didn't passed any URL, so nothing to validate.
"""
if node_url == 'localhost':
return

if 'http' in node_url or 'https' in node_url:
raise ValidationError(f'Pass the following node URL `{node_url}` without protocol (http, https, etc.).')

if re.match(pattern=DOMAIN_NAME_REGEXP, string=node_url) is None:
raise ValidationError(f'The following node URL `{node_url}` is invalid.')
address = AccountAddressField(required=True)
node_url = NodeURLField(allow_none=True, required=False)
2 changes: 1 addition & 1 deletion cli/account/help.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
Provide help messages for command line interface's account commands.
"""
GET_ACCOUNT_BALANCE_ADDRESS_ARGUMENT_HELP_MESSAGE = 'Account address to get a balance by.'
ADDRESS_ARGUMENT_HELP_MESSAGE = 'Account address to get a balance by.'
2 changes: 1 addition & 1 deletion cli/account/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class AccountInterface:
Implements account interface.
"""

async def get_balance(self, address):
def get_balance(self, address):
"""
Get balance of the account by its address.
"""
Expand Down
11 changes: 9 additions & 2 deletions cli/account/service.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
"""
Provide implementation of the account.
"""
import asyncio

from accessify import implements

from cli.account.interfaces import AccountInterface

loop = asyncio.get_event_loop()


@implements(AccountInterface)
class Account:
Expand All @@ -21,8 +25,11 @@ def __init__(self, service):
"""
self.service = service

async def get_balance(self, address):
def get_balance(self, address):
"""
Get balance of the account by its address.
"""
return await self.service.token.get_balance(address=address)
balance = loop.run_until_complete(self.service.token.get_balance(address=address))
return {
'balance': balance,
}, None
1 change: 1 addition & 0 deletions cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Provide constants for command line interface.
"""
ADDRESS_REGEXP = r'^[0-9a-f]{70}$'
HEADER_SIGNATURE_REGEXP = r'^[0-9a-f]{128}$'
DOMAIN_NAME_REGEXP = r'(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]'

PASSED_EXIT_FROM_COMMAND_CODE = 0
Expand Down
Empty file added cli/generic/__init__.py
Empty file.
Empty file added cli/generic/forms/__init__.py
Empty file.
62 changes: 62 additions & 0 deletions cli/generic/forms/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""
Provide implementation of the custom fields.
"""
import re

from marshmallow import (
ValidationError,
fields,
)

from cli.constants import (
ADDRESS_REGEXP,
DOMAIN_NAME_REGEXP,
)


class AccountAddressField(fields.Field):
"""
Implements validation of the account address.
References:
- https://marshmallow.readthedocs.io/en/3.0/custom_fields.html
"""

def _deserialize(self, value, attr, obj, **kwargs):
"""
Validate data (account address) that was passed to field.
"""
address = value

if re.match(pattern=ADDRESS_REGEXP, string=address) is None:
raise ValidationError(f'The following address `{address}` is invalid.')

return address


class NodeURLField(fields.Field):
"""
Implements validation of the node URL.
If node URL is localhost, it means client didn't passed any URL, so nothing to validate.
References:
- https://marshmallow.readthedocs.io/en/3.0/custom_fields.html
"""

def _deserialize(self, value, attr, obj, **kwargs):
"""
Validate data (node URL) that was passed to field.
"""
node_url = value

if node_url == 'localhost':
return node_url

if 'http' in node_url or 'https' in node_url:
raise ValidationError(f'Pass the following node URL `{node_url}` without protocol (http, https, etc.).')

if re.match(pattern=DOMAIN_NAME_REGEXP, string=node_url) is None:
raise ValidationError(f'The following node URL `{node_url}` is invalid.')

return node_url
4 changes: 2 additions & 2 deletions cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def print_result(result):
"""
Print successful result to the terminal.
"""
return click.echo(dict_to_pretty_json(result))
return click.echo(dict_to_pretty_json({'result': result}))


def print_errors(errors):
Expand All @@ -49,7 +49,7 @@ def print_errors(errors):
References:
- https://click.palletsprojects.com/en/7.x/utils/#ansi-colors
"""
click.secho(dict_to_pretty_json(errors), blink=True, bold=True, fg='red')
click.secho(dict_to_pretty_json({'errors': errors}), blink=True, bold=True, fg='red')


def default_node_url():
Expand Down
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ combine_as_imports=True
max-line-length=120
ignore=D200, D413, D107, D100
per-file-ignores=
*/__init__.py: D104, F401, D100,
tests/test_*: D205
*/__init__.py: D104, F401, D100
*/test_*: D205

[coverage:run]
omit =
Expand Down
58 changes: 35 additions & 23 deletions tests/test_account.py → tests/account/test_get_balance.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,7 @@
PASSED_EXIT_FROM_COMMAND_CODE,
)
from cli.entrypoint import cli
from cli.utils import (
dict_to_pretty_json,
return_async_value,
)
from cli.utils import dict_to_pretty_json


def test_get_balance():
Expand All @@ -32,8 +29,10 @@ def test_get_balance():
NODE_IP_ADDRESS_FOR_TESTING,
])

balance = json.loads(result.output).get('result').get('balance')

assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert isinstance(json.loads(result.output), int)
assert isinstance(balance, int)


def test_get_balance_invalid_address():
Expand All @@ -54,9 +53,11 @@ def test_get_balance_invalid_address():
])

expected_error = {
'address': [
f'The following address `{invalid_address}` is invalid.',
],
'errors': {
'address': [
f'The following address `{invalid_address}` is invalid.',
],
},
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
Expand All @@ -66,12 +67,12 @@ def test_get_balance_invalid_address():
def test_get_balance_without_node_url(mocker):
"""
Case: get a balance of an account by address without passing node URL.
Expect: balance is returned.
Expect: balance is returned from node on localhost.
"""
balance_from_localhost = 13500
balance = 13500

mock_account_get_balance = mocker.patch('cli.account.service.Account.get_balance')
mock_account_get_balance.return_value = return_async_value(balance_from_localhost)
mock_account_get_balance = mocker.patch('cli.account.service.loop.run_until_complete')
mock_account_get_balance.return_value = balance

runner = CliRunner()
result = runner.invoke(cli, [
Expand All @@ -81,9 +82,14 @@ def test_get_balance_without_node_url(mocker):
'1120076ecf036e857f42129b58303bcf1e03723764a1702cbe98529802aad8514ee3cf',
])

expected_result = {
'result': {
'balance': 13500,
},
}

assert PASSED_EXIT_FROM_COMMAND_CODE == result.exit_code
assert isinstance(json.loads(result.output), int)
assert str(balance_from_localhost) in result.output
assert expected_result == json.loads(result.output)


def test_get_balance_invalid_node_url():
Expand All @@ -104,9 +110,11 @@ def test_get_balance_invalid_node_url():
])

expected_error = {
'node_url': [
f'The following node URL `{invalid_node_url}` is invalid.',
],
'errors': {
'node_url': [
f'The following node URL `{invalid_node_url}` is invalid.',
],
},
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
Expand All @@ -131,9 +139,11 @@ def test_get_balance_node_url_with_http():
])

expected_error = {
'node_url': [
f'Pass the following node URL `{node_url_with_http_protocol}` without protocol (http, https, etc.).',
],
'errors': {
'node_url': [
f'Pass the following node URL `{node_url_with_http_protocol}` without protocol (http, https, etc.).',
],
},
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
Expand All @@ -158,9 +168,11 @@ def test_get_balance_node_url_with_https():
])

expected_error = {
'node_url': [
f'Pass the following node URL `{node_url_with_https_protocol}` without protocol (http, https, etc.).',
],
'errors': {
'node_url': [
f'Pass the following node URL `{node_url_with_https_protocol}` without protocol (http, https, etc.).',
],
},
}

assert FAILED_EXIT_FROM_COMMAND_CODE == result.exit_code
Expand Down

0 comments on commit f51d2b5

Please sign in to comment.