Skip to content

Commit

Permalink
refactor: integrate "1st class" plugins into project (#6)
Browse files Browse the repository at this point in the history
* fix: layout issues

* fix: add flake8 to lint dependencies

* refactor: move `ape` package to subdir under `src/`

* refactor: migrate `ape-accounts` repo to 1st class plugin undo `src/`

* feat: add `ape compile` 1st class plugin

* fix: isort lint issues

* chore: fixup CI calls

* test: add test for CLI invocation

* chore: fixup testing cli flags

* fix: install backported `importlib-metadata` for Python <3.8

* refactor: remove unnecessary default package call

* refactor: remove export

* fix: need password to delete

* test: add fuzz testing, parametrize more

* refactor: remove "fuzz" extra as it breaks tests
  • Loading branch information
fubuloubu authored Mar 4, 2021
1 parent 21da087 commit 233ffc4
Show file tree
Hide file tree
Showing 26 changed files with 376 additions and 60 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
run: black --check .

- name: Run isort
run: isort --check-only --diff ./ape ./tests
run: isort --check-only .

- name: Run flake8
run: flake8 ./ape ./tests ./setup.py
run: flake8 .

type-check:
runs-on: ubuntu-latest
Expand All @@ -41,7 +41,7 @@ jobs:
run: pip install .[lint]

- name: Run MyPy
run: mypy -p ape
run: mypy .

functional:
runs-on: ubuntu-latest
Expand All @@ -62,7 +62,7 @@ jobs:
run: pip install .[test]

- name: Run Tests
run: pytest -m "not fuzzing" -s --coverage
run: pytest -m "not fuzzing" -s --cov

fuzzing:
runs-on: ubuntu-latest
Expand All @@ -79,7 +79,7 @@ jobs:
python-version: 3.8

- name: Install Dependencies
run: pip install .[fuzz]
run: pip install .[test]

- name: Run Tests
run: pytest -m "fuzzing" --no-cov -s
9 changes: 0 additions & 9 deletions ape/__init__.py

This file was deleted.

4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
requires = ["setuptools>=51.1.1", "wheel", "setuptools_scm[toml]>=5.0"]

[tool.setuptools_scm]
write_to = "ape/version.py"
write_to = "src/ape/version.py"

# NOTE: you have to use single-quoted strings in TOML for regular expressions.
# It's the equivalent of r-strings in Python. Multiline strings are treated as
Expand Down Expand Up @@ -35,7 +35,7 @@ addopts = """
--cov-report term
--cov-report html
--cov-report xml
--cov=ape
--cov=src
"""
python_files = "test_*.py"
testpaths = "tests"
Expand Down
26 changes: 12 additions & 14 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from setuptools import ( # type: ignore
setup,
find_packages,
)
from setuptools import find_packages, setup # type: ignore

extras_require = {
"test": [ # `test` GitHub Action jobs uses this
"pytest>=6.0,<7.0", # Core testing package
"pytest-xdist", # multi-process runner
"pytest-cov", # Coverage analyzer plugin
],
"fuzz": [ # `fuzz` GitHub Action job uses this
"hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer
],
"lint": [
"black>=20.8b1,<21.0", # auto-formatter and linter
"mypy>=0.800,<1.0", # Static type analyzer
"flake8>=3.8.3,<4.0", # Style linter
"isort>=5.7.0,<6.0", # Import sorting linter
],
"doc": [
Expand All @@ -40,24 +36,20 @@
# NOTE: `pip install -e .[dev]` to install package
extras_require["dev"] = (
extras_require["test"]
+ extras_require["fuzz"]
+ extras_require["lint"]
+ extras_require["doc"]
+ extras_require["release"]
+ extras_require["dev"]
)

# NOTE: This comes after the previous so we don't have double dependencies
extras_require["fuzz"] = extras_require["test"] + extras_require["fuzz"]

with open("./README.md") as readme:
long_description = readme.read()


setup(
name="eth-ape",
use_scm_version=True,
setup_requires=['setuptools_scm'],
setup_requires=["setuptools_scm"],
description="Ape Ethereum Framework",
long_description=long_description,
long_description_content_type="text/markdown",
Expand All @@ -69,18 +61,24 @@
"click>=7.1.2",
"eth-account>=0.5.4,<0.6.0",
"pyyaml>=0.2.5",
"importlib-metadata ; python_version<'3.8'",
],
entry_points={
"console_scripts": ["ape=ape._cli:cli"],
},
python_requires=">=3.6,<3.9",
extras_require=extras_require,
py_modules=["ape"],
py_modules=["ape", "ape_accounts", "ape_compile"],
license="Apache-2.0",
zip_safe=False,
keywords="ethereum",
packages=find_packages(exclude=["tests", "tests.*"]),
package_data={"ape_accounts": ["py.typed"]},
packages=find_packages("src"),
package_dir={"": "src"},
package_data={
"ape": ["py.typed"],
"ape_accounts": ["py.typed"],
"ape_compile": ["py.typed"],
},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
Expand Down
13 changes: 13 additions & 0 deletions src/ape/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
try:
from importlib.metadata import PackageNotFoundError, version # type: ignore
except ModuleNotFoundError:
from importlib_metadata import PackageNotFoundError, version # type: ignore

# NOTE: Do this before anything else
from . import _setup # noqa E302

try:
__version__ = version(__name__)
except PackageNotFoundError:
# package is not installed
__version__ = "<unknown>"
1 change: 0 additions & 1 deletion ape/_cli.py → src/ape/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from ape.plugins import CliPlugin, registered_plugins


CLI_PLUGINS = {p.name: p for p in registered_plugins[CliPlugin]}


Expand Down
8 changes: 1 addition & 7 deletions ape/_setup.py → src/ape/_setup.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
# NOTE: This file only executes once on module load
import shutil
from pathlib import Path

from .constants import (
INSTALL_FOLDER,
DATA_FOLDER,
DATA_SUBFOLDERS,
)

from .constants import DATA_FOLDER, DATA_SUBFOLDERS, INSTALL_FOLDER

# create data folder structure
DATA_FOLDER.mkdir(exist_ok=True)
Expand Down
7 changes: 3 additions & 4 deletions ape/_utils.py → src/ape/_utils.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from typing import Dict

import collections
import json
import os
import yaml

from copy import deepcopy
from pathlib import Path as Path
from typing import Dict

import yaml


def deep_merge(dict1, dict2):
Expand Down
1 change: 1 addition & 0 deletions ape/accounts.py → src/ape/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from ape.plugins.account_api import AccountAPI, AccountContainerAPI


# NOTE: This class is an aggregated container for all of the registered containers
class Accounts(AccountContainerAPI):
def __init__(self):
Expand Down
16 changes: 4 additions & 12 deletions ape/config.py → src/ape/config.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
# NOTE: Modules in Python are singletons, this module implements
# all of the configuration items for the ape tool
# https://docs.python.org/3/faq/programming.html#how-do-i-share-global-variables-across-modules
from typing import Dict
import sys as _sys

from pathlib import Path

from .constants import (
INSTALL_FOLDER,
PROJECT_FOLDER,
DATA_FOLDER,
)
from ._utils import (
deep_merge as _deep_merge,
load_config as _load_config,
)
from typing import Dict

from ape import __version__

from ._utils import deep_merge as _deep_merge
from ._utils import load_config as _load_config
from .constants import DATA_FOLDER, INSTALL_FOLDER, PROJECT_FOLDER

# For all HTTP requests we make
_python_version = (
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
7 changes: 3 additions & 4 deletions ape/plugins/__init__.py → src/ape/plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from abc import ABCMeta
from typing import Any, Callable, Dict, Generic, List, Optional, Type, TypeVar, Union

import importlib
import pkgutil
from abc import ABCMeta
from typing import Callable, Dict, Generic, List, Type, TypeVar

import click

Expand Down Expand Up @@ -47,7 +46,7 @@ def data(self):


class CliPlugin(BasePlugin):
provides = click.Group
provides = click.Command


class AccountPlugin(BasePlugin):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
from abc import ABC, abstractmethod
from typing import Iterator, Optional

from eth_account.messages import SignableMessage # type: ignore
from eth_account.datastructures import SignedMessage, SignedTransaction # type: ignore

from eth_account.messages import SignableMessage # type: ignore

from ape import config

Expand Down
File renamed without changes.
14 changes: 14 additions & 0 deletions src/ape_accounts/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from ape import plugins

from ._cli import cli
from .accounts import AccountContainer


@plugins.register(plugins.CliPlugin)
def register_cli():
return cli


@plugins.register(plugins.AccountPlugin)
def register_accounts():
return AccountContainer
75 changes: 75 additions & 0 deletions src/ape_accounts/_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import click

from ape.accounts import accounts

from .accounts import KeyfileAccount


@click.group(short_help="Manage local accounts")
def cli():
"""
Command-line helper for managing local accounts. You can unlock local accounts from
scripts or the console using the accounts.load() method.
"""


# Different name because `list` is a keyword
@cli.command(name="list", short_help="List available accounts")
def _list():
if len(accounts) == 0:
click.echo("No accounts found.")
return

elif len(accounts) > 1:
click.echo(f"Found {len(accounts)} accounts:")

else:
click.echo("Found 1 account:")

for account in accounts:
alias_display = f" (alias: '{account.alias}')" if account.alias else ""
click.echo(f"{account.address}{alias_display}")


@cli.command(short_help="Add a new account with a random private key")
@click.argument("alias")
def generate(alias):
assert alias not in accounts.aliases
a = KeyfileAccount.generate(accounts.path.joinpath(f"{alias}.json"))
click.echo(f"A new account '{a.address}' has been added with the id '{alias}'")


# Different name because `import` is a keyword
@cli.command(name="import", short_help="Add a new account by entering a private key")
@click.argument("alias")
def _import(alias):
if alias in accounts.aliases:
click.echo(f"Account with alias '{alias}' already exists")
return

a = KeyfileAccount.from_key(accounts.DATA_FOLDER.joinpath(f"{alias}.json"))
click.echo(f"A new account '{a.address}' has been added with the id '{alias}'")


@cli.command(short_help="Change the password of an existing account")
@click.argument("alias", type=click.Choice(accounts.aliases))
def change_password(alias):
account = accounts.load(alias)
if not isinstance(account, KeyfileAccount):
click.echo(f"Account '{alias}' cannot change it's password")
return

account.change_password()
click.echo(f"Password has been changed for account '{alias}'")


@cli.command(short_help="Delete an existing account")
@click.argument("alias", type=click.Choice(accounts.aliases))
def delete(alias):
account = accounts.load(alias)
if not isinstance(account, KeyfileAccount):
click.echo(f"Account '{alias}' is not able to be deleted")
return

account.delete()
click.echo(f"Account '{alias}' has been deleted")
Loading

0 comments on commit 233ffc4

Please sign in to comment.