From 3d559067dc97fe8d8d158e884d9f066c0dad3994 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 13:00:00 +0000 Subject: [PATCH 01/10] feat/moved-to-services --- auto_dev/commands/run.py | 293 +------------------------- auto_dev/services/runner/__init__.py | 1 + auto_dev/services/runner/runner.py | 301 +++++++++++++++++++++++++++ 3 files changed, 304 insertions(+), 291 deletions(-) create mode 100644 auto_dev/services/runner/__init__.py create mode 100644 auto_dev/services/runner/runner.py diff --git a/auto_dev/commands/run.py b/auto_dev/commands/run.py index 4a6e5968..33f6ce89 100644 --- a/auto_dev/commands/run.py +++ b/auto_dev/commands/run.py @@ -1,30 +1,12 @@ """Command to run an agent.""" -import os -import sys -import time -import shutil -import platform -import subprocess -from copy import deepcopy -from typing import Any -from pathlib import Path -from textwrap import dedent -from dataclasses import dataclass - -import docker -import requests import rich_click as click -from docker.errors import NotFound from aea.skills.base import PublicId from aea.configurations.base import PackageType -from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE from auto_dev.base import build_cli -from auto_dev.utils import change_dir, map_os_to_env_vars, load_autonolas_yaml -from auto_dev.constants import DOCKERCOMPOSE_TEMPLATE_FOLDER -from auto_dev.exceptions import UserInputError -from auto_dev.cli_executor import CommandExecutor +from auto_dev.utils import load_autonolas_yaml +from auto_dev.services.runner.runner import AgentRunner TENDERMINT_RESET_TIMEOUT = 10 @@ -34,277 +16,6 @@ cli = build_cli() -@dataclass -class AgentRunner: - """Class to manage running an agent.""" - - agent_name: PublicId - verbose: bool - force: bool - logger: Any - fetch: bool = False - - def run(self) -> None: - """Run the agent.""" - agent_path = "." if not self.fetch else self.agent_name.name - if self.fetch: - self.fetch_agent() - if not self.check_agent_exists(locally=True, in_packages=False): - self.logger.error(f"Local agent package {self.agent_name.name} does not exist.") - sys.exit(1) - self.logger.info(f"Changing to directory: {agent_path}") - with change_dir(agent_path): - self.check_tendermint() - self.setup_agent() - self.execute_agent() - self.stop_tendermint() - - def check_agent_exists(self, locally=False, in_packages=True) -> bool: - """Check if the agent exists.""" - - if locally and in_packages: - msg = "Cannot check both locally and in packages." - raise UserInputError(msg) - if locally: - return self._is_locally_fetched() or self.is_in_agent_dir() - if in_packages: - return self._is_in_packages() - return False - - def _is_locally_fetched(self): - return Path(self.agent_name.name).exists() - - def is_in_agent_dir(self): - """Check if the agent is in the agent directory.""" - return Path(DEFAULT_AEA_CONFIG_FILE).exists() - - def _is_in_packages(self): - return Path(self.agent_package_path).exists() - - @property - def agent_package_path(self): - """Get the agent package path.""" - return Path("packages") / self.agent_name.author / "agents" / self.agent_name.name - - @property - def agent_dir(self) -> Path: - """Get the agent directory based on where it is found.""" - if self.is_in_agent_dir(): - return Path() - if self._is_locally_fetched(): - return Path(self.agent_name.name) - if self._is_in_packages(): - return self.agent_package_path - msg = f"Agent not found. {self.agent_name} not found in local packages or agent directory." - raise UserInputError(msg) - - def stop_tendermint(self) -> None: - """Stop Tendermint.""" - self.execute_command(f"docker compose -f {DOCKERCOMPOSE_TEMPLATE_FOLDER}/tendermint.yaml kill") - self.execute_command(f"docker compose -f {DOCKERCOMPOSE_TEMPLATE_FOLDER}/tendermint.yaml down") - self.logger.info("Tendermint stopped. 🛑") - - def check_tendermint(self, retries: int = 0) -> None: - """Check if Tendermint is running.""" - self.logger.info("Checking Tendermint status...") - docker_engine = docker.from_env() - os_name = platform.system() - container_name = "tm_0" - tm_overrides = map_os_to_env_vars(os_name) - try: - self.logger.debug(f"Looking for Tendermint container: {container_name}") - res = docker_engine.containers.get(container_name) - self.logger.info(f"Found Tendermint container with status: {res.status}") - if res.status == "exited": - res.remove() - time.sleep(0.2) - self.check_tendermint(retries + 1) - if res.status == "running": - self.attempt_hard_reset() - except (subprocess.CalledProcessError, RuntimeError, NotFound) as e: - self.logger.info(f"Tendermint container not found or error: {e}") - if retries > 3: - self.logger.exception(f"Tendermint is not running. Please install and run Tendermint using Docker. {e}") - sys.exit(1) - self.logger.info("Starting Tendermint... 🚀") - self.start_tendermint(tm_overrides) - time.sleep(2) - return self.check_tendermint(retries + 1) - if res.status != "running": - self.logger.error("Tendermint is not healthy. Please check the logs.") - sys.exit(1) - - self.logger.info("Tendermint is running and healthy ✅") - return None - - def attempt_hard_reset(self, attempts: int = 0) -> None: - """Attempt to hard reset Tendermint.""" - if attempts >= TENDERMINT_RESET_RETRIES: - self.logger.error(f"Failed to reset Tendermint after {TENDERMINT_RESET_RETRIES} attempts.") - sys.exit(1) - - self.logger.info("Tendermint is running, executing hard reset...") - try: - response = requests.get(TENDERMINT_RESET_ENDPOINT, timeout=TENDERMINT_RESET_TIMEOUT) - if response.status_code == 200: - self.logger.info("Tendermint hard reset successful.") - return - except requests.RequestException as e: - self.logger.info(f"Failed to execute hard reset: {e}") - - self.logger.info(f"Tendermint not ready (attempt {attempts + 1}/{TENDERMINT_RESET_RETRIES}), waiting...") - time.sleep(1) - self.attempt_hard_reset(attempts + 1) - - def fetch_agent(self) -> None: - """Fetch the agent from registry if needed.""" - self.logger.info(f"Fetching agent {self.agent_name} from the local package registry...") - - if self.check_agent_exists(locally=True, in_packages=False): - if not self.force: - self.logger.error(f"Agent `{self.agent_name}` already exists. Use --force to overwrite.") - sys.exit(1) - self.logger.info(f"Removing existing agent `{self.agent_name}` due to --force option.") - self.execute_command(f"rm -rf {self.agent_name.name}") - - command = f"aea -s fetch {self.agent_name} --local" - if not self.execute_command(command): - self.logger.error(f"Failed to fetch agent {self.agent_name}.") - sys.exit(1) - - def setup_agent(self) -> None: - """Setup the agent.""" - if not self.fetch: - self.logger.info(f"Agent author: {self.agent_name.author}") - self.logger.info(f"Agent name: {self.agent_name.name}") - - self.logger.info("Setting up agent keys...") - self.manage_keys() - - self.logger.info("Installing dependencies...") - self.install_dependencies() - - self.logger.info("Setting up certificates...") - self.issue_certificates() - self.logger.info("Agent setup complete. 🎉") - - def manage_keys( - self, - generate_keys: bool = True, - ) -> None: - """Manage keys based on the agent's default ledger configuration.""" - config = load_autonolas_yaml(PackageType.AGENT)[0] - required_ledgers = config["required_ledgers"] - if not required_ledgers: - self.logger.error("No ledgers found in the agent configuration.") - sys.exit(1) - for ledger in required_ledgers: - self.logger.info(f"Processing ledger: {ledger}") - self.setup_ledger_key(ledger, generate_keys) - - def setup_ledger_key(self, ledger: str, generate_keys, existing_key_file: Path | None = None) -> None: - """Setup the agent with the ledger key.""" - key_file = Path(f"{ledger}_private_key.txt") - commands_to_errors = [] - if existing_key_file: - self.logger.info(f"Copying existing key file {existing_key_file} to {key_file}") - shutil.copy(existing_key_file, key_file) - if key_file.exists(): - self.logger.error(f"Key file {key_file} already exists.") - else: - if generate_keys: - self.logger.info(f"Generating key for {ledger}...") - commands_to_errors.append([f"aea -s generate-key {ledger}", f"Key generation failed for {ledger}"]) - commands_to_errors.append([f"aea -s add-key {ledger}", f"Key addition failed for {ledger}"]) - - for command, error in commands_to_errors: - result = self.execute_command(command) - if not result: - self.logger.error(error) - self.logger.info(f"{ledger} key setup complete ✅") - - def install_dependencies(self) -> None: - """Install agent dependencies.""" - self.execute_command("aea -s install") - - def issue_certificates(self) -> None: - """Issue certificates for agent if needed.""" - if not Path("../certs").exists(): - self.execute_command("aea -s issue-certificates") - else: - self.execute_command("cp -r ../certs ./") - - def start_tendermint(self, env_vars=None) -> None: - """Start Tendermint.""" - self.logger.info("Starting Tendermint with docker-compose...") - try: - result = self.execute_command( - f"docker compose -f {DOCKERCOMPOSE_TEMPLATE_FOLDER}/tendermint.yaml up -d --force-recreate", - env_vars=env_vars, - ) - if not result: - msg = "Docker compose command failed to start Tendermint" - raise RuntimeError(msg) - self.logger.info("Tendermint started successfully") - except FileNotFoundError: - self.logger.exception("Docker compose file not found. Please ensure Tendermint configuration exists.") - sys.exit(1) - except docker.errors.DockerException as e: - self.logger.exception( - f"Docker error: {e!s}. Please ensure Docker is running and you have necessary permissions." - ) - sys.exit(1) - except Exception as e: - self.logger.exception(f"Failed to start Tendermint: {e!s}") - - msg = dedent(""" - Please check that: - 1. Docker is installed and running - 2. Docker compose is installed - 3. You have necessary permissions to run Docker commands - 4. The Tendermint configuration file exists and is valid - """) - self.logger.exception(msg) - sys.exit(1) - - def execute_agent( - self, - ) -> None: - """Execute the agent. - - args: background (bool): Run the agent in the background. - """ - self.logger.info("Starting agent execution...") - try: - result = self.execute_command("aea -s run", verbose=True) - if result: - self.logger.info("Agent execution completed successfully. 😎") - else: - self.logger.error("Agent execution failed.") - sys.exit(1) - except RuntimeError as error: - self.logger.exception(f"Agent ended with error: {error}") - self.logger.info("Agent execution complete. 😎") - - def execute_command(self, command: str, verbose=None, env_vars=None) -> None: - """Execute a shell command.""" - current_vars = deepcopy(os.environ) - if env_vars: - current_vars.update(env_vars) - cli_executor = CommandExecutor(command=command.split(" ")) - result = cli_executor.execute(stream=True, verbose=verbose, env_vars=current_vars) - if not result: - self.logger.error(f"Command failed: {command}") - self.logger.error(f"Error: {cli_executor.stderr}") - msg = f"Command failed: {command}" - raise RuntimeError(msg) - return result - - def get_version(self) -> str: - """Get the version of the agent.""" - agent_config = load_autonolas_yaml(PackageType.AGENT, self.agent_dir)[0] - return agent_config["version"] - - @cli.command() @click.argument( "agent_public_id", diff --git a/auto_dev/services/runner/__init__.py b/auto_dev/services/runner/__init__.py new file mode 100644 index 00000000..d6a16e20 --- /dev/null +++ b/auto_dev/services/runner/__init__.py @@ -0,0 +1 @@ +"""Runner service package.""" diff --git a/auto_dev/services/runner/runner.py b/auto_dev/services/runner/runner.py new file mode 100644 index 00000000..2f76806f --- /dev/null +++ b/auto_dev/services/runner/runner.py @@ -0,0 +1,301 @@ +"""Runner service to manage agent execution.""" + +import os +import sys +import time +import shutil +import platform +import subprocess +from copy import deepcopy +from typing import Any +from pathlib import Path +from textwrap import dedent +from dataclasses import dataclass + +import docker +import requests +from docker.errors import NotFound +from aea.skills.base import PublicId +from aea.configurations.base import PackageType +from aea.configurations.constants import DEFAULT_AEA_CONFIG_FILE + +from auto_dev.utils import change_dir, map_os_to_env_vars, load_autonolas_yaml +from auto_dev.constants import DOCKERCOMPOSE_TEMPLATE_FOLDER +from auto_dev.exceptions import UserInputError +from auto_dev.cli_executor import CommandExecutor + + +TENDERMINT_RESET_TIMEOUT = 10 +TENDERMINT_RESET_ENDPOINT = "http://localhost:8080/hard_reset" +TENDERMINT_RESET_RETRIES = 20 + + +@dataclass +class AgentRunner: + """Class to manage running an agent.""" + + agent_name: PublicId + verbose: bool + force: bool + logger: Any + fetch: bool = False + + def run(self) -> None: + """Run the agent.""" + agent_path = "." if not self.fetch else self.agent_name.name + if self.fetch: + self.fetch_agent() + if not self.check_agent_exists(locally=True, in_packages=False): + self.logger.error(f"Local agent package {self.agent_name.name} does not exist.") + sys.exit(1) + self.logger.info(f"Changing to directory: {agent_path}") + with change_dir(agent_path): + self.check_tendermint() + self.setup_agent() + self.execute_agent() + self.stop_tendermint() + + def check_agent_exists(self, locally=False, in_packages=True) -> bool: + """Check if the agent exists.""" + + if locally and in_packages: + msg = "Cannot check both locally and in packages." + raise UserInputError(msg) + if locally: + return self._is_locally_fetched() or self.is_in_agent_dir() + if in_packages: + return self._is_in_packages() + return False + + def _is_locally_fetched(self): + return Path(self.agent_name.name).exists() + + def is_in_agent_dir(self): + """Check if the agent is in the agent directory.""" + return Path(DEFAULT_AEA_CONFIG_FILE).exists() + + def _is_in_packages(self): + return Path(self.agent_package_path).exists() + + @property + def agent_package_path(self): + """Get the agent package path.""" + return Path("packages") / self.agent_name.author / "agents" / self.agent_name.name + + @property + def agent_dir(self) -> Path: + """Get the agent directory based on where it is found.""" + if self.is_in_agent_dir(): + return Path() + if self._is_locally_fetched(): + return Path(self.agent_name.name) + if self._is_in_packages(): + return self.agent_package_path + msg = f"Agent not found. {self.agent_name} not found in local packages or agent directory." + raise UserInputError(msg) + + def stop_tendermint(self) -> None: + """Stop Tendermint.""" + self.execute_command(f"docker compose -f {DOCKERCOMPOSE_TEMPLATE_FOLDER}/tendermint.yaml kill") + self.execute_command(f"docker compose -f {DOCKERCOMPOSE_TEMPLATE_FOLDER}/tendermint.yaml down") + self.logger.info("Tendermint stopped. 🛑") + + def check_tendermint(self, retries: int = 0) -> None: + """Check if Tendermint is running.""" + self.logger.info("Checking Tendermint status...") + docker_engine = docker.from_env() + os_name = platform.system() + container_name = "tm_0" + tm_overrides = map_os_to_env_vars(os_name) + try: + self.logger.debug(f"Looking for Tendermint container: {container_name}") + res = docker_engine.containers.get(container_name) + self.logger.info(f"Found Tendermint container with status: {res.status}") + if res.status == "exited": + res.remove() + time.sleep(0.2) + self.check_tendermint(retries + 1) + if res.status == "running": + self.attempt_hard_reset() + except (subprocess.CalledProcessError, RuntimeError, NotFound) as e: + self.logger.info(f"Tendermint container not found or error: {e}") + if retries > 3: + self.logger.exception(f"Tendermint is not running. Please install and run Tendermint using Docker. {e}") + sys.exit(1) + self.logger.info("Starting Tendermint... 🚀") + self.start_tendermint(tm_overrides) + time.sleep(2) + return self.check_tendermint(retries + 1) + if res.status != "running": + self.logger.error("Tendermint is not healthy. Please check the logs.") + sys.exit(1) + + self.logger.info("Tendermint is running and healthy ✅") + return None + + def attempt_hard_reset(self, attempts: int = 0) -> None: + """Attempt to hard reset Tendermint.""" + if attempts >= TENDERMINT_RESET_RETRIES: + self.logger.error(f"Failed to reset Tendermint after {TENDERMINT_RESET_RETRIES} attempts.") + sys.exit(1) + + self.logger.info("Tendermint is running, executing hard reset...") + try: + response = requests.get(TENDERMINT_RESET_ENDPOINT, timeout=TENDERMINT_RESET_TIMEOUT) + if response.status_code == 200: + self.logger.info("Tendermint hard reset successful.") + return + except requests.RequestException as e: + self.logger.info(f"Failed to execute hard reset: {e}") + + self.logger.info(f"Tendermint not ready (attempt {attempts + 1}/{TENDERMINT_RESET_RETRIES}), waiting...") + time.sleep(1) + self.attempt_hard_reset(attempts + 1) + + def fetch_agent(self) -> None: + """Fetch the agent from registry if needed.""" + self.logger.info(f"Fetching agent {self.agent_name} from the local package registry...") + + if self.check_agent_exists(locally=True, in_packages=False): + if not self.force: + self.logger.error(f"Agent `{self.agent_name}` already exists. Use --force to overwrite.") + sys.exit(1) + self.logger.info(f"Removing existing agent `{self.agent_name}` due to --force option.") + self.execute_command(f"rm -rf {self.agent_name.name}") + + command = f"aea -s fetch {self.agent_name} --local" + if not self.execute_command(command): + self.logger.error(f"Failed to fetch agent {self.agent_name}.") + sys.exit(1) + + def setup_agent(self) -> None: + """Setup the agent.""" + if not self.fetch: + self.logger.info(f"Agent author: {self.agent_name.author}") + self.logger.info(f"Agent name: {self.agent_name.name}") + + self.logger.info("Setting up agent keys...") + self.manage_keys() + + self.logger.info("Installing dependencies...") + self.install_dependencies() + + self.logger.info("Setting up certificates...") + self.issue_certificates() + self.logger.info("Agent setup complete. 🎉") + + def manage_keys( + self, + generate_keys: bool = True, + ) -> None: + """Manage keys based on the agent's default ledger configuration.""" + config = load_autonolas_yaml(PackageType.AGENT)[0] + required_ledgers = config["required_ledgers"] + if not required_ledgers: + self.logger.error("No ledgers found in the agent configuration.") + sys.exit(1) + for ledger in required_ledgers: + self.logger.info(f"Processing ledger: {ledger}") + self.setup_ledger_key(ledger, generate_keys) + + def setup_ledger_key(self, ledger: str, generate_keys, existing_key_file: Path | None = None) -> None: + """Setup the agent with the ledger key.""" + key_file = Path(f"{ledger}_private_key.txt") + commands_to_errors = [] + if existing_key_file: + self.logger.info(f"Copying existing key file {existing_key_file} to {key_file}") + shutil.copy(existing_key_file, key_file) + if key_file.exists(): + self.logger.error(f"Key file {key_file} already exists.") + else: + if generate_keys: + self.logger.info(f"Generating key for {ledger}...") + commands_to_errors.append([f"aea -s generate-key {ledger}", f"Key generation failed for {ledger}"]) + commands_to_errors.append([f"aea -s add-key {ledger}", f"Key addition failed for {ledger}"]) + + for command, error in commands_to_errors: + result = self.execute_command(command) + if not result: + self.logger.error(error) + self.logger.info(f"{ledger} key setup complete ✅") + + def install_dependencies(self) -> None: + """Install agent dependencies.""" + self.execute_command("aea -s install") + + def issue_certificates(self) -> None: + """Issue certificates for agent if needed.""" + if not Path("../certs").exists(): + self.execute_command("aea -s issue-certificates") + else: + self.execute_command("cp -r ../certs ./") + + def start_tendermint(self, env_vars=None) -> None: + """Start Tendermint.""" + self.logger.info("Starting Tendermint with docker-compose...") + try: + result = self.execute_command( + f"docker compose -f {DOCKERCOMPOSE_TEMPLATE_FOLDER}/tendermint.yaml up -d --force-recreate", + env_vars=env_vars, + ) + if not result: + msg = "Docker compose command failed to start Tendermint" + raise RuntimeError(msg) + self.logger.info("Tendermint started successfully") + except FileNotFoundError: + self.logger.exception("Docker compose file not found. Please ensure Tendermint configuration exists.") + sys.exit(1) + except docker.errors.DockerException as e: + self.logger.exception( + f"Docker error: {e!s}. Please ensure Docker is running and you have necessary permissions." + ) + sys.exit(1) + except Exception as e: + self.logger.exception(f"Failed to start Tendermint: {e!s}") + + msg = dedent(""" + Please check that: + 1. Docker is installed and running + 2. Docker compose is installed + 3. You have necessary permissions to run Docker commands + 4. The Tendermint configuration file exists and is valid + """) + self.logger.exception(msg) + sys.exit(1) + + def execute_agent( + self, + ) -> None: + """Execute the agent. + - args: background (bool): Run the agent in the background. + """ + self.logger.info("Starting agent execution...") + try: + result = self.execute_command("aea -s run", verbose=True) + if result: + self.logger.info("Agent execution completed successfully. 😎") + else: + self.logger.error("Agent execution failed.") + sys.exit(1) + except RuntimeError as error: + self.logger.exception(f"Agent ended with error: {error}") + self.logger.info("Agent execution complete. 😎") + + def execute_command(self, command: str, verbose=None, env_vars=None) -> None: + """Execute a shell command.""" + current_vars = deepcopy(os.environ) + if env_vars: + current_vars.update(env_vars) + cli_executor = CommandExecutor(command=command.split(" ")) + result = cli_executor.execute(stream=True, verbose=verbose, env_vars=current_vars) + if not result: + self.logger.error(f"Command failed: {command}") + self.logger.error(f"Error: {cli_executor.stderr}") + msg = f"Command failed: {command}" + raise RuntimeError(msg) + return result + + def get_version(self) -> str: + """Get the version of the agent.""" + agent_config = load_autonolas_yaml(PackageType.AGENT, self.agent_dir)[0] + return agent_config["version"] From 6e66f2cc9f46e978b0fd5958b1df51fa13b9bd22 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 13:06:57 +0000 Subject: [PATCH 02/10] refactor:moved-to-new-service --- auto_dev/commands/run.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/auto_dev/commands/run.py b/auto_dev/commands/run.py index 33f6ce89..a7db629a 100644 --- a/auto_dev/commands/run.py +++ b/auto_dev/commands/run.py @@ -16,7 +16,12 @@ cli = build_cli() -@cli.command() +@cli.group() +def run() -> None: + """Command group for running agents either in development mode or in production mode.""" + + +@run.command() @click.argument( "agent_public_id", type=PublicId.from_str, @@ -26,13 +31,14 @@ @click.option("--force", is_flag=True, help="Force overwrite of existing agent", default=False) @click.option("--fetch/--no-fetch", help="Fetch from registry or use local agent package", default=True) @click.pass_context -def run(ctx, agent_public_id: PublicId, verbose: bool, force: bool, fetch: bool) -> None: +def dev(ctx, agent_public_id: PublicId, verbose: bool, force: bool, fetch: bool) -> None: """Run an agent from the local packages registry or a local path. Example usage: - adev run eightballer/my_agent # Fetch and run from registry - adev run eightballer/my_agent --no-fetch # Run local agent package named my_agent + adev run dev eightballer/my_agent # Fetch and run from registry + adev run dev eightballer/my_agent --no-fetch # Run local agent package named my_agent """ + if not agent_public_id: # We set fetch to false if the agent is not provided, as we assume the user wants to run the agent locally. fetch = False From 4c5484e403d21d586e32a1569e3c403c55fa0829 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 13:16:10 +0000 Subject: [PATCH 03/10] refactor:implemented-base-class --- auto_dev/commands/convert.py | 4 ++-- auto_dev/commands/publish.py | 9 +++------ auto_dev/commands/run.py | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/auto_dev/commands/convert.py b/auto_dev/commands/convert.py index 15dde7b7..621b67a2 100644 --- a/auto_dev/commands/convert.py +++ b/auto_dev/commands/convert.py @@ -13,7 +13,7 @@ from auto_dev.constants import DEFAULT_ENCODING from auto_dev.exceptions import UserInputError from auto_dev.scaffolder import BasePackageScaffolder -from auto_dev.commands.run import AgentRunner +from auto_dev.services.runner.runner import DevAgentRunner JINJA_SUFFIX = ".jinja" @@ -42,7 +42,7 @@ def __init__(self, agent_public_id: PublicId, service_public_id: PublicId): self.service_public_id = ( PublicId.from_str(service_public_id) if isinstance(service_public_id, str) else service_public_id ) - self.agent_runner = AgentRunner(self.agent_public_id, verbose=True, force=True, logger=logger) + self.agent_runner = DevAgentRunner(self.agent_public_id, verbose=True, force=True, logger=logger) self.validate() self._post_init() diff --git a/auto_dev/commands/publish.py b/auto_dev/commands/publish.py index dea9e9f1..0a7fdc42 100644 --- a/auto_dev/commands/publish.py +++ b/auto_dev/commands/publish.py @@ -1,13 +1,12 @@ """This module contains the logic for the publish command.""" - import rich_click as click from aea.configurations.base import PublicId from auto_dev.base import build_cli from auto_dev.constants import AGENT_PUBLISHED_SUCCESS_MSG from auto_dev.exceptions import OperationError -from auto_dev.commands.run import AgentRunner +from auto_dev.commands.run import DevAgentRunner from auto_dev.services.package_manager.index import PackageManager @@ -46,7 +45,7 @@ def publish(ctx, public_id: PublicId = None, force: bool = False) -> None: logger = ctx.obj["LOGGER"] try: - agent_runner = AgentRunner( + agent_runner = DevAgentRunner( agent_name=public_id, logger=logger, verbose=verbose, @@ -54,9 +53,7 @@ def publish(ctx, public_id: PublicId = None, force: bool = False) -> None: ) if not agent_runner.is_in_agent_dir(): msg = "Not in an agent directory (aea-config.yaml not found) Please enter the agent directory to publish" - raise OperationError( - msg - ) + raise OperationError(msg) package_manager = PackageManager(verbose=verbose) package_manager.publish_agent(force=force, new_public_id=public_id) click.secho(AGENT_PUBLISHED_SUCCESS_MSG, fg="green") diff --git a/auto_dev/commands/run.py b/auto_dev/commands/run.py index a7db629a..49d45691 100644 --- a/auto_dev/commands/run.py +++ b/auto_dev/commands/run.py @@ -6,7 +6,7 @@ from auto_dev.base import build_cli from auto_dev.utils import load_autonolas_yaml -from auto_dev.services.runner.runner import AgentRunner +from auto_dev.services.runner.runner import DevAgentRunner TENDERMINT_RESET_TIMEOUT = 10 @@ -46,7 +46,7 @@ def dev(ctx, agent_public_id: PublicId, verbose: bool, force: bool, fetch: bool) agent_public_id = PublicId.from_json(agent_config) logger = ctx.obj["LOGGER"] - runner = AgentRunner( + runner = DevAgentRunner( agent_name=agent_public_id, verbose=verbose, force=force, From 340e40ccfbfd24a39cbeedc6a5a050df7ad2f4fd Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 13:36:12 +0000 Subject: [PATCH 04/10] refactor:implemented-simple-tests --- auto_dev/commands/run.py | 26 +++++++++++++++++++++++++- tests/test_run.py | 21 +++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/test_run.py diff --git a/auto_dev/commands/run.py b/auto_dev/commands/run.py index 49d45691..0925d1f4 100644 --- a/auto_dev/commands/run.py +++ b/auto_dev/commands/run.py @@ -6,7 +6,7 @@ from auto_dev.base import build_cli from auto_dev.utils import load_autonolas_yaml -from auto_dev.services.runner.runner import DevAgentRunner +from auto_dev.services.runner import DevAgentRunner, ProdAgentRunner TENDERMINT_RESET_TIMEOUT = 10 @@ -57,5 +57,29 @@ def dev(ctx, agent_public_id: PublicId, verbose: bool, force: bool, fetch: bool) logger.info("Agent run complete. 😎") +@run.command() +@click.argument( + "service_public_id", + type=PublicId.from_str, + required=False, +) +@click.option("-v", "--verbose", is_flag=True, help="Verbose mode.", default=False) +@click.pass_context +def prod(ctx, service_public_id: PublicId, verbose: bool): + """Run an agent in production mode. + + Example usage: + adev run prod eightballer/my_service + """ + logger = ctx.obj["LOGGER"] + runner = ProdAgentRunner( + agent_name=service_public_id, + verbose=verbose, + logger=logger, + ) + runner.run() + logger.info("Agent run complete. 😎") + + if __name__ == "__main__": cli() # pylint: disable=no-value-for-parameter diff --git a/tests/test_run.py b/tests/test_run.py new file mode 100644 index 00000000..15864bff --- /dev/null +++ b/tests/test_run.py @@ -0,0 +1,21 @@ +"""Tests for the run command.""" + +import pytest +from click.testing import CliRunner + +from auto_dev.commands.run import dev, prod + + +@pytest.mark.parametrize( + "group", + [ + (dev,), + (prod,), + ], +) +def test_executes_help(group): + """Test that the help group is executed.""" + runner = CliRunner() + result = runner.invoke(group, ["--help"]) + assert result.exit_code == 0 + assert "Usage" in result.output From c65d3a07b34218c5c81124c3d782455a4f598789 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 19:33:00 +0000 Subject: [PATCH 05/10] feat:service-attempts-to-start --- auto_dev/commands/deps.py | 29 ++++++++++++++++++++++------- auto_dev/commands/run.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 48 insertions(+), 9 deletions(-) diff --git a/auto_dev/commands/deps.py b/auto_dev/commands/deps.py index 11e99410..63356c4a 100644 --- a/auto_dev/commands/deps.py +++ b/auto_dev/commands/deps.py @@ -493,8 +493,9 @@ class VersionSetLoader: autonomy_dependencies: AutonomyDependencies poetry_dependencies: PoetryDependencies - def __init__(self, config_file: Path = DEFAULT_ADEV_CONFIG_FILE): + def __init__(self, config_file: Path = DEFAULT_ADEV_CONFIG_FILE, **kwargs): self.config_file = config_file + self.latest = kwargs.get("latest", True) def write_config( self, @@ -552,16 +553,17 @@ def handle_output(issues, changes) -> None: sys.exit(1) -def get_update_command(poetry_dependencies: Dependency) -> str: +def get_update_command(poetry_dependencies: Dependency, strict: bool = False) -> str: """Get the update command.""" issues = [] cmd = "poetry add " + pre_fix = "==" if strict else "<=" for dependency in track(poetry_dependencies): click.echo(f" Verifying: {dependency.name}") raw = toml.load("pyproject.toml")["tool"]["poetry"]["dependencies"] current_version = str(raw[dependency.name]) - expected_version = f"=={dependency.get_latest_version()[1:]}" + expected_version = f"'{pre_fix}{dependency.get_latest_version()[1:]}'" if current_version.find(expected_version) == -1: issues.append( f"Update the poetry version of {dependency.name} from `{current_version}` to `{expected_version}`\n" @@ -584,10 +586,24 @@ def get_update_command(poetry_dependencies: Dependency) -> str: help="Auto approve the changes.", is_flag=True, ) +@click.option( + "--latest", + default=True, + help="Select the latest version releases.", + is_flag=True, +) +@click.option( + "--strict/--no-strict", + default=False, + help="Enforce strict versioning.", + is_flag=True, +) @click.pass_context -def verify( +def bump( ctx: click.Context, auto_approve: bool = False, + latest: bool = True, + strict: bool = False, ) -> None: """Verify the packages.json file. @@ -608,10 +624,9 @@ def verify( changes = [] click.echo("Verifying autonomy dependencies... 📝") - version_set_loader = VersionSetLoader() + version_set_loader = VersionSetLoader(latest=latest) version_set_loader.load_config() if (Path("packages") / "packages.json").exists(): - version_set_loader.load_config() for dependency in track(version_set_loader.autonomy_dependencies.upstream_dependency): click.echo(f" Verifying: {dependency.name}") remote_packages = dependency.get_all_autonomy_packages() @@ -630,7 +645,7 @@ def verify( changes.append(dependency.name) click.echo("Verifying poetry dependencies... 📝") - cmd, poetry_issues = get_update_command(version_set_loader.poetry_dependencies.poetry_dependencies) + cmd, poetry_issues = get_update_command(version_set_loader.poetry_dependencies.poetry_dependencies, strict=strict) issues.extend(poetry_issues) if issues: diff --git a/auto_dev/commands/run.py b/auto_dev/commands/run.py index 0925d1f4..d0572e7c 100644 --- a/auto_dev/commands/run.py +++ b/auto_dev/commands/run.py @@ -1,5 +1,8 @@ """Command to run an agent.""" +import sys +from pathlib import Path + import rich_click as click from aea.skills.base import PublicId from aea.configurations.base import PackageType @@ -64,18 +67,39 @@ def dev(ctx, agent_public_id: PublicId, verbose: bool, force: bool, fetch: bool) required=False, ) @click.option("-v", "--verbose", is_flag=True, help="Verbose mode.", default=False) +@click.option("--force/--no-force", is_flag=True, help="Force overwrite of existing service", default=False) +@click.option("--fetch/--no-fetch", help="Fetch from registry or use local service package", default=True) +@click.option("--keysfile", help="Path to the private keys file.", type=click.File(), default="keys.json") +@click.option("--number_of_agents", "-n", help="Number of agents to run.", type=int, default=1) @click.pass_context -def prod(ctx, service_public_id: PublicId, verbose: bool): +def prod( + ctx, + service_public_id: PublicId, + verbose: bool, + force: bool, + fetch: bool, + keysfile: click.File, + number_of_agents: int, +) -> None: """Run an agent in production mode. Example usage: adev run prod eightballer/my_service """ + logger = ctx.obj["LOGGER"] + if not Path(keysfile.name).exists(): + logger.error(f"Keys file not found at {keysfile.name}") + sys.exit(1) + runner = ProdAgentRunner( - agent_name=service_public_id, + service_public_id=service_public_id, verbose=verbose, logger=logger, + force=force, + fetch=fetch, + keysfile=Path(keysfile.name).absolute(), + number_of_agents=number_of_agents, ) runner.run() logger.info("Agent run complete. 😎") From c50e80770c3fdd09877911d47dea9a6a3234205d Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 19:33:40 +0000 Subject: [PATCH 06/10] feat/moved-to-services --- .../repo/templates/autonomy/adev_config.yaml | 3 +- .../templates/services/service.yaml.jinja | 3 +- auto_dev/services/runner/__init__.py | 7 + auto_dev/services/runner/base.py | 22 ++ auto_dev/services/runner/prod_runner.py | 253 ++++++++++++++++++ auto_dev/services/runner/runner.py | 13 +- scripts/demos/run_prod.sh | 27 ++ 7 files changed, 319 insertions(+), 9 deletions(-) create mode 100644 auto_dev/services/runner/base.py create mode 100644 auto_dev/services/runner/prod_runner.py create mode 100755 scripts/demos/run_prod.sh diff --git a/auto_dev/data/repo/templates/autonomy/adev_config.yaml b/auto_dev/data/repo/templates/autonomy/adev_config.yaml index e5f01766..81e2837d 100644 --- a/auto_dev/data/repo/templates/autonomy/adev_config.yaml +++ b/auto_dev/data/repo/templates/autonomy/adev_config.yaml @@ -39,4 +39,5 @@ poetry_dependencies: version: 0.2.90 location: remote url: https://api.github.com/repos/8ball030/auto_dev - extras: all \ No newline at end of file + extras: + - all \ No newline at end of file diff --git a/auto_dev/data/templates/services/service.yaml.jinja b/auto_dev/data/templates/services/service.yaml.jinja index ec940619..2b31dfd5 100644 --- a/auto_dev/data/templates/services/service.yaml.jinja +++ b/auto_dev/data/templates/services/service.yaml.jinja @@ -12,8 +12,7 @@ agent: {{ agent_public_id}} number_of_agents: {{ number_of_agents }} deployment: agent: - ports: - 0: {} + ports: {} volumes: {} {% if overrides %}--- {{overrides }} diff --git a/auto_dev/services/runner/__init__.py b/auto_dev/services/runner/__init__.py index d6a16e20..d97328f6 100644 --- a/auto_dev/services/runner/__init__.py +++ b/auto_dev/services/runner/__init__.py @@ -1 +1,8 @@ """Runner service package.""" + +from .runner import DevAgentRunner +from .prod_runner import ProdAgentRunner + + +DevAgentRunner = DevAgentRunner +ProdAgentRunner = ProdAgentRunner diff --git a/auto_dev/services/runner/base.py b/auto_dev/services/runner/base.py new file mode 100644 index 00000000..5e7d7c93 --- /dev/null +++ b/auto_dev/services/runner/base.py @@ -0,0 +1,22 @@ +"""Abstract base class for agent runners.""" + +from abc import ABC, abstractmethod + + +class AgentRunner(ABC): + """Class to manage running an agent.""" + + @abstractmethod + def run(self) -> None: + """Run the agent.""" + raise NotImplementedError + + @abstractmethod + def check_exists(self) -> bool: + """Check if the agent exists.""" + raise NotImplementedError + + @abstractmethod + def setup(self) -> None: + """Setup the agent.""" + raise NotImplementedError diff --git a/auto_dev/services/runner/prod_runner.py b/auto_dev/services/runner/prod_runner.py new file mode 100644 index 00000000..fe28a1e2 --- /dev/null +++ b/auto_dev/services/runner/prod_runner.py @@ -0,0 +1,253 @@ +"""Prod runner for running an agent.""" + +import os +import sys +import json +from copy import deepcopy +from typing import Any +from pathlib import Path +from contextlib import contextmanager, redirect_stdout +from dataclasses import dataclass + +import rich +from aea.skills.base import PublicId +from aea.cli.push_all import push_all_packages +from aea.configurations.base import PackageType +from aea.cli.registry.settings import REGISTRY_REMOTE +from aea.configurations.constants import PACKAGES, SERVICES, DEFAULT_SERVICE_CONFIG_FILE +from autonomy.configurations.base import PACKAGE_TYPE_TO_CONFIG_CLASS + +from auto_dev.utils import change_dir, load_autonolas_yaml +from auto_dev.exceptions import UserInputError +from auto_dev.cli_executor import CommandExecutor +from auto_dev.services.runner.base import AgentRunner + + +TENDERMINT_RESET_TIMEOUT = 10 +TENDERMINT_RESET_ENDPOINT = "http://localhost:8080/hard_reset" +TENDERMINT_RESET_RETRIES = 20 + + +@contextmanager +def with_spinner(): + """Context manager to show spinner.""" + console = rich.get_console() + with console.status("Please wait...", spinner="aesthetic"): + yield + + +@dataclass +class ProdAgentRunner(AgentRunner): + """Class to manage running an agent.""" + + service_public_id: PublicId + verbose: bool + force: bool + logger: Any + fetch: bool = False + keysfile: Path = "keys.json" + number_of_agents: int = 1 + + def run(self) -> None: + """Run the agent.""" + service_path = "." if not self.fetch else self.service_public_id.name + if self.fetch: + self.fetch_package() + if not self.check_exists(locally=True, in_packages=False): + self.logger.error(f"Local service package {self.service_public_id} does not exist.") + sys.exit(1) + + self.push_all_packages() + self.logger.debug(f"Changing to directory: {service_path}") + with change_dir(service_path): + self.setup() + self.execute_agent() + + def check_exists(self, locally=False, in_packages=True) -> bool: + """Check if the agent exists.""" + + if locally and in_packages: + msg = "Cannot check both locally and in packages." + raise UserInputError(msg) + if locally: + return self._is_locally_fetched() or self.is_in_service_dir() + if in_packages: + return self._is_in_packages() + return False + + def _is_locally_fetched(self): + return Path(self.service_public_id.name).exists() + + def is_in_service_dir(self): + """Check if the agent is in the agent directory.""" + return Path(DEFAULT_SERVICE_CONFIG_FILE).exists() + + def _is_in_packages(self): + return Path(self.service_package_path).exists() + + @property + def service_package_path(self): + """Get the agent package path.""" + return Path(PACKAGES) / self.service_public_id.author / SERVICES / self.service_public_id.name + + @property + def service_dir(self) -> Path: + """Get the agent directory based on where it is found.""" + if self.is_in_service_dir(): + return Path() + if self._is_locally_fetched(): + return Path(self.service_public_id.name) + if self._is_in_packages(): + return self.service_package_path + msg = f"Service not found. {self.service_public_id} not found in local packages or agent directory." + raise UserInputError(msg) + + def fetch_package(self) -> None: + """Fetch the package from registry if needed.""" + self.logger.info(f"Fetching service {self.service_public_id} from the local package registry...") + + if self.check_exists(locally=True, in_packages=False): + if not self.force: + self.logger.error(f"Agent `{self.service_public_id}` already exists. Use --force to overwrite.") + sys.exit(1) + self.logger.info(f"Removing existing service`{self.service_public_id}` due to --force option.") + self.execute_command(f"rm -rf {self.service_public_id.name}") + + command = f"autonomy -s fetch {self.service_public_id} --local --service" + if not self.execute_command(command): + self.logger.error(f"Failed to fetch agent {self.service_public_id}.") + sys.exit(1) + + def is_service_consensus(self) -> bool: + """Process the service and determine if it requires consensus, i.e. tendermint.""" + _config, *_overrides = load_autonolas_yaml(PackageType.SERVICE, self.service_dir) + + def validate(self) -> None: + """Validate the provided service to determine whether it can be built. + + Returns + ------- + None + Raises: + UserInputError: If the service is not valid. + + """ + + if not self.check_exists(locally=True, in_packages=False): + self.logger.error(f"Service {self.service_public_id} not found.") + sys.exit(1) + _service, *_overrides = load_autonolas_yaml(PackageType.SERVICE, self.service_dir) + # We need to check if the keyfile has the required keys for the service + if not self.keysfile.exists(): + self.logger.error(f"Keys file {self.keysfile} not found.") + sys.exit(1) + if not self.keysfile.is_file(): + self.logger.error(f"Keys file {self.keysfile} is not a file.") + sys.exit(1) + + available_keys = json.loads(self.keysfile.read_text()) + if len(available_keys) < 1: + self.logger.error(f"Keys file {self.keysfile} does not contain any keys.") + sys.exit(1) + if len(available_keys) != self.number_of_agents: + msg = ( + f"Number of keys in {self.keysfile} : {len(available_keys)} " + + f"does not match the number of agents: {self.number_of_agents}" + ) + self.logger.error(msg) + sys.exit(1) + + # We now know we need to set the ALL_PARTICIPANTS key in the service configuration if it is present. + + def setup(self) -> None: + """Setup the agent.""" + if not self.fetch: + self.logger.info(f"Service author: {self.service_public_id.author}") + self.logger.info(f"Service name: {self.service_public_id.name}") + + self.logger.info("Setting up the agent service...") + self.validate() + self.build_images() + self.manage_keys() + self.build_deployment() + self.logger.info("Agent Service setup complete. 🎉") + + def build_images(self) -> None: + """Build the deployment.""" + self.logger.info("Building the docker images...") + self.execute_command("autonomy build-image --extra-dependency hypothesis", spinner=True) + self.logger.info("Deployment images built successfully. 🎉") + + def push_all_packages(self) -> None: + """Push all packages to the registry.""" + self.logger.info("Pushing all packages to the registry...") + # We silence all output from click + with open(os.devnull, "w", encoding="utf-8") as f, redirect_stdout(f): + push_all_packages(REGISTRY_REMOTE, retries=3, package_type_config_class=PACKAGE_TYPE_TO_CONFIG_CLASS) + self.logger.info("All packages pushed successfully. 🎉") + + def build_deployment(self) -> None: + """Build the deployment.""" + self.logger.info("Building the deployment...") + env_vars = self.generate_env_vars() + self.execute_command( + f"autonomy deploy build {self.keysfile} --o abci_build", + env_vars=env_vars, + ) + self.logger.info("Deployment built successfully. 🎉") + + def manage_keys( + self, + ) -> None: + """Manage keys based on the services default ledger configuration.""" + with open(self.keysfile, encoding="utf-8") as file: + self.all_participants = [f["address"] for f in json.load(file)] + + def generate_env_vars(self) -> dict: + """Generate the environment variables for the deployment.""" + return { + "ALL_PARTICIPANTS": json.dumps(self.all_participants), + } + + def execute_agent( + self, + ) -> None: + """Execute the agent. + - args: background (bool): Run the agent in the background. + """ + self.logger.info("Starting agent execution...") + try: + result = self.execute_command( + "docker compose -f abci_build/docker-compose.yaml up -d --force-recreate --remove-orphans", verbose=True + ) + if result: + self.logger.info("Agent execution completed successfully. 😎") + else: + self.logger.error("Agent execution failed.") + sys.exit(1) + except RuntimeError as error: + self.logger.exception(f"Agent ended with error: {error}") + self.logger.info("Agent execution complete. 😎") + + def execute_command(self, command: str, verbose=None, env_vars=None, spinner=False) -> None: + """Execute a shell command.""" + current_vars = deepcopy(os.environ) + if env_vars: + current_vars.update(env_vars) + cli_executor = CommandExecutor(command=command.split(" ")) + if spinner: + with with_spinner(): + result = cli_executor.execute(stream=True, verbose=verbose, env_vars=current_vars) + else: + result = cli_executor.execute(stream=True, verbose=verbose, env_vars=current_vars) + if not result: + self.logger.error(f"Command failed: {command}") + self.logger.error(f"Error: {cli_executor.stderr}") + msg = f"Command failed: {command}" + raise RuntimeError(msg) + return result + + def get_version(self) -> str: + """Get the version of the agent.""" + agent_config = load_autonolas_yaml(PackageType.SERVICE, self.service_dir)[0] + return agent_config["version"] diff --git a/auto_dev/services/runner/runner.py b/auto_dev/services/runner/runner.py index 2f76806f..8fd023f5 100644 --- a/auto_dev/services/runner/runner.py +++ b/auto_dev/services/runner/runner.py @@ -23,6 +23,7 @@ from auto_dev.constants import DOCKERCOMPOSE_TEMPLATE_FOLDER from auto_dev.exceptions import UserInputError from auto_dev.cli_executor import CommandExecutor +from auto_dev.services.runner.base import AgentRunner TENDERMINT_RESET_TIMEOUT = 10 @@ -31,7 +32,7 @@ @dataclass -class AgentRunner: +class DevAgentRunner(AgentRunner): """Class to manage running an agent.""" agent_name: PublicId @@ -45,17 +46,17 @@ def run(self) -> None: agent_path = "." if not self.fetch else self.agent_name.name if self.fetch: self.fetch_agent() - if not self.check_agent_exists(locally=True, in_packages=False): + if not self.check_exists(locally=True, in_packages=False): self.logger.error(f"Local agent package {self.agent_name.name} does not exist.") sys.exit(1) self.logger.info(f"Changing to directory: {agent_path}") with change_dir(agent_path): self.check_tendermint() - self.setup_agent() + self.setup() self.execute_agent() self.stop_tendermint() - def check_agent_exists(self, locally=False, in_packages=True) -> bool: + def check_exists(self, locally=False, in_packages=True) -> bool: """Check if the agent exists.""" if locally and in_packages: @@ -156,7 +157,7 @@ def fetch_agent(self) -> None: """Fetch the agent from registry if needed.""" self.logger.info(f"Fetching agent {self.agent_name} from the local package registry...") - if self.check_agent_exists(locally=True, in_packages=False): + if self.check_exists(locally=True, in_packages=False): if not self.force: self.logger.error(f"Agent `{self.agent_name}` already exists. Use --force to overwrite.") sys.exit(1) @@ -168,7 +169,7 @@ def fetch_agent(self) -> None: self.logger.error(f"Failed to fetch agent {self.agent_name}.") sys.exit(1) - def setup_agent(self) -> None: + def setup(self) -> None: """Setup the agent.""" if not self.fetch: self.logger.info(f"Agent author: {self.agent_name.author}") diff --git a/scripts/demos/run_prod.sh b/scripts/demos/run_prod.sh new file mode 100755 index 00000000..a2127099 --- /dev/null +++ b/scripts/demos/run_prod.sh @@ -0,0 +1,27 @@ +# We use gum to create a pretty demo for the cli tool. +# https://github.com/charmbracelet/gum?tab=readme-ov-file +# This is the source of the demo within the readme. + +#! /bin/env bash + +set -euo pipefail + +source scripts/demos/base.sh + +export SLEEP_TIME=0 +sleep $SLEEP_TIME + +gum format """ +we have the agent service in the local packages directory. +Lets run it in production mode. +""" +sleep $SLEEP_TIME +call_and_wait "tree -L 3 packages/author" $SLEEP_TIME + +call_and_wait "adev run prod author/$AGENT_NAME" 4 + + + + + + From 0e206879e0b2d2f46f5e5806483ac6820715ddc2 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Mon, 27 Jan 2025 19:54:03 +0000 Subject: [PATCH 07/10] feat/bumped-no-strict --- auto_dev/commands/deps.py | 22 ++++++++++++++++++++++ poetry.lock | 20 ++++++++++---------- pyproject.toml | 14 +++++++------- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/auto_dev/commands/deps.py b/auto_dev/commands/deps.py index 63356c4a..b38aa33f 100644 --- a/auto_dev/commands/deps.py +++ b/auto_dev/commands/deps.py @@ -37,6 +37,8 @@ from pathlib import Path from dataclasses import dataclass +import pip +import pkg_resources import toml import yaml import requests @@ -231,6 +233,10 @@ def get_latest_version(self) -> str: if self.location == DependencyLocation.LOCAL: return self.version return self._get_latest_remote_version() + + def get_installed_version(self) -> str: + """Use pip to get the installed version.""" + return pkg_resources.get_distribution(self.name).version def _get_latest_remote_version(self) -> str: """Get the latest remote version.""" @@ -578,6 +584,19 @@ def get_update_command(poetry_dependencies: Dependency, strict: bool = False) -> cmd += f"{plugin}@{expected_version} " return cmd, issues +def update_pyproject_with_current_versions(version_set_loader: VersionSetLoader) -> None: + """ + We collect the actual versions of everything installed + by iteracting over the version set loader + we then write this to the adev config file. + """ + for dependency in version_set_loader.poetry_dependencies.poetry_dependencies: + + current_version = dependency.get_installed_version() + dependency.version = current_version + click.echo("Updating the adev config file with the current versions.") + version_set_loader.write_config() + @deps.command() @click.option( @@ -656,6 +675,9 @@ def bump( os.system(cmd) # noqa changes.append("poetry dependencies") + + update_pyproject_with_current_versions(version_set_loader) + handle_output(issues, changes) diff --git a/poetry.lock b/poetry.lock index 546d435b..39d4ba2f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2827,13 +2827,13 @@ pytest = ">=7.0.0,<7.3.0" [[package]] name = "open-aea-ledger-cosmos" -version = "1.60.0" +version = "1.61.0" description = "Python package wrapping the public and private key cryptography and ledger api of Cosmos." optional = false python-versions = "*" files = [ - {file = "open_aea_ledger_cosmos-1.60.0-py3-none-any.whl", hash = "sha256:53a1c8a04a12a9e18a03a7ecc1988efef6e3712cf6408ae4c46c18d8d66fba31"}, - {file = "open_aea_ledger_cosmos-1.60.0.tar.gz", hash = "sha256:df48bf695de632b768baf968e6de66e27d7350af2edfac715a5635389d891b75"}, + {file = "open_aea_ledger_cosmos-1.61.0-py3-none-any.whl", hash = "sha256:c7568509bfdc290081693e03dcbdbdec6682ed005fd022219f22ac1303852510"}, + {file = "open_aea_ledger_cosmos-1.61.0.tar.gz", hash = "sha256:dd9d0a63f07371e7a81ee2b76ba68110a8f6ad442829e0fd8119fc37b10a6ad8"}, ] [package.dependencies] @@ -2845,13 +2845,13 @@ pycryptodome = ">=3.10.1,<4.0.0" [[package]] name = "open-aea-ledger-ethereum" -version = "1.60.0" +version = "1.61.0" description = "Python package wrapping the public and private key cryptography and ledger api of Ethereum." optional = false python-versions = "*" files = [ - {file = "open_aea_ledger_ethereum-1.60.0-py3-none-any.whl", hash = "sha256:0026ae8faa987f651234d3d125998a445b414a65847a121fd033f49b99de0bf0"}, - {file = "open_aea_ledger_ethereum-1.60.0.tar.gz", hash = "sha256:932219edff66500f29f17ab47d487f2995e4770456031cfbacfc0eea94571c6c"}, + {file = "open_aea_ledger_ethereum-1.61.0-py3-none-any.whl", hash = "sha256:aae960b982d3868b9ee6b182247deb76c071f08ca412ff644d7ef3bc92d1660d"}, + {file = "open_aea_ledger_ethereum-1.61.0.tar.gz", hash = "sha256:cfe8de152244de11b69a2c82cec02c9b0b06ce1dd55fa6ae863f6abcd1ac8d66"}, ] [package.dependencies] @@ -2862,13 +2862,13 @@ web3 = ">=6.0.0,<7" [[package]] name = "open-aea-ledger-solana" -version = "1.60.0" +version = "1.61.0" description = "Python package wrapping the public and private key cryptography and ledger api of solana." optional = false python-versions = "*" files = [ - {file = "open_aea_ledger_solana-1.60.0-py3-none-any.whl", hash = "sha256:60cc967879e6044bd9c6b33e0dc4ee0199c46c32f4ee2d8de36d39920896be9a"}, - {file = "open_aea_ledger_solana-1.60.0.tar.gz", hash = "sha256:d47688bbba4a4ff06ec0d6f4e3b7d56b21dc03270933e5b1c3a50340a97fac6f"}, + {file = "open_aea_ledger_solana-1.61.0-py3-none-any.whl", hash = "sha256:07067ae5da33c8dbc5819f99a4125b425d25f8192f14395317a09f95d9e2a8fe"}, + {file = "open_aea_ledger_solana-1.61.0.tar.gz", hash = "sha256:2f11f471326921c8fa49ad783fe472cb2dae55d3ab78e8fceeb1a36a89486782"}, ] [package.dependencies] @@ -5035,4 +5035,4 @@ doc = ["mkdocstrings"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.12" -content-hash = "3f287072c5b7e0720da6175fbc5cbf79928f2d8c2e8fadc88a8f51611fe1b909" +content-hash = "c37364641159a3f69c6dbe31fd5d3cbaa6e831274935d26ce26ec7043c2df6e7" diff --git a/pyproject.toml b/pyproject.toml index a37f26d9..0ed272cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,13 +36,13 @@ exclude = "/(\n \\.eggs\n | \\.git\n | \\.hg\n | \\.mypy_cache\n | \\.tox [tool.poetry.dependencies] python = ">=3.9,<3.12" -open-autonomy = "==0.18.4" -open-aea = "==1.60.0" -open-aea-test-autonomy = "==0.18.4" -open-aea-ledger-ethereum = "==1.60.0" -open-aea-ledger-solana = "==1.60.0" -open-aea-ledger-cosmos = "==1.60.0" -open-aea-cli-ipfs = "==1.60.0" +open-autonomy = "<=0.18.4" +open-aea = "<=1.61.0" +open-aea-test-autonomy = "<=0.18.4" +open-aea-ledger-ethereum = "<=1.61.0" +open-aea-ledger-solana = "<=1.61.0" +open-aea-ledger-cosmos = "<=1.61.0" +open-aea-cli-ipfs = "<=1.61.0" web3 = ">=6.0.0,<7.0.0" ipfshttpclient = "==0.8.0a2" typing_extensions = ">=3.10.0.2" From 687151324c6b2b1df0b031adb980d7b87c547069 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Tue, 28 Jan 2025 12:51:35 +0000 Subject: [PATCH 08/10] feat:expanded-allowed-versions --- .env.template | 1 + 1 file changed, 1 insertion(+) diff --git a/.env.template b/.env.template index cae0b8e8..d2d37774 100644 --- a/.env.template +++ b/.env.template @@ -1,2 +1,3 @@ export PYTHONWARNINGS="ignore" export PYTHONPATH=. +export COVERAGE_WARNINGS="ignore" From a70d5d366395e65bdd60ea67cabbc64fe1287655 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Tue, 28 Jan 2025 12:53:44 +0000 Subject: [PATCH 09/10] test:added-ignore-warnings --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0ed272cf..7cc21e40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,4 +111,4 @@ adev = "auto_dev.cli:cli" [tool.pytest.ini_options] -addopts = "--cov=auto_dev --cov-report html" \ No newline at end of file +addopts = "--cov=auto_dev --cov-report html -i" \ No newline at end of file From b781122b0c6dbbdf815f31b96709e8c528a28412 Mon Sep 17 00:00:00 2001 From: 8ball030 <8baller@station.codes> Date: Tue, 28 Jan 2025 21:29:22 +0000 Subject: [PATCH 10/10] feat:addedfilter-warnings --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7cc21e40..eaf39fff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,4 +111,5 @@ adev = "auto_dev.cli:cli" [tool.pytest.ini_options] -addopts = "--cov=auto_dev --cov-report html -i" \ No newline at end of file +addopts = "--cov=auto_dev --cov-report html" +filterwarnings = ["ignore:.*CoverageWarning.*:Warning"] \ No newline at end of file