Skip to content
This repository has been archived by the owner on Sep 1, 2021. It is now read-only.

Commit

Permalink
Infura now requires an API key, fixes #170
Browse files Browse the repository at this point in the history
Introduces `python-dotenv` to load environment variables and inject API
keys for both Etherscan and Infura.
Also bumps Pillow version and pins hostpython3 to match python3 closes #166.
  • Loading branch information
AndreMiras committed Mar 22, 2020
1 parent 54016df commit ae984c4
Show file tree
Hide file tree
Showing 10 changed files with 37 additions and 93 deletions.
12 changes: 7 additions & 5 deletions buildozer.spec
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ package.domain = com.github.andremiras
source.dir = src

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas,md,json
source.include_exts = py,png,jpg,kv,atlas,md,json,env

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png
Expand Down Expand Up @@ -42,7 +42,7 @@ requirements =
android,
attrdict==2.0.0,
certifi==2018.10.15,
cffi,
cffi==1.13.2,
chardet==3.0.4,
cytoolz==0.9.0,
eth-abi==2.0.0,
Expand All @@ -56,6 +56,7 @@ requirements =
eth-utils==1.7.0,
gevent,
hexbytes==0.1.0,
hostpython3==3.7.1,
https://github.com/corpetty/py-etherscan-api/archive/cb91fb3.tar.gz,
idna==2.7,
Kivy==1.11.1,
Expand All @@ -68,19 +69,20 @@ requirements =
openssl,
oscpy==0.3.0,
parsimonious==0.8.1,
Pillow==5.2.0,
Pillow==7.0.0,
plyer==1.3.1,
pycryptodome==3.4.6,
pyetheroll==20191108,
pyetheroll==20200320,
Pygments==2.2.0,
python-dotenv==0.12.0,
python3==3.7.1,
pyzbar==0.1.8,
qrcode==6.0,
raven==6.10.0,
requests==2.20.0,
requests-cache==0.4.13,
rlp==1.0.3,
setuptools==40.9.0,
setuptools==42.0.2,
toolz==0.9.0,
urllib3==1.24.1,
web3==5.2.0
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ kivyunittest==0.1.8
layoutmargin==20190911
mypy==0.730
oscpy==0.3.0
Pillow==5.2.0
Pillow==7.0.0
plyer==1.3.1
pyetheroll==20191108
pytest
python-dotenv==0.12.0
pyzbar==0.1.8
qrcode==6.0
raven==6.10.0
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def read(fname):
'layoutmargin',
'oscpy',
'pyetheroll',
'python-dotenv',
'raven',
'requests-cache',
'web3',
Expand Down
1 change: 0 additions & 1 deletion src/etherollapp/api_key.json

This file was deleted.

2 changes: 2 additions & 0 deletions src/etherollapp/env.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ETHERSCAN_API_KEY=E9K4A1AC8H1V3ZIR1DAIKZ6B961CRXF2DR
WEB3_INFURA_PROJECT_ID=90d5bc91c74e4c6899001dc189da590f
4 changes: 2 additions & 2 deletions src/etherollapp/etheroll/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os

BASE_DIR = os.path.dirname(os.path.dirname(__file__))
BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
# default pyethapp keystore path
KEYSTORE_DIR_SUFFIX = ".config/pyethapp/keystore/"
API_KEY_PATH = os.path.join(BASE_DIR, 'api_key.json')
ENV_PATH = os.path.join(BASE_DIR, 'env.env')
NO_ACCOUNT_SELECTED_STRING = 'No account selected'
9 changes: 5 additions & 4 deletions src/etherollapp/etheroll/controller.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python
from dotenv import load_dotenv
from eth_utils import to_checksum_address
from kivy.app import App
from kivy.clock import Clock, mainthread
Expand All @@ -11,13 +12,13 @@
from raven import Client
from requests.exceptions import ConnectionError

from etherollapp.etheroll.constants import API_KEY_PATH
from etherollapp.etheroll.constants import ENV_PATH
from etherollapp.etheroll.flashqrcode import FlashQrCodeScreen
from etherollapp.etheroll.settings import Settings
from etherollapp.etheroll.settings_screen import SettingsScreen
from etherollapp.etheroll.switchaccount import SwitchAccountScreen
from etherollapp.etheroll.ui_utils import Dialog, load_kv_from_py
from etherollapp.etheroll.utils import get_etherscan_api_key, run_in_thread
from etherollapp.etheroll.utils import run_in_thread
from etherollapp.osc.osc_app_server import OscAppServer
from etherollapp.sentry_utils import configure_sentry
from etherollapp.service.utils import start_roll_polling_service
Expand Down Expand Up @@ -81,8 +82,7 @@ def pyetheroll(self):
"""
from pyetheroll.etheroll import Etheroll
chain_id = Settings.get_stored_network()
api_key = get_etherscan_api_key(API_KEY_PATH)
return Etheroll.get_or_create(api_key, chain_id)
return Etheroll.get_or_create(chain_id)

@property
def account_utils(self):
Expand Down Expand Up @@ -469,6 +469,7 @@ def build(self):


def main():
load_dotenv(dotenv_path=ENV_PATH)
# only send Android errors to Sentry
in_debug = platform != "android"
client = configure_sentry(in_debug)
Expand Down
35 changes: 0 additions & 35 deletions src/etherollapp/etheroll/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import json
import logging
import os
import threading
from io import StringIO

Expand All @@ -12,8 +10,6 @@
def run_in_thread(fn):
"""
Decorator to run a function in a thread.
>>> 1 + 1
2
>>> @run_in_thread
... def threaded_sleep(seconds):
... from time import sleep
Expand Down Expand Up @@ -53,37 +49,6 @@ def check_request_write_permission():
return had_permission


def get_etherscan_api_key(api_key_path: str = None) -> str:
"""
Tries to retrieve etherscan API key from path or from environment.
The files content should be in the form:
```json
{ "key" : "YourApiKeyToken" }
```
"""
DEFAULT_API_KEY_TOKEN = "YourApiKeyToken"
etherscan_api_key = os.environ.get("ETHERSCAN_API_KEY")
if etherscan_api_key is not None:
return etherscan_api_key
elif api_key_path is None:
logger.warning(
"Cannot get Etherscan API key. "
f"No path provided, defaulting to {DEFAULT_API_KEY_TOKEN}."
)
return DEFAULT_API_KEY_TOKEN
else:
try:
with open(api_key_path, mode="r") as key_file:
etherscan_api_key = json.loads(key_file.read())["key"]
except FileNotFoundError:
logger.warning(
f"Cannot get Etherscan API key. File {api_key_path} not found,"
f" defaulting to {DEFAULT_API_KEY_TOKEN}."
)
return DEFAULT_API_KEY_TOKEN
return etherscan_api_key


class StringIOCBWrite(StringIO):
"""Inherits StringIO, provides callback on write."""

Expand Down
8 changes: 4 additions & 4 deletions src/etherollapp/service/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import os
from time import sleep, time

from dotenv import load_dotenv
from eth_accounts.account_utils import AccountUtils
from kivy.app import App
from kivy.logger import Logger
Expand All @@ -25,9 +26,8 @@
from pyetheroll.etheroll import Etheroll
from raven import Client

from etherollapp.etheroll.constants import API_KEY_PATH
from etherollapp.etheroll.constants import ENV_PATH
from etherollapp.etheroll.settings import Settings
from etherollapp.etheroll.utils import get_etherscan_api_key
from etherollapp.osc.osc_app_client import OscAppClient
from etherollapp.sentry_utils import configure_sentry

Expand Down Expand Up @@ -122,8 +122,7 @@ def pyetheroll(self):
Also recreates the object if the chain_id changed.
"""
chain_id = Settings.get_stored_network()
api_key = get_etherscan_api_key(API_KEY_PATH)
return Etheroll.get_or_create(api_key, chain_id)
return Etheroll.get_or_create(chain_id)

def pull_account_rolls(self, account):
"""
Expand Down Expand Up @@ -189,6 +188,7 @@ def do_notify(self, merged_logs):


def main():
load_dotenv(dotenv_path=ENV_PATH)
# only send Android errors to Sentry
in_debug = platform != "android"
client = configure_sentry(in_debug)
Expand Down
55 changes: 14 additions & 41 deletions src/etherollapp/tests/etheroll/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,18 @@
from unittest import mock
from threading import Thread

from etherollapp.etheroll.utils import get_etherscan_api_key
from etherollapp.etheroll.utils import run_in_thread


class TestUtils:
def test_get_etherscan_api_key(self):
"""
Verifies the key can be retrieved from either:
1) environment
2) file
3) or fallbacks on default key
"""
expected_key = "0102030405060708091011121314151617"
# 1) environment
with mock.patch.dict(
"os.environ", {"ETHERSCAN_API_KEY": expected_key}
):
actual_key = get_etherscan_api_key()
assert actual_key == expected_key
# 2) file
read_data = '{ "key" : "%s" }' % (expected_key)
api_key_path = "api_key.json"
with mock.patch(
"builtins.open", mock.mock_open(read_data=read_data)
) as m_open:
actual_key = get_etherscan_api_key(api_key_path=api_key_path)
assert expected_key == actual_key
# verifies the file was read
assert m_open.call_args_list == [mock.call(api_key_path, mode="r")]
# 3) or fallbacks on default key
with mock.patch("builtins.open") as m_open, mock.patch(
"etherollapp.etheroll.utils.logger"
) as m_logger:
m_open.side_effect = FileNotFoundError
actual_key = get_etherscan_api_key(api_key_path)
assert "YourApiKeyToken" == actual_key
# verifies the fallback warning was logged
assert m_logger.warning.call_args_list == [
mock.call(
"Cannot get Etherscan API key. "
"File api_key.json not found, "
"defaulting to YourApiKeyToken."
)
]

def test_run_in_thread(fn):
@run_in_thread
def threaded_sleep(seconds):
from time import sleep
sleep(seconds)

thread = threaded_sleep(0.1)
assert isinstance(thread, Thread)
assert thread.is_alive() is True
thread.join()
assert thread.is_alive() is False

0 comments on commit ae984c4

Please sign in to comment.