diff --git a/.mdformat.toml b/.mdformat.toml deleted file mode 100644 index 01b2fb06..00000000 --- a/.mdformat.toml +++ /dev/null @@ -1 +0,0 @@ -number = true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f7b6ea95..e15dfcf4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.5.0 hooks: - id: check-yaml @@ -10,24 +10,24 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 23.7.0 + rev: 23.12.0 hooks: - id: black name: black - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + rev: 6.1.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.4.1 + rev: v1.7.1 hooks: - id: mypy additional_dependencies: [types-setuptools, pydantic] - repo: https://github.com/executablebooks/mdformat - rev: 0.7.14 + rev: 0.7.17 hooks: - id: mdformat additional_dependencies: [mdformat-gfm, mdformat-frontmatter] diff --git a/README.md b/README.md index d547cd7a..5a481371 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,8 @@ To run your bot against a live network, this SDK includes a simple runner you ca $ silverback run "example:app" --network :mainnet:alchemy ``` +**NOTE**: The example is designed to work with Python 3.9+, and we suggest using 3.11+ for speed. + ## Docker Usage ```sh diff --git a/example.py b/example.py index 60d13456..1359f684 100644 --- a/example.py +++ b/example.py @@ -1,4 +1,4 @@ -from typing import Annotated +from typing import Annotated # NOTE: Only Python 3.9+ from ape import chain from ape.api import BlockAPI diff --git a/setup.py b/setup.py index 9c7911ff..bd8015d9 100644 --- a/setup.py +++ b/setup.py @@ -5,20 +5,20 @@ extras_require = { "test": [ # `test` GitHub Action jobs uses this "pytest>=6.0", # Core testing package - "pytest-xdist", # multi-process runner + "pytest-xdist", # Multi-process runner "pytest-cov", # Coverage analyzer plugin "hypothesis>=6.2.0,<7.0", # Strategy-based fuzzer ], "lint": [ - "black>=23.7.0", # auto-formatter and linter - "mypy>=1.4.1,<2", # Static type analyzer + "black>=23.12.0,<24", # Auto-formatter and linter + "mypy>=1.7.1,<2", # Static type analyzer "types-setuptools", # Needed for mypy type shed - "flake8>=5.0.4", # Style linter - "isort>=5.10.1", # Import sorting linter - "mdformat>=0.7.16", # Auto-formatter for markdown + "flake8>=6.1.0,<7", # Style linter + "isort>=5.10.1,<6", # Import sorting linter + "mdformat>=0.7.17", # Auto-formatter for markdown "mdformat-gfm>=0.3.5", # Needed for formatting GitHub-flavored markdown "mdformat-frontmatter>=0.4.1", # Needed for frontmatters-style headers in issue templates - "mdformat-pyproject>=0.0.1", # So we can keep config for mdformat in `pyproject.toml` + "mdformat-pyproject>=0.0.1", # Allows configuring in pyproject.toml ], "doc": [ "myst-parser>=1.0.0,<2", # Parse markdown docs @@ -67,9 +67,11 @@ url="https://github.com/ApeWorX/silverback", include_package_data=True, install_requires=[ - "eth-ape>=0.6.19,<1.0", - "taskiq[metrics]>=0.6.0,<0.7.0", "click", # Use same version as eth-ape + "eth-ape>=0.7.0,<1.0", + "eth-pydantic-types", # Use same version as eth-ape + "pydantic_settings", # Use same version as eth-ape + "taskiq[metrics]>=0.10.4,<0.11.0", ], entry_points={ "console_scripts": ["silverback=silverback._cli:cli"], diff --git a/silverback/_cli.py b/silverback/_cli.py index d5cf5356..4a604690 100644 --- a/silverback/_cli.py +++ b/silverback/_cli.py @@ -3,7 +3,14 @@ from concurrent.futures import ThreadPoolExecutor import click -from ape.cli import AccountAliasPromptChoice, ape_cli_context, network_option, verbosity_option +from ape.cli import ( + AccountAliasPromptChoice, + ConnectedProviderCommand, + ape_cli_context, + network_option, + verbosity_option, +) +from ape.exceptions import Abort from taskiq import AsyncBroker from taskiq.cli.worker.run import shutdown_broker from taskiq.receiver import Receiver @@ -36,10 +43,18 @@ def _account_callback(ctx, param, val): def _network_callback(ctx, param, val): - if val: - os.environ["SILVERBACK_NETWORK_CHOICE"] = val + # NOTE: Make sure both of these have the same setting + if env_network_choice := os.environ.get("SILVERBACK_NETWORK_CHOICE"): + if val.network_choice != env_network_choice: + raise Abort( + f"Network choice '{val.network_choice}' does not " + f"match environment variable '{env_network_choice}'." + ) + + # else it matches, no issue + else: - val = os.environ.get("SILVERBACK_NETWORK_CHOICE", "") + os.environ["SILVERBACK_NETWORK_CHOICE"] = val.network_choice return val @@ -64,10 +79,13 @@ async def run_worker(broker: AsyncBroker, worker_count=2, shutdown_timeout=90): await shutdown_broker(broker, shutdown_timeout) -@cli.command(help="Run Silverback application client") +@cli.command(cls=ConnectedProviderCommand, help="Run Silverback application client") @ape_cli_context() @verbosity_option() -@network_option(default=None, callback=_network_callback) +@network_option( + default=os.environ.get("SILVERBACK_NETWORK_CHOICE", "auto"), + callback=_network_callback, +) @click.option("--account", type=AccountAliasPromptChoice(), callback=_account_callback) @click.option( "--runner", @@ -76,22 +94,24 @@ async def run_worker(broker: AsyncBroker, worker_count=2, shutdown_timeout=90): ) @click.option("-x", "--max-exceptions", type=int, default=3) @click.argument("path") -def run(cli_ctx, network, account, runner, max_exceptions, path): - with cli_ctx.network_manager.parse_network_choice(network): - app = import_from_string(path) - runner = runner(app, max_exceptions=max_exceptions) - asyncio.run(runner.run()) +def run(cli_ctx, account, runner, max_exceptions, path): + app = import_from_string(path) + runner = runner(app, max_exceptions=max_exceptions) + asyncio.run(runner.run()) -@cli.command(help="Run Silverback application task workers") +@cli.command(cls=ConnectedProviderCommand, help="Run Silverback application task workers") @ape_cli_context() @verbosity_option() -@network_option(default=None, callback=_network_callback) +@network_option( + default=os.environ.get("SILVERBACK_NETWORK_CHOICE", "auto"), + callback=_network_callback, +) @click.option("--account", type=AccountAliasPromptChoice(), callback=_account_callback) @click.option("-w", "--workers", type=int, default=2) @click.option("-x", "--max-exceptions", type=int, default=3) @click.option("-s", "--shutdown_timeout", type=int, default=90) @click.argument("path") -def worker(cli_ctx, network, account, workers, max_exceptions, shutdown_timeout, path): +def worker(cli_ctx, account, workers, max_exceptions, shutdown_timeout, path): app = import_from_string(path) asyncio.run(run_worker(app.broker, worker_count=workers, shutdown_timeout=shutdown_timeout)) diff --git a/silverback/application.py b/silverback/application.py index 527b85f5..75717c91 100644 --- a/silverback/application.py +++ b/silverback/application.py @@ -127,7 +127,7 @@ def get_startup_handler(self) -> Optional[AsyncTaskiqDecoratedTask]: Returns: Optional[AsyncTaskiqDecoratedTask]: Returns decorated task, if one has been created. """ - return self.broker.available_tasks.get("silverback_startup") + return self.broker.find_task("silverback_startup") def get_shutdown_handler(self) -> Optional[AsyncTaskiqDecoratedTask]: """ @@ -136,7 +136,7 @@ def get_shutdown_handler(self) -> Optional[AsyncTaskiqDecoratedTask]: Returns: Optional[AsyncTaskiqDecoratedTask]: Returns decorated task, if one has been created. """ - return self.broker.available_tasks.get("silverback_shutdown") + return self.broker.find_task("silverback_shutdown") def get_block_handler(self) -> Optional[AsyncTaskiqDecoratedTask]: """ @@ -145,7 +145,7 @@ def get_block_handler(self) -> Optional[AsyncTaskiqDecoratedTask]: Returns: Optional[AsyncTaskiqDecoratedTask]: Returns decorated task, if one has been created. """ - return self.broker.available_tasks.get("block") + return self.broker.find_task("block") def get_event_handler( self, event_target: AddressType, event_name: str @@ -160,7 +160,7 @@ def get_event_handler( Returns: Optional[AsyncTaskiqDecoratedTask]: Returns decorated task, if one has been created. """ - return self.broker.available_tasks.get(f"{event_target}/event/{event_name}") + return self.broker.find_task(f"{event_target}/event/{event_name}") def on_( self, diff --git a/silverback/middlewares.py b/silverback/middlewares.py index 5551aa16..e6029b7f 100644 --- a/silverback/middlewares.py +++ b/silverback/middlewares.py @@ -82,7 +82,7 @@ def pre_execute(self, message: TaskiqMessage) -> TaskiqMessage: elif "event" in message.task_name: # NOTE: Just in case the user doesn't specify type as `ContractLog` - message.args[0] = ContractLog.parse_obj(message.args[0]) + message.args[0] = ContractLog.model_validate(message.args[0]) logger.info(f"{self._create_label(message)} - Started") return message diff --git a/silverback/settings.py b/silverback/settings.py index 281c270b..8c7adc71 100644 --- a/silverback/settings.py +++ b/silverback/settings.py @@ -2,7 +2,7 @@ from ape.api import AccountAPI, ProviderContextManager from ape.utils import ManagerAccessMixin -from pydantic import BaseSettings +from pydantic_settings import BaseSettings, SettingsConfigDict from taskiq import AsyncBroker, InMemoryBroker, PrometheusMiddleware, TaskiqMiddleware from ._importer import import_from_string @@ -38,9 +38,7 @@ class Settings(BaseSettings, ManagerAccessMixin): # Used for persistent store PERSISTENCE_CLASS: Optional[str] = None - class Config: - env_prefix = "SILVERBACK_" - case_sensitive = True + model_config = SettingsConfigDict(env_prefix="SILVERBACK_", case_sensitive=True) def get_broker(self) -> AsyncBroker: broker_class = import_from_string(self.BROKER_CLASS)