diff --git a/poetry.lock b/poetry.lock index 8e9675fe6..fef13a57f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -52,6 +52,23 @@ python-versions = ">=3.6" [package.dependencies] typing_extensions = ">=3.7.2" +[[package]] +name = "anyio" +version = "3.2.1" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "main" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] +test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=6.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16)"] + [[package]] name = "appdirs" version = "1.4.4" @@ -138,6 +155,17 @@ category = "main" optional = false python-versions = ">=3.5.3" +[[package]] +name = "asyncclick" +version = "8.0.1.3" +description = "Composable command line interface toolkit, async version" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "asyncpg" version = "0.23.0" @@ -1738,6 +1766,14 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "sniffio" +version = "1.2.0" +description = "Sniff out which async library your code is running under" +category = "main" +optional = false +python-versions = ">=3.5" + [[package]] name = "strict-rfc3339" version = "0.7" @@ -2027,7 +2063,7 @@ pytezos = ["pytezos"] [metadata] lock-version = "1.1" python-versions = "^3.8" -content-hash = "bd7aa720274de2f7a22adeb348ae0b520828f515361f59a965c3912a96d1485e" +content-hash = "694c587044b8e4955161d4b3ad46b2572694e81a318c9343f110ca057730d201" [metadata.files] aiohttp = [ @@ -2081,6 +2117,10 @@ aiosqlite = [ {file = "aiosqlite-0.16.1-py3-none-any.whl", hash = "sha256:1df802815bb1e08a26c06d5ea9df589bcb8eec56e5f3378103b0f9b223c6703c"}, {file = "aiosqlite-0.16.1.tar.gz", hash = "sha256:2e915463164efa65b60fd1901aceca829b6090082f03082618afca6fb9c8fdf7"}, ] +anyio = [ + {file = "anyio-3.2.1-py3-none-any.whl", hash = "sha256:442678a3c7e1cdcdbc37dcfe4527aa851b1b0c9162653b516e9f509821691d50"}, + {file = "anyio-3.2.1.tar.gz", hash = "sha256:07968db9fa7c1ca5435a133dc62f988d84ef78e1d9b22814a59d1c62618afbc5"}, +] appdirs = [ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, @@ -2129,6 +2169,9 @@ async-timeout = [ {file = "async-timeout-3.0.1.tar.gz", hash = "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f"}, {file = "async_timeout-3.0.1-py3-none-any.whl", hash = "sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"}, ] +asyncclick = [ + {file = "asyncclick-8.0.1.3.tar.gz", hash = "sha256:30e4d61ad272640e21d3d819ac57facddf07437e431589f1e64a967a64f562aa"}, +] asyncpg = [ {file = "asyncpg-0.23.0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f86378bbfbec7334af03bad4d5fd432149286665ecc8bfbcb7135da56b15d34b"}, {file = "asyncpg-0.23.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:255839c8c52ebd72d6d0159564d7eb8f70fcf6cc9ce7cdc7e98328fd3279bf52"}, @@ -3110,6 +3153,10 @@ six = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +sniffio = [ + {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, + {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, +] strict-rfc3339 = [ {file = "strict-rfc3339-0.7.tar.gz", hash = "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277"}, ] diff --git a/pyproject.toml b/pyproject.toml index 1447e952e..aacfee881 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,6 @@ tortoise-orm = "0.17.5" pydantic = "^1.8.1" aiosignalrcore = "^0.9.2" fcache = "^0.4.7" -click = "^8.0.1" pyee = "^8.1.0" APScheduler = "^3.7.0" sentry-sdk = "^1.1.0" @@ -39,6 +38,8 @@ aiolimiter = "^1.0.0-beta.1" tabulate = "^0.8.9" python-dotenv = "^0.18.0" pytezos = {version = "^3.2.4", optional = true} +asyncclick = "^8.0.1" +anyio = "^3.2.1" [tool.poetry.dev-dependencies] black = "^20.8b1" diff --git a/src/demo_hic_et_nunc/docker/Dockerfile b/src/demo_hic_et_nunc/docker/Dockerfile index 0222a6010..cb8962acf 100644 --- a/src/demo_hic_et_nunc/docker/Dockerfile +++ b/src/demo_hic_et_nunc/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_hic_et_nunc /home/dipdup/demo_hic_et_nunc \ No newline at end of file diff --git a/src/demo_quipuswap/docker/Dockerfile b/src/demo_quipuswap/docker/Dockerfile index 12822b003..7fd103bb0 100644 --- a/src/demo_quipuswap/docker/Dockerfile +++ b/src/demo_quipuswap/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_quipuswap /home/dipdup/demo_quipuswap \ No newline at end of file diff --git a/src/demo_registrydao/docker/Dockerfile b/src/demo_registrydao/docker/Dockerfile index 97d457c7e..790b9b31d 100644 --- a/src/demo_registrydao/docker/Dockerfile +++ b/src/demo_registrydao/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_registrydao /home/dipdup/demo_registrydao \ No newline at end of file diff --git a/src/demo_tezos_domains/docker/Dockerfile b/src/demo_tezos_domains/docker/Dockerfile index 64a0cbfd3..b33f2651b 100644 --- a/src/demo_tezos_domains/docker/Dockerfile +++ b/src/demo_tezos_domains/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_tezos_domains /home/dipdup/demo_tezos_domains \ No newline at end of file diff --git a/src/demo_tezos_domains_big_map/docker/Dockerfile b/src/demo_tezos_domains_big_map/docker/Dockerfile index e7e29cea5..0d834fbeb 100644 --- a/src/demo_tezos_domains_big_map/docker/Dockerfile +++ b/src/demo_tezos_domains_big_map/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_tezos_domains_big_map /home/dipdup/demo_tezos_domains_big_map \ No newline at end of file diff --git a/src/demo_tzbtc/docker/Dockerfile b/src/demo_tzbtc/docker/Dockerfile index e27e3a3dd..8e8bf121f 100644 --- a/src/demo_tzbtc/docker/Dockerfile +++ b/src/demo_tzbtc/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_tzbtc /home/dipdup/demo_tzbtc \ No newline at end of file diff --git a/src/demo_tzcolors/docker/Dockerfile b/src/demo_tzcolors/docker/Dockerfile index ea24a3588..ba78f6736 100644 --- a/src/demo_tzcolors/docker/Dockerfile +++ b/src/demo_tzcolors/docker/Dockerfile @@ -1,2 +1,2 @@ -FROM dipdup/dipdup:2.0.0 +FROM dipdup/dipdup:2.0.1 COPY demo_tzcolors /home/dipdup/demo_tzcolors \ No newline at end of file diff --git a/src/dipdup/__init__.py b/src/dipdup/__init__.py index f424995fb..eecd1bcdc 100644 --- a/src/dipdup/__init__.py +++ b/src/dipdup/__init__.py @@ -9,4 +9,4 @@ '0.1': False, '1.0': False, '1.1': True, -} \ No newline at end of file +} diff --git a/src/dipdup/__main__.py b/src/dipdup/__main__.py index a9748e4c0..f044dc92a 100644 --- a/src/dipdup/__main__.py +++ b/src/dipdup/__main__.py @@ -1,4 +1,14 @@ +import logging + from dipdup.cli import cli +from dipdup.exceptions import DipDupError if __name__ == '__main__': - cli(prog_name='dipdup') # type: ignore + try: + cli(prog_name='dipdup', standalone_mode=False) # type: ignore + except KeyboardInterrupt: + pass + except DipDupError as e: + logging.critical(e.__repr__()) + logging.info(e.format()) + quit(e.exit_code) diff --git a/src/dipdup/cli.py b/src/dipdup/cli.py index 6945e2328..15a3de5b4 100644 --- a/src/dipdup/cli.py +++ b/src/dipdup/cli.py @@ -1,42 +1,27 @@ -import asyncio import fileinput import logging import os from dataclasses import dataclass -from functools import wraps from os.path import dirname, exists, join from typing import List, cast -import click +import asyncclick as click import sentry_sdk from dotenv import load_dotenv from fcache.cache import FileCache # type: ignore from sentry_sdk.integrations.aiohttp import AioHttpIntegration -from dipdup import __spec_version__, __version__, spec_version_mapping, spec_reindex_mapping +from dipdup import __spec_version__, __version__, spec_reindex_mapping, spec_version_mapping from dipdup.codegen import DEFAULT_DOCKER_ENV_FILE, DEFAULT_DOCKER_IMAGE, DEFAULT_DOCKER_TAG, DipDupCodeGenerator from dipdup.config import DipDupConfig, LoggingConfig, PostgresDatabaseConfig from dipdup.dipdup import DipDup -from dipdup.exceptions import ConfigurationError, DipDupError, MigrationRequiredError +from dipdup.exceptions import ConfigurationError, MigrationRequiredError from dipdup.hasura import HasuraGateway from dipdup.utils import set_decimal_context, tortoise_wrapper _logger = logging.getLogger('dipdup.cli') -def click_command_wrapper(fn): - @wraps(fn) - def wrapper(*args, **kwargs): - try: - return asyncio.run(fn(*args, **kwargs)) - except DipDupError as e: - _logger.critical(e.__repr__()) - _logger.info(e.format()) - quit(e.exit_code) - - return wrapper - - @dataclass class CLIContext: config_paths: List[str] @@ -50,7 +35,6 @@ class CLIContext: @click.option('--env-file', '-e', type=str, multiple=True, help='Path to .env file', default=[]) @click.option('--logging-config', '-l', type=str, help='Path to logging YAML config', default='logging.yml') @click.pass_context -@click_command_wrapper async def cli(ctx, config: List[str], env_file: List[str], logging_config: str): try: path = join(os.getcwd(), logging_config) @@ -94,7 +78,6 @@ async def cli(ctx, config: List[str], env_file: List[str], logging_config: str): @click.option('--reindex', is_flag=True, help='Drop database and start indexing from scratch') @click.option('--oneshot', is_flag=True, help='Synchronize indexes wia REST and exit without starting WS connection') @click.pass_context -@click_command_wrapper async def run(ctx, reindex: bool, oneshot: bool) -> None: config: DipDupConfig = ctx.obj.config config.initialize() @@ -104,7 +87,6 @@ async def run(ctx, reindex: bool, oneshot: bool) -> None: @cli.command(help='Initialize new dipdup project') @click.pass_context -@click_command_wrapper async def init(ctx): config: DipDupConfig = ctx.obj.config config.pre_initialize() @@ -114,7 +96,6 @@ async def init(ctx): @cli.command(help='Migrate project to the new spec version') @click.pass_context -@click_command_wrapper async def migrate(ctx): def _bump_spec_version(spec_version: str): for config_path in ctx.obj.config_paths: @@ -141,32 +122,12 @@ def _bump_spec_version(spec_version: str): @cli.command(help='Clear development request cache') @click.pass_context -@click_command_wrapper async def clear_cache(ctx): FileCache('dipdup', flag='cs').clear() -@cli.command(help='Configure Hasura GraphQL Engine') -@click.option('--reset', is_flag=True, help='Reset metadata before configuring') -@click.pass_context -@click_command_wrapper -async def configure_hasura(ctx, reset: bool): - config: DipDupConfig = ctx.obj.config - url = config.database.connection_string - models = f'{config.package}.models' - if not config.hasura: - _logger.error('`hasura` config section is empty') - return - hasura_gateway = HasuraGateway(config.package, config.hasura, cast(PostgresDatabaseConfig, config.database)) - - async with tortoise_wrapper(url, models): - async with hasura_gateway: - await hasura_gateway.configure(reset) - - @cli.group() @click.pass_context -@click_command_wrapper async def docker(ctx): ... @@ -176,7 +137,29 @@ async def docker(ctx): @click.option('--tag', '-t', type=str, help='DipDup Docker tag', default=DEFAULT_DOCKER_TAG) @click.option('--env-file', '-e', type=str, help='Path to env_file', default=DEFAULT_DOCKER_ENV_FILE) @click.pass_context -@click_command_wrapper async def docker_init(ctx, image: str, tag: str, env_file: str): config: DipDupConfig = ctx.obj.config await DipDupCodeGenerator(config, {}).generate_docker(image, tag, env_file) + + +@cli.group() +@click.pass_context +async def hasura(ctx): + ... + + +@hasura.command(name='configure', help='Configure Hasura GraphQL Engine') +@click.option('--reset', is_flag=True, help='Reset metadata before configuring') +@click.pass_context +async def hasura_configure(ctx, reset: bool): + config: DipDupConfig = ctx.obj.config + url = config.database.connection_string + models = f'{config.package}.models' + if not config.hasura: + _logger.error('`hasura` config section is empty') + return + hasura_gateway = HasuraGateway(config.package, config.hasura, cast(PostgresDatabaseConfig, config.database)) + + async with tortoise_wrapper(url, models): + async with hasura_gateway: + await hasura_gateway.configure(reset) diff --git a/src/dipdup/exceptions.py b/src/dipdup/exceptions.py index 52948d7ec..a5a9a61c2 100644 --- a/src/dipdup/exceptions.py +++ b/src/dipdup/exceptions.py @@ -112,12 +112,13 @@ def format_help(self) -> str: ) message = _migration_required_message.format(version_table=version_table) if self.reindex: - message += _tab +_reindexing_required_message + message += _tab + _reindexing_required_message return message class ReindexingRequiredError(DipDupError): """Performed migration requires reindexing""" + def format_help(self) -> str: return _reindexing_required_message