diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 4d48e1f1ed..f6673d3a1e 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -299,7 +299,7 @@ jobs: pip install tox sudo apt-get install -y protobuf-compiler - name: Run all tests - run: tox -e py3.7-cov -- --ignore=tests/test_docs --ignore=tests/test_examples --ignore=tests/test_packages/test_contracts --ignore=tests/test_packages/test_skills -m 'not unstable' + run: tox -e py3.7-cov -- --ignore=tests/test_docs --ignore=tests/test_examples --ignore=tests/test_packages/test_contracts --ignore=tests/test_packages/test_skills_integration -m 'not unstable' continue-on-error: true - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/HISTORY.md b/HISTORY.md index ac78542d04..4bc703799b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,25 @@ # Release History +## 0.6.3 (2020-10-16) + +- Adds skill testing tools and documentation +- Adds human readable log output regarding configuration for p2p_libp2p connection +- Adds support to install PyPI dependencies from AEABuilder and MultiAgentManager +- Adds CLI upgrade command to upgrade entire agent project and components +- Extends CLI remove command to include option to remove dependencies +- Extends SOEF chain identifier support +- Adds CLI transfer command to transfer wealth +- Adds integration tests for skills generic buyer and seller using skill testing tool +- Adds validation of component configurations when setting componenet configuration overrides +- Multiple refactoring of internal configuration and helper objects and methods +- Fix a bug on CLI push local with latest rather than version specifier +- Adds readmes in all agent projects +- Adds agent name in logger paths of runnable objects +- Fixes tac skills to work with and without erc1155 contract +- Adds additional validations on message flow +- Multiple docs updates based on user feedback +- Multiple additional tests and test stability fixes + ## 0.6.2 (2020-10-01) - Adds MultiAgentManager to manage multiple agent projects programmatically diff --git a/Makefile b/Makefile index 6ffffecb1a..054e05cfd4 100644 --- a/Makefile +++ b/Makefile @@ -78,7 +78,7 @@ common_checks: security misc_checks lint static docs .PHONY: test test: - pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections tests/ --cov-report=html --cov-report=xml --cov-report=term-missing --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections --cov-config=.coveragerc + pytest -rfE --doctest-modules aea packages/fetchai/protocols packages/fetchai/connections packages/fetchai/skills/generic_buyer tests/ --cov-report=html --cov-report=xml --cov-report=term-missing --cov-report=term --cov=aea --cov=packages/fetchai/protocols --cov=packages/fetchai/connections --cov=packages/fetchai/skills/generic_buyer --cov-config=.coveragerc find . -name ".coverage*" -not -name ".coveragerc" -exec rm -fr "{}" \; .PHONY: test-sub diff --git a/aea/__version__.py b/aea/__version__.py index 5a9f575037..035180108f 100644 --- a/aea/__version__.py +++ b/aea/__version__.py @@ -22,7 +22,7 @@ __title__ = "aea" __description__ = "Autonomous Economic Agent framework" __url__ = "https://github.com/fetchai/agents-aea.git" -__version__ = "0.6.2" +__version__ = "0.6.3" __author__ = "Fetch.AI Limited" __license__ = "Apache-2.0" __copyright__ = "2019 Fetch.AI Limited" diff --git a/aea/aea.py b/aea/aea.py index be8265b06e..529e86f2ab 100644 --- a/aea/aea.py +++ b/aea/aea.py @@ -19,8 +19,8 @@ """This module contains the implementation of an autonomous economic agent (AEA).""" import datetime -import logging from asyncio import AbstractEventLoop +from logging import Logger from multiprocessing.pool import AsyncResult from typing import ( Any, @@ -48,7 +48,7 @@ ) from aea.exceptions import AEAException from aea.helpers.exception_policy import ExceptionPolicyEnum -from aea.helpers.logging import AgentLoggerAdapter, WithLogger +from aea.helpers.logging import AgentLoggerAdapter, get_logger from aea.identity.base import Identity from aea.mail.base import Envelope from aea.protocols.base import Message @@ -60,7 +60,7 @@ from aea.skills.error.handlers import ErrorHandler -class AEA(Agent, WithLogger): +class AEA(Agent): """This class implements an autonomous economic agent.""" RUN_LOOPS: Dict[str, Type[BaseAgentLoop]] = { @@ -99,7 +99,7 @@ def __init__( :param resources: the resources (protocols and skills) of the agent. :param loop: the event loop to run the connections. :param period: period to call agent's act - :param exeution_timeout: amount of time to limit single act/handle to execute. + :param execution_timeout: amount of time to limit single act/handle to execute. :param max_reactions: the processing rate of envelopes per tick (i.e. single loop). :param decision_maker_handler_class: the class implementing the decision maker handler to be used. :param skill_exception_policy: the skill exception policy enum @@ -117,6 +117,10 @@ def __init__( self._skills_exception_policy = skill_exception_policy self._connection_exception_policy = connection_exception_policy + aea_logger = AgentLoggerAdapter( + logger=get_logger(__name__, identity.name), agent_name=identity.name, + ) + super().__init__( identity=identity, connections=[], @@ -124,13 +128,9 @@ def __init__( period=period, loop_mode=loop_mode, runtime_mode=runtime_mode, + logger=cast(Logger, aea_logger), ) - aea_logger = AgentLoggerAdapter( - logger=logging.getLogger(__name__), agent_name=identity.name - ) - WithLogger.__init__(self, logger=cast(logging.Logger, aea_logger)) - self.max_reactions = max_reactions decision_maker_handler = decision_maker_handler_class( identity=identity, wallet=wallet @@ -388,7 +388,7 @@ def teardown(self) -> None: :return: None """ - self.logger.debug("[{}]: Calling teardown method...".format(self.name)) + self.logger.debug("Calling teardown method...") self.resources.teardown() def get_task_result(self, task_id: int) -> AsyncResult: diff --git a/aea/aea_builder.py b/aea/aea_builder.py index 29cf5b0169..40ab4c20fe 100644 --- a/aea/aea_builder.py +++ b/aea/aea_builder.py @@ -17,6 +17,7 @@ # # ------------------------------------------------------------------------------ + """This module contains utilities for building an AEA.""" import itertools @@ -24,22 +25,10 @@ import logging.config import os import pprint -from collections import defaultdict, deque +from collections import defaultdict from copy import copy, deepcopy from pathlib import Path -from typing import ( - Any, - Collection, - Deque, - Dict, - List, - Optional, - Set, - Tuple, - Type, - Union, - cast, -) +from typing import Any, Collection, Dict, List, Optional, Set, Tuple, Type, Union, cast import jsonschema from packaging.specifiers import SpecifierSet @@ -77,16 +66,18 @@ DecisionMakerHandler as DefaultDecisionMakerHandler, ) from aea.exceptions import AEAException -from aea.helpers.base import load_module +from aea.helpers.base import find_topological_order, load_env_file, load_module from aea.helpers.exception_policy import ExceptionPolicyEnum -from aea.helpers.logging import AgentLoggerAdapter +from aea.helpers.install_dependency import install_dependency +from aea.helpers.logging import AgentLoggerAdapter, WithLogger, get_logger from aea.identity.base import Identity from aea.registries.resources import Resources PathLike = Union[os.PathLike, Path, str] -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) +DEFAULT_ENV_DOTFILE = ".env" class _DependenciesManager: @@ -231,8 +222,13 @@ def pypi_dependencies(self) -> Dependencies: ) return all_pypi_dependencies + def install_dependencies(self) -> None: + """Install extra dependencies for components.""" + for name, d in self.pypi_dependencies.items(): + install_dependency(name, d) + -class AEABuilder: +class AEABuilder(WithLogger): # pylint: disable=too-many-public-methods """ This class helps to build an AEA. @@ -302,6 +298,7 @@ def __init__(self, with_default_packages: bool = True): :param with_default_packages: add the default packages. """ + WithLogger.__init__(self, logger=_default_logger) self._with_default_packages = with_default_packages self._reset(is_full_reset=True) @@ -422,7 +419,7 @@ def set_decision_maker_handler( _class = getattr(module, class_name) self._decision_maker_handler_class = _class except Exception as e: # pragma: nocover - logger.error( + self.logger.error( "Could not locate decision maker handler for dotted path '{}', class name '{}' and file path '{}'. Error message: {}".format( dotted_path, class_name, file_path, e ) @@ -852,6 +849,10 @@ def _process_connection_ids( return sorted_selected_connections_ids + def install_pypi_dependencies(self) -> None: + """Install components extra dependecies.""" + self._package_dependency_manager.install_dependencies() + def build(self, connection_ids: Optional[Collection[PublicId]] = None,) -> AEA: """ Build the AEA. @@ -868,11 +869,11 @@ def build(self, connection_ids: Optional[Collection[PublicId]] = None,) -> AEA: :raises ValueError: if we cannot """ self._check_we_can_build() - resources = Resources() wallet = Wallet( copy(self.private_key_paths), copy(self.connection_private_key_paths) ) identity = self._build_identity_from_wallet(wallet) + resources = Resources(identity.name) self._load_and_add_components(ComponentType.PROTOCOL, resources, identity.name) self._load_and_add_components(ComponentType.CONTRACT, resources, identity.name) self._load_and_add_components( @@ -1082,10 +1083,10 @@ def _check_pypi_dependencies(self, configuration: ComponentConfiguration): all_pypi_dependencies, configuration.pypi_dependencies ) for pkg_name, dep_info in all_pypi_dependencies.items(): - set_specifier = SpecifierSet(dep_info.get("version", "")) + set_specifier = SpecifierSet(dep_info.version) if not is_satisfiable(set_specifier): raise AEAException( - f"Conflict on package {pkg_name}: specifier set '{dep_info['version']}' not satisfiable." + f"Conflict on package {pkg_name}: specifier set '{dep_info.version}' not satisfiable." ) @staticmethod @@ -1266,12 +1267,10 @@ def _find_import_order( - detect if there are cycles - import skills from the leaves of the dependency graph, by finding a topological ordering. """ - # the adjacency list for the dependency graph - depends_on: Dict[ComponentId, Set[ComponentId]] = defaultdict(set) # the adjacency list for the inverse dependency graph - supports: Dict[ComponentId, Set[ComponentId]] = defaultdict(set) - # nodes with no incoming edges - roots = copy(skill_ids) + dependency_to_supported_dependencies: Dict[ + ComponentId, Set[ComponentId] + ] = defaultdict(set) for skill_id in skill_ids: component_path = self._find_component_directory_from_component_id( aea_project_path, skill_id @@ -1283,31 +1282,16 @@ def _find_import_order( ), ) - if len(configuration.skills) != 0: - roots.remove(skill_id) - - depends_on[skill_id].update( - [ - ComponentId(ComponentType.SKILL, skill) - for skill in configuration.skills - ] - ) + if skill_id not in dependency_to_supported_dependencies: + dependency_to_supported_dependencies[skill_id] = set() for dependency in configuration.skills: - supports[ComponentId(ComponentType.SKILL, dependency)].add(skill_id) - - # find topological order (Kahn's algorithm) - queue: Deque[ComponentId] = deque() - order = [] - queue.extend(roots) - while len(queue) > 0: - current = queue.pop() - order.append(current) - for node in supports[current]: # pragma: nocover - depends_on[node].discard(current) - if len(depends_on[node]) == 0: - queue.append(node) - - if any(len(edges) > 0 for edges in depends_on.values()): + dependency_to_supported_dependencies[ + ComponentId(ComponentType.SKILL, dependency) + ].add(skill_id) + + try: + order = find_topological_order(dependency_to_supported_dependencies) + except ValueError: raise AEAException("Cannot load skills, there is a cyclic dependency.") return order @@ -1337,6 +1321,8 @@ def from_aea_project( ) builder = AEABuilder(with_default_packages=False) + load_env_file(str(aea_project_path / DEFAULT_ENV_DOTFILE)) + # load agent configuration file configuration_file = cls.get_configuration_file_path(aea_project_path) agent_configuration = cls.loader.load(configuration_file.open()) @@ -1401,14 +1387,14 @@ def _load_and_add_components( component = self._component_instances[component_type][configuration] if configuration.component_type != ComponentType.SKILL: component.logger = cast( - logging.Logger, make_logger(configuration, agent_name) + logging.Logger, make_component_logger(configuration, agent_name) ) else: new_configuration = self._overwrite_custom_configuration(configuration) if new_configuration.is_abstract_component: load_aea_package(configuration) continue - _logger = make_logger(new_configuration, agent_name) + _logger = make_component_logger(new_configuration, agent_name) component = load_component_from_config( new_configuration, logger=_logger, **kwargs ) @@ -1442,7 +1428,7 @@ def _overwrite_custom_configuration(self, configuration: ComponentConfiguration) return new_configuration -def make_logger( +def make_component_logger( configuration: ComponentConfiguration, agent_name: str, ) -> Optional[logging.Logger]: """ @@ -1456,5 +1442,5 @@ def make_logger( # skip because skill object already have their own logger from the skill context. return None logger_name = f"aea.packages.{configuration.author}.{configuration.component_type.to_plural()}.{configuration.name}" - _logger = AgentLoggerAdapter(logging.getLogger(logger_name), agent_name) + _logger = AgentLoggerAdapter(get_logger(logger_name, agent_name), agent_name) return cast(logging.Logger, _logger) diff --git a/aea/agent.py b/aea/agent.py index ac15fa6cfb..f3b66a2d7e 100644 --- a/aea/agent.py +++ b/aea/agent.py @@ -21,21 +21,23 @@ import datetime import logging from asyncio import AbstractEventLoop +from logging import Logger from typing import Any, Callable, Dict, List, Optional, Tuple, Type from aea.abstract_agent import AbstractAgent from aea.connections.base import Connection from aea.exceptions import AEAException +from aea.helpers.logging import WithLogger from aea.identity.base import Identity from aea.mail.base import Envelope from aea.multiplexer import InBox, OutBox from aea.runtime import AsyncRuntime, BaseRuntime, RuntimeStates, ThreadedRuntime -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) -class Agent(AbstractAgent): +class Agent(AbstractAgent, WithLogger): """This class provides an abstract base class for a generic agent.""" RUNTIMES: Dict[str, Type[BaseRuntime]] = { @@ -52,6 +54,7 @@ def __init__( period: float = 1.0, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, + logger: Logger = _default_logger, ) -> None: """ Instantiate the agent. @@ -65,6 +68,7 @@ def __init__( :return: None """ + WithLogger.__init__(self, logger=logger) self._connections = connections self._identity = identity self._period = period @@ -197,7 +201,7 @@ def start(self) -> None: if was_started: self.runtime.wait_completed(sync=True) - else: + else: #  pragma: nocover raise AEAException("Failed to start runtime! Was it already started?") def stop(self) -> None: @@ -253,7 +257,7 @@ def exception_handler( :return: bool, propagate exception if True otherwise skip it. """ - logger.exception( + self.logger.exception( f"Exception {repr(exception)} raised during {repr(function)} call." ) return True diff --git a/aea/agent_loop.py b/aea/agent_loop.py index 2285f02451..6d3fd506ca 100644 --- a/aea/agent_loop.py +++ b/aea/agent_loop.py @@ -21,7 +21,6 @@ """This module contains the implementation of an agent loop using asyncio.""" import asyncio import datetime -import logging from abc import ABC, abstractmethod from asyncio import CancelledError from asyncio.events import AbstractEventLoop @@ -40,10 +39,7 @@ Runnable, ) from aea.helpers.exec_timeout import ExecTimeoutThreadGuard, TimeoutException -from aea.helpers.logging import WithLogger - - -logger = logging.getLogger(__name__) +from aea.helpers.logging import WithLogger, get_logger class AgentLoopException(AEAException): @@ -75,6 +71,7 @@ def __init__( :params agent: Agent or AEA to run. :params loop: optional asyncio event loop. if not specified a new loop will be created. """ + logger = get_logger(__name__, agent.name) WithLogger.__init__(self, logger) Runnable.__init__(self, loop=loop, threaded=threaded) @@ -123,7 +120,7 @@ async def _stop(self) -> None: with suppress(BaseException): await t self._state.set(AgentLoopStates.stopped) - logger.debug("agent loop stopped") + self.logger.debug("agent loop stopped") async def _gather_tasks(self) -> None: """Wait till first task exception.""" diff --git a/aea/cli/add.py b/aea/cli/add.py index ae3c86133c..9cd30a0861 100644 --- a/aea/cli/add.py +++ b/aea/cli/add.py @@ -32,6 +32,7 @@ copy_package_directory, find_item_in_distribution, find_item_locally, + get_item_id_present, get_package_path, is_fingerprint_correct, is_item_present, @@ -102,9 +103,10 @@ def add_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: ) ) if is_item_present(ctx, item_type, item_public_id): + present_item_id = get_item_id_present(ctx, item_type, item_public_id) raise click.ClickException( - "A {} with id '{}/{}' already exists. Aborting...".format( - item_type, item_public_id.author, item_public_id.name + "A {} with id '{}' already exists. Aborting...".format( + item_type, present_item_id ) ) @@ -112,11 +114,13 @@ def add_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: is_local = ctx.config.get("is_local") ctx.clean_paths.append(dest_path) - if is_local_item(item_public_id): + + is_distributed_item = is_local_item(item_public_id) + if is_local and is_distributed_item: source_path = find_item_in_distribution(ctx, item_type, item_public_id) package_path = copy_package_directory(source_path, dest_path) - elif is_local: - source_path = find_item_locally(ctx, item_type, item_public_id) + elif is_local and not is_distributed_item: + source_path, _ = find_item_locally(ctx, item_type, item_public_id) package_path = copy_package_directory(source_path, dest_path) else: package_path = fetch_package( @@ -129,6 +133,7 @@ def add_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: _add_item_deps(ctx, item_type, item_config) register_item(ctx, item_type, item_config.public_id) + click.echo(f"Successfully added {item_type} '{item_config.public_id}'.") def _add_item_deps(ctx: Context, item_type: str, item_config) -> None: diff --git a/aea/cli/config.py b/aea/cli/config.py index a896e4eea0..1328e5030f 100644 --- a/aea/cli/config.py +++ b/aea/cli/config.py @@ -16,19 +16,29 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - -"""Implementation of the 'aea list' subcommand.""" - -from typing import Dict, List, cast +"""Implementation of the 'aea config' subcommand.""" +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple, Union import click -from aea.cli.utils.click_utils import AEAJsonPathType +from aea.cli.utils.config import ( + _try_get_configuration_object_from_aea_config, + handle_dotted_path, +) from aea.cli.utils.constants import FALSE_EQUIVALENTS, FROM_STRING_TO_TYPE from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.cli.utils.generic import get_parent_object, load_yaml +from aea.configurations.base import ( + AgentConfig, + ComponentId, + DEFAULT_AEA_CONFIG_FILE, + PackageType, + SkillConfig, +) from aea.configurations.loader import ConfigLoader +from aea.exceptions import AEAException @click.group() @@ -39,11 +49,11 @@ def config(click_context): # pylint: disable=unused-argument @config.command() -@click.argument("JSON_PATH", required=True, type=AEAJsonPathType()) +@click.argument("JSON_PATH", required=True) @pass_ctx -def get(ctx: Context, json_path: List[str]): +def get(ctx: Context, json_path: str): """Get a field.""" - value = _get_config_value(ctx, json_path) + value = ConfigGetSet(ctx, json_path).get() click.echo(value) @@ -54,89 +64,285 @@ def get(ctx: Context, json_path: List[str]): type=click.Choice(["str", "int", "bool", "float"]), help="Specify the type of the value.", ) -@click.argument("JSON_PATH", required=True, type=AEAJsonPathType()) +@click.argument("JSON_PATH", required=True) @click.argument("VALUE", required=True, type=str) @pass_ctx def set_command( ctx: Context, - json_path: List[str], + json_path: str, value: str, type: str, # pylint: disable=redefined-builtin ): """Set a field.""" - _set_config(ctx, json_path, value, type) + ConfigGetSet(ctx, json_path).set(value, type) + +class ConfigGetSet: + """Tool to get/set value in agent config.""" -def _get_config_value(ctx: Context, json_path: List[str]): - config_loader = cast(ConfigLoader, ctx.config.get("configuration_loader")) - configuration_file_path = cast(str, ctx.config.get("configuration_file_path")) + def __init__(self, ctx: Context, dotted_path: str) -> None: + """Init tool. - configuration_object = load_yaml(configuration_file_path) - config_loader.validator.validate(instance=configuration_object) + :param ctx: click context + :param dotted_path: str with attribute path to get/set + """ + self.dotted_path = dotted_path + self.ctx = ctx - parent_object_path = json_path[:-1] - attribute_name = json_path[-1] - parent_object = _get_and_validate_parent_obj( - configuration_object, parent_object_path, attribute_name - ) + ( + self.json_path, + self.path_to_resource_configuration, + self.config_loader, + self.component_id, + ) = self._handle_dotted_path() - return parent_object.get(attribute_name) + def _handle_dotted_path( + self, + ) -> Tuple[List[str], Path, ConfigLoader, Optional[ComponentId]]: + """Handle dotted path.""" + try: + return handle_dotted_path(self.dotted_path, self.agent_config.author) + except AEAException as e: + raise click.ClickException(*e.args) + @property + def parent_obj_path(self) -> List[str]: + """Get the parent object (dotted) path.""" + return self.json_path[:-1] -def _set_config(ctx: Context, json_path: List[str], value: str, type_str: str) -> None: - config_loader = cast(ConfigLoader, ctx.config.get("configuration_loader")) - configuration_file_path = cast(str, ctx.config.get("configuration_file_path")) + @property + def attr_name(self) -> str: + """Attribute name.""" + return self.json_path[-1] - configuration_object = load_yaml(configuration_file_path) - config_loader.validator.validate(instance=configuration_object) + def get(self) -> Union[str, int]: + """Get config value.""" + if self.component_id: + return self._get_component_value() - parent_object_path = json_path[:-1] - attribute_name = json_path[-1] - parent_object = _get_and_validate_parent_obj( - configuration_object, parent_object_path, attribute_name - ) + return self._get_agent_value() - type_ = FROM_STRING_TO_TYPE[type_str] - try: - if type_ != bool: - parent_object[attribute_name] = type_(value) + def _get_agent_value(self) -> Union[str, int]: + """Get config value for agent config.""" + configuration_object = self._load_configuration_object() + return self._get_value_from_configuration_object(configuration_object) + + def _get_component_value(self) -> Union[str, int]: + """Get config value for component section in agent config or component package.""" + configuration_object_from_agent = self._get_configuration_object_from_agent() + try: + if not configuration_object_from_agent: + raise click.ClickException("") + return self._get_value_from_configuration_object( + configuration_object_from_agent + ) + except click.ClickException: + configuration_object = self._load_configuration_object() + return self._get_value_from_configuration_object(configuration_object) + + @property + def is_target_agent(self) -> bool: + """Is target of get/update is agent config.""" + return self.component_id is None + + def _load_configuration_object(self) -> Dict: + """Load configuration object for component/agent.""" + if self.is_target_agent: + configuration_object = self.agent_config.json else: - parent_object[attribute_name] = value not in FALSE_EQUIVALENTS - except ValueError: # pragma: no cover - raise click.ClickException("Cannot convert {} to type {}".format(value, type_)) + configuration_object = load_yaml(str(self.path_to_resource_configuration)) + + self.config_loader.validate(configuration_object) + return configuration_object + + def _get_configuration_object_from_agent( + self, + ) -> Optional[Dict[str, Union[str, int]]]: + """Get component configuration object from agent component configurations.""" + if not self.component_id: # pragma: nocover + raise ValueError("component in not set") - try: - configuration_obj = config_loader.configuration_class.from_json( - configuration_object + return _try_get_configuration_object_from_aea_config( + self.ctx, self.component_id ) - config_loader.validate(configuration_obj.json) - config_loader.dump(configuration_obj, open(configuration_file_path, "w")) - except Exception: - raise click.ClickException("Attribute or value not valid.") - - -def _get_and_validate_parent_obj( - conf_obj: Dict, parent_obj_path: List, attr_name: str -) -> Dict: - """ - Get and validate parent object. - - :param conf_obj: configuration object. - :param parent_obj_path: parent object path. - :param attr_name: attribute name. - - :return: parent object. - :raises: ClickException if attribute is not valid. - """ - try: - parent_obj = get_parent_object(conf_obj, parent_obj_path) - except ValueError as e: - raise click.ClickException(str(e)) - - if attr_name not in parent_obj: - raise click.ClickException("Attribute '{}' not found.".format(attr_name)) - if not isinstance(parent_obj.get(attr_name), (str, int, bool, float)): - raise click.ClickException( - "Attribute '{}' is not of primitive type.".format(attr_name) + + def _get_value_from_configuration_object( + self, conf_obj: Dict[str, Union[str, int]] + ) -> Any: + """Get value from configuration object.""" + return self._get_parent_object(conf_obj).get(self.attr_name) + + def _get_parent_object( + self, conf_obj: Dict[str, Union[str, int]] + ) -> Dict[str, Union[str, int]]: + """ + Get and validate parent object. + + :param conf_obj: configuration object. + + :return: parent object. + :raises: ClickException if attribute is not valid. + """ + parent_obj_path = self.parent_obj_path + attr_name = self.attr_name + try: + parent_obj = get_parent_object(conf_obj, parent_obj_path) + except ValueError as e: + raise click.ClickException(str(e)) + + if attr_name not in parent_obj: + raise click.ClickException("Attribute '{}' not found.".format(attr_name)) + if not isinstance(parent_obj.get(attr_name), (str, int, bool, float)): + raise click.ClickException( + "Attribute '{}' is not of primitive type.".format(attr_name) + ) + return parent_obj + + @property + def agent_config(self) -> AgentConfig: + """Return current context AgentConfig.""" + return self.ctx.agent_config + + def _check_set_field_name(self) -> None: + """ + Check field names on set operation. + + :return: None + + :raises: click.ClickException is field is not allowed to be changeed. + """ + top_level_key = self.json_path[0] + + if self.component_id: + config_class = self.component_id.package_type.configuration_class() + else: + config_class = type(self.agent_config) + + if top_level_key not in config_class.FIELDS_ALLOWED_TO_UPDATE: + raise click.ClickException( + f"Field `{top_level_key}` is not allowed to change!" + ) + if config_class == SkillConfig: + if top_level_key not in SkillConfig.FIELDS_WITH_NESTED_FIELDS: + return # pragma: nocover + if len(self.json_path) < 3: + path = ".".join(self.json_path) + raise click.ClickException(f"Path '{path}' not valid for skill.") + second_level_key = self.json_path[1] + third_level_key = self.json_path[2] + if third_level_key not in SkillConfig.NESTED_FIELDS_ALLOWED_TO_UPDATE: + raise click.ClickException( # pragma: nocover + f"Field `{top_level_key}.{second_level_key}.{third_level_key}` is not allowed to change!" + ) + + def _fix_component_id_version(self) -> None: + """Update self.component_id with actual version defined in agent instead of latest.""" + if not self.component_id: # pragma: nocover: check for mypy + raise ValueError("Component id is not set") + + component_id = None + + for component_id in self.agent_config.package_dependencies: + if ( + component_id.author == self.component_id.author + and component_id.package_type == self.component_id.package_type + and component_id.name == self.component_id.name + ): + break + else: # pragma: nocover # should be always ok, cause component has to be alrady registered + raise ValueError("component is not registered?") + + self.component_id = component_id + + def _parent_object_for_agent_component_configuration( + self, + ) -> Dict[str, Union[str, int]]: + if not self.component_id: # pragma: nocover: check for mypy + raise ValueError("no component specified") + configuration_object = self.agent_config.component_configurations.get( + self.component_id, {} ) - return parent_obj + self.agent_config.component_configurations[ + self.component_id + ] = configuration_object + parent_object = configuration_object + # get or create parent object in component configuration + for i in self.parent_obj_path: + if i not in parent_object: + parent_object[i] = {} + parent_object = parent_object[i] + return parent_object + + def set(self, value: str, type_str: str) -> None: + """ + Set config value. + + :param value: value to set + :param type_str: name of the value type. + + :return None + """ + # do a check across real configuration + self._check_set_field_name() + + configuration_object = self._load_configuration_object() + parent_configuration_object = self._get_parent_object(configuration_object) + + if self.component_id: + # component. parent is component config in agent config + self._fix_component_id_version() + parent_configuration_object = ( + self._parent_object_for_agent_component_configuration() + ) + self._update_object(parent_configuration_object, type_str, value) + agent_configuration_object = self.agent_config.json + else: + # already agent + self._update_object(parent_configuration_object, type_str, value) + agent_configuration_object = configuration_object + self._dump_agent_configuration(agent_configuration_object) + + def _update_object(self, parent_object: Dict, type_str: str, value: str) -> None: + """ + Update dict with value converted to type. + + :param parent_object: dict where value should be updated, + :param: type_str: type name to convert value on update. + :param value: str of the value to set. + + :return: None + """ + type_ = FROM_STRING_TO_TYPE[type_str] + try: + if type_ != bool: + parent_object[self.attr_name] = type_(value) + else: + parent_object[self.attr_name] = value not in FALSE_EQUIVALENTS + except ValueError: # pragma: no cover + raise click.ClickException( + "Cannot convert {} to type {}".format(value, type_) + ) + + @property + def agent_config_loader(self) -> ConfigLoader: + """Return agent config loader.""" + return ConfigLoader.from_configuration_type(PackageType.AGENT) + + @property + def agent_config_file_path(self) -> Path: + """Return agent config file path.""" + return Path(".") / DEFAULT_AEA_CONFIG_FILE + + def _dump_agent_configuration( + self, agent_configuration_object: Dict[str, Union[str, int]] + ) -> None: + """Save agent configuration.""" + try: + configuration_obj = self.agent_config_loader.configuration_class.from_json( + agent_configuration_object + ) + self.agent_config_loader.validate(configuration_obj.json) + with open(self.agent_config_file_path, "w") as file_pointer: + self.agent_config_loader.dump(configuration_obj, file_pointer) + except Exception as e: # pragma: nocover + raise click.ClickException(f"Attribute or value not valid. {e}") diff --git a/aea/cli/core.py b/aea/cli/core.py index 829a63a0d3..53a4fdafed 100644 --- a/aea/cli/core.py +++ b/aea/cli/core.py @@ -18,7 +18,6 @@ # # ------------------------------------------------------------------------------ - """Core definitions for the AEA command-line tool.""" import click @@ -54,6 +53,8 @@ from aea.cli.run import run from aea.cli.scaffold import scaffold from aea.cli.search import search +from aea.cli.transfer import transfer +from aea.cli.upgrade import upgrade from aea.cli.utils.config import get_or_create_cli_config from aea.cli.utils.constants import AUTHOR_KEY from aea.cli.utils.context import Context @@ -65,6 +66,7 @@ @click.version_option(aea.__version__, prog_name="aea") @simple_verbosity_option(logger, default="INFO") @click.option( + "-s", "--skip-consistency-check", "skip_consistency_check", is_flag=True, @@ -143,3 +145,5 @@ def _init_gui() -> None: cli.add_command(run) cli.add_command(scaffold) cli.add_command(search) +cli.add_command(transfer) +cli.add_command(upgrade) diff --git a/aea/cli/freeze.py b/aea/cli/freeze.py index 6dbae8eca0..8e741a2f75 100644 --- a/aea/cli/freeze.py +++ b/aea/cli/freeze.py @@ -50,5 +50,5 @@ def _get_deps(click_context: click.core.Context) -> List[str]: for dependency_name, dependency_data in sorted( ctx.get_dependencies().items(), key=lambda x: x[0] ): - deps.append(dependency_name + dependency_data.get("version", "")) + deps.append(dependency_name + dependency_data.version) return deps diff --git a/aea/cli/generate.py b/aea/cli/generate.py index 2eef2171b5..be3e7df825 100644 --- a/aea/cli/generate.py +++ b/aea/cli/generate.py @@ -104,7 +104,9 @@ def _generate_item(click_context, item_type, specification_path): output_path = os.path.join(ctx.cwd, item_type_plural) protocol_generator = ProtocolGenerator(specification_path, output_path) - protocol_generator.generate() + warning_message = protocol_generator.generate() + if warning_message is not None: + click.echo(warning_message) # Add the item to the configurations logger.debug( diff --git a/aea/cli/generate_wealth.py b/aea/cli/generate_wealth.py index c8d346a4a0..d1c2f1beb2 100644 --- a/aea/cli/generate_wealth.py +++ b/aea/cli/generate_wealth.py @@ -19,16 +19,15 @@ """Implementation of the 'aea generate_wealth' subcommand.""" -from typing import Dict, Optional, cast +from typing import cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project -from aea.cli.utils.package_utils import verify_or_create_private_keys_ctx +from aea.cli.utils.package_utils import get_wallet_from_context from aea.crypto.helpers import try_generate_testnet_wealth from aea.crypto.registries import faucet_apis_registry, make_faucet_api_cls -from aea.crypto.wallet import Wallet @click.command() @@ -45,12 +44,11 @@ @check_aea_project def generate_wealth(click_context, sync, type_): """Generate wealth for address on test network.""" - _try_generate_wealth(click_context, type_, sync) + ctx = cast(Context, click_context.obj) + _try_generate_wealth(ctx, type_, sync) -def _try_generate_wealth( - click_context: click.core.Context, type_: str, sync: bool -) -> None: +def _try_generate_wealth(ctx: Context, type_: str, sync: bool) -> None: """ Try generate wealth for the provided network identifier. @@ -59,14 +57,7 @@ def _try_generate_wealth( :param sync: whether to sync or not :return: None """ - ctx = cast(Context, click_context.obj) - verify_or_create_private_keys_ctx(ctx=ctx) - - private_key_paths = { - config_pair[0]: config_pair[1] - for config_pair in ctx.agent_config.private_key_paths.read_all() - } # type: Dict[str, Optional[str]] - wallet = Wallet(private_key_paths) + wallet = get_wallet_from_context(ctx) try: address = wallet.addresses[type_] faucet_api_cls = make_faucet_api_cls(type_) diff --git a/aea/cli/get_address.py b/aea/cli/get_address.py index 632353e26a..3ff3f84dae 100644 --- a/aea/cli/get_address.py +++ b/aea/cli/get_address.py @@ -25,9 +25,8 @@ from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project -from aea.cli.utils.package_utils import verify_or_create_private_keys_ctx +from aea.cli.utils.package_utils import get_wallet_from_context from aea.crypto.registries import crypto_registry -from aea.crypto.wallet import Wallet @click.command() @@ -41,11 +40,12 @@ @check_aea_project def get_address(click_context, type_): """Get the address associated with the private key.""" - address = _try_get_address(click_context, type_) + ctx = cast(Context, click_context.obj) + address = _try_get_address(ctx, type_) click.echo(address) -def _try_get_address(click_context, type_): +def _try_get_address(ctx: Context, type_: str): """ Try to get address. @@ -54,15 +54,8 @@ def _try_get_address(click_context, type_): :return: address. """ - ctx = cast(Context, click_context.obj) - verify_or_create_private_keys_ctx(ctx=ctx) - - private_key_paths = { - config_pair[0]: config_pair[1] - for config_pair in ctx.agent_config.private_key_paths.read_all() - } + wallet = get_wallet_from_context(ctx) try: - wallet = Wallet(private_key_paths) address = wallet.addresses[type_] return address except ValueError as e: # pragma: no cover diff --git a/aea/cli/get_wealth.py b/aea/cli/get_wealth.py index 021c48faa1..02f294286b 100644 --- a/aea/cli/get_wealth.py +++ b/aea/cli/get_wealth.py @@ -19,18 +19,14 @@ """Implementation of the 'aea get_wealth' subcommand.""" -from typing import Dict, Optional, cast +from typing import cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project -from aea.cli.utils.package_utils import ( - try_get_balance, - verify_or_create_private_keys_ctx, -) +from aea.cli.utils.package_utils import get_wallet_from_context, try_get_balance from aea.crypto.registries import ledger_apis_registry -from aea.crypto.wallet import Wallet @click.command() @@ -44,16 +40,11 @@ @check_aea_project def get_wealth(click_context, type_): """Get the wealth associated with the private key.""" - wealth = _try_get_wealth(click_context, type_) + ctx = cast(Context, click_context.obj) + wealth = _try_get_wealth(ctx, type_) click.echo(wealth) -def _try_get_wealth(click_context: click.core.Context, type_: str): - ctx = cast(Context, click_context.obj) - verify_or_create_private_keys_ctx(ctx=ctx) - private_key_paths = { - config_pair[0]: config_pair[1] - for config_pair in ctx.agent_config.private_key_paths.read_all() - } # type: Dict[str, Optional[str]] - wallet = Wallet(private_key_paths) +def _try_get_wealth(ctx: Context, type_: str): + wallet = get_wallet_from_context(ctx) return try_get_balance(ctx.agent_config, wallet, type_) diff --git a/aea/cli/install.py b/aea/cli/install.py index 88ce64dfd5..82f869ebe9 100644 --- a/aea/cli/install.py +++ b/aea/cli/install.py @@ -16,21 +16,18 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """Implementation of the 'aea install' subcommand.""" -import pprint -import subprocess # nosec import sys -from typing import List, Optional, cast +from typing import Optional, cast import click from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project from aea.cli.utils.loggers import logger -from aea.configurations.base import Dependency from aea.exceptions import AEAException, enforce +from aea.helpers.install_dependency import install_dependency, run_install_subprocess @click.command() @@ -68,61 +65,11 @@ def do_install(ctx: Context, requirement: Optional[str] = None) -> None: logger.debug("Installing all the dependencies...") dependencies = ctx.get_dependencies() for name, d in dependencies.items(): - _install_dependency(name, d) + install_dependency(name, d) except AEAException as e: raise click.ClickException(str(e)) -def _install_dependency(dependency_name: str, dependency: Dependency): - click.echo("Installing {}...".format(pprint.pformat(dependency_name))) - try: - index = dependency.get("index", None) - git_url = dependency.get("git", None) - revision = dependency.get("ref", "") - version_constraint = dependency.get("version", "") - command = [sys.executable, "-m", "pip", "install"] - if git_url is not None: - command += ["-i", index] if index is not None else [] - command += ["git+" + git_url + "@" + revision + "#egg=" + dependency_name] - else: - command += ["-i", index] if index is not None else [] - command += [dependency_name + version_constraint] - logger.debug("Calling '{}'".format(" ".join(command))) - return_code = _run_install_subprocess(command) - if return_code == 1: - # try a second time - return_code = _run_install_subprocess(command) - enforce(return_code == 0, "Return code != 0.") - except Exception as e: - raise AEAException( - "An error occurred while installing {}, {}: {}".format( - dependency_name, dependency, str(e) - ) - ) - - -def _run_install_subprocess( - install_command: List[str], install_timeout: float = 300 -) -> int: - """ - Try executing install command. - - :param install_command: list strings of the command - :param install_timeout: timeout to wait pip to install - :return: the return code of the subprocess - """ - try: - subp = subprocess.Popen(install_command) # nosec - subp.wait(install_timeout) - return_code = subp.returncode - finally: - poll = subp.poll() - if poll is None: # pragma: no cover - subp.terminate() - subp.wait(30) - return return_code - - def _install_from_requirement(file: str, install_timeout: float = 300) -> None: """ Install from requirements. @@ -133,7 +80,7 @@ def _install_from_requirement(file: str, install_timeout: float = 300) -> None: :return: None """ try: - returncode = _run_install_subprocess( + returncode = run_install_subprocess( [sys.executable, "-m", "pip", "install", "-r", file], install_timeout ) enforce(returncode == 0, "Return code != 0.") diff --git a/aea/cli/interact.py b/aea/cli/interact.py index c9c59b6f31..35f065dc11 100644 --- a/aea/cli/interact.py +++ b/aea/cli/interact.py @@ -157,7 +157,7 @@ def _try_construct_envelope( performative_str = "bytes" performative = DefaultMessage.Performative(performative_str) click.echo( - "Provide message of protocol fetchai/default:0.6.0 for performative {}:".format( + "Provide message of protocol fetchai/default:0.7.0 for performative {}:".format( performative_str ) ) diff --git a/aea/cli/push.py b/aea/cli/push.py index 631e31a2cc..a8a438633f 100644 --- a/aea/cli/push.py +++ b/aea/cli/push.py @@ -113,9 +113,7 @@ def _save_item_locally(ctx: Context, item_type: str, item_id: PublicId) -> None: _check_package_public_id(source_path, item_type, item_id) copytree(source_path, target_path) click.echo( - '{} "{}" successfully saved in packages folder.'.format( - item_type.title(), item_id - ) + f'{item_type.title()} "{item_id}" successfully saved in packages folder.' ) @@ -125,10 +123,10 @@ def _check_package_public_id(source_path, item_type, item_id) -> None: item_author = config.get("author", "") item_name = config.get("name", "") item_version = config.get("version", "") - if ( - item_id.name != item_name - or item_id.author != item_author - or item_id.version != item_version + actual_item_id = PublicId(item_author, item_name, item_version) + if not actual_item_id.same_prefix(item_id) or ( + not item_id.package_version.is_latest + and item_id.version != actual_item_id.version ): raise click.ClickException( "Version, name or author does not match. Expected '{}', found '{}'".format( diff --git a/aea/cli/registry/add.py b/aea/cli/registry/add.py index 6b3b3aae32..ed5d0b2b38 100644 --- a/aea/cli/registry/add.py +++ b/aea/cli/registry/add.py @@ -21,9 +21,7 @@ import os from pathlib import Path -import click - -from aea.cli.registry.utils import download_file, extract, request_api +from aea.cli.registry.utils import download_file, extract, get_package_meta from aea.cli.utils.loggers import logger from aea.configurations.base import PublicId @@ -44,18 +42,14 @@ def fetch_package(obj_type: str, public_id: PublicId, cwd: str, dest: str) -> Pa public_id=public_id, obj_type=obj_type ) ) - author, name, version = public_id.author, public_id.name, public_id.version - item_type_plural = obj_type + "s" # used for API and folder paths - - api_path = "/{}/{}/{}/{}".format(item_type_plural, author, name, version) - resp = request_api("GET", api_path) - file_url = resp["file"] logger.debug( "Downloading {obj_type} {public_id}...".format( public_id=public_id, obj_type=obj_type ) ) + package_meta = get_package_meta(obj_type, public_id) + file_url = package_meta["file"] filepath = download_file(file_url, cwd) # next code line is needed because the items are stored in tarball packages as folders @@ -66,8 +60,8 @@ def fetch_package(obj_type: str, public_id: PublicId, cwd: str, dest: str) -> Pa ) ) extract(filepath, dest) - click.echo( - "Successfully fetched {obj_type}: {public_id}.".format( + logger.debug( + "Successfully fetched {obj_type} '{public_id}'.".format( public_id=public_id, obj_type=obj_type ) ) diff --git a/aea/cli/registry/utils.py b/aea/cli/registry/utils.py index 720edb1b1a..3c9cc80dce 100644 --- a/aea/cli/registry/utils.py +++ b/aea/cli/registry/utils.py @@ -18,7 +18,6 @@ # ------------------------------------------------------------------------------ """Utils used for operating Registry with CLI.""" - import os import tarfile from json.decoder import JSONDecodeError @@ -28,7 +27,10 @@ from aea.cli.registry.settings import AUTH_TOKEN_KEY, REGISTRY_API_URL from aea.cli.utils.config import get_or_create_cli_config +from aea.cli.utils.context import Context from aea.cli.utils.loggers import logger +from aea.cli.utils.package_utils import find_item_locally +from aea.configurations.base import PublicId def get_auth_token() -> str: @@ -209,3 +211,44 @@ def is_auth_token_present(): :return: bool is logged in. """ return get_auth_token() is not None + + +def get_package_meta(obj_type: str, public_id: PublicId) -> dict: + """ + Get package meta data from remote registry. + + :param obj_type: str. component type + :param public_id: component public id + + :return: dict with package details + """ + api_path = f"/{obj_type}s/{public_id.author}/{public_id.name}/{public_id.version}" + resp = request_api("GET", api_path) + return resp + + +def get_latest_version_available_in_registry( + ctx: Context, item_type: str, item_public_id: PublicId +) -> PublicId: + """ + Get latest avalable package version public id. + + :param ctx: Context object. + :param item_type: the item type. + :param item_public_id: the item public id. + :return: PublicId + """ + is_local = ctx.config.get("is_local") + try: + if is_local: + _, item_config = find_item_locally(ctx, item_type, item_public_id) + latest_item_public_id = item_config.public_id + else: + package_meta = get_package_meta(item_type, item_public_id) + latest_item_public_id = PublicId.from_str(package_meta["public_id"]) + except Exception: # pylint: disable=broad-except + raise click.ClickException( + f"Package {item_public_id} details can not be fetched from the registry!" + ) + + return latest_item_public_id diff --git a/aea/cli/remove.py b/aea/cli/remove.py index d301368503..799b7a0e60 100644 --- a/aea/cli/remove.py +++ b/aea/cli/remove.py @@ -16,27 +16,48 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """Implementation of the 'aea remove' subcommand.""" import os import shutil +from collections import defaultdict +from contextlib import contextmanager from pathlib import Path +from typing import Dict, Generator, Optional, Set, Tuple, cast import click from aea.cli.utils.click_utils import PublicIdParameter +from aea.cli.utils.config import load_item_config from aea.cli.utils.context import Context from aea.cli.utils.decorators import check_aea_project, pass_ctx from aea.cli.utils.loggers import logger -from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE, PublicId +from aea.cli.utils.package_utils import get_item_public_id_by_author_name +from aea.configurations.base import ( + AgentConfig, + ComponentId, + ComponentType, + DEFAULT_AEA_CONFIG_FILE, + PackageConfiguration, + PackageId, + PublicId, +) @click.group() +@click.option( + "-w", + "--with-dependencies", + is_flag=True, + help="Remove obsolete dependencies not required anymore.", +) @click.pass_context @check_aea_project -def remove(click_context): # pylint: disable=unused-argument +def remove(click_context, with_dependencies): # pylint: disable=unused-argument """Remove a resource from the agent.""" + ctx = cast(Context, click_context.obj) + if with_dependencies: + ctx.set_config("with_dependencies", True) @remove.command() @@ -87,65 +108,361 @@ def skill(ctx: Context, skill_id): remove_item(ctx, "skill", skill_id) -def remove_item(ctx: Context, item_type: str, item_id: PublicId) -> None: - """ - Remove an item from the configuration file and agent, given the public id. +class ItemRemoveHelper: + """Helper to check dependencies on removing component from agent config.""" - :param ctx: Context object. - :param item_type: type of item. - :param item_id: item public ID. + def __init__(self, agent_config: AgentConfig) -> None: + """Init helper.""" + self._agent_config = agent_config - :return: None - :raises ClickException: if some error occures. + def get_agent_dependencies_with_reverse_dependencies( + self, + ) -> Dict[PackageId, Set[PackageId]]: + """ + Get all reverse dependencices in agent. + + :return: dict with PackageId: and set of PackageIds that uses this package + + Return example: + { + PackageId(protocol, fetchai/default:0.7.0): { + PackageId(skill, fetchai/echo:0.9.0), + PackageId(skill, fetchai/error:0.7.0) + }, + PackageId(connection, fetchai/stub:0.11.0): set(), + PackageId(skill, fetchai/error:0.7.0): set(), + PackageId(skill, fetchai/echo:0.9.0): set()} + ) + """ + return self.get_item_dependencies_with_reverse_dependencies( + self._agent_config, None + ) + + @staticmethod + def get_item_config(package_id: PackageId) -> PackageConfiguration: + """Get item config for item,_type and public_id.""" + return load_item_config( + str(package_id.package_type), + package_path=Path("vendor") + / package_id.public_id.author + / f"{str(package_id.package_type)}s" + / package_id.public_id.name, + ) + + @staticmethod + def _get_item_requirements( + item: PackageConfiguration, + ) -> Generator[PackageId, None, None]: + """ + List all the requiemenents for item provided. + + :return: generator with package ids: (type, public_id) + """ + for item_type in map(str, ComponentType): + items = getattr(item, f"{item_type}s", set()) + for item_public_id in items: + yield PackageId(item_type, item_public_id) + + def get_item_dependencies_with_reverse_dependencies( + self, item: PackageConfiguration, package_id: Optional[PackageId] = None + ) -> Dict[PackageId, Set[PackageId]]: + """ + Get item dependencies. + + It's recursive and provides all the sub dependencies. + + :return: dict with PackageId: and set of PackageIds that uses this package + """ + result: defaultdict = defaultdict(set) + + for dep_package_id in self._get_item_requirements(item): + if package_id is None: + _ = result[dep_package_id] # init default dict value + else: + result[dep_package_id].add(package_id) + if not self.is_present_in_agent_config(dep_package_id): + continue + dep_item = self.get_item_config(dep_package_id) + for item_key, deps in self.get_item_dependencies_with_reverse_dependencies( + dep_item, dep_package_id + ).items(): + result[item_key] = result[item_key].union(deps) + + return result + + def is_present_in_agent_config(self, package_id: PackageId) -> bool: + """Check item is in agent config.""" + current_item = get_item_public_id_by_author_name( + self._agent_config, + str(package_id.package_type), + package_id.public_id.author, + package_id.public_id.name, + ) + return bool(current_item) + + def check_remove( + self, item_type: str, item_public_id: PublicId + ) -> Tuple[Set[PackageId], Set[PackageId], Dict[PackageId, Set[PackageId]]]: + """ + Check item can be removed from agent. + + required by - set of components that requires this component + can be deleted - set of dependencies used only by component so can be deleted + can not be deleted - dict - keys - packages can not be deleted, values are set of packages requireed by. + + :return: Tuple[required by, can be deleted, can not be deleted.] + """ + package_id = PackageId(item_type, item_public_id) + item = self.get_item_config(package_id) + agent_deps = self.get_agent_dependencies_with_reverse_dependencies() + item_deps = self.get_item_dependencies_with_reverse_dependencies( + item, package_id + ) + can_be_removed = set() + can_not_be_removed = dict() + + for dep_key, deps in item_deps.items(): + if agent_deps[dep_key] == deps: + can_be_removed.add(dep_key) + else: + can_not_be_removed[dep_key] = agent_deps[dep_key] - deps + + return agent_deps[package_id], can_be_removed, can_not_be_removed + + +@contextmanager +def remove_unused_component_configurations(ctx: Context): """ - item_name = item_id.name - item_type_plural = "{}s".format(item_type) - existing_item_ids = getattr(ctx.agent_config, item_type_plural) - existing_items_name_to_ids = { - public_id.name: public_id for public_id in existing_item_ids - } - - agent_name = ctx.agent_config.agent_name - click.echo( - "Removing {item_type} '{item_name}' from the agent '{agent_name}'...".format( - agent_name=agent_name, item_type=item_type, item_name=item_name + Remove all component configurations for items not registered and dump agent config. + + Context manager! + Clean all configurations on enter, restore actual configurations and dump agent config. + """ + saved_configuration = ctx.agent_config.component_configurations + ctx.agent_config.component_configurations = {} + try: + yield + finally: + for component_id in ctx.agent_config.package_dependencies: + if component_id in saved_configuration: + ctx.agent_config.component_configurations[ + component_id + ] = saved_configuration[component_id] + + with open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as f: + ctx.agent_loader.dump(ctx.agent_config, f) + + +class RemoveItem: + """Implementation of item remove from the project.""" + + def __init__( + self, + ctx: Context, + item_type: str, + item_id: PublicId, + with_dependencies: bool, + force: bool = False, + ) -> None: + """ + Init remove item tool. + + :param ctx: click context. + :param item_type: str, package type + :param item_id: PublicId of the item to remove. + :oaram force: bool. if True remove even required by another package. + + :return: None + """ + self.ctx = ctx + self.force = force + self.item_type = item_type + self.item_id = item_id + self.with_dependencies = with_dependencies + self.item_type_plural = "{}s".format(item_type) + self.item_name = item_id.name + + self.current_item = self.get_current_item() + self.required_by: Set[PackageId] = set() + self.dependencies_can_be_removed: Set[PackageId] = set() + try: + ( + self.required_by, + self.dependencies_can_be_removed, + *_, + ) = ItemRemoveHelper(self.agent_config).check_remove( + self.item_type, self.current_item + ) + except FileNotFoundError: + pass # item registered but not present on filesystem + + def get_current_item(self) -> PublicId: + """Return public id of the item already presents in agent config.""" + current_item = get_item_public_id_by_author_name( + self.ctx.agent_config, + self.item_type, + self.item_id.author, + self.item_id.name, ) - ) + if not current_item: # pragma: nocover # actually checked in check_item_present + raise click.ClickException( + "The {} '{}' is not supported.".format(self.item_type, self.item_id) + ) + return current_item - if item_name not in existing_items_name_to_ids.keys() and not any( - item_id.same_prefix(existing_id) for existing_id in existing_item_ids - ): - raise click.ClickException( - "The {} '{}' is not supported.".format(item_type, item_id) + def remove(self) -> None: + """Remove item and it's dependencies if specified.""" + click.echo( + "Removing {item_type} '{item_name}' from the agent '{agent_name}'...".format( + agent_name=self.agent_name, + item_type=self.item_type, + item_name=self.item_name, + ) + ) + self.remove_item() + if self.with_dependencies: + self.remove_dependencies() + click.echo( + "{item_type} '{item_name}' was removed from the agent '{agent_name}'...".format( + agent_name=self.agent_name, + item_type=self.item_type, + item_name=self.item_name, + ) ) - item_folder = Path(ctx.cwd, "vendor", item_id.author, item_type_plural, item_name) - if not item_folder.exists(): - # check if it is present in custom packages. - item_folder = Path(ctx.cwd, item_type_plural, item_name) - if not item_folder.exists(): + @property + def agent_items(self) -> Set[PublicId]: + """Return items registered with agent of the same type as item.""" + return getattr(self.agent_config, self.item_type_plural, set) + + @property + def is_required_by(self) -> bool: + """Is required by any other registered component in the agent.""" + return bool(self.required_by) + + def remove_item(self) -> None: + """ + Remove item. + + Removed from the filesystem. + Removed from the agent configuration + + Does not remove dependencies, please use `remove_dependencies`. + """ + if (not self.force) and self.is_required_by: raise click.ClickException( - "{} {} not found. Aborting.".format(item_type.title(), item_name) + f"Package {self.item_type} {self.item_id} can not be removed because it is required by {','.join(map(str, self.required_by))}" ) - if ( - item_folder.exists() and not ctx.agent_config.author == item_id.author - ): # pragma: no cover + self._remove_package() + self._remove_from_config() + + @property + def cwd(self) -> str: + """Get current workdir.""" + return self.ctx.cwd + + @property + def agent_config(self) -> AgentConfig: + """Get agent config from context.""" + return self.ctx.agent_config + + @property + def agent_name(self) -> str: + """Get agent name.""" + return self.ctx.agent_config.agent_name + + def _get_item_folder(self) -> Path: + """Get item package folder.""" + item_folder = Path( + self.cwd, + "vendor", + self.item_id.author, + self.item_type_plural, + self.item_name, + ) + if not item_folder.exists(): + # check if it is present in custom packages. + item_folder = Path(self.cwd, self.item_type_plural, self.item_name) + if not item_folder.exists(): + raise click.ClickException( + "{} {} not found. Aborting.".format( + self.item_type.title(), self.item_name + ) + ) + if ( + item_folder.exists() + and not self.agent_config.author == self.item_id.author + ): # pragma: no cover + raise click.ClickException( + "{} {} author is different from {} agent author. " + "Please fix the author field.".format( + self.item_name, self.item_type, self.agent_name + ) + ) + logger.debug( + "Removing local {} {}.".format(self.item_type, self.item_name) + ) # pragma: no cover + return item_folder + + def _remove_package(self) -> None: + """Remove package from filesystem.""" + item_folder = self._get_item_folder() + try: + shutil.rmtree(item_folder) + except BaseException: raise click.ClickException( - "{} {} author is different from {} agent author. " - "Please fix the author field.".format(item_name, item_type, agent_name) + f"An error occurred during {item_folder} removing." ) + + def _remove_from_config(self) -> None: + """Remove item from agent config.""" + current_item = self.get_current_item() logger.debug( - "Removing local {} {}.".format(item_type, item_name) - ) # pragma: no cover + "Removing the {} from {}".format(self.item_type, DEFAULT_AEA_CONFIG_FILE) + ) + self.agent_items.remove(current_item) + self.agent_config.component_configurations.pop( + ComponentId(self.item_type, current_item), None + ) + self._dump_agent_config() - try: - shutil.rmtree(item_folder) - except BaseException: - raise click.ClickException("An error occurred.") - - # removing the item from the configurations. - item_public_id = existing_items_name_to_ids[item_name] - logger.debug("Removing the {} from {}".format(item_type, DEFAULT_AEA_CONFIG_FILE)) - existing_item_ids.remove(item_public_id) - with open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as f: - ctx.agent_loader.dump(ctx.agent_config, f) + def _dump_agent_config(self) -> None: + """Save agent config to the filesystem.""" + with open(os.path.join(self.ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as f: + self.ctx.agent_loader.dump(self.agent_config, f) + + def remove_dependencies(self) -> None: + """Remove all the dependecies related only to the package.""" + if not self.dependencies_can_be_removed: + return + click.echo( + f"Removing obsolete dependencies for {self.agent_name}: {self.dependencies_can_be_removed}..." + ) + for dependency in self.dependencies_can_be_removed: + RemoveItem( + self.ctx, + str(dependency.package_type), + dependency.public_id, + with_dependencies=False, + force=True, + ).remove_item() + click.echo( + f"{str(dependency.package_type).capitalize()} {dependency.public_id} was removed from {self.agent_name}." + ) + + +def remove_item(ctx: Context, item_type: str, item_id: PublicId) -> None: + """ + Remove an item from the configuration file and agent, given the public id. + + :param ctx: Context object. + :param item_type: type of item. + :param item_id: item public ID. + + :return: None + :raises ClickException: if some error occures. + """ + with remove_unused_component_configurations(ctx): + RemoveItem( + ctx, item_type, item_id, cast(bool, ctx.config.get("with_dependencies")) + ).remove() diff --git a/aea/cli/run.py b/aea/cli/run.py index 1d7e9bb160..bd86b07973 100644 --- a/aea/cli/run.py +++ b/aea/cli/run.py @@ -26,7 +26,7 @@ from aea import __version__ from aea.aea import AEA -from aea.aea_builder import AEABuilder +from aea.aea_builder import AEABuilder, DEFAULT_ENV_DOTFILE from aea.cli.install import do_install from aea.cli.utils.click_utils import ConnectionsOption from aea.cli.utils.constants import AEA_LOGO, REQUIREMENTS @@ -51,7 +51,7 @@ "env_file", type=click.Path(), required=False, - default=".env", + default=DEFAULT_ENV_DOTFILE, help="Specify an environment file (default: .env)", ) @click.option( diff --git a/aea/cli/transfer.py b/aea/cli/transfer.py new file mode 100644 index 0000000000..344263d425 --- /dev/null +++ b/aea/cli/transfer.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2020 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Implementation of the 'aea transfer' subcommand.""" +import time +from typing import Optional, cast + +import click + +from aea.cli.get_address import _try_get_address +from aea.cli.utils.context import Context +from aea.cli.utils.decorators import check_aea_project +from aea.cli.utils.package_utils import get_wallet_from_context, try_get_balance +from aea.common import Address +from aea.crypto.ledger_apis import LedgerApis +from aea.crypto.registries import ledger_apis_registry + + +DEFAULT_SETTLE_TIMEOUT = 60 + + +@click.command() +@click.argument( + "type_", + metavar="TYPE", + type=click.Choice(ledger_apis_registry.supported_ids), + required=True, +) +@click.argument( + "address", type=str, required=True, +) +@click.argument( + "amount", type=int, required=True, +) +@click.argument("fee", type=int, required=False, default=100) +@click.option("-y", "--yes", type=bool, is_flag=True, default=False) +@click.option("--settle-timeout", type=int, default=DEFAULT_SETTLE_TIMEOUT) +@click.option("--sync", type=bool, is_flag=True, default=False) +@click.pass_context +@check_aea_project +def transfer(click_context, type_, address, amount, fee, yes, settle_timeout, sync): + """Get the wealth associated with the private key.""" + ctx = cast(Context, click_context.obj) + try: + own_address = _try_get_address(ctx, type_) + except KeyError: + raise click.ClickException( + f"No private key registered for `{type_}` in wallet!" + ) + if not yes: + click.confirm( + f"You are about to transfer from {own_address} to {address} on ledger {type_} the amount {amount} with fee {fee}. Do you want to continue?", + abort=True, + ) + + tx_digest = do_transfer(ctx, type_, address, amount, fee) + + if not tx_digest: + raise click.ClickException("Failed to send a transaction!") + + if sync: + click.echo("Transaction set. Waiting to be settled...") + wait_tx_settled(type_, tx_digest, timeout=settle_timeout) + click.echo( + f"Transaction successfully settled. Sent {amount} with fee {fee} to {address}, transaction digest: {tx_digest}" + ) + else: + click.echo( + f"Transaction successfully submitted. Sending {amount} with fee {fee} to {address}, transaction digest: {tx_digest}" + ) + + +def wait_tx_settled( + identifier: str, tx_digest: str, timeout=DEFAULT_SETTLE_TIMEOUT +) -> None: + """ + Wait transaction is settled succesfuly. + + :param identifier: str, ledger id + :param tx_digest: str, transaction digest + :param timeout: int, timeout in seconds efore timeout error raised + + :return: None + raises TimeoutError on timeout + """ + t = time.time() + while True: + if time.time() - t > timeout: + raise TimeoutError() + if LedgerApis.is_transaction_settled(identifier, tx_digest): + return + time.sleep(1) + + +def do_transfer( + ctx: Context, identifier: str, address: Address, amount: int, tx_fee: int +) -> Optional[str]: + """ + Perform wealth transfer to another account. + + :param ctx: click context + :param identifier: str, ledger id to perform transfer operation + :param address: address of the recepient + :param amount: int, amount of wealth to transfer + :param tx_fee: int, fee for transaction + + :return: str, transaction digest or None if failed. + """ + click.echo("Starting transfer ...") + wallet = get_wallet_from_context(ctx) + source_address = wallet.addresses[identifier] + + balance = int(try_get_balance(ctx.agent_config, wallet, identifier)) + total_payable = amount + tx_fee + if total_payable > balance: + raise click.ClickException( + f"Balance is not enough! Available={balance}, required={total_payable}!" + ) + + tx_nonce = LedgerApis.generate_tx_nonce(identifier, source_address, address) + transaction = LedgerApis.get_transfer_transaction( + identifier, source_address, address, amount, tx_fee, tx_nonce + ) + tx_signed = wallet.sign_transaction(identifier, transaction) + return LedgerApis.send_signed_transaction(identifier, tx_signed) diff --git a/aea/cli/upgrade.py b/aea/cli/upgrade.py new file mode 100644 index 0000000000..e46b9b6fcf --- /dev/null +++ b/aea/cli/upgrade.py @@ -0,0 +1,328 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""Implementation of the 'aea upgrade' subcommand.""" +from contextlib import suppress +from typing import Dict, Iterable, List, Set, Tuple, cast + +import click + +from aea.cli.add import add_item +from aea.cli.registry.utils import get_latest_version_available_in_registry +from aea.cli.remove import ( + ItemRemoveHelper, + RemoveItem, + remove_unused_component_configurations, +) +from aea.cli.utils.click_utils import PublicIdParameter +from aea.cli.utils.context import Context +from aea.cli.utils.decorators import check_aea_project, clean_after, pass_ctx +from aea.cli.utils.package_utils import ( + get_item_public_id_by_author_name, + is_item_present, +) +from aea.configurations.base import PackageId, PublicId + + +@click.group(invoke_without_command=True) +@click.option("--local", is_flag=True, help="For upgrading from local folder.") +@click.pass_context +@check_aea_project +def upgrade(click_context, local): + """Upgrade agent's component.""" + ctx = cast(Context, click_context.obj) + if local: + ctx.set_config("is_local", True) + + if click_context.invoked_subcommand is None: + upgrade_project(ctx) + + +@upgrade.command() +@click.argument("connection_public_id", type=PublicIdParameter(), required=True) +@pass_ctx +def connection(ctx: Context, connection_public_id: PublicId): + """Upgrade a connection at the configuration file.""" + upgrade_item(ctx, "connection", connection_public_id) + + +@upgrade.command() +@click.argument("contract_public_id", type=PublicIdParameter(), required=True) +@pass_ctx +def contract(ctx: Context, contract_public_id: PublicId): + """Upgrade a contract at the configuration file.""" + upgrade_item(ctx, "contract", contract_public_id) + + +@upgrade.command() +@click.argument("protocol_public_id", type=PublicIdParameter(), required=True) +@pass_ctx +def protocol(ctx: Context, protocol_public_id): + """Upgrade a protocol for the agent.""" + upgrade_item(ctx, "protocol", protocol_public_id) + + +@upgrade.command() +@click.argument("skill_public_id", type=PublicIdParameter(), required=True) +@pass_ctx +def skill(ctx: Context, skill_public_id: PublicId): + """Upgrade a skill for the agent.""" + upgrade_item(ctx, "skill", skill_public_id) + + +@clean_after +def upgrade_project(ctx: Context) -> None: # pylint: disable=unused-argument + """Perform project upgrade.""" + click.echo("Starting project upgrade...") + + item_remover = ItemRemoveHelper(ctx.agent_config) + agent_items = item_remover.get_agent_dependencies_with_reverse_dependencies() + items_to_upgrade = set() + upgraders: List[ItemUpgrader] = [] + shared_deps: Set[PackageId] = set() + shared_deps_to_remove = set() + items_to_upgrade_dependencies = set() + + for package_id, deps in agent_items.items(): + if deps: + continue + + with suppress(UpgraderException): + item_upgrader = ItemUpgrader( + ctx, str(package_id.package_type), package_id.public_id.to_latest() + ) + new_version = item_upgrader.check_upgrade_is_required() + items_to_upgrade.add((package_id, new_version)) + upgraders.append(item_upgrader) + shared_deps.update(item_upgrader.deps_can_not_be_removed.keys()) + items_to_upgrade_dependencies.update(item_upgrader.dependencies) + items_to_upgrade_dependencies.add(package_id) + + if not items_to_upgrade: + click.echo("Everything is already up to date!") + return + + for dep in shared_deps: + if agent_items[dep] - items_to_upgrade_dependencies: + # shared deps not resolved, nothing to do next + continue # pragma: nocover + # add it to remove + shared_deps_to_remove.add(dep) + + with remove_unused_component_configurations(ctx): + if shared_deps_to_remove: + click.echo( + f"Removing shared dependencies: {', '.join(map(str, shared_deps_to_remove))}..." + ) + for dep in shared_deps_to_remove: + RemoveItem( + ctx, + str(dep.package_type), + dep.public_id, + with_dependencies=False, + force=True, + ).remove_item() + click.echo("Shared dependencies removed.") + + for upgrader in upgraders: + upgrader.remove_item() + upgrader.add_item() + + click.echo("Finished project upgrade. Everything is up to date now!") + + +class UpgraderException(Exception): + """Base upgrader exception.""" + + +class NotAddedException(UpgraderException): + """Item was not found in agent cause not added.""" + + +class AlreadyActualVersionException(UpgraderException): + """Actual version already installed.""" + + def __init__(self, version: str): + """Init exception.""" + super().__init__(version) + self.version = version + + +class IsRequiredException(UpgraderException): + """Package can not be upgraded cause required by another.""" + + def __init__(self, required_by: Iterable[PackageId]): + """Init exception.""" + super().__init__(required_by) + self.required_by = required_by + + +class ItemUpgrader: + """Tool to upgrade agent's item .""" + + def __init__(self, ctx: Context, item_type: str, item_public_id: PublicId) -> None: + """ + Init item upgrader. + + :param ctx: context + :param item_type: str, type of the package + :param item_public_id: item to upgrade. + """ + self.ctx = ctx + self.ctx.set_config("with_dependencies", True) + self.item_type = item_type + self.item_public_id = item_public_id + self.current_item_public_id = self.get_current_item() + ( + self.in_requirements, + self.deps_can_be_removed, + self.deps_can_not_be_removed, + ) = self.get_dependencies() + self.dependencies: Set[PackageId] = set() + self.dependencies.update(self.deps_can_be_removed) + self.dependencies.update(self.deps_can_not_be_removed) + + def get_current_item(self) -> PublicId: + """Return public id of the item already presents in agent config.""" + self.check_item_present() + current_item = get_item_public_id_by_author_name( + self.ctx.agent_config, + self.item_type, + self.item_public_id.author, + self.item_public_id.name, + ) + if not current_item: # pragma: nocover # actually checked in check_item_present + raise ValueError("Item not found!") + return current_item + + def check_item_present(self) -> None: + """Check item going to be upgraded already registered in agent.""" + if not is_item_present(self.ctx, self.item_type, self.item_public_id): + raise NotAddedException() + + def get_dependencies( + self, + ) -> Tuple[Set[PackageId], Set[PackageId], Dict[PackageId, Set[PackageId]]]: + """ + Return dependency details for item. + + :return: same as for ItemRemoveHelper.check_remove + """ + return ItemRemoveHelper(self.ctx.agent_config).check_remove( + self.item_type, self.current_item_public_id + ) + + def check_upgrade_is_required(self) -> str: + """ + Check upgrade is required otherwise raise UpgraderException. + + :return: new version of the package. + """ + if self.in_requirements: + # check if we trying to upgrade some component dependency + raise IsRequiredException(self.in_requirements) + + if self.item_public_id.version != "latest": + new_item = self.item_public_id + else: + new_item = get_latest_version_available_in_registry( + self.ctx, self.item_type, self.item_public_id + ) + + if self.current_item_public_id.version == new_item.version: + raise AlreadyActualVersionException(new_item.version) + return new_item.version + + def remove_item(self) -> None: + """Remove item from agent.""" + remove_item = RemoveItem( + self.ctx, + self.item_type, + self.item_public_id, + with_dependencies=True, + force=True, + ) + remove_item.remove() + click.echo(f"Item { self.item_type} {self.item_public_id} removed!") + + def add_item(self) -> None: + """Add new package version to agent.""" + click.echo(f"Adding item {self.item_type} {self.item_public_id}.") + add_item(self.ctx, str(self.item_type), self.item_public_id) + + +@clean_after +def upgrade_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: + """ + Upgrade an item. + + :param ctx: Context object. + :param item_type: the item type. + :param item_public_id: the item public id. + :return: None + """ + try: + with remove_unused_component_configurations(ctx): + item_upgrader = ItemUpgrader(ctx, item_type, item_public_id) + click.echo( + "Upgrading {} '{}/{}' from version '{}' to '{}' for the agent '{}'...".format( + item_type, + item_public_id.author, + item_public_id.name, + item_upgrader.current_item_public_id.version, + item_public_id.version, + ctx.agent_config.agent_name, + ) + ) + version = item_upgrader.check_upgrade_is_required() + + item_upgrader.remove_item() + item_upgrader.add_item() + + click.echo( + "The {} '{}/{}' for the agent '{}' has been successfully upgraded from version '{}' to '{}'.".format( + item_type, + item_public_id.author, + item_public_id.name, + ctx.agent_config.agent_name, + item_upgrader.current_item_public_id.version, + version, + ) + ) + + except NotAddedException: + raise click.ClickException( + "A {} with id '{}/{}' is not registered. Please use the `add` command. Aborting...".format( + item_type, item_public_id.author, item_public_id.name + ), + ) + except AlreadyActualVersionException as e: + raise click.ClickException( + "The {} with id '{}/{}' already has version '{}'. Nothing to upgrade.".format( + item_type, item_public_id.author, item_public_id.name, e.version, + ) + ) + except IsRequiredException as e: + raise click.ClickException( + "Can not upgrade {} '{}/{}' because it is required by '{}'".format( + item_type, + item_public_id.author, + item_public_id.name, + ", ".join(map(str, e.required_by)), + ) + ) diff --git a/aea/cli/utils/click_utils.py b/aea/cli/utils/click_utils.py index 5af58b86b7..3ae04cba21 100644 --- a/aea/cli/utils/click_utils.py +++ b/aea/cli/utils/click_utils.py @@ -16,7 +16,6 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """Module with click utils of the aea cli.""" import os @@ -26,9 +25,8 @@ import click -from aea.cli.utils.config import handle_dotted_path, try_to_load_agent_config +from aea.cli.utils.config import try_to_load_agent_config from aea.configurations.base import DEFAULT_AEA_CONFIG_FILE, PublicId -from aea.exceptions import AEAException class ConnectionsOption(click.Option): @@ -119,35 +117,3 @@ def convert(self, value, param, ctx): ) finally: os.chdir(cwd) - - -class AEAJsonPathType(click.ParamType): - """This class implements the JSON-path parameter type for the AEA CLI tool.""" - - name = "json-path" - - def convert(self, value, param, ctx): - """Separate the path between path to resource and json path to attribute. - - Allowed values: - 'agent.an_attribute_name' - 'protocols.my_protocol.an_attribute_name' - 'connections.my_connection.an_attribute_name' - 'contracts.my_contract.an_attribute_name' - 'skills.my_skill.an_attribute_name' - 'vendor.author.[protocols|connections|skills].package_name.attribute_name - """ - try: - ( - json_path, - path_to_resource_configuration, - config_loader, - ) = handle_dotted_path(value) - except AEAException as e: - self.fail(str(e)) - else: - ctx.obj.set_config( - "configuration_file_path", path_to_resource_configuration - ) - ctx.obj.set_config("configuration_loader", config_loader) - return json_path diff --git a/aea/cli/utils/config.py b/aea/cli/utils/config.py index 9ca1237d60..558c478f72 100644 --- a/aea/cli/utils/config.py +++ b/aea/cli/utils/config.py @@ -16,14 +16,12 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """A module with config tools of the aea cli.""" - import logging import logging.config import os from pathlib import Path -from typing import Dict, Tuple +from typing import Dict, List, Optional, Set, Tuple import click import jsonschema @@ -38,13 +36,16 @@ from aea.cli.utils.exceptions import AEAConfigException from aea.cli.utils.generic import load_yaml from aea.configurations.base import ( + ComponentId, + ComponentType, DEFAULT_AEA_CONFIG_FILE, PackageConfiguration, PackageType, + PublicId, _get_default_configuration_file_name_from_type, ) from aea.configurations.loader import ConfigLoader, ConfigLoaders -from aea.exceptions import AEAEnforceError, AEAException +from aea.exceptions import AEAEnforceError, AEAException, enforce def try_to_load_agent_config( @@ -141,7 +142,9 @@ def load_item_config(item_type: str, package_path: Path) -> PackageConfiguration return item_config -def handle_dotted_path(value: str) -> Tuple: +def handle_dotted_path( + value: str, author: str +) -> Tuple[List[str], Path, ConfigLoader, Optional[ComponentId]]: """Separate the path between path to resource and json path to attribute. Allowed values: @@ -150,11 +153,17 @@ def handle_dotted_path(value: str) -> Tuple: 'connections.my_connection.an_attribute_name' 'contracts.my_contract.an_attribute_name' 'skills.my_skill.an_attribute_name' - 'vendor.author.[protocols|connections|skills].package_name.attribute_name + 'vendor.author.[protocols|contracts|connections|skills].package_name.attribute_name + + We also return the component id to retrieve the configuration of a specific + component. Notice that at this point we don't know the version, + so we put 'latest' as version, but later we will ignore it because + we will filter with only the component prefix (i.e. the triple type, author and name). :param value: dotted path. + :param author: the author string. - :return: Tuple[list of settings dict keys, filepath, config loader]. + :return: Tuple[list of settings dict keys, filepath, config loader, component id]. """ parts = value.split(".") @@ -165,7 +174,7 @@ def handle_dotted_path(value: str) -> Tuple: ) if ( - len(parts) < 1 + len(parts) < 2 or parts[0] == "agent" and len(parts) < 2 or parts[0] == "vendor" @@ -182,10 +191,26 @@ def handle_dotted_path(value: str) -> Tuple: resource_type_plural = "agents" path_to_resource_configuration = Path(DEFAULT_AEA_CONFIG_FILE) json_path = parts[1:] + component_id = None elif root == "vendor": + # parse json path resource_author = parts[1] resource_type_plural = parts[2] resource_name = parts[3] + + # extract component id + resource_type_singular = resource_type_plural[:-1] + try: + component_type = ComponentType(resource_type_singular) + except ValueError as e: + raise AEAException( + f"'{resource_type_plural}' is not a valid component type. Please use one of {ComponentType.plurals()}." + ) from e + component_id = ComponentId( + component_type, PublicId(resource_author, resource_name) + ) + + # find path to the resource directory path_to_resource_directory = ( Path(".") / "vendor" @@ -199,7 +224,7 @@ def handle_dotted_path(value: str) -> Tuple: ) json_path = parts[4:] if not path_to_resource_directory.exists(): - raise AEAException( + raise AEAException( # pragma: nocover "Resource vendor/{}/{}/{} does not exist.".format( resource_author, resource_type_plural, resource_name ) @@ -208,6 +233,16 @@ def handle_dotted_path(value: str) -> Tuple: # navigate the resources of the agent to reach the target configuration file. resource_type_plural = root resource_name = parts[1] + + # extract component id + resource_type_singular = resource_type_plural[:-1] + component_type = ComponentType(resource_type_singular) + resource_author = author + component_id = ComponentId( + component_type, PublicId(resource_author, resource_name) + ) + + # find path to the resource directory path_to_resource_directory = Path(".") / resource_type_plural / resource_name path_to_resource_configuration = ( path_to_resource_directory @@ -222,7 +257,7 @@ def handle_dotted_path(value: str) -> Tuple: ) config_loader = ConfigLoader.from_configuration_type(resource_type_plural[:-1]) - return json_path, path_to_resource_configuration, config_loader + return json_path, path_to_resource_configuration, config_loader, component_id def update_item_config(item_type: str, package_path: Path, **kwargs) -> None: @@ -266,3 +301,59 @@ def validate_item_config(item_type: str, package_path: Path) -> None: field_name, item_type ) ) + + +def _try_get_configuration_object_from_aea_config( + ctx: Context, component_id: ComponentId +) -> Optional[Dict]: + """ + Try to get the configuration object in the AEA config. + + The result is not guaranteed because there might not be any + + :param ctx: the CLI context. + :param component_id: the component id whose prefix points to the relevant + custom configuration in the AEA configuration file. + :return: the configuration object to get/set an attribute. + """ + if component_id is None: + # this is the case when the prefix of the json path is 'agent'. + return None # pragma: nocover + type_, author, name = ( + component_id.component_type, + component_id.author, + component_id.name, + ) + component_ids = set(ctx.agent_config.component_configurations.keys()) + true_component_id = _try_get_component_id_from_prefix( + component_ids, (type_, author, name) + ) + if true_component_id is not None: + return ctx.agent_config.component_configurations.get(true_component_id) + return None + + +def _try_get_component_id_from_prefix( + component_ids: Set[ComponentId], component_prefix: Tuple[ComponentType, str, str] +) -> Optional[ComponentId]: + """ + Find the component id matching a component prefix. + + :param component_ids: the set of component id. + :param component_prefix: the component prefix. + :return: the component id that matches the prefix. + :raises ValueError: if there are more than two components as candidate results. + """ + type_, author, name = component_prefix + results = list( + filter( + lambda x: x.component_type == type_ + and x.author == author + and x.name == name, + component_ids, + ) + ) + if len(results) == 0: + return None + enforce(len(results) == 1, f"Expected only one component, found {len(results)}.") + return results[0] diff --git a/aea/cli/utils/generic.py b/aea/cli/utils/generic.py index 582d673b2b..8108c68aa7 100644 --- a/aea/cli/utils/generic.py +++ b/aea/cli/utils/generic.py @@ -44,12 +44,16 @@ def get_parent_object(obj: Dict, dotted_path: List[str]): current_object = current_object.get(current_attribute_name, None) # if the dictionary does not have the key we want, fail. if current_object is None: - raise ValueError("Cannot get attribute '{}'".format(current_attribute_name)) + raise ValueError(f"Cannot get attribute '{current_attribute_name}'.") + if not isinstance(current_object, dict): + raise ValueError( + f"Attribute '{current_attribute_name}' is not a dictionary." + ) index += 1 # if we are not at the last step and the attribute value is not a dictionary, fail. if isinstance(current_object, dict): return current_object - raise ValueError("The target object is not a dictionary.") + raise ValueError("The target object is not a dictionary.") # pragma: nocover def load_yaml(filepath: str) -> Dict: diff --git a/aea/cli/utils/package_utils.py b/aea/cli/utils/package_utils.py index bcf491d2b6..b9726e5246 100644 --- a/aea/cli/utils/package_utils.py +++ b/aea/cli/utils/package_utils.py @@ -16,14 +16,13 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """Module with package utils of the aea cli.""" import os import re import shutil from pathlib import Path -from typing import List, Optional +from typing import Dict, List, Optional, Set, Tuple import click from jsonschema import ValidationError @@ -34,6 +33,7 @@ from aea.cli.utils.loggers import logger from aea.configurations.base import ( AgentConfig, + ComponentConfiguration, DEFAULT_AEA_CONFIG_FILE, PackageType, PublicId, @@ -49,6 +49,7 @@ from aea.crypto.helpers import verify_or_create_private_keys from aea.crypto.ledger_apis import DEFAULT_LEDGER_CONFIGS, LedgerApis from aea.crypto.wallet import Wallet +from aea.exceptions import AEAEnforceError ROOT = Path(".") @@ -222,14 +223,18 @@ def copy_package_directory(src: Path, dst: str) -> Path: return Path(dst) -def find_item_locally(ctx, item_type, item_public_id) -> Path: +def find_item_locally( + ctx, item_type, item_public_id +) -> Tuple[Path, ComponentConfiguration]: """ Find an item in the local registry. :param ctx: the CLI context. :param item_type: the type of the item to load. One of: protocols, connections, skills :param item_public_id: the public id of the item to find. - :return: path to the package directory (either in registry or in aea directory). + + :return: tuple of path to the package directory (either in registry or in aea directory) and component configuration + :raises SystemExit: if the search fails. """ item_type_plural = item_type + "s" @@ -271,7 +276,7 @@ def find_item_locally(ctx, item_type, item_public_id) -> Path: "Cannot find {} with author and version specified.".format(item_type) ) - return package_path + return package_path, item_configuration def find_item_in_distribution( # pylint: disable=unused-argument @@ -396,8 +401,7 @@ def register_item(ctx: Context, item_type: str, item_public_id: PublicId) -> Non logger.debug( "Registering the {} into {}".format(item_type, DEFAULT_AEA_CONFIG_FILE) ) - item_type_plural = item_type + "s" - supported_items = getattr(ctx.agent_config, item_type_plural) + supported_items = get_items(ctx.agent_config, item_type) supported_items.add(item_public_id) ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") @@ -440,14 +444,67 @@ def is_item_present( :return: boolean is item present. """ # check item presence only by author/package_name pair, without version. - item_type_plural = item_type + "s" - items_in_config = set( - map(lambda x: (x.author, x.name), getattr(ctx.agent_config, item_type_plural)) - ) + item_path = get_package_path(ctx, item_type, item_public_id, is_vendor=is_vendor) - return (item_public_id.author, item_public_id.name,) in items_in_config and Path( - item_path - ).exists() + registered_item_public_id = get_item_public_id_by_author_name( + ctx.agent_config, item_type, item_public_id.author, item_public_id.name + ) + is_item_registered = registered_item_public_id is not None + + return is_item_registered and Path(item_path).exists() + + +def get_item_id_present( + ctx: Context, item_type: str, item_public_id: PublicId +) -> PublicId: + """ + Get the item present in AEA. + + :param ctx: context object. + :param item_type: type of an item. + :param item_public_id: PublicId of an item. + + :return: boolean is item present. + :raises: AEAEnforceError + """ + registered_item_public_id = get_item_public_id_by_author_name( + ctx.agent_config, item_type, item_public_id.author, item_public_id.name + ) + if registered_item_public_id is None: + raise AEAEnforceError("Cannot find item.") # pragma: nocover + return registered_item_public_id + + +def get_item_public_id_by_author_name( + agent_config: AgentConfig, item_type: str, author: str, name: str +) -> Optional[PublicId]: + """ + Get component public_id by author and namme. + + :param agent_config: AgentConfig + :param item_type: str. component type: connection, skill, contract, protocol + :param author: str. author name + :param name: str. component name + + :return: PublicId + """ + items_in_config = { + (x.author, x.name): x for x in get_items(agent_config, item_type) + } + return items_in_config.get((author, name), None) + + +def get_items(agent_config: AgentConfig, item_type: str) -> Set[PublicId]: + """ + Get all items of certain type registered in AgentConfig. + + :param agent_config: AgentConfig + :param item_type: str. component type: connection, skill, contract, protocol + + :return: set of public ids + """ + item_type_plural = item_type + "s" + return getattr(agent_config, item_type_plural) def is_local_item(item_public_id: PublicId) -> bool: @@ -482,10 +539,35 @@ def try_get_balance( # pylint: disable=unused-argument try: if type_ not in DEFAULT_LEDGER_CONFIGS: # pragma: no cover raise ValueError("No ledger api config for {} available.".format(type_)) - address = wallet.addresses[type_] + address = wallet.addresses.get(type_) + if address is None: # pragma: no cover + raise ValueError("No key '{}' in wallet.".format(type_)) balance = LedgerApis.get_balance(type_, address) if balance is None: # pragma: no cover raise ValueError("No balance returned!") return balance except ValueError as e: # pragma: no cover raise click.ClickException(str(e)) + + +def get_wallet_from_context(ctx: Context) -> Wallet: + """ + Get wallet from current click Context. + + :param ctx: click context + + :return: wallet + """ + verify_or_create_private_keys_ctx(ctx=ctx) + wallet = get_wallet_from_agent_config(ctx.agent_config) + return wallet + + +def get_wallet_from_agent_config(agent_config: AgentConfig) -> Wallet: + """Get wallet from agent_cofig provided.""" + private_key_paths: Dict[str, Optional[str]] = { + config_pair[0]: config_pair[1] + for config_pair in agent_config.private_key_paths.read_all() + } + wallet = Wallet(private_key_paths) + return wallet diff --git a/aea/components/base.py b/aea/components/base.py index d02c1ef295..50f3255cac 100644 --- a/aea/components/base.py +++ b/aea/components/base.py @@ -36,7 +36,7 @@ from aea.helpers.logging import WithLogger -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class Component(ABC, WithLogger): @@ -141,5 +141,5 @@ def load_aea_package(configuration: ComponentConfiguration) -> None: spec = importlib.util.spec_from_file_location(import_path, subpackage_init_file) module = importlib.util.module_from_spec(spec) sys.modules[import_path] = module - logger.debug(f"loading {import_path}: {module}") + _default_logger.debug(f"loading {import_path}: {module}") spec.loader.exec_module(module) # type: ignore diff --git a/aea/components/loader.py b/aea/components/loader.py index c2a4ad8f0e..f9de80e60c 100644 --- a/aea/components/loader.py +++ b/aea/components/loader.py @@ -18,7 +18,6 @@ # ------------------------------------------------------------------------------ """This module contains utilities for loading components.""" -import logging import re from typing import Dict, Type @@ -31,9 +30,6 @@ from aea.skills.base import Skill -logger = logging.getLogger(__name__) - - def component_type_to_class(component_type: ComponentType) -> Type[Component]: """ Get the component class from the component type. diff --git a/aea/configurations/base.py b/aea/configurations/base.py index 75160b6424..0692d55eb6 100644 --- a/aea/configurations/base.py +++ b/aea/configurations/base.py @@ -34,6 +34,7 @@ Any, Collection, Dict, + FrozenSet, Generic, List, Optional, @@ -50,10 +51,11 @@ import semver from packaging.specifiers import SpecifierSet from packaging.version import Version +from urllib3.util import Url, parse_url from aea.__version__ import __version__ as __aea_version__ from aea.exceptions import enforce -from aea.helpers.base import recursive_update +from aea.helpers.base import RegexConstrainedString, recursive_update from aea.helpers.ipfs.base import IPFSHashOnly @@ -80,16 +82,172 @@ "contract.yaml", ] -Dependency = dict -""" -A dependency is a dictionary with the following (optional) keys: +DEFAULT_PYPI_INDEX_URL = "https://pypi.org/simple" +DEFAULT_GIT_REF = "master" + + +class PyPIPackageName(RegexConstrainedString): + """A PyPI Package name.""" + + REGEX = re.compile(r"^([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9._-]*[A-Za-z0-9])$") + + +class GitRef(RegexConstrainedString): + """ + A Git reference. + + It can be a branch name, a commit hash or a tag. + """ + + REGEX = re.compile(r"^[A-Za-z0-9/.\-_]+$") + + +class Dependency: + """ + This class represents a PyPI dependency. + + It contains the following information: - version: a version specifier(s) (e.g. '==0.1.0'). - index: the PyPI index where to download the package from (default: https://pypi.org) - git: the URL to the Git repository (e.g. https://github.com/fetchai/agents-aea.git) - ref: either the branch name, the tag, the commit number or a Git reference (default: 'master'.) -If the 'git' field is set, the 'version' field will be ignored. -These fields will be forwarded to the 'pip' command. -""" + + If the 'git' field is set, the 'version' field will be ignored. + These fields will be forwarded to the 'pip' command. + """ + + def __init__( + self, + name: Union[PyPIPackageName, str], + version: Union[str, SpecifierSet] = "", + index: Optional[Union[str, Url]] = None, + git: Optional[Union[str, Url]] = None, + ref: Optional[Union[GitRef, str]] = None, + ): + """ + Initialize a PyPI dependency. + + :param name: the package name. + :param version: the specifier set object + :param index: the URL to the PyPI server. + :param git: the URL to a git repository. + :param ref: the Git reference (branch/commit/tag). + """ + self._name: PyPIPackageName = PyPIPackageName(name) + self._version: SpecifierSet = self._parse_version(version) + self._index: Optional[Url] = self._parse_url( + index + ) if index is not None else None + self._git: Optional[Url] = self._parse_url(git) if git is not None else None + self._ref: Optional[GitRef] = GitRef(ref) if ref is not None else None + + @property + def name(self) -> str: + """Get the name.""" + return str(self._name) + + @property + def version(self) -> str: + """Get the version.""" + return str(self._version) + + @property + def index(self) -> Optional[str]: + """Get the index.""" + return str(self._index) if self._index else None + + @property + def git(self) -> Optional[str]: + """Get the git.""" + return str(self._git) if self._git else None + + @property + def ref(self) -> Optional[str]: + """Get the ref.""" + return str(self._ref) if self._ref else None + + @staticmethod + def _parse_version(version: Union[str, SpecifierSet]) -> SpecifierSet: + """ + Parse a version specifier set. + + :param version: the version, a string or a SpecifierSet instance. + :return: the SpecifierSet instance. + """ + return version if isinstance(version, SpecifierSet) else SpecifierSet(version) + + @staticmethod + def _parse_url(url: Union[str, Url]) -> Url: + """ + Parse an URL. + + :param url: the URL, in either string or an urllib3.Url instance. + :return: the urllib3.Url instance. + """ + return url if isinstance(url, Url) else parse_url(url) + + @classmethod + def from_json(cls, obj: Dict[str, Dict[str, str]]) -> "Dependency": + """Parse a dependency object from a dictionary.""" + if len(obj) != 1: + raise ValueError(f"Only one key allowed, found {set(obj.keys())}") + name, attributes = list(obj.items())[0] + allowed_keys = {"version", "index", "git", "ref"} + not_allowed_keys = set(attributes.keys()).difference(allowed_keys) + if len(not_allowed_keys) > 0: + raise ValueError(f"Not allowed keys: {not_allowed_keys}") + + version = attributes.get("version", "") + index = attributes.get("index", None) + git = attributes.get("git", None) + ref = attributes.get("ref", None) + + return Dependency(name=name, version=version, index=index, git=git, ref=ref) + + def to_json(self) -> Dict[str, Dict[str, str]]: + """Transform the object to JSON.""" + result = {} + if self.version != "": + result["version"] = self.version + if self.index is not None: + result["index"] = self.index + if self.git is not None: + result["git"] = cast(str, self.git) + if self.ref is not None: + result["ref"] = cast(str, self.ref) + return {self.name: result} + + def get_pip_install_args(self) -> List[str]: + """Get 'pip install' arguments.""" + name = self.name + index = self.index + git_url = self.git + revision = self.ref if self.ref is not None else DEFAULT_GIT_REF + version_constraint = str(self.version) + command: List[str] = [] + if index is not None: + command += ["-i", index] + if git_url is not None: + command += ["git+" + git_url + "@" + revision + "#egg=" + name] + else: + command += [name + version_constraint] + return command + + def __str__(self) -> str: + """Get the string representation.""" + return f"{self.__class__.__name__}(name='{self.name}', version='{self.version}', index='{self.index}', git='{self.git}', ref='{self.ref}')" + + def __eq__(self, other): + """Compare with another object.""" + return ( + isinstance(other, Dependency) + and self._name == other._name + and self._version == other._version + and self._index == other._index + and self._git == other._git + and self._ref == other._ref + ) + Dependencies = Dict[str, Dependency] """ @@ -100,6 +258,36 @@ We cannot have two items with the same package name since the keys of a YAML object form a set. """ + +def dependencies_from_json(obj: Dict[str, Dict]) -> Dependencies: + """ + Parse a JSON object to get an instance of Dependencies. + + :param obj: a dictionary whose keys are package names and values are dictionary with package specifications. + :return: a Dependencies object. + """ + return {key: Dependency.from_json({key: value}) for key, value in obj.items()} + + +def dependencies_to_json(dependencies: Dependencies) -> Dict[str, Dict]: + """ + Transform a Dependencies object into a JSON object. + + :param dependencies: an instance of "Dependencies" type. + :return: a dictionary whose keys are package names and + values are the JSON version of a Dependency object. + """ + result = {} + for key, value in dependencies.items(): + dep_to_json = value.to_json() + package_name = list(dep_to_json.items())[0][0] + enforce( + key == package_name, f"Names of dependency differ: {key} != {package_name}" + ) + result[key] = dep_to_json[key] + return result + + VersionInfoClass = semver.VersionInfo PackageVersionLike = Union[str, semver.VersionInfo] @@ -225,6 +413,16 @@ def to_configuration_type(self) -> PackageType: """Get package type for component type.""" return PackageType(self.value) + @staticmethod + def plurals() -> Collection[str]: + """ + Get the collection of type names, plural. + + >>> ComponentType.plurals() + ['protocols', 'connections', 'skills', 'contracts'] + """ + return list(map(lambda x: x.to_plural(), ComponentType)) + def to_plural(self) -> str: """ Get the plural version of the component type. @@ -444,6 +642,17 @@ def to_latest(self) -> "PublicId": """Return the same public id, but with latest version.""" return PublicId(self.author, self.name, self.LATEST_VERSION) + @classmethod + def is_valid_str(cls, public_id_string: str) -> bool: + """ + Check if a string is a public id. + + :param public_id_string: the public id in string format. + :return: bool indicating validity + """ + match = re.match(cls.PUBLIC_ID_REGEX, public_id_string) + return match is not None + @classmethod def from_str(cls, public_id_string: str) -> "PublicId": """ @@ -676,6 +885,10 @@ def __str__(self): package_type=self.package_type.value, public_id=self.public_id, ) + def __repr__(self): + """Get the object representation in string.""" + return f"PackageId{self.__str__()}" + def __eq__(self, other): """Compare with another object.""" return ( @@ -759,6 +972,7 @@ class PackageConfiguration(Configuration, ABC): default_configuration_filename: str package_type: PackageType + FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset() def __init__( self, @@ -873,12 +1087,7 @@ def __init__( fingerprint, fingerprint_ignore_patterns, ) - self._pypi_dependencies = dependencies if dependencies is not None else {} - - @property - def pypi_dependencies(self) -> Dependencies: - """Get PyPI dependencies.""" - return self._pypi_dependencies + self.pypi_dependencies: Dependencies = dependencies if dependencies is not None else {} @property def component_type(self) -> ComponentType: @@ -936,6 +1145,8 @@ class ConnectionConfig(ComponentConfiguration): default_configuration_filename = DEFAULT_CONNECTION_CONFIG_FILE package_type = PackageType.CONNECTION + FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["config"]) + def __init__( self, name: str = "", @@ -1026,7 +1237,7 @@ def json(self) -> Dict: "restricted_to_protocols": sorted( map(str, self.restricted_to_protocols) ), - "dependencies": self.dependencies, + "dependencies": dependencies_to_json(self.dependencies), } ) @@ -1039,7 +1250,7 @@ def from_json(cls, obj: Dict): } excluded_protocols = obj.get("excluded_protocols", set()) excluded_protocols = {PublicId.from_str(id_) for id_ in excluded_protocols} - dependencies = obj.get("dependencies", {}) + dependencies = dependencies_from_json(obj.get("dependencies", {})) protocols = {PublicId.from_str(id_) for id_ in obj.get("protocols", set())} return ConnectionConfig( name=cast(str, obj.get("name")), @@ -1079,6 +1290,8 @@ class ProtocolConfig(ComponentConfiguration): default_configuration_filename = DEFAULT_PROTOCOL_CONFIG_FILE package_type = PackageType.PROTOCOL + FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset() + def __init__( self, name: str, @@ -1119,14 +1332,14 @@ def json(self) -> Dict: "aea_version": self.aea_version, "fingerprint": self.fingerprint, "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, - "dependencies": self.dependencies, + "dependencies": dependencies_to_json(self.dependencies), } ) @classmethod def from_json(cls, obj: Dict): """Initialize from a JSON object.""" - dependencies = cast(Dependencies, obj.get("dependencies", {})) + dependencies = dependencies_from_json(obj.get("dependencies", {})) return ProtocolConfig( name=cast(str, obj.get("name")), author=cast(str, obj.get("author")), @@ -1174,6 +1387,14 @@ class SkillConfig(ComponentConfiguration): default_configuration_filename = DEFAULT_SKILL_CONFIG_FILE package_type = PackageType.SKILL + FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset( + ["behaviours", "handlers", "models", "is_abstract"] + ) + FIELDS_WITH_NESTED_FIELDS: FrozenSet[str] = frozenset( + ["behaviours", "handlers", "models"] + ) + NESTED_FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset(["args"]) + def __init__( self, name: str, @@ -1256,13 +1477,10 @@ def json(self) -> Dict: "behaviours": {key: b.json for key, b in self.behaviours.read_all()}, "handlers": {key: h.json for key, h in self.handlers.read_all()}, "models": {key: m.json for key, m in self.models.read_all()}, - "dependencies": self.dependencies, + "dependencies": dependencies_to_json(self.dependencies), "is_abstract": self.is_abstract, } ) - if result["is_abstract"] is False: - result.pop("is_abstract") - return result @classmethod @@ -1288,7 +1506,7 @@ def from_json(cls, obj: Dict): skills = cast( List[PublicId], [PublicId.from_str(id_) for id_ in obj.get("skills", [])], ) - dependencies = cast(Dependencies, obj.get("dependencies", {})) + dependencies = dependencies_from_json(obj.get("dependencies", {})) description = cast(str, obj.get("description", "")) skill_config = SkillConfig( name=name, @@ -1352,6 +1570,14 @@ def _update_skill_component_config(type_plural: str, data: Dict): component_config = cast( SkillComponentConfiguration, registry.read(component_name) ) + component_data_keys = set(component_data.keys()) + unallowed_keys = component_data_keys.difference( + SkillConfig.NESTED_FIELDS_ALLOWED_TO_UPDATE + ) + if len(unallowed_keys) > 0: + raise ValueError( + f"These fields of skill component configuration '{component_name}' of skill '{self.public_id}' are not allowed to change: {unallowed_keys}." + ) recursive_update(component_config.args, component_data.get("args", {})) _update_skill_component_config("behaviours", data) @@ -1366,6 +1592,27 @@ class AgentConfig(PackageConfiguration): default_configuration_filename = DEFAULT_AEA_CONFIG_FILE package_type = PackageType.AGENT + FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset( + [ + "description", + "registry_path", + "logging_config", + "private_key_paths", + "connection_private_key_paths", + "loop_mode", + "runtime_mode", + "execution_timeout", + "timeout", + "period", + "max_reactions", + "skill_exception_policy", + "connection_exception_policy", + "default_connection", + "default_ledger", + "default_routing", + ] + ) + def __init__( self, agent_name: str, @@ -1438,6 +1685,7 @@ def __init__( ) # type: Dict[PublicId, PublicId] self.loop_mode = loop_mode self.runtime_mode = runtime_mode + # this attribute will be set through the setter below self._component_configurations: Dict[ComponentId, Dict] = {} self.component_configurations = ( component_configurations if component_configurations is not None else {} @@ -1457,12 +1705,19 @@ def component_configurations(self, d: Dict[ComponentId, Dict]) -> None: PackageType.CONTRACT: self.contracts, PackageType.SKILL: self.skills, } - for component_id, _ in d.items(): + for component_id, component_configuration in d.items(): enforce( component_id.public_id in package_type_to_set[component_id.package_type], f"Component {component_id} not declared in the agent configuration.", ) + from aea.configurations.loader import ( # pylint: disable=import-outside-toplevel,cyclic-import + ConfigLoader, + ) + + ConfigLoader.validate_component_configuration( + component_id, component_configuration + ) self._component_configurations = d @property @@ -1863,6 +2118,8 @@ class ContractConfig(ComponentConfiguration): default_configuration_filename = DEFAULT_CONTRACT_CONFIG_FILE package_type = PackageType.CONTRACT + FIELDS_ALLOWED_TO_UPDATE: FrozenSet[str] = frozenset([]) + def __init__( self, name: str, @@ -1943,14 +2200,16 @@ def json(self) -> Dict: "fingerprint_ignore_patterns": self.fingerprint_ignore_patterns, "class_name": self.class_name, "contract_interface_paths": self.contract_interface_paths, - "dependencies": self.dependencies, + "dependencies": dependencies_to_json(self.dependencies), } ) @classmethod def from_json(cls, obj: Dict): """Initialize from a JSON object.""" - dependencies = cast(Dependencies, obj.get("dependencies", {})) + dependencies = cast( + Dependencies, dependencies_from_json(obj.get("dependencies", {})) + ) return ContractConfig( name=cast(str, obj.get("name")), author=cast(str, obj.get("author")), diff --git a/aea/configurations/constants.py b/aea/configurations/constants.py index 169bb58945..435a026a02 100644 --- a/aea/configurations/constants.py +++ b/aea/configurations/constants.py @@ -26,13 +26,13 @@ from aea.crypto.helpers import PRIVATE_KEY_PATH_SCHEMA -DEFAULT_CONNECTION = PublicId.from_str("fetchai/stub:0.10.0") -DEFAULT_PROTOCOL = PublicId.from_str("fetchai/default:0.6.0") -DEFAULT_SKILL = PublicId.from_str("fetchai/error:0.6.0") +DEFAULT_CONNECTION = PublicId.from_str("fetchai/stub:0.11.0") +DEFAULT_PROTOCOL = PublicId.from_str("fetchai/default:0.7.0") +DEFAULT_SKILL = PublicId.from_str("fetchai/error:0.7.0") DEFAULT_LEDGER = FetchAICrypto.identifier DEFAULT_PRIVATE_KEY_FILE = PRIVATE_KEY_PATH_SCHEMA.format(DEFAULT_LEDGER) DEFAULT_REGISTRY_PATH = DRP DEFAULT_LICENSE = DL -SIGNING_PROTOCOL = PublicId.from_str("fetchai/signing:0.4.0") -STATE_UPDATE_PROTOCOL = PublicId.from_str("fetchai/state_update:0.4.0") +SIGNING_PROTOCOL = PublicId.from_str("fetchai/signing:0.5.0") +STATE_UPDATE_PROTOCOL = PublicId.from_str("fetchai/state_update:0.5.0") LOCAL_PROTOCOLS = [DEFAULT_PROTOCOL, SIGNING_PROTOCOL, STATE_UPDATE_PROTOCOL] diff --git a/aea/configurations/loader.py b/aea/configurations/loader.py index 2779585959..608c643d40 100644 --- a/aea/configurations/loader.py +++ b/aea/configurations/loader.py @@ -22,7 +22,6 @@ import inspect import json import os -import re from copy import deepcopy from pathlib import Path from typing import Dict, Generic, List, TextIO, Type, TypeVar, Union, cast @@ -30,7 +29,6 @@ import jsonschema import yaml from jsonschema import Draft4Validator -from yaml import SafeLoader from aea.configurations.base import ( AgentConfig, @@ -45,7 +43,7 @@ PublicId, SkillConfig, ) -from aea.helpers.base import yaml_dump, yaml_dump_all, yaml_load, yaml_load_all +from aea.helpers.yaml_utils import yaml_dump, yaml_dump_all, yaml_load, yaml_load_all _CUR_DIR = os.path.dirname(inspect.getfile(inspect.currentframe())) # type: ignore @@ -194,14 +192,29 @@ def load_protocol_specification(self, file_pointer: TextIO) -> T: def validate(self, json_data: Dict) -> None: """ - Validate a JSON object. + Validate a JSON object against the right JSON schema. :param json_data: the JSON data. :return: None. """ if self.configuration_class.package_type == PackageType.AGENT: json_data_copy = deepcopy(json_data) - json_data_copy.pop("component_configurations", None) + + # validate component_configurations + component_configurations = json_data_copy.pop( + "component_configurations", {} + ) + for idx, component_configuration_json in enumerate( + component_configurations + ): + component_id = self._split_component_id_and_config( + idx, component_configuration_json + ) + self.validate_component_configuration( + component_id, component_configuration_json + ) + + # validate agent config self._validator.validate(instance=json_data_copy) else: self._validator.validate(instance=json_data) @@ -237,26 +250,6 @@ def from_configuration_type( configuration_type = PackageType(configuration_type) return ConfigLoaders.from_package_type(configuration_type) - def _validate(self, json_data: Dict) -> None: - """ - Validate a configuration file. - - :param json_data: the JSON object of the configuration file to validate. - :return: None - :raises ValidationError: if the file doesn't comply with the JSON schema. - | ValueError: if other consistency checks fail. - """ - # this might raise ValidationError. - self.validate(json_data) - - expected_type = self.configuration_class.package_type - if expected_type != PackageType.AGENT and "type" in json_data: - actual_type = PackageType(json_data["type"]) - if expected_type != actual_type: - raise ValueError( - f"The field type is not correct: expected {expected_type}, found {actual_type}." - ) - def _load_component_config(self, file_pointer: TextIO) -> T: """Load a component configuration.""" configuration_file_json = yaml_load(file_pointer) @@ -264,7 +257,7 @@ def _load_component_config(self, file_pointer: TextIO) -> T: def _load_from_json(self, configuration_file_json: Dict) -> T: """Load component configuration from JSON object.""" - self._validate(configuration_file_json) + self.validate(configuration_file_json) key_order = list(configuration_file_json.keys()) configuration_obj = self.configuration_class.from_json(configuration_file_json) configuration_obj._key_order = key_order # pylint: disable=protected-access @@ -283,7 +276,7 @@ def load_agent_config_from_json( if len(configuration_json) == 0: raise ValueError("Agent configuration file was empty.") agent_config_json = configuration_json[0] - self._validate(agent_config_json) + self.validate(agent_config_json) key_order = list(agent_config_json.keys()) agent_configuration_obj = cast( AgentConfig, self.configuration_class.from_json(agent_config_json) @@ -332,8 +325,8 @@ def _dump_agent_config( ) -> None: """Dump agent configuration.""" agent_config_part = configuration.ordered_json + self.validate(agent_config_part) agent_config_part.pop("component_configurations") - self.validator.validate(instance=agent_config_part) result = [agent_config_part] + configuration.component_configurations_json() yaml_dump_all(result, file_pointer) @@ -361,7 +354,7 @@ def _process_component_section( component_id = self._split_component_id_and_config( component_index, component_configuration_json ) - self._validate_component_configuration( + self.validate_component_configuration( component_id, component_configuration_json ) return component_id @@ -397,7 +390,7 @@ def _split_component_id_and_config( return component_id @staticmethod - def _validate_component_configuration( + def validate_component_configuration( component_id: ComponentId, configuration: Dict ) -> None: """ @@ -419,7 +412,7 @@ def _validate_component_configuration( ) except jsonschema.ValidationError as e: raise ValueError( - f"Configuration of component {component_id} is not valid." + f"Configuration of component {component_id} is not valid. {e}" ) from e @@ -502,26 +495,3 @@ def _load_configuration_object( ) ) return configuration_object - - -def _config_loader(): - envvar_matcher = re.compile(r"\${([^}^{]+)\}") - - def envvar_constructor(_loader, node): # pragma: no cover - """Extract the matched value, expand env variable, and replace the match.""" - node_value = node.value - match = envvar_matcher.match(node_value) - env_var = match.group()[2:-1] - - # check for defaults - var_name, default_value = env_var.split(":") - var_name = var_name.strip() - default_value = default_value.strip() - var_value = os.getenv(var_name, default_value) - return var_value + node_value[match.end() :] - - yaml.add_implicit_resolver("!envvar", envvar_matcher, None, SafeLoader) - yaml.add_constructor("!envvar", envvar_constructor, SafeLoader) - - -_config_loader() diff --git a/aea/configurations/project.py b/aea/configurations/project.py index 11e6631807..5fcfc53445 100644 --- a/aea/configurations/project.py +++ b/aea/configurations/project.py @@ -22,6 +22,7 @@ from typing import Dict, List, Set from aea.aea import AEA +from aea.aea_builder import AEABuilder from aea.cli.registry.fetch import fetch_agent from aea.cli.utils.context import Context from aea.configurations.base import PublicId @@ -38,7 +39,7 @@ def __init__(self, public_id: PublicId, path: str): @classmethod def load(cls, working_dir: str, public_id: PublicId) -> "Project": - """Load project with given pubblic_id to working_dir.""" + """Load project with given public_id to working_dir.""" ctx = Context(cwd=working_dir) path = os.path.join(working_dir, public_id.author, public_id.name) fetch_agent( @@ -55,13 +56,19 @@ class AgentAlias: """Agent alias representation.""" def __init__( - self, project: Project, agent_name: str, config: List[Dict], agent: AEA + self, + project: Project, + agent_name: str, + config: List[Dict], + agent: AEA, + builder: AEABuilder, ): - """Init agent alias with project, config, name, agent.""" + """Init agent alias with project, config, name, agent, builder.""" self.project = project self.config = config self.agent_name = agent_name self.agent = agent + self.builder = builder self.project.agents.add(self.agent_name) def remove_from_project(self): diff --git a/aea/configurations/pypi.py b/aea/configurations/pypi.py index 5dd0bd0953..4324c79ec1 100644 --- a/aea/configurations/pypi.py +++ b/aea/configurations/pypi.py @@ -21,7 +21,7 @@ """This module contains a checker for PyPI version consistency.""" import operator from collections import defaultdict -from typing import Dict, Set, cast +from typing import Dict, List, Set, cast from packaging.specifiers import Specifier, SpecifierSet from packaging.version import InvalidVersion, Version @@ -34,6 +34,47 @@ def and_(s1: SpecifierSet, s2: SpecifierSet): return operator.and_(s1, s2) +def _handle_compatibility_operator( + all_specifiers: List[Specifier], + operator_to_specifiers: Dict[str, Set[Specifier]], + specifier: Specifier, +) -> None: + """ + Handle a specifier with operator '~='. + + Split specifier of the form "~=" in two specifiers: + - >= + - < + Also handle micro-numbers, i.e. "~=" + (see last examples of https://www.python.org/dev/peps/pep-0440/#compatible-release) + + :param all_specifiers: the list of all specifiers (to be populated). + :param operator_to_specifiers: a mapping from operator to specifiers (to be populated). + :param specifier: the specifier to process. + :return: None + """ + spec_version = Version(specifier.version) + base_version = spec_version.base_version + parts = base_version.split(".") + index_to_update = -2 + if ( + spec_version.is_prerelease + or spec_version.is_devrelease + or spec_version.is_postrelease + ): + # if it is a pre-release, ignore the suffix. + index_to_update += 1 + parts = parts[:-1] + # bump second-to-last part + parts[index_to_update] = str(int(parts[index_to_update]) + 1) + upper_version = Version(".".join(parts)) + spec_1 = Specifier(">=" + str(spec_version)) + spec_2 = Specifier("<" + str(upper_version)) + all_specifiers.extend([spec_1, spec_2]) + operator_to_specifiers[spec_1.operator].add(spec_1) + operator_to_specifiers[spec_2.operator].add(spec_2) + + def is_satisfiable(specifier_set: SpecifierSet) -> bool: """ Check if the specifier set is satisfiable. @@ -68,7 +109,7 @@ def is_satisfiable(specifier_set: SpecifierSet) -> bool: :return: False if the constraints are surely non-satisfiable, True if we don't know. """ # group single specifiers by operator - all_specifiers = [] + all_specifiers: List[Specifier] = [] operator_to_specifiers: Dict[str, Set[Specifier]] = defaultdict(set) # pre-processing for specifier in list(specifier_set): @@ -80,20 +121,11 @@ def is_satisfiable(specifier_set: SpecifierSet) -> bool: except InvalidVersion: continue - # split specifier "~=" in two specifiers: - # - >= - # - < - # this is not the full story. we should check the version number - # up to the last zero, which might be the micro number. - # e.g. see last examples of https://www.python.org/dev/peps/pep-0440/#compatible-release + # handle specifiers with '~=' operators. if specifier.operator == "~=": - spec_version = Version(specifier.version) - upper_major_version = Version(str(spec_version.major + 1)) - spec_1 = Specifier(">=" + str(spec_version)) - spec_2 = Specifier("<" + str(upper_major_version)) - all_specifiers.extend([spec_1, spec_2]) - operator_to_specifiers[spec_1.operator].add(spec_1) - operator_to_specifiers[spec_2.operator].add(spec_2) + _handle_compatibility_operator( + all_specifiers, operator_to_specifiers, specifier + ) else: all_specifiers.append(specifier) operator_to_specifiers[specifier.operator].add(specifier) @@ -192,12 +224,12 @@ def is_simple_dep(dep: Dependency) -> bool: :param dep: the dependency :return: whether it is a simple dependency or not """ - return len(dep) == 0 or len(dep) == 1 and "version" in dep + return dep.index is None and dep.git is None def to_set_specifier(dep: Dependency) -> SpecifierSet: """Get the set specifier. It assumes to be a simple dependency (see above).""" - return dep["version"] + return SpecifierSet(dep.version) def merge_dependencies(dep1: Dependencies, dep2: Dependencies) -> Dependencies: @@ -216,9 +248,20 @@ def merge_dependencies(dep1: Dependencies, dep2: Dependencies) -> Dependencies: for pkg_name, info in dep2.items(): if not is_simple_dep(info): continue - new_specifier = SpecifierSet(info.get("version", "")) - old_specifier = SpecifierSet(result.get(pkg_name, {}).get("version", "")) + new_specifier = SpecifierSet(info.version) + old_specifier = ( + SpecifierSet(result[pkg_name].version) + if pkg_name in result + else SpecifierSet("") + ) combined_specifier = and_(new_specifier, old_specifier) - result[pkg_name] = {"version": str(combined_specifier)} + new_info = Dependency( + name=info.name, + version=combined_specifier, + index=info.index, + git=info.git, + ref=info.ref, + ) + result[pkg_name] = new_info return result diff --git a/aea/configurations/schemas/aea-config_schema.json b/aea/configurations/schemas/aea-config_schema.json index f82b322c8d..cd428abe2f 100644 --- a/aea/configurations/schemas/aea-config_schema.json +++ b/aea/configurations/schemas/aea-config_schema.json @@ -130,7 +130,7 @@ "loop_mode": { "$ref": "definitions.json#/definitions/loop_mode" }, - "runtime_mode": { + "runtime_mode": { "$ref": "definitions.json#/definitions/runtime_mode" } } diff --git a/aea/configurations/schemas/connection-config_schema.json b/aea/configurations/schemas/connection-config_schema.json index 50c9d9d5e2..011f96bf3b 100644 --- a/aea/configurations/schemas/connection-config_schema.json +++ b/aea/configurations/schemas/connection-config_schema.json @@ -7,6 +7,7 @@ "name", "author", "version", + "type", "license", "aea_version", "class_name", @@ -24,7 +25,7 @@ "$ref": "definitions.json#/definitions/package_version" }, "type": { - "$ref": "definitions.json#/definitions/component_type" + "enum": ["connection"] }, "license": { "$ref": "definitions.json#/definitions/license" diff --git a/aea/configurations/schemas/contract-config_schema.json b/aea/configurations/schemas/contract-config_schema.json index 0ad8333401..cf5766141e 100644 --- a/aea/configurations/schemas/contract-config_schema.json +++ b/aea/configurations/schemas/contract-config_schema.json @@ -7,6 +7,7 @@ "name", "author", "version", + "type", "license", "aea_version", "class_name" @@ -22,7 +23,7 @@ "$ref": "definitions.json#/definitions/package_version" }, "type": { - "$ref": "definitions.json#/definitions/component_type" + "enum": ["contract"] }, "license": { "$ref": "definitions.json#/definitions/license" @@ -51,9 +52,6 @@ } } }, - "path_to_contract_interface": { - "type": "string" - }, "class_name": { "type": "string" } diff --git a/aea/configurations/schemas/protocol-config_schema.json b/aea/configurations/schemas/protocol-config_schema.json index ba34921514..a6f23f63c6 100644 --- a/aea/configurations/schemas/protocol-config_schema.json +++ b/aea/configurations/schemas/protocol-config_schema.json @@ -7,6 +7,7 @@ "name", "author", "version", + "type", "license", "aea_version" ], @@ -21,7 +22,7 @@ "$ref": "definitions.json#/definitions/package_version" }, "type": { - "$ref": "definitions.json#/definitions/component_type" + "enum": ["protocol"] }, "license": { "$ref": "definitions.json#/definitions/license" diff --git a/aea/configurations/schemas/skill-config_schema.json b/aea/configurations/schemas/skill-config_schema.json index 69f0532f36..63d8319d84 100644 --- a/aea/configurations/schemas/skill-config_schema.json +++ b/aea/configurations/schemas/skill-config_schema.json @@ -7,6 +7,7 @@ "name", "author", "version", + "type", "license", "aea_version", "protocols", @@ -24,7 +25,7 @@ "$ref": "definitions.json#/definitions/package_version" }, "type": { - "$ref": "definitions.json#/definitions/component_type" + "enum": ["skill"] }, "license": { "$ref": "definitions.json#/definitions/license" diff --git a/aea/connections/base.py b/aea/connections/base.py index a34afba31e..3f6b9b137b 100644 --- a/aea/connections/base.py +++ b/aea/connections/base.py @@ -20,7 +20,6 @@ """The base connection package.""" import asyncio import inspect -import logging import re from abc import ABC, abstractmethod from contextlib import contextmanager @@ -35,6 +34,7 @@ from aea.exceptions import enforce from aea.helpers.async_utils import AsyncState from aea.helpers.base import load_module +from aea.helpers.logging import get_logger from aea.identity.base import Identity @@ -42,9 +42,6 @@ from aea.mail.base import Address, Envelope # pragma: no cover -logger = logging.getLogger(__name__) - - class ConnectionStates(Enum): """Connection states enum.""" @@ -66,7 +63,7 @@ def __init__( crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, - **kwargs + **kwargs, ): """ Initialize the connection. @@ -109,6 +106,22 @@ def _ensure_connected(self) -> None: # pragma: nocover if not self.is_connected: raise ConnectionError("Connection is not connected! Connect first!") + @staticmethod + def _ensure_valid_envelope_for_external_comms(envelope: "Envelope") -> None: + """ + Ensure the envelope sender and to are valid addresses for agent-to-agent communication. + + :param envelope: the envelope + """ + enforce( + not envelope.is_sender_public_id, + f"Sender field of envelope is public id, needs to be address. Found={envelope.sender}", + ) + enforce( + not envelope.is_to_public_id, + f"To field of envelope is public id, needs to be address. Found={envelope.to}", + ) + @contextmanager def _connect_context(self) -> Generator: """Set state connecting, disconnecteing, dicsconnected during connect method.""" @@ -221,7 +234,7 @@ def from_config( configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, - **kwargs + **kwargs, ) -> "Connection": """ Load a connection from a configuration. @@ -248,6 +261,7 @@ def from_config( filter(lambda x: re.match(connection_class_name, x[0]), classes) ) name_to_class = dict(connection_classes) + logger = get_logger(__name__, identity.name) logger.debug("Processing connection {}".format(connection_class_name)) connection_class = name_to_class.get(connection_class_name, None) enforce( @@ -258,7 +272,7 @@ def from_config( configuration=configuration, identity=identity, crypto_store=crypto_store, - **kwargs + **kwargs, ) @property diff --git a/aea/connections/stub/connection.py b/aea/connections/stub/connection.py index 63f718e25b..7ae029bbb9 100644 --- a/aea/connections/stub/connection.py +++ b/aea/connections/stub/connection.py @@ -36,7 +36,7 @@ from aea.mail.base import Envelope -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger("aea.packages.fetchai.connections.stub") INPUT_FILE_KEY = "input_file" OUTPUT_FILE_KEY = "output_file" @@ -44,7 +44,7 @@ DEFAULT_OUTPUT_FILE_NAME = "./output_file" SEPARATOR = b"," -PUBLIC_ID = PublicId.from_str("fetchai/stub:0.10.0") +PUBLIC_ID = PublicId.from_str("fetchai/stub:0.11.0") def _encode(e: Envelope, separator: bytes = SEPARATOR): @@ -71,7 +71,7 @@ def _decode(e: bytes, separator: bytes = SEPARATOR): ) ) - to = split[0].decode("utf-8").strip() + to = split[0].decode("utf-8").strip().lstrip("\x00") sender = split[1].decode("utf-8").strip() protocol_id = PublicId.from_str(split[2].decode("utf-8").strip()) # protobuf messages cannot be delimited as they can contain an arbitrary byte sequence; however @@ -89,7 +89,8 @@ def lock_file(file_descriptor: IO[bytes]): :param file_descriptor: file descriptio of file to lock. """ with exception_log_and_reraise( - logger.error, f"Couldn't acquire lock for file {file_descriptor.name}: {{}}" + _default_logger.error, + f"Couldn't acquire lock for file {file_descriptor.name}: {{}}", ): file_lock.lock(file_descriptor, file_lock.LOCK_EX) @@ -102,7 +103,7 @@ def lock_file(file_descriptor: IO[bytes]): def write_envelope(envelope: Envelope, file_pointer: IO[bytes]) -> None: """Write envelope to file.""" encoded_envelope = _encode(envelope, separator=SEPARATOR) - logger.debug("write {}: to {}".format(encoded_envelope, file_pointer.name)) + _default_logger.debug("write {}: to {}".format(encoded_envelope, file_pointer.name)) write_with_lock(file_pointer, encoded_envelope) @@ -122,14 +123,16 @@ def _process_line(line: bytes) -> Optional[Envelope]: :return: Envelope :raise: Exception """ - logger.debug("processing: {!r}".format(line)) + _default_logger.debug("processing: {!r}".format(line)) envelope = None # type: Optional[Envelope] try: envelope = _decode(line, separator=SEPARATOR) except ValueError as e: - logger.error("Bad formatted line: {!r}. {}".format(line, e)) + _default_logger.error("Bad formatted line: {!r}. {}".format(line, e)) except Exception as e: # pragma: nocover # pylint: disable=broad-except - logger.exception("Error when processing a line. Message: {}".format(str(e))) + _default_logger.exception( + "Error when processing a line. Message: {}".format(str(e)) + ) return envelope @@ -219,7 +222,7 @@ async def read_envelopes(self) -> None: if self.in_queue is None: # pragma: nocover raise ValueError("Input queue not initialized.") - logger.debug("Read messages!") + self.logger.debug("Read messages!") async for data in self._file_read_and_trunc(delay=self.read_delay): lines = self._split_messages(data) for line in lines: @@ -228,7 +231,7 @@ async def read_envelopes(self) -> None: if envelope is None: continue - logger.debug(f"Add envelope {envelope}") + self.logger.debug(f"Add envelope {envelope}") await self.in_queue.put(envelope) @classmethod @@ -246,7 +249,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: """Receive an envelope.""" self._ensure_connected() if self.in_queue is None: # pragma: nocover - logger.error("Input queue not initialized.") + self.logger.error("Input queue not initialized.") return None try: @@ -255,7 +258,7 @@ async def receive(self, *args, **kwargs) -> Optional["Envelope"]: self.logger.debug("Receive cancelled.") return None except Exception: # pylint: disable=broad-except - logger.exception("Stub connection receive error:") + self.logger.exception("Stub connection receive error:") return None async def connect(self) -> None: @@ -284,7 +287,7 @@ async def _stop_read_envelopes(self) -> None: except CancelledError: pass # task was cancelled, that was expected except BaseException: # pragma: nocover # pylint: disable=broad-except - logger.exception( + self.logger.exception( "during envelop read" ) # do not raise exception cause it's on task stop @@ -313,6 +316,7 @@ async def send(self, envelope: Envelope) -> None: :return: None """ self._ensure_connected() + self._ensure_valid_envelope_for_external_comms(envelope) await self.loop.run_in_executor( self._write_pool, write_envelope, envelope, self.output_file ) diff --git a/aea/connections/stub/connection.yaml b/aea/connections/stub/connection.yaml index 4f7c8c820c..015ce263bd 100644 --- a/aea/connections/stub/connection.yaml +++ b/aea/connections/stub/connection.yaml @@ -1,6 +1,6 @@ name: stub author: fetchai -version: 0.10.0 +version: 0.11.0 type: connection description: The stub connection implements a connection stub which reads/writes messages from/to file. @@ -8,8 +8,8 @@ license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: __init__.py: QmWwepN9Fy9gHAp39vUGFSLdnB9JZjdyE3STnbowSUhJkC - connection.py: QmRSc6Edy9u9yQMRtvs7MZeq1NRDT57gRy6T19QVLdCkus - readme.md: QmQ2Y2sk2xnH45KzxdMna1pUXDGQ5S32fJk1qwdTSfAGc2 + connection.py: QmRRre8r1yAj2w7EPp7hMc53zkc74PUfSMf4Cuwpzds4Q2 + readme.md: QmTac8uG5sT9igmMhfUbWMpyNUG5Dsj3JJZBBdfT1wTfvA fingerprint_ignore_patterns: [] protocols: [] class_name: StubConnection diff --git a/aea/connections/stub/readme.md b/aea/connections/stub/readme.md index 177d94c9ef..5db901637e 100644 --- a/aea/connections/stub/readme.md +++ b/aea/connections/stub/readme.md @@ -2,6 +2,6 @@ A simple connection for communication with an AEA, using the file system as a point of data exchange. ## Usage -First, add the connection to your AEA project: `aea add connection fetchai/stub:0.10.0`. (If you have created your AEA project with `aea create` then the connection will already be available by default.) +First, add the connection to your AEA project: `aea add connection fetchai/stub:0.11.0`. (If you have created your AEA project with `aea create` then the connection will already be available by default.) Optionally, in the `connection.yaml` file under `config` set the `input_file` and `output_file` to the desired file path. The `stub` connection reads encoded envelopes from the `input_file` and writes encoded envelopes to the `output_file`. diff --git a/aea/contracts/base.py b/aea/contracts/base.py index 21b34fd78b..df55fb103c 100644 --- a/aea/contracts/base.py +++ b/aea/contracts/base.py @@ -34,7 +34,7 @@ contract_registry: Registry["Contract"] = Registry["Contract"]() -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class Contract(Component): @@ -113,7 +113,7 @@ def from_config(cls, configuration: ContractConfig, **kwargs) -> "Contract": filter(lambda x: re.match(contract_class_name, x[0]), classes) ) name_to_class = dict(contract_classes) - logger.debug(f"Processing contract {contract_class_name}") + _default_logger.debug(f"Processing contract {contract_class_name}") contract_class = name_to_class.get(contract_class_name, None) enforce( contract_class is not None, @@ -197,11 +197,13 @@ def get_state( def _try_to_register_contract(configuration: ContractConfig): """Register a contract to the registry.""" if str(configuration.public_id) in contract_registry.specs: # pragma: nocover - logger.warning( + _default_logger.warning( f"Skipping registration of contract {configuration.public_id} since already registered." ) return - logger.debug(f"Registering contract {configuration.public_id}") # pragma: nocover + _default_logger.debug( + f"Registering contract {configuration.public_id}" + ) # pragma: nocover try: # pragma: nocover contract_registry.register( id_=str(configuration.public_id), @@ -211,6 +213,8 @@ def _try_to_register_contract(configuration: ContractConfig): ) except AEAException as e: # pragma: nocover if "Cannot re-register id:" in str(e): - logger.warning("Already registered: {}".format(configuration.class_name)) + _default_logger.warning( + "Already registered: {}".format(configuration.class_name) + ) else: raise e diff --git a/aea/crypto/base.py b/aea/crypto/base.py index 2f70e8513b..d39912b205 100644 --- a/aea/crypto/base.py +++ b/aea/crypto/base.py @@ -212,6 +212,15 @@ def get_hash(message: bytes) -> str: :return: the hash of the message. """ + @classmethod + @abstractmethod + def is_valid_address(cls, address: Address) -> bool: + """ + Check if the address is valid. + + :param address: the address to validate + """ + class LedgerApi(Helper, ABC): """Interface for ledger APIs.""" diff --git a/aea/crypto/cosmos.py b/aea/crypto/cosmos.py index 59533cd3d9..cd81b7cb19 100644 --- a/aea/crypto/cosmos.py +++ b/aea/crypto/cosmos.py @@ -32,7 +32,7 @@ from typing import Any, BinaryIO, Dict, List, Optional, Tuple import requests -from bech32 import bech32_encode, convertbits +from bech32 import bech32_decode, bech32_encode, convertbits from ecdsa import SECP256k1, SigningKey, VerifyingKey from ecdsa.util import sigencode_string_canonize @@ -42,7 +42,7 @@ from aea.helpers.base import try_decorator -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) _COSMOS = "cosmos" TESTNET_NAME = "testnet" @@ -167,6 +167,16 @@ def get_hash(message: bytes) -> str: digest = hashlib.sha256(message).hexdigest() return digest + @classmethod + def is_valid_address(cls, address: Address) -> bool: + """ + Check if the address is valid. + + :param address: the address to validate + """ + result = bech32_decode(address) + return result != (None, None) and result[0] == cls.address_prefix + class CosmosCrypto(Crypto[SigningKey]): """Class wrapping the Account Generation from Ethereum ledger.""" @@ -372,7 +382,7 @@ def get_balance(self, address: Address) -> Optional[int]: @try_decorator( "Encountered exception when trying get balance: {}", - logger_method=logger.warning, + logger_method=_default_logger.warning, ) def _try_get_balance(self, address: Address) -> Optional[int]: """Try get the balance of a given account.""" @@ -540,7 +550,7 @@ def get_handle_transaction( @staticmethod @try_decorator( "Encountered exception when trying to execute wasm transaction: {}", - logger_method=logger.warning, + logger_method=_default_logger.warning, ) def try_execute_wasm_transaction( tx_signed: Any, signed_tx_filename: str = "tx.signed" @@ -571,7 +581,7 @@ def try_execute_wasm_transaction( @staticmethod @try_decorator( "Encountered exception when trying to execute wasm query: {}", - logger_method=logger.warning, + logger_method=_default_logger.warning, ) def try_execute_wasm_query( contract_address: Address, query_msg: Any @@ -690,7 +700,7 @@ def _get_transaction( @try_decorator( "Encountered exception when trying to get account number and sequence: {}", - logger_method=logger.warning, + logger_method=_default_logger.warning, ) def _try_get_account_number_and_sequence( self, address: Address @@ -723,7 +733,7 @@ def send_signed_transaction(self, tx_signed: Any) -> Optional[str]: elif self.is_transfer_transaction(tx_signed): tx_digest = self._try_send_signed_transaction(tx_signed) else: # pragma: nocover - logger.warning( + _default_logger.warning( "Cannot send transaction. Unknown transaction type: {}".format( tx_signed ) @@ -752,7 +762,8 @@ def is_transfer_transaction(tx_signed: Any) -> bool: return result @try_decorator( - "Encountered exception when trying to send tx: {}", logger_method=logger.warning + "Encountered exception when trying to send tx: {}", + logger_method=_default_logger.warning, ) def _try_send_signed_transaction(self, tx_signed: Any) -> Optional[str]: """ @@ -766,6 +777,8 @@ def _try_send_signed_transaction(self, tx_signed: Any) -> Optional[str]: response = requests.post(url=url, json=tx_signed) if response.status_code == 200: tx_digest = response.json()["txhash"] + else: # pragma: nocover + _default_logger.error("Cannot send transaction: {}".format(response.json())) return tx_digest def get_transaction_receipt(self, tx_digest: str) -> Optional[Any]: @@ -780,7 +793,7 @@ def get_transaction_receipt(self, tx_digest: str) -> Optional[Any]: @try_decorator( "Encountered exception when trying to get transaction receipt: {}", - logger_method=logger.warning, + logger_method=_default_logger.warning, ) def _try_get_transaction_receipt(self, tx_digest: str) -> Optional[Any]: """ @@ -929,7 +942,7 @@ def get_wealth(self, address: Address) -> None: @classmethod @try_decorator( "An error occured while attempting to request a faucet request:\n{}", - logger_method=logger.error, + logger_method=_default_logger.error, ) def _try_create_faucet_claim(cls, address: Address) -> Optional[str]: """ @@ -947,9 +960,9 @@ def _try_create_faucet_claim(cls, address: Address) -> Optional[str]: data = response.json() uid = data["uid"] - logger.info("Wealth claim generated, uid: {}".format(uid)) + _default_logger.info("Wealth claim generated, uid: {}".format(uid)) else: # pragma: no cover - logger.warning( + _default_logger.warning( "Response: {}, Text: {}".format(response.status_code, response.text) ) @@ -958,7 +971,7 @@ def _try_create_faucet_claim(cls, address: Address) -> Optional[str]: @classmethod @try_decorator( "An error occured while attempting to request a faucet request:\n{}", - logger_method=logger.error, + logger_method=_default_logger.error, ) def _try_check_faucet_claim(cls, uid: str) -> Optional[CosmosFaucetStatus]: """ @@ -969,7 +982,7 @@ def _try_check_faucet_claim(cls, uid: str) -> Optional[CosmosFaucetStatus]: """ response = requests.get(cls._faucet_status_uri(uid)) if response.status_code != 200: # pragma: nocover - logger.warning( + _default_logger.warning( "Response: {}, Text: {}".format(response.status_code, response.text) ) return None diff --git a/aea/crypto/ethereum.py b/aea/crypto/ethereum.py index 0f4eb47d3c..eea476ce29 100644 --- a/aea/crypto/ethereum.py +++ b/aea/crypto/ethereum.py @@ -40,7 +40,7 @@ from aea.helpers.base import try_decorator -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) _ETHEREUM = "ethereum" GAS_ID = "gwei" @@ -332,7 +332,7 @@ def get_transfer_transaction( # pylint: disable=arguments-differ gas_estimate = self._try_get_gas_estimate(transaction) if gas_estimate is not None and tx_fee <= gas_estimate: # pragma: no cover - logger.warning( + _default_logger.warning( "Needed to increase tx_fee to cover the gas consumption of the transaction. Estimated gas consumption is: {}.".format( gas_estimate ) @@ -382,7 +382,9 @@ def _try_send_signed_transaction(self, tx_signed: Any) -> Optional[str]: tx_signed.rawTransaction ) tx_digest = hex_value.hex() - logger.debug("Successfully sent transaction with digest: {}".format(tx_digest)) + _default_logger.debug( + "Successfully sent transaction with digest: {}".format(tx_digest) + ) return tx_digest def get_transaction_receipt(self, tx_digest: str) -> Optional[Any]: @@ -498,12 +500,21 @@ def try_estimate_gas(self, tx: Dict[str, Any]) -> Dict[str, Any]: # try estimate the gas and update the transaction dict _tx = cast(TxParams, tx) gas_estimate = self.api.eth.estimateGas(transaction=_tx) - logger.debug("gas estimate: {}".format(gas_estimate)) + _default_logger.debug("gas estimate: {}".format(gas_estimate)) tx["gas"] = gas_estimate except Exception as e: # pylint: disable=broad-except # pragma: nocover - logger.debug("Error when trying to estimate gas: {}".format(e)) + _default_logger.debug("Error when trying to estimate gas: {}".format(e)) return tx + @classmethod + def is_valid_address(cls, address: Address) -> bool: + """ + Check if the address is valid. + + :param address: the address to validate + """ + return Web3.isAddress(address) + class EthereumFaucetApi(FaucetApi): """Ethereum testnet faucet API.""" @@ -534,17 +545,17 @@ def _try_get_wealth(address: Address) -> None: """ response = requests.get(ETHEREUM_TESTNET_FAUCET_URL + address) if response.status_code // 100 == 5: - logger.error("Response: {}".format(response.status_code)) + _default_logger.error("Response: {}".format(response.status_code)) elif response.status_code // 100 in [3, 4]: response_dict = json.loads(response.text) - logger.warning( + _default_logger.warning( "Response: {}\nMessage: {}".format( response.status_code, response_dict.get("message") ) ) elif response.status_code // 100 == 2: response_dict = json.loads(response.text) - logger.info( + _default_logger.info( "Response: {}\nMessage: {}".format( response.status_code, response_dict.get("message") ) diff --git a/aea/crypto/helpers.py b/aea/crypto/helpers.py index cffc763e35..64cfc94b1a 100644 --- a/aea/crypto/helpers.py +++ b/aea/crypto/helpers.py @@ -30,7 +30,7 @@ PRIVATE_KEY_PATH_SCHEMA = "{}_private_key.txt" -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) def verify_or_create_private_keys( @@ -106,10 +106,10 @@ def try_validate_private_key_path( private_key_path, e ) if exit_on_error: - logger.exception(error_msg) # show exception traceback on exit + _default_logger.exception(error_msg) # show exception traceback on exit sys.exit(1) else: # pragma: no cover - logger.error(error_msg) + _default_logger.error(error_msg) raise diff --git a/aea/crypto/ledger_apis.py b/aea/crypto/ledger_apis.py index 5773b5b83f..5f7883bddd 100644 --- a/aea/crypto/ledger_apis.py +++ b/aea/crypto/ledger_apis.py @@ -18,7 +18,6 @@ # ------------------------------------------------------------------------------ """Module wrapping all the public and private keys cryptography.""" -import logging from typing import Any, Dict, Optional, Tuple, Union from aea.common import Address @@ -47,8 +46,6 @@ FetchAIApi.identifier: {"address": FETCHAI_DEFAULT_ADDRESS}, } # type: Dict[str, Dict[str, Union[str, int]]] -logger = logging.getLogger(__name__) - class LedgerApis: """Store all the ledger apis we initialise.""" diff --git a/aea/crypto/wallet.py b/aea/crypto/wallet.py index e030ef31d7..bfa7060a81 100644 --- a/aea/crypto/wallet.py +++ b/aea/crypto/wallet.py @@ -26,7 +26,7 @@ from aea.crypto.registries import make_crypto -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class CryptoStore: @@ -149,7 +149,7 @@ def sign_message( """ crypto_object = self.crypto_objects.get(crypto_id, None) if crypto_object is None: - logger.warning( + _default_logger.warning( "No crypto object for crypto_id={} in wallet!".format(crypto_id) ) signature = None # type: Optional[str] @@ -167,7 +167,7 @@ def sign_transaction(self, crypto_id: str, transaction: Any) -> Optional[Any]: """ crypto_object = self.crypto_objects.get(crypto_id, None) if crypto_object is None: - logger.warning( + _default_logger.warning( "No crypto object for crypto_id={} in wallet!".format(crypto_id) ) signed_transaction = None # type: Optional[Any] diff --git a/aea/decision_maker/base.py b/aea/decision_maker/base.py index 2565b84f75..26d8d5b0c6 100644 --- a/aea/decision_maker/base.py +++ b/aea/decision_maker/base.py @@ -19,7 +19,6 @@ """This module contains the decision maker class.""" import hashlib -import logging import threading from abc import ABC, abstractmethod from queue import Queue @@ -30,14 +29,12 @@ from aea.crypto.wallet import Wallet from aea.helpers.async_friendly_queue import AsyncFriendlyQueue +from aea.helpers.logging import WithLogger, get_logger from aea.helpers.transaction.base import Terms from aea.identity.base import Identity from aea.protocols.base import Message -logger = logging.getLogger(__name__) - - def _hash(access_code: str) -> str: """ Get the hash of the access code. @@ -235,7 +232,7 @@ def protected_get( return internal_message -class DecisionMakerHandler(ABC): +class DecisionMakerHandler(WithLogger, ABC): """This class implements the decision maker.""" self_address: str = "decision_maker" @@ -246,8 +243,11 @@ def __init__(self, identity: Identity, wallet: Wallet, **kwargs): :param identity: the identity :param wallet: the wallet + :param logger: the logger :param kwargs: the key word arguments """ + logger = get_logger(__name__, identity.name) + WithLogger.__init__(self, logger=logger) self._identity = identity self._wallet = wallet self._context = SimpleNamespace(**kwargs) @@ -288,7 +288,7 @@ def handle(self, message: Message) -> None: """ -class DecisionMaker: +class DecisionMaker(WithLogger): """This class implements the decision maker.""" def __init__( @@ -300,6 +300,7 @@ def __init__( :param agent_name: the agent name :param decision_maker_handler: the decision maker handler """ + WithLogger.__init__(self, logger=decision_maker_handler.logger) self._agent_name = decision_maker_handler.identity.name self._queue_access_code = uuid4().hex self._message_in_queue = ProtectedQueue( @@ -330,7 +331,7 @@ def start(self) -> None: """Start the decision maker.""" with self._lock: if not self._stopped: # pragma: no cover - logger.debug( + self.logger.debug( "[{}]: Decision maker already started.".format(self._agent_name) ) return @@ -346,7 +347,7 @@ def stop(self) -> None: self.message_in_queue.put(None) if self._thread is not None: self._thread.join() - logger.debug("[{}]: Decision Maker stopped.".format(self._agent_name)) + self.logger.debug("[{}]: Decision Maker stopped.".format(self._agent_name)) self._thread = None def execute(self) -> None: @@ -365,7 +366,7 @@ def execute(self) -> None: ) # type: Optional[Message] if message is None: - logger.debug( + self.logger.debug( "[{}]: Received empty message. Quitting the processing loop...".format( self._agent_name ) diff --git a/aea/decision_maker/default.py b/aea/decision_maker/default.py index a43d6c1973..54d6bae65f 100644 --- a/aea/decision_maker/default.py +++ b/aea/decision_maker/default.py @@ -55,7 +55,7 @@ QUANTITY_SHIFT = 100 -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class SigningDialogues(BaseSigningDialogues): @@ -304,7 +304,7 @@ def is_affordable(self, terms: Terms) -> bool: if self.is_initialized: is_affordable = self.is_affordable_transaction(terms) else: - logger.warning( + _default_logger.warning( "Cannot verify whether transaction is affordable as ownership state is not initialized. Assuming it is!" ) is_affordable = True @@ -535,7 +535,7 @@ def is_utility_enhancing( self.utility_diff_from_transaction(ownership_state, terms) >= 0.0 ) else: - logger.warning( + _default_logger.warning( "Cannot verify whether transaction improves utility as preferences are not initialized. Assuming it does!" ) is_utility_enhancing = True @@ -585,7 +585,7 @@ def handle(self, message: Message) -> None: elif isinstance(message, StateUpdateMessage): self._handle_state_update_message(message) else: # pragma: no cover - logger.error( + self.logger.error( "[{}]: cannot handle message={} of type={}".format( self.agent_name, message, type(message) ) @@ -599,7 +599,7 @@ def _handle_signing_message(self, signing_msg: SigningMessage) -> None: :return: None """ if not self.context.goal_pursuit_readiness.is_ready: - logger.debug( + self.logger.debug( "[{}]: Preferences and ownership state not initialized!".format( self.agent_name ) @@ -609,7 +609,7 @@ def _handle_signing_message(self, signing_msg: SigningMessage) -> None: Optional[SigningDialogue], self.signing_dialogues.update(signing_msg) ) if signing_dialogue is None: # pragma: no cover - logger.error( + self.logger.error( "[{}]: Could not construct signing dialogue. Aborting!".format( self.agent_name ) @@ -622,7 +622,7 @@ def _handle_signing_message(self, signing_msg: SigningMessage) -> None: elif signing_msg.performative == SigningMessage.Performative.SIGN_TRANSACTION: self._handle_transaction_signing(signing_msg, signing_dialogue) else: # pragma: no cover - logger.error( + self.logger.error( "[{}]: Unexpected transaction message performative".format( self.agent_name ) @@ -716,7 +716,7 @@ def _handle_state_update_message( self.state_update_dialogues.update(state_update_msg), ) if state_update_dialogue is None: # pragma: no cover - logger.error( + self.logger.error( "[{}]: Could not construct state_update dialogue. Aborting!".format( self.agent_name ) @@ -724,7 +724,7 @@ def _handle_state_update_message( return if state_update_msg.performative == StateUpdateMessage.Performative.INITIALIZE: - logger.warning( + self.logger.warning( "[{}]: Applying ownership_state and preferences initialization!".format( self.agent_name ) @@ -741,7 +741,7 @@ def _handle_state_update_message( GoalPursuitReadiness.Status.READY ) elif state_update_msg.performative == StateUpdateMessage.Performative.APPLY: - logger.info("[{}]: Applying state update!".format(self.agent_name)) + self.logger.info("[{}]: Applying state update!".format(self.agent_name)) self.context.ownership_state.apply_delta( delta_amount_by_currency_id=state_update_msg.amount_by_currency_id, delta_quantities_by_good_id=state_update_msg.quantities_by_good_id, diff --git a/aea/helpers/async_utils.py b/aea/helpers/async_utils.py index 80c6218b5b..c0707e5392 100644 --- a/aea/helpers/async_utils.py +++ b/aea/helpers/async_utils.py @@ -55,7 +55,7 @@ ) -logger = logging.getLogger(__file__) +_default_logger = logging.getLogger(__file__) def ensure_list(value: Any) -> List: @@ -121,7 +121,7 @@ def _state_changed(self, state: Any) -> None: try: callback_fn(state) except Exception: # pylint: disable=broad-except - logger.exception(f"Exception on calling {callback_fn}") + _default_logger.exception(f"Exception on calling {callback_fn}") for watcher in list(self._watchers): if state not in watcher._states: # type: ignore # pylint: disable=protected-access # pragma: nocover @@ -331,10 +331,10 @@ def start(self) -> None: def run(self) -> None: """Run code inside thread.""" - logger.debug("Starting threaded asyncio loop...") + _default_logger.debug("Starting threaded asyncio loop...") asyncio.set_event_loop(self._loop) self._loop.run_forever() - logger.debug("Asyncio loop has been stopped.") + _default_logger.debug("Asyncio loop has been stopped.") def call(self, coro: Awaitable) -> Any: """ @@ -346,18 +346,18 @@ def call(self, coro: Awaitable) -> Any: def stop(self) -> None: """Stop event loop in thread.""" - logger.debug("Stopping...") + _default_logger.debug("Stopping...") if not self.is_alive(): # pragma: nocover return if self._loop.is_running(): - logger.debug("Stopping loop...") + _default_logger.debug("Stopping loop...") self._loop.call_soon_threadsafe(self._loop.stop) - logger.debug("Wait thread to join...") + _default_logger.debug("Wait thread to join...") self.join(10) - logger.debug("Stopped.") + _default_logger.debug("Stopped.") class AwaitableProc: @@ -504,7 +504,7 @@ def start(self) -> bool: :return: bool started or not. """ if self._task and not self._task.done(): - logger.debug(f"{self} already running") + _default_logger.debug(f"{self} already running") return False self._is_running = False @@ -572,7 +572,7 @@ def wait_completed( :return: awaitable if sync is False, otherise None """ if not self._task: - logger.warning("Runnable is not started") + _default_logger.warning("Runnable is not started") return ready_future if self._got_result and not force_result: @@ -647,7 +647,7 @@ async def _wait(self) -> None: def stop(self, force: bool = False) -> None: """Stop runnable.""" - logger.debug(f"{self} is going to be stopped {self._task}") + _default_logger.debug(f"{self} is going to be stopped {self._task}") if not self._task or not self._loop: return diff --git a/aea/helpers/base.py b/aea/helpers/base.py index b4a22ced9f..40d78d1796 100644 --- a/aea/helpers/base.py +++ b/aea/helpers/base.py @@ -31,97 +31,16 @@ import sys import time import types -from collections import OrderedDict, UserString +from collections import UserString, defaultdict, deque +from copy import copy from functools import wraps from pathlib import Path -from typing import Any, Callable, Dict, List, TextIO, Union +from typing import Any, Callable, Deque, Dict, List, Set, TypeVar, Union -import yaml from dotenv import load_dotenv -logger = logging.getLogger(__name__) - - -def _ordered_loading(fun: Callable): - # for pydocstyle - def ordered_load(stream: TextIO): - object_pairs_hook = OrderedDict - - class OrderedLoader(yaml.SafeLoader): - """A wrapper for safe yaml loader.""" - - pass - - def construct_mapping(loader, node): - loader.flatten_mapping(node) - return object_pairs_hook(loader.construct_pairs(node)) - - OrderedLoader.add_constructor( - yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping - ) - return fun(stream, Loader=OrderedLoader) # nosec - - return ordered_load - - -def _ordered_dumping(fun: Callable): - # for pydocstyle - def ordered_dump(data, stream=None, **kwds): - class OrderedDumper(yaml.SafeDumper): - """A wrapper for safe yaml loader.""" - - pass - - def _dict_representer(dumper, data): - return dumper.represent_mapping( - yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items() - ) - - OrderedDumper.add_representer(OrderedDict, _dict_representer) - return fun(data, stream, Dumper=OrderedDumper, **kwds) # nosec - - return ordered_dump - - -@_ordered_loading -def yaml_load(*args, **kwargs) -> Dict[str, Any]: - """ - Load a yaml from a file pointer in an ordered way. - - :return: the yaml - """ - return yaml.load(*args, **kwargs) # nosec - - -@_ordered_loading -def yaml_load_all(*args, **kwargs) -> List[Dict[str, Any]]: - """ - Load a multi-paged yaml from a file pointer in an ordered way. - - :return: the yaml - """ - return list(yaml.load_all(*args, **kwargs)) # nosec - - -@_ordered_dumping -def yaml_dump(*args, **kwargs) -> None: - """ - Dump multi-paged yaml data to a yaml file in an ordered way. - - :return None - """ - yaml.dump(*args, **kwargs) # nosec - - -@_ordered_dumping -def yaml_dump_all(*args, **kwargs) -> None: - """ - Dump multi-paged yaml data to a yaml file in an ordered way. - - :return None - """ - yaml.dump_all(*args, **kwargs) # nosec +_default_logger = logging.getLogger(__name__) def _get_module(spec): @@ -143,12 +62,12 @@ def locate(path: str) -> Any: spec_name = ".".join(parts[: n + 1]) module_location = os.path.join(file_location, "__init__.py") spec = importlib.util.spec_from_file_location(spec_name, module_location) - logger.debug("Trying to import {}".format(module_location)) + _default_logger.debug("Trying to import {}".format(module_location)) nextmodule = _get_module(spec) if nextmodule is None: module_location = file_location + ".py" spec = importlib.util.spec_from_file_location(spec_name, module_location) - logger.debug("Trying to import {}".format(module_location)) + _default_logger.debug("Trying to import {}".format(module_location)) nextmodule = _get_module(spec) if nextmodule: @@ -426,3 +345,72 @@ def recursive_update(to_update: Dict, new_values: Dict) -> None: recursive_update(value_to_update, value) else: to_update[key] = value + + +def _get_aea_logger_name_prefix(module_name: str, agent_name: str) -> str: + """ + Get the logger name prefix. + + It consists of a dotted path with: + - the name of the package, 'aea'; + - the agent name; + - the rest of the dotted path. + + >>> _get_aea_logger_name_prefix("aea.path.to.package", "myagent") + 'aea.myagent.path.to.package' + + :param module_name: the module name. + :param agent_name: the agent name. + :return: the logger name prefix. + """ + module_name_parts = module_name.split(".") + root = module_name_parts[0] + postfix = module_name_parts[1:] + return ".".join([root, agent_name, *postfix]) + + +T = TypeVar("T") + + +def find_topological_order(adjacency_list: Dict[T, Set[T]]) -> List[T]: + """ + Compute the topological order of a graph (using Kahn's algorithm). + + :param adjacency_list: the adjacency list of the graph. + :return: the topological order for the graph (as a sequence of nodes) + :raises ValueError: if the graph contains a cycle. + """ + # compute inverse adjacency list and the roots of the DAG. + adjacency_list = copy(adjacency_list) + visited: Set[T] = set() + roots: Set[T] = set() + inverse_adjacency_list: Dict[T, Set[T]] = defaultdict(set) + # compute both roots and inv. adj. list in one pass. + for start_node, end_nodes in adjacency_list.items(): + if start_node not in visited: + roots.add(start_node) + visited.update([start_node, *end_nodes]) + for end_node in end_nodes: + roots.discard(end_node) + inverse_adjacency_list[end_node].add(start_node) + + # compute the topological order + queue: Deque[T] = deque() + order = [] + queue.extendleft(sorted(roots)) + while len(queue) > 0: + current = queue.pop() + order.append(current) + next_nodes = adjacency_list.get(current, set()) + for node in next_nodes: + inverse_adjacency_list[node].discard(current) + if len(inverse_adjacency_list[node]) == 0: + queue.append(node) + + # remove all the edges + adjacency_list[current] = set() + + if any(len(edges) > 0 for edges in inverse_adjacency_list.values()): + raise ValueError("Graph has at least one cycle.") + + return order diff --git a/aea/helpers/exec_timeout.py b/aea/helpers/exec_timeout.py index b6b605cb18..07c66fa4cd 100644 --- a/aea/helpers/exec_timeout.py +++ b/aea/helpers/exec_timeout.py @@ -31,7 +31,7 @@ from typing import Optional, Type -logger = logging.getLogger(__file__) +_default_logger = logging.getLogger(__file__) class TimeoutResult: @@ -271,7 +271,7 @@ def _set_timeout_watch(self) -> None: :return: None """ if not self._supervisor_thread: - logger.warning( + _default_logger.warning( "ExecTimeoutThreadGuard is used but not started! No timeout wil be applied!" ) return diff --git a/aea/helpers/install_dependency.py b/aea/helpers/install_dependency.py new file mode 100644 index 0000000000..c2fb2654fa --- /dev/null +++ b/aea/helpers/install_dependency.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""Helper to install python dependecies.""" +import pprint +import subprocess # nosec +import sys +from typing import List + +import click + +from aea.cli.utils.loggers import logger +from aea.configurations.base import Dependency +from aea.exceptions import AEAException, enforce + + +def install_dependency(dependency_name: str, dependency: Dependency) -> None: + """ + Install python dependency to the current python environment. + + :param dependency_name: name of the python package + :param dependency: Dependency specification + + :return: None + """ + click.echo("Installing {}...".format(pprint.pformat(dependency_name))) + try: + pip_args = dependency.get_pip_install_args() + command = [sys.executable, "-m", "pip", "install", *pip_args] + logger.debug("Calling '{}'".format(" ".join(command))) + return_code = run_install_subprocess(command) + if return_code == 1: + # try a second time + return_code = run_install_subprocess(command) + enforce(return_code == 0, "Return code != 0.") + except Exception as e: + raise AEAException( + "An error occurred while installing {}, {}: {}".format( + dependency_name, dependency, str(e) + ) + ) + + +def run_install_subprocess( + install_command: List[str], install_timeout: float = 300 +) -> int: + """ + Try executing install command. + + :param install_command: list strings of the command + :param install_timeout: timeout to wait pip to install + :return: the return code of the subprocess + """ + try: + subp = subprocess.Popen(install_command) # nosec + subp.wait(install_timeout) + return_code = subp.returncode + finally: + poll = subp.poll() + if poll is None: # pragma: no cover + subp.terminate() + subp.wait(30) + return return_code diff --git a/aea/helpers/logging.py b/aea/helpers/logging.py index 8a085528a5..bb61c740a9 100644 --- a/aea/helpers/logging.py +++ b/aea/helpers/logging.py @@ -21,6 +21,14 @@ from logging import Logger, LoggerAdapter from typing import Any, MutableMapping, Optional, Tuple, cast +from aea.helpers.base import _get_aea_logger_name_prefix + + +def get_logger(module_path: str, agent_name: str) -> Logger: + """Get the logger based on a module path and agent name.""" + logger = logging.getLogger(_get_aea_logger_name_prefix(module_path, agent_name)) + return logger + class AgentLoggerAdapter(LoggerAdapter): """This class is a logger adapter that prepends the agent name to log messages.""" diff --git a/aea/helpers/multiaddr/base.py b/aea/helpers/multiaddr/base.py index 01d12a4289..430f847c69 100644 --- a/aea/helpers/multiaddr/base.py +++ b/aea/helpers/multiaddr/base.py @@ -20,6 +20,7 @@ """This module contains multiaddress class.""" from binascii import unhexlify +from typing import Optional import base58 import multihash # type: ignore @@ -92,25 +93,51 @@ def _hex_to_bytes(hexed): class MultiAddr: """Protocol Labs' Multiaddress representation of a network address.""" - def __init__(self, host: str, port: int, public_key: str): + def __init__( + self, + host: str, + port: int, + public_key: Optional[str] = None, + multihash_id: Optional[str] = None, + ): """ Initialize a multiaddress. :param host: ip host of the address - :param host: port number of the address - :param host: hex encoded public key. Must conform to Bitcoin EC encoding standard for Secp256k1 + :param port: port number of the address + :param public_key: hex encoded public key. Must conform to Bitcoin EC encoding standard for Secp256k1 + :param multihash_id: a multihash of the public key """ self._host = host self._port = port - try: - VerifyingKey._from_compressed(_hex_to_bytes(public_key), curves.SECP256k1) - except keys.MalformedPointError as e: # pragma: no cover - raise Exception("Malformed public key:{}".format(str(e))) - - self._public_key = public_key - self._peerid = self.compute_peerid(self._public_key) + if public_key is not None: + try: + VerifyingKey._from_compressed( + _hex_to_bytes(public_key), curves.SECP256k1 + ) + except keys.MalformedPointError as e: # pragma: no cover + raise ValueError( + "Malformed public key '{}': {}".format(public_key, str(e)) + ) + + self._public_key = public_key + self._peerid = self.compute_peerid(self._public_key) + elif multihash_id is not None: + try: + multihash.decode(base58.b58decode(multihash_id)) + except Exception as e: + raise ValueError( + "Malformed multihash '{}': {}".format(multihash_id, str(e)) + ) + + self._public_key = "" + self._peerid = multihash_id + else: + raise ValueError( # pragma: no cover + "MultiAddr requires either public_key or multihash_id to be provided." + ) @staticmethod def compute_peerid(public_key: str) -> str: @@ -133,6 +160,19 @@ def compute_peerid(public_key: str) -> str: key_mh = multihash.digest(key_serialized, algo) return base58.b58encode(key_mh.encode()).decode() + @classmethod + def from_string(cls, maddr: str) -> "MultiAddr": + """ + Construct a MultiAddr object from its string format + + :param maddr: multiaddress string + """ + parts = maddr.split("/") + if len(parts) != 7 or not parts[4].isdigit(): + raise ValueError("Malformed multiaddress '{}'".format(maddr)) + + return cls(host=parts[2], port=int(parts[4]), multihash_id=parts[6]) + @property def public_key(self) -> str: """Get the public key.""" @@ -143,6 +183,16 @@ def peer_id(self) -> str: """Get the peer id.""" return self._peerid + @property + def host(self) -> str: + """Get the peer host.""" + return self._host + + @property + def port(self) -> int: + """Get the peer port.""" + return self._port + def format(self) -> str: """Canonical representation of a multiaddress.""" return f"/dns4/{self._host}/tcp/{self._port}/p2p/{self._peerid}" diff --git a/aea/helpers/multiple_executor.py b/aea/helpers/multiple_executor.py index 3fa00c859e..2967b3afea 100644 --- a/aea/helpers/multiple_executor.py +++ b/aea/helpers/multiple_executor.py @@ -41,7 +41,7 @@ ) -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) TaskAwaitable = Union[Task, Future] @@ -209,11 +209,11 @@ async def wait_future(future): try: await future except KeyboardInterrupt: # pragma: nocover - logger.exception("KeyboardInterrupt in task!") + _default_logger.exception("KeyboardInterrupt in task!") if not skip_exceptions: raise except Exception as e: # pylint: disable=broad-except # handle any exception with own code. - logger.exception("Exception in task!") + _default_logger.exception("Exception in task!") if not skip_exceptions: await self._handle_exception(self._future_task[future], e) @@ -234,14 +234,14 @@ async def _handle_exception( :param exc: Exception raised :return: None """ - logger.exception(f"Exception raised during {task.id} running.") - logger.info(f"Exception raised during {task.id} running.") + _default_logger.exception(f"Exception raised during {task.id} running.") + _default_logger.info(f"Exception raised during {task.id} running.") if self._task_fail_policy == ExecutorExceptionPolicies.propagate: raise exc if self._task_fail_policy == ExecutorExceptionPolicies.log_only: pass elif self._task_fail_policy == ExecutorExceptionPolicies.stop_all: - logger.info( + _default_logger.info( "Stopping executor according to fail policy cause exception raised in task" ) self.stop() diff --git a/aea/helpers/search/models.py b/aea/helpers/search/models.py index 164b52dc59..1e615d9474 100644 --- a/aea/helpers/search/models.py +++ b/aea/helpers/search/models.py @@ -30,7 +30,7 @@ from aea.exceptions import enforce -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class Location: @@ -66,6 +66,12 @@ def __eq__(self, other): return False # pragma: nocover return self.latitude == other.latitude and self.longitude == other.longitude + def __str__(self): + """Get the string representation of the data model.""" + return "Location(latitude={},longitude={})".format( + self.latitude, self.longitude + ) + """ The allowable types that an Attribute can have @@ -117,6 +123,12 @@ def __eq__(self, other): and self.is_required == other.is_required ) + def __str__(self): + """Get the string representation of the data model.""" + return "Attribute(name={},type={},is_required={})".format( + self.name, self.type, self.is_required + ) + class DataModel: """Implements an OEF data model.""" @@ -133,7 +145,7 @@ def __init__(self, name: str, attributes: List[Attribute], description: str = "" attributes, key=lambda x: x.name ) # type: List[Attribute] self._check_validity() - self.attributes_by_name = {a.name: a for a in attributes} + self.attributes_by_name = {a.name: a for a in self.attributes} self.description = description def _check_validity(self): @@ -154,6 +166,12 @@ def __eq__(self, other) -> bool: and self.attributes == other.attributes ) + def __str__(self): + """Get the string representation of the data model.""" + return "DataModel(name={},attributes={},description={})".format( + self.name, {a.name: str(a) for a in self.attributes}, self.description + ) + def generate_data_model( model_name: str, attribute_values: Mapping[str, ATTRIBUTE_TYPES] @@ -268,6 +286,12 @@ def _check_consistency(self): ) ) + def __str__(self): + """Get the string representation of the description.""" + return "Description(values={},data_model={})".format( + self._values, self.data_model + ) + @classmethod def encode( cls, description_protobuf_object, description_object: "Description" @@ -540,6 +564,10 @@ def __eq__(self, other): and self.type == other.type ) + def __str__(self): + """Get the string representation of the constraint type.""" + return "ConstraintType(value={},type={})".format(self.value, self.type) + class ConstraintExpr(ABC): """Implementation of the constraint language to query the OEF node.""" @@ -806,6 +834,12 @@ def __eq__(self, other): and self.constraint_type == other.constraint_type ) + def __str__(self): + """Get the string representation of the constraint.""" + return "Constraint(attribute_name={},constraint_type={})".format( + self.attribute_name, self.constraint_type + ) + class Query: """This class lets you build a query for the OEF.""" @@ -859,7 +893,7 @@ def check_validity(self): ) ) if len(self.constraints) < 1: - logger.warning( + _default_logger.warning( "DEPRECATION WARNING: " "Invalid input value for type '{}': empty list of constraints. The number of " "constraints must be at least 1.".format(type(self).__name__) @@ -878,6 +912,12 @@ def __eq__(self, other): and self.model == other.model ) + def __str__(self): + """Get the string representation of the constraint.""" + return "Query(constraints={},model={})".format( + [str(c) for c in self.constraints], self.model + ) + @classmethod def encode(cls, query_protobuf_object, query_object: "Query") -> None: """ diff --git a/aea/helpers/win32.py b/aea/helpers/win32.py index d48b67874f..dcbaaf1853 100644 --- a/aea/helpers/win32.py +++ b/aea/helpers/win32.py @@ -23,7 +23,7 @@ import platform -logger = logging.getLogger(__file__) +_default_logger = logging.getLogger(__name__) def enable_ctrl_c_support() -> None: # pragma: no cover @@ -34,4 +34,4 @@ def enable_ctrl_c_support() -> None: # pragma: no cover kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) # type: ignore if not kernel32.SetConsoleCtrlHandler(None, False): - logger.debug(f"SetConsoleCtrlHandler Error: {ctypes.get_last_error()}") # type: ignore + _default_logger.debug(f"SetConsoleCtrlHandler Error: {ctypes.get_last_error()}") # type: ignore diff --git a/aea/helpers/yaml_utils.py b/aea/helpers/yaml_utils.py new file mode 100644 index 0000000000..1c4922cefd --- /dev/null +++ b/aea/helpers/yaml_utils.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2020 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Helper functions related to YAML loading/dumping.""" +import os +import re +from collections import OrderedDict +from typing import Any, Dict, List, Match, Optional, Sequence, TextIO, cast + +import yaml +from yaml import MappingNode + + +class _AEAYamlLoader(yaml.SafeLoader): + """ + Custom yaml.SafeLoader for the AEA framework. + + It extends the default SafeLoader in two ways: + - loads YAML configurations while *remembering the order of the fields*; + - resolves the environment variables at loading time. + + This class is for internal usage only; please use + the public functions of the module 'yaml_load' and 'yaml_load_all'. + """ + + envvar_matcher = re.compile(r"\${([^}^{]+)\}") + envvar_key = "!envvar" + + def __init__(self, *args, **kwargs): + """ + Initialize the AEAYamlLoader. + + It adds a YAML Loader constructor to use 'OderedDict' to load the files. + """ + super().__init__(*args, **kwargs) + _AEAYamlLoader.add_constructor( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, self._construct_mapping + ) + _AEAYamlLoader.add_constructor(self.envvar_key, self._envvar_constructor) + self._add_implicit_resolver_if_not_present_already() + + def _add_implicit_resolver_if_not_present_already(self) -> None: + """Add implicit resolver for environment variables, if not present already.""" + if self.envvar_key not in dict(self.yaml_implicit_resolvers.get(None, [])): + _AEAYamlLoader.add_implicit_resolver( + self.envvar_key, self.envvar_matcher, None + ) + + @staticmethod + def _construct_mapping(loader: "_AEAYamlLoader", node: MappingNode): + """Construct a YAML mapping with OrderedDict.""" + object_pairs_hook = OrderedDict + loader.flatten_mapping(node) + return object_pairs_hook(loader.construct_pairs(node)) + + @staticmethod + def _envvar_constructor(_loader: "_AEAYamlLoader", node: MappingNode) -> str: + """Extract the matched value, expand env variable, and replace the match.""" + node_value = node.value + match = _AEAYamlLoader.envvar_matcher.match(node_value) + match = cast(Match[str], match) + env_var = match.group()[2:-1] + + # check for defaults + var_split = env_var.split(":") + if len(var_split) == 2: + var_name, default_value = var_split + elif len(var_split) == 1: + var_name, default_value = var_split[0], "" + else: + raise ValueError(f"Cannot resolve environment variable '{env_var}'.") + var_name = var_name.strip() + default_value = default_value.strip() + var_value = os.getenv(var_name, default_value) + return var_value + node_value[match.end() :] + + +class _AEAYamlDumper(yaml.SafeDumper): + """ + Custom yaml.SafeDumper for the AEA framework. + + It extends the default SafeDumper so to dump + YAML configurations while *following the order of the fields*. + + This class is for internal usage only; please use + the public functions of the module 'yaml_dump' and 'yaml_dump_all'. + """ + + def __init__(self, *args, **kwargs): + """ + Initialize the AEAYamlDumper. + + It adds a YAML Dumper representer to use 'OderedDict' to dump the files. + """ + super().__init__(*args, **kwargs) + _AEAYamlDumper.add_representer(OrderedDict, self._dict_representer) + + @staticmethod + def _dict_representer(dumper: "_AEAYamlDumper", data: OrderedDict) -> MappingNode: + """Use a custom representer.""" + return dumper.represent_mapping( + yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items() + ) + + +def yaml_load(stream: TextIO) -> Dict[str, Any]: + """ + Load a yaml from a file pointer in an ordered way. + + :param stream: file pointer to the input file. + :return: the dictionary object with the YAML file content. + """ + return yaml.load(stream, Loader=_AEAYamlLoader) # nosec + + +def yaml_load_all(stream: TextIO) -> List[Dict[str, Any]]: + """ + Load a multi-paged yaml from a file pointer in an ordered way. + + :param stream: file pointer to the input file. + :return: the list of dictionary objects with the (multi-paged) YAML file content. + """ + return list(yaml.load_all(stream, Loader=_AEAYamlLoader)) # nosec + + +def yaml_dump(data: Dict, stream: Optional[TextIO] = None) -> None: + """ + Dump YAML data to a yaml file in an ordered way. + + :param data: the data to write. + :param stream: (optional) the file to write on. + :return: None + """ + yaml.dump(data, stream=stream, Dumper=_AEAYamlDumper) # nosec + + +def yaml_dump_all(data: Sequence[Dict], stream: Optional[TextIO] = None) -> None: + """ + Dump YAML data to a yaml file in an ordered way. + + :param data: the data to write. + :param stream: (optional) the file to write on. + :return: None + """ + yaml.dump_all(data, stream=stream, Dumper=_AEAYamlDumper) # nosec diff --git a/aea/launcher.py b/aea/launcher.py index 7f1198b21e..773d8312f6 100644 --- a/aea/launcher.py +++ b/aea/launcher.py @@ -45,7 +45,7 @@ from aea.runtime import AsyncRuntime -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) def load_agent(agent_dir: Union[PathLike, str]) -> AEA: @@ -103,25 +103,25 @@ def stop_event_thread(): try: stop_event.wait() except (KeyboardInterrupt, EOFError, BrokenPipeError) as e: # pragma: nocover - logger.error( + _default_logger.error( f"Exception raised in stop_event_thread {e} {type(e)}. Skip it, looks process is closed." ) finally: - logger.debug("_run_agent: stop event raised. call agent.stop") + _default_logger.debug("_run_agent: stop event raised. call agent.stop") agent.runtime.stop() Thread(target=stop_event_thread, daemon=True).start() try: agent.start() except KeyboardInterrupt: # pragma: nocover - logger.debug("_run_agent: keyboard interrupt") + _default_logger.debug("_run_agent: keyboard interrupt") except BaseException as e: # pragma: nocover - logger.exception("exception in _run_agent") + _default_logger.exception("exception in _run_agent") exc = AEAException(f"Raised {type(e)}({e})") exc.__traceback__ = e.__traceback__ raise exc finally: - logger.debug("_run_agent: call agent.stop") + _default_logger.debug("_run_agent: call agent.stop") agent.stop() @@ -192,12 +192,12 @@ def start(self) -> Tuple[Callable, Sequence[Any]]: def stop(self): """Stop task.""" if self._future.done(): - logger.debug("Stop called, but task is already done.") + _default_logger.debug("Stop called, but task is already done.") return try: self._stop_event.set() except (FileNotFoundError, BrokenPipeError, EOFError) as e: # pragma: nocover - logger.error( + _default_logger.error( f"Exception raised in task.stop {e} {type(e)}. Skip it, looks process is closed." ) diff --git a/aea/mail/base.py b/aea/mail/base.py index a21fb58527..96ddfc2115 100644 --- a/aea/mail/base.py +++ b/aea/mail/base.py @@ -30,7 +30,7 @@ from aea.protocols.base import Message -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class AEAConnectionError(Exception): @@ -203,7 +203,9 @@ def _get_public_ids_from_uri( f"Invalid package type {package_type} in uri for envelope context." ) except ValueError as e: - logger.debug(f"URI - {uri.path} - not a valid package_id id. Error: {e}") + _default_logger.debug( + f"URI - {uri.path} - not a valid package_id id. Error: {e}" + ) return (skill_id, connection_id) def __str__(self): @@ -322,6 +324,10 @@ def __init__( :param message: the protocol-specific message. :param context: the optional envelope context. """ + enforce(isinstance(to, str), f"To must be string. Found '{type(to)}'") + enforce( + isinstance(sender, str), f"Sender must be string. Found '{type(sender)}'" + ) if isinstance(message, Message): message = self._check_consistency(message, to, sender) self._to = to @@ -338,6 +344,7 @@ def to(self) -> Address: @to.setter def to(self, to: Address) -> None: """Set address of receiver.""" + enforce(isinstance(to, str), f"To must be string. Found '{type(to)}'") self._to = to @property @@ -348,6 +355,9 @@ def sender(self) -> Address: @sender.setter def sender(self, sender: Address) -> None: """Set address of sender.""" + enforce( + isinstance(sender, str), f"Sender must be string. Found '{type(sender)}'" + ) self._sender = sender @property @@ -406,6 +416,16 @@ def connection_id(self) -> Optional[PublicId]: connection_id = self.context.connection_id return connection_id + @property + def is_sender_public_id(self): + """Check if sender is a public id.""" + return PublicId.is_valid_str(self.sender) + + @property + def is_to_public_id(self): + """Check if to is a public id.""" + return PublicId.is_valid_str(self.to) + @staticmethod def _check_consistency(message: Message, to: str, sender: str) -> Message: """Check consistency of sender and to.""" diff --git a/aea/manager.py b/aea/manager.py index 8dff9ff952..33257b4d23 100644 --- a/aea/manager.py +++ b/aea/manager.py @@ -528,7 +528,22 @@ def _build_agent_alias( default_ledger, self._create_private_key(agent_name, default_ledger) ) agent = builder.build() - return AgentAlias(project, agent_name, json_config, agent) + return AgentAlias(project, agent_name, json_config, agent, builder) + + def install_pypi_dependencies(self) -> None: + """Install dependencies for every project has at least one agent alias.""" + for project in self._projects.values(): + self._install_pypi_dependencies_for_project(project) + + def _install_pypi_dependencies_for_project(self, project: Project) -> None: + """Install dependencies for project specified if has at least one agent alias.""" + if not project.agents: + return + self._install_pypi_dependencies_for_agent(list(project.agents)[0]) + + def _install_pypi_dependencies_for_agent(self, agent_name: str) -> None: + """Install dependencies for the agent registered.""" + self._agents[agent_name].builder.install_pypi_dependencies() def _make_config( self, diff --git a/aea/multiplexer.py b/aea/multiplexer.py index ea384fe5a1..a545b2afff 100644 --- a/aea/multiplexer.py +++ b/aea/multiplexer.py @@ -31,9 +31,8 @@ from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.helpers.async_utils import AsyncState, Runnable, ThreadedAsyncRunner from aea.helpers.exception_policy import ExceptionPolicyEnum -from aea.helpers.logging import WithLogger +from aea.helpers.logging import WithLogger, get_logger from aea.mail.base import AEAConnectionError, Empty, Envelope, EnvelopeContext -from aea.mail.base import logger as default_logger from aea.protocols.base import Message @@ -77,6 +76,7 @@ def __init__( loop: Optional[AbstractEventLoop] = None, exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, threaded: bool = False, + agent_name: str = "standalone", ): """ Initialize the connection multiplexer. @@ -89,7 +89,8 @@ def __init__( :param agent_name: the name of the agent that owns the multiplexer, for logging purposes. """ self._exception_policy: ExceptionPolicyEnum = exception_policy - WithLogger.__init__(self, default_logger) + logger = get_logger(__name__, agent_name) + WithLogger.__init__(self, logger=logger) Runnable.__init__(self, loop=loop, threaded=threaded) self._connections: List[Connection] = [] self._id_to_connection: Dict[PublicId, Connection] = {} diff --git a/aea/protocols/base.py b/aea/protocols/base.py index b9eae56380..46058fe971 100644 --- a/aea/protocols/base.py +++ b/aea/protocols/base.py @@ -36,7 +36,7 @@ from aea.exceptions import enforce -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) Address = str @@ -68,7 +68,7 @@ def __init__(self, body: Optional[Dict] = None, **kwargs): try: self._is_consistent() except Exception as e: # pylint: disable=broad-except - logger.error(e) + _default_logger.error(e) @property def has_sender(self) -> bool: @@ -90,6 +90,10 @@ def sender(self) -> Address: def sender(self, sender: Address) -> None: """Set the sender of the message.""" enforce(self._sender is None, "Sender already set.") + enforce( + isinstance(sender, str), + f"Sender must be string type. Found '{type(sender)}'", + ) self._sender = sender @property @@ -108,6 +112,7 @@ def to(self) -> Address: def to(self, to: Address) -> None: """Set address of receiver.""" enforce(self._to is None, "To already set.") + enforce(isinstance(to, str), f"To must be string type. Found '{type(to)}'") self._to = to @property diff --git a/aea/protocols/default/README.md b/aea/protocols/default/README.md index 52298fad7d..d691d80686 100644 --- a/aea/protocols/default/README.md +++ b/aea/protocols/default/README.md @@ -10,7 +10,7 @@ This is a protocol for two agents exchanging any bytes messages. --- name: default author: fetchai -version: 0.6.0 +version: 0.7.0 description: A protocol for exchanging any bytes message. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/aea/protocols/default/message.py b/aea/protocols/default/message.py index fcda5a2636..a11232cff4 100644 --- a/aea/protocols/default/message.py +++ b/aea/protocols/default/message.py @@ -28,7 +28,7 @@ from aea.protocols.default.custom_types import ErrorCode as CustomErrorCode -logger = logging.getLogger("aea.protocols.default.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.default.message") DEFAULT_BODY_SIZE = 4 @@ -36,7 +36,7 @@ class DefaultMessage(Message): """A protocol for exchanging any bytes message.""" - protocol_id = ProtocolId.from_str("fetchai/default:0.6.0") + protocol_id = ProtocolId.from_str("fetchai/default:0.7.0") ErrorCode = CustomErrorCode @@ -240,7 +240,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/aea/protocols/default/protocol.yaml b/aea/protocols/default/protocol.yaml index 4e9fc8fb9f..04ed35ed0b 100644 --- a/aea/protocols/default/protocol.yaml +++ b/aea/protocols/default/protocol.yaml @@ -1,18 +1,18 @@ name: default author: fetchai -version: 0.6.0 +version: 0.7.0 type: protocol description: A protocol for exchanging any bytes message. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmVXQi6676YTqbeYTRNyUw7Ln962VnKKF4HN1ren7TeNk9 + README.md: Qma6jLUtwFdNMLAq5Z79tAv8vgBBDjw3RVUfr5ab36nAug __init__.py: QmWpWuGcXu2SodVGUZiAdMrF8Tn7MAPDbiGh7vd9nEfEX6 custom_types.py: QmRcgwDdTxkSHyfF9eoMtsb5P5GJDm4oyLq5W6ZBko1MFU default.proto: QmNzMUvXkBm5bbitR5Yi49ADiwNn1FhCvXqSKKoqAPZyXv default_pb2.py: QmSRFi1s3jcqnPuk4yopJeNuC6o58RL7dvEdt85uns3B3N dialogues.py: Qmc991snbS7DwFxo1cKcq1rQ2uj7y8ukp14kfe2zve387C - message.py: QmQBdmhR6GXkcfatPWnfaA87c5GnmEztU3vLEGCMWMH2kV + message.py: QmVCPyC32cbriCbJE3UxNcvmdKMHgosWbrtPzbN4jLEabX serialization.py: QmRF7XPNEk1emKmFMZaYBCF3gr4CdkMXagoSpjQEmnRGG6 fingerprint_ignore_patterns: [] dependencies: diff --git a/aea/protocols/dialogue/base.py b/aea/protocols/dialogue/base.py index 52b154609b..8be684e778 100644 --- a/aea/protocols/dialogue/base.py +++ b/aea/protocols/dialogue/base.py @@ -27,16 +27,36 @@ import itertools import secrets +import sys from abc import ABC +from collections import namedtuple from enum import Enum from inspect import signature from typing import Callable, Dict, FrozenSet, List, Optional, Set, Tuple, Type, cast from aea.common import Address -from aea.exceptions import enforce +from aea.exceptions import AEAEnforceError, enforce from aea.protocols.base import Message +if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 7): + DialogueMessage = namedtuple( # pragma: no cover + "DialogueMessage", + ["performative", "contents", "is_incoming", "target"], + rename=False, + module="aea.protocols.dialogues.base", + ) + DialogueMessage.__new__.__defaults__ = (dict(), None, None) # pragma: no cover +else: + DialogueMessage = namedtuple( # pylint: disable=unexpected-keyword-arg + "DialogueMessage", + ["performative", "contents", "is_incoming", "target"], + rename=False, + defaults=[dict(), None, None], + module="aea.protocols.dialogues.base", + ) + + class InvalidDialogueMessage(Exception): """Exception for adding invalid message to a dialogue.""" @@ -359,6 +379,15 @@ def rules(self) -> "Rules": raise ValueError("Rules is not set.") return self._rules + @property + def message_class(self) -> Type[Message]: + """ + Get the message class. + + :return: the message class + """ + return self._message_class + @property def is_self_initiated(self) -> bool: """ @@ -570,6 +599,7 @@ def reply( self, performative: Message.Performative, target_message: Optional[Message] = None, + target: Optional[int] = None, **kwargs, ) -> Message: """ @@ -578,6 +608,7 @@ def reply( Note if no target_message is provided, the last message in the dialogue will be replied to. :param target_message: the message to reply to. + :param target: the id of the message to reply to. :param performative: the performative of the reply message. :param kwargs: the content of the reply message. @@ -587,20 +618,25 @@ def reply( if last_message is None: raise ValueError("Cannot reply in an empty dialogue!") - if target_message is None: - target_message = last_message - else: - enforce( - self._has_message( - target_message # type: ignore - ), - "The target message does not exist in this dialogue.", - ) + if target_message is None and target is None: + target = last_message.message_id + elif target_message is not None and target is None: + target = target_message.message_id + elif target_message is not None and target is not None: + if target != target_message.message_id: + raise AEAEnforceError( + "The provided target and target_message do not match." + ) + + enforce( + self._has_message_id(target), # type: ignore + "The target message does not exist in this dialogue.", + ) reply = self._message_class( dialogue_reference=self.dialogue_label.dialogue_reference, message_id=last_message.message_id + 1, - target=target_message.message_id, + target=target, performative=performative, **kwargs, ) @@ -996,6 +1032,24 @@ def dialogue_stats(self) -> DialogueStats: """ return self._dialogue_stats + @property + def message_class(self) -> Type[Message]: + """ + Get the message class. + + :return: the message class + """ + return self._message_class + + @property + def dialogue_class(self) -> Type[Dialogue]: + """ + Get the dialogue class. + + :return: the dialogue class + """ + return self._dialogue_class + def get_dialogues_with_counterparty(self, counterparty: Address) -> List[Dialogue]: """ Get the dialogues by address. @@ -1035,13 +1089,14 @@ def _counterparty_from_message(self, message: Message) -> Address: ) return counterparty - def new_self_initiated_dialogue_reference(self) -> Tuple[str, str]: + @classmethod + def new_self_initiated_dialogue_reference(cls) -> Tuple[str, str]: """ Return a dialogue label for a new self initiated dialogue. :return: the next nonce """ - return self._generate_dialogue_nonce(), Dialogue.UNASSIGNED_DIALOGUE_REFERENCE + return cls._generate_dialogue_nonce(), Dialogue.UNASSIGNED_DIALOGUE_REFERENCE def create( self, counterparty: Address, performative: Message.Performative, **kwargs, @@ -1141,7 +1196,7 @@ def update(self, message: Message) -> Optional[Dialogue]: ) enforce( message.to == self.self_address, - "Message to and dialogue self address do not match.", + f"Message to and dialogue self address do not match. Got 'to={message.to}' expected 'to={self.self_address}'.", ) dialogue_reference = message.dialogue_reference diff --git a/aea/protocols/generator/base.py b/aea/protocols/generator/base.py index 79725aaf03..a94dd3bddc 100644 --- a/aea/protocols/generator/base.py +++ b/aea/protocols/generator/base.py @@ -20,7 +20,6 @@ # pylint: skip-file import itertools -import logging import os import shutil from datetime import date @@ -55,9 +54,6 @@ from aea.protocols.generator.extract_specification import extract -logger = logging.getLogger(__name__) - - def _copyright_header_str(author: str) -> str: """ Produce the copyright header text for a protocol. @@ -614,7 +610,7 @@ def _message_class_str(self) -> str: cls_str += self._import_from_custom_types_module() cls_str += ( self.indent - + '\nlogger = logging.getLogger("aea.packages.{}.protocols.{}.message")\n'.format( + + '\n_default_logger = logging.getLogger("aea.packages.{}.protocols.{}.message")\n'.format( self.protocol_specification.author, self.protocol_specification.name ) ) @@ -876,7 +872,7 @@ def _message_class_str(self) -> str: self.indent + "except (AEAEnforceError, ValueError, KeyError) as e:\n" ) self._change_indent(1) - cls_str += self.indent + "logger.error(str(e))\n" + cls_str += self.indent + "_default_logger.error(str(e))\n" cls_str += self.indent + "return False\n\n" self._change_indent(-1) cls_str += self.indent + "return True\n" @@ -1936,7 +1932,7 @@ def generate_protobuf_only_mode(self) -> None: shutil.rmtree(output_folder) raise SyntaxError("Error in the protocol buffer schema code:\n" + msg) - def generate_full_mode(self) -> None: + def generate_full_mode(self) -> Optional[str]: """ Run the generator in "full" mode: @@ -1946,7 +1942,7 @@ def generate_full_mode(self) -> None: d) applies black formatting e) applies isort formatting - :return: None + :return: optional warning message """ # Run protobuf only mode self.generate_protobuf_only_mode() @@ -1998,13 +1994,14 @@ def generate_full_mode(self) -> None: try_run_isort_formatting(self.path_to_generated_protocol_package) # Warn if specification has custom types + incomplete_generation_warning_msg = None # type: Optional[str] if len(self.spec.all_custom_types) > 0: incomplete_generation_warning_msg = "The generated protocol is incomplete, because the protocol specification contains the following custom types: {}. Update the generated '{}' file with the appropriate implementations of these custom types.".format( self.spec.all_custom_types, CUSTOM_TYPES_DOT_PY_FILE_NAME ) - logger.warning(incomplete_generation_warning_msg) + return incomplete_generation_warning_msg - def generate(self, protobuf_only: bool = False) -> None: + def generate(self, protobuf_only: bool = False) -> Optional[str]: """ Run the generator. If in "full" mode (protobuf_only is False), it: @@ -2017,9 +2014,11 @@ def generate(self, protobuf_only: bool = False) -> None: If in "protobuf only" mode (protobuf_only is True), it only does a) and b). :param protobuf_only: mode of running the generator. - :return: None + :return: optional warning message. """ + message = None if protobuf_only: self.generate_protobuf_only_mode() else: - self.generate_full_mode() + message = self.generate_full_mode() + return message diff --git a/aea/protocols/signing/README.md b/aea/protocols/signing/README.md index 72850bf2d7..209b894bcd 100644 --- a/aea/protocols/signing/README.md +++ b/aea/protocols/signing/README.md @@ -10,7 +10,7 @@ This is a protocol for communication between a skill and a decision maker. --- name: signing author: fetchai -version: 0.4.0 +version: 0.5.0 description: A protocol for communication between skills and decision maker. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/aea/protocols/signing/message.py b/aea/protocols/signing/message.py index f0edf46a61..38a069a582 100644 --- a/aea/protocols/signing/message.py +++ b/aea/protocols/signing/message.py @@ -35,7 +35,7 @@ from aea.protocols.signing.custom_types import Terms as CustomTerms -logger = logging.getLogger("aea.packages.fetchai.protocols.signing.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.signing.message") DEFAULT_BODY_SIZE = 4 @@ -43,7 +43,7 @@ class SigningMessage(Message): """A protocol for communication between skills and decision maker.""" - protocol_id = ProtocolId.from_str("fetchai/signing:0.4.0") + protocol_id = ProtocolId.from_str("fetchai/signing:0.5.0") ErrorCode = CustomErrorCode @@ -292,7 +292,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/aea/protocols/signing/protocol.yaml b/aea/protocols/signing/protocol.yaml index d1043f789c..5c0830c962 100644 --- a/aea/protocols/signing/protocol.yaml +++ b/aea/protocols/signing/protocol.yaml @@ -1,16 +1,16 @@ name: signing author: fetchai -version: 0.4.0 +version: 0.5.0 type: protocol description: A protocol for communication between skills and decision maker. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmQGHs1QRtbTnqV4X8kzDyofu6U7Gx1WteC2MurAkcTpWG + README.md: QmcZzuS6qt2wE75iznTytDp8NGWTBR7jsXonjNWYhYAHcw __init__.py: Qmd7JYjcrD95jdYfSZs6j7UX5TPZfPYXuTFrUzS3FHCxhS custom_types.py: Qmc7sAyCQbAaVs5dZf9hFkTrB2BG8VAioWzbyKBAybrQ1J dialogues.py: QmQ1WKs3Dn15oDSwpc4N8hdADLxrn76U4X5SiLAmyGiPPY - message.py: QmQV6iT67wo4Q9L5XGKbjyf83YB8kD9iGPQ5URxRE9sxyY + message.py: QmZcdSTCGhHnUV3WtRBUWPu66Qa66fv8wfQm994473JAcd serialization.py: QmbXSvWYfEmYyGdfuTXVJYaJfzqVLtMaLR8H3UCLZkvbSC signing.proto: QmcxyLzqhTE9xstAEzCVH17osbLxmSdALx9njmuPjhjrvZ signing_pb2.py: QmY3Ak5ih5zGvKjeZ5EnzrGX4tMYn5dWpjPArQwFeJpVKu diff --git a/aea/protocols/state_update/README.md b/aea/protocols/state_update/README.md index 4f1100612b..f8bbfa66f8 100644 --- a/aea/protocols/state_update/README.md +++ b/aea/protocols/state_update/README.md @@ -10,7 +10,7 @@ This is a protocol for updating the state of a decision maker. --- name: state_update author: fetchai -version: 0.4.0 +version: 0.5.0 description: A protocol for state updates to the decision maker state. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/aea/protocols/state_update/message.py b/aea/protocols/state_update/message.py index e62470fbd9..2c58d72ec7 100644 --- a/aea/protocols/state_update/message.py +++ b/aea/protocols/state_update/message.py @@ -27,7 +27,9 @@ from aea.protocols.base import Message -logger = logging.getLogger("aea.packages.fetchai.protocols.state_update.message") +_default_logger = logging.getLogger( + "aea.packages.fetchai.protocols.state_update.message" +) DEFAULT_BODY_SIZE = 4 @@ -35,7 +37,7 @@ class StateUpdateMessage(Message): """A protocol for state updates to the decision maker state.""" - protocol_id = ProtocolId.from_str("fetchai/state_update:0.4.0") + protocol_id = ProtocolId.from_str("fetchai/state_update:0.5.0") class Performative(Message.Performative): """Performatives for the state_update protocol.""" @@ -344,7 +346,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/aea/protocols/state_update/protocol.yaml b/aea/protocols/state_update/protocol.yaml index 6adc5e1b0c..eba215e7ec 100644 --- a/aea/protocols/state_update/protocol.yaml +++ b/aea/protocols/state_update/protocol.yaml @@ -1,15 +1,15 @@ name: state_update author: fetchai -version: 0.4.0 +version: 0.5.0 type: protocol description: A protocol for state updates to the decision maker state. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmQ16KZTu6zC7GgAkAYnis5exdeUWLK77nkLTHEENuUNKM + README.md: QmZaiA3tRSsvofPcAdjKEZZh9RAyZL9Wk8vXHUoGy9enLi __init__.py: QmUrvqDr24Ph1nnUqjTUPh9QoftuTsef3Dj3yzPUMY38fu dialogues.py: Qmd59WgpFccLn1zhpLdwm3zDCmCsjSoQXVn6M7PgFwwkgR - message.py: QmWzC79P44rpzdW5p5sdhxW5wv8CCrg4RzCgAnLPuPgyzC + message.py: QmPAi5XBDsH7Sk1HMK7c7vXjrTbdJ6JYhAtHqoKk9whbAu serialization.py: QmciaNPHkpyxWLYVtBPnYkKHj6Ur9E3CPJ9QvWbXFD91Yw state_update.proto: QmdmEUSa7PDxJ98ZmGE7bLFPmUJv8refgbkHPejw6uDdwD state_update_pb2.py: QmQr5KXhapRv9AnfQe7Xbr5bBqYWp9DEMLjxX8UWmK75Z4 diff --git a/aea/registries/base.py b/aea/registries/base.py index 2d32db2864..93e86103cb 100644 --- a/aea/registries/base.py +++ b/aea/registries/base.py @@ -20,7 +20,6 @@ """This module contains registries.""" import copy -import logging from abc import ABC, abstractmethod from operator import itemgetter from typing import Dict, Generic, List, Optional, Set, Tuple, TypeVar, cast @@ -33,12 +32,10 @@ PublicId, SkillId, ) -from aea.helpers.logging import WithLogger +from aea.helpers.logging import WithLogger, get_logger from aea.skills.base import Behaviour, Handler, Model -logger = logging.getLogger(__name__) - Item = TypeVar("Item") ItemId = TypeVar("ItemId") SkillComponentType = TypeVar("SkillComponentType", Handler, Behaviour, Model) @@ -47,8 +44,13 @@ class Registry(Generic[ItemId, Item], WithLogger, ABC): """This class implements an abstract registry.""" - def __init__(self): - """Initialize the registry.""" + def __init__(self, agent_name: str = "standalone"): + """ + Initialize the registry. + + :param agent_name: the name of the agent + """ + logger = get_logger(__name__, agent_name) super().__init__(logger) @abstractmethod @@ -189,13 +191,15 @@ def teardown(self) -> None: class AgentComponentRegistry(Registry[ComponentId, Component]): """This class implements a simple dictionary-based registry for agent components.""" - def __init__(self) -> None: + def __init__(self, **kwargs) -> None: """ Instantiate the registry. + :param kwargs: kwargs + :return: None """ - super().__init__() + super().__init__(**kwargs) self._components_by_type: Dict[ComponentType, Dict[PublicId, Component]] = {} self._registered_keys: Set[ComponentId] = set() @@ -327,13 +331,15 @@ class ComponentRegistry( ): """This class implements a generic registry for skill components.""" - def __init__(self) -> None: + def __init__(self, **kwargs) -> None: """ Instantiate the registry. + :param kwargs: kwargs + :return: None """ - super().__init__() + super().__init__(**kwargs) self._items: PublicIdRegistry[ Dict[str, SkillComponentType] ] = PublicIdRegistry() @@ -504,13 +510,15 @@ def teardown(self) -> None: class HandlerRegistry(ComponentRegistry[Handler]): """This class implements the handlers registry.""" - def __init__(self) -> None: + def __init__(self, **kwargs) -> None: """ Instantiate the registry. + :param kwargs: kwargs + :return: None """ - super().__init__() + super().__init__(**kwargs) # nested public id registries; one for protocol ids, one for skill ids self._items_by_protocol_and_skill = PublicIdRegistry[ PublicIdRegistry[Handler] diff --git a/aea/registries/filter.py b/aea/registries/filter.py index 38c434b953..8add2fc25d 100644 --- a/aea/registries/filter.py +++ b/aea/registries/filter.py @@ -18,20 +18,17 @@ # ------------------------------------------------------------------------------ """This module contains registries.""" -import logging from typing import List, Optional from aea.configurations.base import PublicId, SkillId from aea.helpers.async_friendly_queue import AsyncFriendlyQueue +from aea.helpers.logging import WithLogger, get_logger from aea.protocols.base import Message from aea.registries.resources import Resources from aea.skills.base import Behaviour, Handler -logger = logging.getLogger(__name__) - - -class Filter: +class Filter(WithLogger): """This class implements the filter of an AEA.""" def __init__( @@ -43,6 +40,8 @@ def __init__( :param resources: the resources :param decision_maker_out_queue: the decision maker queue """ + logger = get_logger(__name__, resources.agent_name) + WithLogger.__init__(self, logger=logger) self._resources = resources self._decision_maker_out_queue = decision_maker_out_queue @@ -106,7 +105,7 @@ async def get_internal_message(self) -> Optional[Message]: def handle_internal_message(self, internal_message: Optional[Message]) -> None: """Handlle internal message.""" if internal_message is None: - logger.warning("Got 'None' while processing internal messages.") + self.logger.warning("Got 'None' while processing internal messages.") return self._handle_internal_message(internal_message) @@ -122,7 +121,7 @@ def _handle_new_behaviours(self) -> None: is_dynamically_added=True, ) except ValueError as e: - logger.warning( + self.logger.warning( "Error when trying to add a new behaviour: {}".format(str(e)) ) @@ -138,7 +137,7 @@ def _handle_new_handlers(self) -> None: is_dynamically_added=True, ) except ValueError as e: - logger.warning( + self.logger.warning( "Error when trying to add a new handler: {}".format(str(e)) ) @@ -147,17 +146,19 @@ def _handle_internal_message(self, message: Message): try: skill_id = PublicId.from_str(message.to) except ValueError: - logger.warning("Invalid public id as destination={}".format(message.to)) + self.logger.warning( + "Invalid public id as destination={}".format(message.to) + ) return handler = self.resources.handler_registry.fetch_by_protocol_and_skill( message.protocol_id, skill_id, ) if handler is not None: - logger.debug( + self.logger.debug( "Calling handler {} of skill {}".format(type(handler), skill_id) ) handler.handle(message) else: - logger.warning( + self.logger.warning( "No internal handler fetched for skill_id={}".format(skill_id) ) diff --git a/aea/registries/resources.py b/aea/registries/resources.py index d136a7678c..813a8d3336 100644 --- a/aea/registries/resources.py +++ b/aea/registries/resources.py @@ -45,16 +45,17 @@ class Resources: """This class implements the object that holds the resources of an AEA.""" - def __init__(self) -> None: + def __init__(self, agent_name: str = "standalone") -> None: """ Instantiate the resources. :return None """ - self._component_registry = AgentComponentRegistry() - self._handler_registry = HandlerRegistry() - self._behaviour_registry = ComponentRegistry[Behaviour]() - self._model_registry = ComponentRegistry[Model]() + self._agent_name = agent_name + self._component_registry = AgentComponentRegistry(agent_name=agent_name) + self._handler_registry = HandlerRegistry(agent_name=agent_name) + self._behaviour_registry = ComponentRegistry[Behaviour](agent_name=agent_name) + self._model_registry = ComponentRegistry[Model](agent_name=agent_name) self._registries = [ self._component_registry, @@ -63,6 +64,11 @@ def __init__(self) -> None: self._model_registry, ] # type: List[Registry] + @property + def agent_name(self) -> str: + """Get the agent name.""" + return self._agent_name + @property def component_registry(self) -> AgentComponentRegistry: """Get the agent component registry.""" diff --git a/aea/runner.py b/aea/runner.py index 5c6791925b..a39ec281af 100644 --- a/aea/runner.py +++ b/aea/runner.py @@ -34,7 +34,7 @@ from aea.runtime import AsyncRuntime -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) class AEAInstanceTask(AbstractExecutorTask): @@ -54,7 +54,7 @@ def start(self) -> None: try: self._agent.start() except BaseException: - logger.exception("Exceptions raised in runner task.") + _default_logger.exception("Exceptions raised in runner task.") raise def stop(self) -> None: diff --git a/aea/runtime.py b/aea/runtime.py index e6af111556..4fa437df39 100644 --- a/aea/runtime.py +++ b/aea/runtime.py @@ -16,12 +16,9 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - - """This module contains the implementation of runtime for economic agent (AEA).""" import asyncio -import logging from asyncio.events import AbstractEventLoop from concurrent.futures._base import CancelledError from contextlib import suppress @@ -34,13 +31,11 @@ from aea.decision_maker.base import DecisionMaker, DecisionMakerHandler from aea.helpers.async_utils import Runnable from aea.helpers.exception_policy import ExceptionPolicyEnum +from aea.helpers.logging import WithLogger, get_logger from aea.multiplexer import AsyncMultiplexer from aea.skills.tasks import TaskManager -logger = logging.getLogger(__name__) - - class _StopRuntime(Exception): """ Exception to stop runtime. @@ -71,7 +66,7 @@ class RuntimeStates(Enum): error = "error" -class BaseRuntime(Runnable): +class BaseRuntime(Runnable, WithLogger): """Abstract runtime class to create implementations.""" RUN_LOOPS: Dict[str, Type[BaseAgentLoop]] = { @@ -85,7 +80,7 @@ def __init__( agent: AbstractAgent, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, - threaded=False, + threaded: bool = False, ) -> None: """ Init runtime. @@ -96,6 +91,8 @@ def __init__( :return: None """ Runnable.__init__(self, threaded=threaded, loop=loop if not threaded else None) + logger = get_logger(__name__, agent.name) + WithLogger.__init__(self, logger=logger) self._agent: AbstractAgent = agent self._state: AsyncState = AsyncState(RuntimeStates.stopped, RuntimeStates) self._state.add_callback(self._log_runtime_state) @@ -139,7 +136,10 @@ def _get_multiplexer_instance(self) -> AsyncMultiplexer: self._agent, "_connection_exception_policy", ExceptionPolicyEnum.propagate ) return AsyncMultiplexer( - self._agent.connections, loop=self.loop, exception_policy=exception_policy + self._agent.connections, + loop=self.loop, + exception_policy=exception_policy, + agent_name=self._agent.name, ) def _get_main_loop_class(self, loop_mode: str) -> Type[BaseAgentLoop]: @@ -185,16 +185,16 @@ def _get_main_loop_instance(self, loop_mode: str) -> BaseAgentLoop: def _log_runtime_state(self, state) -> None: """Log a runtime state changed.""" - logger.debug(f"[{self._agent.name}]: Runtime state changed to {state}.") + self.logger.debug(f"[{self._agent.name}]: Runtime state changed to {state}.") def _teardown(self) -> None: """Tear down runtime.""" - logger.debug("[{}]: Runtime teardown...".format(self._agent.name)) + self.logger.debug("[{}]: Runtime teardown...".format(self._agent.name)) if self._decision_maker is not None: # pragma: nocover self.decision_maker.stop() self.task_manager.stop() self._agent.teardown() - logger.debug("[{}]: Runtime teardown completed".format(self._agent.name)) + self.logger.debug("[{}]: Runtime teardown completed".format(self._agent.name)) @property def is_running(self) -> bool: @@ -290,7 +290,7 @@ async def stop_runtime(self) -> None: self.multiplexer.stop() await self.multiplexer.wait_completed() - logger.debug("Runtime loop stopped!") + self.logger.debug("Runtime loop stopped!") async def run_runtime(self) -> None: """Run agent and starts multiplexer.""" @@ -310,16 +310,16 @@ async def _start_multiplexer(self) -> None: async def _start_agent_loop(self) -> None: """Start agent main loop asynchronous way.""" - logger.debug("[{}]: Runtime started".format(self._agent.name)) + self.logger.debug("[{}]: Runtime started".format(self._agent.name)) await self.multiplexer.connection_status.wait(ConnectionStates.connected) self.task_manager.start() if self._decision_maker is not None: # pragma: nocover self.decision_maker.start() - logger.debug("[{}]: Calling setup method...".format(self._agent.name)) + self.logger.debug("[{}]: Calling setup method...".format(self._agent.name)) self._agent.setup() - logger.debug("[{}]: Run main loop...".format(self._agent.name)) + self.logger.debug("[{}]: Run main loop...".format(self._agent.name)) self.main_loop.start() self._state.set(RuntimeStates.running) try: diff --git a/aea/skills/base.py b/aea/skills/base.py index 25b1756a0c..7c97f7c731 100644 --- a/aea/skills/base.py +++ b/aea/skills/base.py @@ -43,7 +43,7 @@ from aea.configurations.loader import load_component_configuration from aea.context.base import AgentContext from aea.exceptions import AEAException, enforce -from aea.helpers.base import load_module +from aea.helpers.base import _get_aea_logger_name_prefix, load_module from aea.helpers.logging import AgentLoggerAdapter from aea.multiplexer import MultiplexerStatus, OutBox from aea.protocols.base import Message @@ -749,6 +749,7 @@ def from_config( skill_context = SkillContext() skill_context.set_agent_context(agent_context) logger_name = f"aea.packages.{configuration.author}.skills.{configuration.name}" + logger_name = _get_aea_logger_name_prefix(logger_name, agent_context.agent_name) _logger = AgentLoggerAdapter( logging.getLogger(logger_name), agent_context.agent_name ) diff --git a/aea/skills/error/skill.yaml b/aea/skills/error/skill.yaml index c6ecba3c6e..92474217db 100644 --- a/aea/skills/error/skill.yaml +++ b/aea/skills/error/skill.yaml @@ -1,6 +1,6 @@ name: error author: fetchai -version: 0.6.0 +version: 0.7.0 type: skill description: The error skill implements basic error handling required by all AEAs. license: Apache-2.0 @@ -11,7 +11,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: [] behaviours: {} handlers: @@ -20,3 +20,4 @@ handlers: class_name: ErrorHandler models: {} dependencies: {} +is_abstract: false diff --git a/aea/skills/scaffold/skill.yaml b/aea/skills/scaffold/skill.yaml index bdda9507e7..6fcdeb71ad 100644 --- a/aea/skills/scaffold/skill.yaml +++ b/aea/skills/scaffold/skill.yaml @@ -30,3 +30,4 @@ models: foo: bar class_name: MyModel dependencies: {} +is_abstract: false diff --git a/aea/test_tools/generic.py b/aea/test_tools/generic.py index 74743bdd15..126da70a68 100644 --- a/aea/test_tools/generic.py +++ b/aea/test_tools/generic.py @@ -18,16 +18,25 @@ # ------------------------------------------------------------------------------ """This module contains generic tools for AEA end-to-end testing.""" +from collections import OrderedDict from pathlib import Path -from typing import Any, Dict, List - -import yaml +from typing import Any, Dict, List, cast from aea.cli.utils.config import handle_dotted_path -from aea.configurations.base import PublicId +from aea.configurations.base import ( + CRUDCollection, + ComponentConfiguration, + PackageConfiguration, + PackageType, + PublicId, + SkillConfig, + dependencies_from_json, +) from aea.connections.stub.connection import write_envelope from aea.exceptions import enforce +from aea.helpers.yaml_utils import yaml_dump, yaml_dump_all from aea.mail.base import Envelope +from aea.test_tools.constants import DEFAULT_AUTHOR def write_envelope_to_file(envelope: Envelope, file_path: str) -> None: @@ -66,24 +75,95 @@ def read_envelope_from_file(file_path: str): return Envelope(to=to, sender=sender, protocol_id=protocol_id, message=message,) -def _nested_set(dic: Dict, keys: List, value: Any) -> None: +def _nested_set( + configuration_obj: PackageConfiguration, keys: List, value: Any +) -> None: """ - Nested set a value to a dict. + Nested set a value to a dict. Force sets the values, overwriting any present values, but maintaining schema validation. - :param dic: target dict + :param configuration_obj: configuration object :param keys: list of keys. :param value: a value to set. :return: None. """ - for key in keys[:-1]: - dic = dic.setdefault(key, {}) - dic[keys[-1]] = value - -def force_set_config(dotted_path: str, value: Any) -> None: + def get_nested_ordered_dict_from_dict(input_dict: Dict) -> Dict: + _dic = {} + for _key, _value in input_dict.items(): + if isinstance(_value, dict): + _dic[_key] = OrderedDict(get_nested_ordered_dict_from_dict(_value)) + else: + _dic[_key] = _value + return _dic + + def get_nested_ordered_dict_from_keys_and_value( + keys: List[str], value: Any + ) -> Dict: + _dic = ( + OrderedDict(get_nested_ordered_dict_from_dict(value)) + if isinstance(value, dict) + else value + ) + for key in keys[::-1]: + _dic = OrderedDict({key: _dic}) + return _dic + + root_key = keys[0] + if ( + isinstance(configuration_obj, SkillConfig) + and root_key in SkillConfig.FIELDS_WITH_NESTED_FIELDS + ): + root_attr = getattr(configuration_obj, root_key) + length = len(keys) + if length < 3 or keys[2] not in SkillConfig.NESTED_FIELDS_ALLOWED_TO_UPDATE: + raise ValueError(f"Invalid keys={keys}.") # pragma: nocover + skill_component_id = keys[1] + skill_component_config = root_attr.read(skill_component_id) + if length == 3 and isinstance(value, dict): # root.skill_component_id.args + # set all args + skill_component_config.args = get_nested_ordered_dict_from_dict(value) + elif len(keys) >= 4: # root.skill_component_id.args.[keys] + # update some args + dic = get_nested_ordered_dict_from_keys_and_value(keys[3:], value) + skill_component_config.args.update(dic) + else: + raise ValueError( # pragma: nocover + f"Invalid keys={keys} and values={value}." + ) + root_attr.update(skill_component_id, skill_component_config) + else: + root_attr = getattr(configuration_obj, root_key) + if isinstance(root_attr, CRUDCollection): + if isinstance(value, dict) and len(keys) == 1: # root. + for _key, _value in value.items(): + dic = get_nested_ordered_dict_from_keys_and_value([_key], _value) + root_attr.update(_key, dic[_key]) + elif len(keys) >= 2: # root.[keys] + dic = get_nested_ordered_dict_from_keys_and_value(keys[1:], value) + root_attr.update(keys[1], dic[keys[1]]) + else: + raise ValueError( # pragma: nocover + f"Invalid keys={keys} and values={value}." + ) + elif root_key == "dependencies": + enforce( + isinstance(configuration_obj, ComponentConfiguration), + "Cannot only set dependencies to ComponentConfiguration instances.", + ) + configuration_obj = cast(ComponentConfiguration, configuration_obj) + new_pypi_dependencies = dependencies_from_json(value) + configuration_obj.pypi_dependencies = new_pypi_dependencies + else: + dic = get_nested_ordered_dict_from_keys_and_value(keys, value) + setattr(configuration_obj, root_key, dic[root_key]) + + +def nested_set_config( + dotted_path: str, value: Any, author: str = DEFAULT_AUTHOR +) -> None: """ - Set an AEA config without validation. + Set an AEA config with nested values. Run from agent's directory. @@ -97,16 +177,24 @@ def force_set_config(dotted_path: str, value: Any) -> None: :param dotted_path: dotted path to a setting. :param value: a value to assign. Must be of yaml serializable type. + :param author: the author name, used to parse the dotted path. :return: None. """ - settings_keys, file_path, _ = handle_dotted_path(dotted_path) - - settings = {} - with open(file_path, "r") as f: - settings = yaml.safe_load(f) - - _nested_set(settings, settings_keys, value) - - with open(file_path, "w") as f: - yaml.dump(settings, f, default_flow_style=False) + settings_keys, config_file_path, config_loader, _ = handle_dotted_path( + dotted_path, author + ) + + with config_file_path.open() as fp: + config = config_loader.load(fp) + + _nested_set(config, settings_keys, value) + + if config.package_type == PackageType.AGENT: + json_data = config.ordered_json + component_configurations = json_data.pop("component_configurations") + yaml_dump_all( + [json_data] + component_configurations, config_file_path.open("w") + ) + else: + yaml_dump(config.ordered_json, config_file_path.open("w")) diff --git a/aea/test_tools/test_cases.py b/aea/test_tools/test_cases.py index ac19fa1dee..8d4a6a0a3e 100644 --- a/aea/test_tools/test_cases.py +++ b/aea/test_tools/test_cases.py @@ -53,7 +53,7 @@ from aea.test_tools.constants import DEFAULT_AUTHOR from aea.test_tools.exceptions import AEATestingException from aea.test_tools.generic import ( - force_set_config, + nested_set_config, read_envelope_from_file, write_envelope_to_file, ) @@ -61,7 +61,7 @@ from tests.conftest import ROOT_DIR -logger = logging.getLogger(__name__) +_default_logger = logging.getLogger(__name__) CLI_LOG_OPTION = ["-v", "OFF"] @@ -125,10 +125,10 @@ def set_config(cls, dotted_path: str, value: Any, type_: str = "str") -> Result: ) @classmethod - def force_set_config(cls, dotted_path: str, value: Any) -> None: + def nested_set_config(cls, dotted_path: str, value: Any) -> None: """Force set config.""" with cd(cls._get_cwd()): - force_set_config(dotted_path, value) + nested_set_config(dotted_path, value) @classmethod def disable_aea_logging(cls) -> None: @@ -227,29 +227,44 @@ def start_thread(cls, target: Callable, **kwargs) -> Thread: return thread @classmethod - def create_agents(cls, *agents_names: str) -> None: + def create_agents( + cls, *agents_names: str, is_local: bool = True, is_empty: bool = False + ) -> None: """ Create agents in current working directory. :param agents_names: str agent names. + :param is_local: a flag for local folder add True by default. + :param empty: optional boolean flag for skip adding default dependencies. :return: None """ + cli_args = ["create", "--local", "--empty"] + if not is_local: # pragma: nocover + cli_args.remove("--local") + if not is_empty: # pragma: nocover + cli_args.remove("--empty") for name in set(agents_names): - cls.run_cli_command("create", "--local", name, "--author", cls.author) + cls.run_cli_command(*cli_args, name) cls.agents.add(name) @classmethod - def fetch_agent(cls, public_id: str, agent_name: str) -> None: + def fetch_agent( + cls, public_id: str, agent_name: str, is_local: bool = True + ) -> None: """ Create agents in current working directory. :param public_id: str public id :param agents_name: str agent name. + :param is_local: a flag for local folder add True by default. :return: None """ - cls.run_cli_command("fetch", "--local", public_id, "--alias", agent_name) + cli_args = ["fetch", "--local"] + if not is_local: # pragma: nocover + cli_args.remove("--local") + cls.run_cli_command(*cli_args, public_id, "--alias", agent_name) cls.agents.add(agent_name) @classmethod @@ -728,14 +743,14 @@ def missing_from_output( if is_terminating: cls.terminate_agents(process) if missing_strings != []: - logger.info( + _default_logger.info( "Non-empty missing strings, stderr:\n{}".format(cls.stderr[process.pid]) ) - logger.info("=====================") - logger.info( + _default_logger.info("=====================") + _default_logger.info( "Non-empty missing strings, stdout:\n{}".format(cls.stdout[process.pid]) ) - logger.info("=====================") + _default_logger.info("=====================") return missing_strings @classmethod @@ -813,12 +828,15 @@ class AEATestCaseEmpty(BaseAEATestCase): This test case will create a default AEA project. """ + IS_LOCAL = True + IS_EMPTY = False + @classmethod def setup_class(cls): """Set up the test class.""" super(AEATestCaseEmpty, cls).setup_class() cls.agent_name = "agent-" + "".join(random.choices(string.ascii_lowercase, k=5)) - cls.create_agents(cls.agent_name) + cls.create_agents(cls.agent_name, is_local=cls.IS_LOCAL, is_empty=cls.IS_EMPTY) cls.set_agent_context(cls.agent_name) diff --git a/aea/test_tools/test_skill.py b/aea/test_tools/test_skill.py new file mode 100644 index 0000000000..883688d152 --- /dev/null +++ b/aea/test_tools/test_skill.py @@ -0,0 +1,432 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains test case classes based on pytest for AEA skill testing.""" + +import asyncio +from pathlib import Path +from queue import Queue +from types import SimpleNamespace +from typing import Any, Dict, Optional, Tuple, Type, Union, cast + +from aea.context.base import AgentContext +from aea.exceptions import AEAEnforceError +from aea.identity.base import Identity +from aea.mail.base import Address +from aea.multiplexer import AsyncMultiplexer, Multiplexer, OutBox +from aea.protocols.base import Message +from aea.protocols.dialogue.base import Dialogue, DialogueMessage, Dialogues +from aea.skills.base import Skill +from aea.skills.tasks import TaskManager + + +COUNTERPARTY_NAME = "counterparty" + + +class BaseSkillTestCase: + """A class to test a skill.""" + + path_to_skill: Union[Path, str] = Path(".") + _skill: Skill + _multiplexer: AsyncMultiplexer + _outbox: OutBox + + @property + def skill(self) -> Skill: + """Get the skill.""" + try: + value = self._skill + except AttributeError: + raise ValueError("Ensure skill is set during setup_class.") + return value + + def get_quantity_in_outbox(self) -> int: + """Get the quantity of envelopes in the outbox.""" + return self._multiplexer.out_queue.qsize() + + def get_message_from_outbox(self) -> Optional[Message]: + """Get message from outbox.""" + if self._outbox.empty(): + return None + envelope = self._multiplexer.out_queue.get_nowait() + return envelope.message + + def get_quantity_in_decision_maker_inbox(self) -> int: + """Get the quantity of messages in the decision maker inbox.""" + return self._skill.skill_context.decision_maker_message_queue.qsize() + + def get_message_from_decision_maker_inbox(self) -> Optional[Message]: + """Get message from decision maker inbox.""" + if self._skill.skill_context.decision_maker_message_queue.empty(): + return None + return self._skill.skill_context.decision_maker_message_queue.get_nowait() + + def assert_quantity_in_outbox(self, expected_quantity) -> None: + """Assert the quantity of messages in the outbox.""" + quantity = self.get_quantity_in_outbox() + assert ( # nosec + quantity == expected_quantity + ), f"Invalid number of messages in outbox. Expected {expected_quantity}. Found {quantity}." + + def assert_quantity_in_decision_making_queue(self, expected_quantity) -> None: + """Assert the quantity of messages in the decision maker queue.""" + quantity = self.get_quantity_in_decision_maker_inbox() + assert ( # nosec + quantity == expected_quantity + ), f"Invalid number of messages in decision maker queue. Expected {expected_quantity}. Found {quantity}." + + @staticmethod + def message_has_attributes( + actual_message: Message, message_type: Type[Message], **kwargs, + ) -> Tuple[bool, str]: + """ + Evaluates whether a message's attributes match the expected attributes provided. + + :param actual_message: the actual message + :param message_type: the expected message type + :param kwargs: other expected message attributes + + :return: boolean result of the evaluation and accompanied message + """ + if ( + type(actual_message) # pylint: disable=unidiomatic-typecheck + != message_type + ): + return ( + False, + "The message types do not match. Actual type: {}. Expected type: {}".format( + type(actual_message), message_type + ), + ) + + for attribute_name, expected_value in kwargs.items(): + actual_value = getattr(actual_message, attribute_name) + if actual_value != expected_value: + return ( + False, + f"The '{attribute_name}' fields do not match. Actual '{attribute_name}': {actual_value}. Expected '{attribute_name}': {expected_value}", + ) + + return True, "The message has the provided expected attributes." + + def build_incoming_message( + self, + message_type: Type[Message], + performative: Message.Performative, + dialogue_reference: Optional[Tuple[str, str]] = None, + message_id: Optional[int] = None, + target: Optional[int] = None, + to: Optional[Address] = None, + sender: Address = COUNTERPARTY_NAME, + **kwargs, + ) -> Message: + """ + Quickly create an incoming message with the provided attributes. + + For any attribute not provided, the corresponding default value in message is used. + + :param message_type: the type of the message + :param dialogue_reference: the dialogue_reference + :param message_id: the message_id + :param target: the target + :param performative: the performative + :param to: the 'to' address + :param sender: the 'sender' address + :param kwargs: other attributes + + :return: the created incoming message + """ + message_attributes = dict() # type: Dict[str, Any] + + default_dialogue_reference = Dialogues.new_self_initiated_dialogue_reference() + dialogue_reference = ( + default_dialogue_reference + if dialogue_reference is None + else dialogue_reference + ) + message_attributes["dialogue_reference"] = dialogue_reference + if message_id is not None: + message_attributes["message_id"] = message_id + if target is not None: + message_attributes["target"] = target + message_attributes["performative"] = performative + message_attributes.update(kwargs) + + incoming_message = message_type(**message_attributes) + incoming_message.sender = sender + incoming_message.to = ( + self.skill.skill_context.agent_address if to is None else to + ) + + return incoming_message + + def build_incoming_message_for_skill_dialogue( + self, + dialogue: Dialogue, + performative: Message.Performative, + message_type: Optional[Type[Message]] = None, + dialogue_reference: Optional[Tuple[str, str]] = None, + message_id: Optional[int] = None, + target: Optional[int] = None, + to: Optional[Address] = None, + sender: Optional[Address] = None, + **kwargs, + ) -> Message: + """ + Quickly create an incoming message with the provided attributes for a dialogue. + + For any attribute not provided, a value based on the dialogue is used. + These values are shown in parantheses in the list of parameters below. + + NOTE: This method must be used with care. The dialogue provided is part of the skill + which is being tested. Because for any unspecified attribute, a "correct" value is used, + the test will be, by design, insured to pass on these values. + + :param dialogue: the dialogue to which the incoming message is intended + :param performative: the performative of the message + :param message_type: (the message_class of the provided dialogue) the type of the message + :param dialogue_reference: (the dialogue_reference of the provided dialogue) the dialogue reference of the message + :param message_id: (the id of the last message in the provided dialogue + 1) the id of the message + :param target: (the id of the last message in the provided dialogue) the target of the message + :param to: (the agent address associated with this skill) the receiver of the message + :param sender: (the counterperty in the provided dialogue) the sender of the message + :param kwargs: other attributes + + :return: the created incoming message + """ + if dialogue is None: + raise AEAEnforceError("dialogue cannot be None.") + + if dialogue.last_message is None: + raise AEAEnforceError("dialogue cannot be empty.") + + message_type = ( + message_type if message_type is not None else dialogue.message_class + ) + dialogue_reference = ( + dialogue_reference + if dialogue_reference is not None + else dialogue.dialogue_label.dialogue_reference + ) + message_id = ( + message_id + if message_id is not None + else dialogue.last_message.message_id + 1 + ) + target = target if target is not None else dialogue.last_message.message_id + to = to if to is not None else dialogue.self_address + sender = ( + sender + if sender is not None + else dialogue.dialogue_label.dialogue_opponent_addr + ) + + incoming_message = self.build_incoming_message( + message_type=message_type, + performative=performative, + dialogue_reference=dialogue_reference, + message_id=message_id, + target=target, + to=to, + sender=sender, + **kwargs, + ) + + return incoming_message + + @staticmethod + def _provide_unspecified_fields( + message: DialogueMessage, last_is_incoming: Optional[bool], message_id: int + ) -> Tuple[bool, int]: + """ + Specifies values (an interpretation) for the unspecified fields of a DialogueMessage. + + For an unspecified is_incoming, the opposite of the last_is_incoming value is used. + For an unspecified target, the message_id of the previous message (message_id - 1) is used. + + :param message: the DialogueMessage + :param last_is_incoming: the is_incoming value of the previous DialogueMessage + :param message_id: the message_id of this DialogueMessage + + :return: the is_incoming and target values + """ + default_is_incoming = not last_is_incoming + is_incoming = default_is_incoming if message[2] is None else message[2] + + default_target = message_id - 1 + target = default_target if message[3] is None else message[3] + return is_incoming, target + + @staticmethod + def _non_initial_incoming_message_dialogue_reference( + dialogue: Dialogue, + ) -> Tuple[str, str]: + """ + Specifies the dialogue reference of a non-initial incoming message for a dialogue. + + It uses a complete version of the reference in the dialogue if it is incomplete, + otherwise it uses the reference in the dialogue. + + :param dialogue: the dialogue to which the incoming message is intended + :return: its dialogue reference + """ + dialogue_reference = ( + dialogue.dialogue_label.dialogue_reference[0], + Dialogues._generate_dialogue_nonce() # pylint: disable=protected-access + if dialogue.dialogue_label.dialogue_reference[1] + == Dialogue.UNASSIGNED_DIALOGUE_REFERENCE + else dialogue.dialogue_label.dialogue_reference[1], + ) + return dialogue_reference + + def _extract_message_fields( + self, message: DialogueMessage, index: int, last_is_incoming: bool, + ) -> Tuple[Message.Performative, Dict, int, bool, int]: + """ + Extracts message attributes from a dialogue message. + + :param message: the dialogue message + :param index: the index of this dialogue message in the sequence of messages + :param message: the is_incoming of the last message in the sequence + + :return: the performative, contents, message_id, is_incoming, target of the message + """ + performative = message[0] + contents = message[1] + message_id = index + 1 + is_incoming, target = self._provide_unspecified_fields( + message, last_is_incoming=last_is_incoming, message_id=message_id, + ) + return performative, contents, message_id, is_incoming, target + + def prepare_skill_dialogue( + self, + dialogues: Dialogues, + messages: Tuple[DialogueMessage, ...], + counterparty: Address = COUNTERPARTY_NAME, + ) -> Dialogue: + """ + Quickly create a dialogue. + + The 'messages' argument is a tuple of DialogueMessages. + For every DialogueMessage (performative, contents, is_incoming, target): + - if 'is_incoming' is not provided: for the first message it is assumed False (outgoing), + for any other message, it is the opposite of the one preceding it. + - if 'target' is not provided: for the first message it is assumed 0, + for any other message, it is the index of the message before it in the tuple of messages + 1. + + :param dialogues: a dialogues class + :param counterparty: the message_id + :param messages: the dialogue_reference + + :return: the created incoming message + """ + if len(messages) == 0: + raise AEAEnforceError("the list of messages must be positive.") + + ( + performative, + contents, + message_id, + is_incoming, + target, + ) = self._extract_message_fields(messages[0], index=0, last_is_incoming=True) + + if is_incoming: # first message from the opponent + dialogue_reference = dialogues.new_self_initiated_dialogue_reference() + message = self.build_incoming_message( + message_type=dialogues.message_class, + dialogue_reference=dialogue_reference, + message_id=message_id, + target=target, + performative=performative, + to=self.skill.skill_context.agent_address, + sender=counterparty, + **contents, + ) + dialogue = cast(Dialogue, dialogues.update(message)) + if dialogue is None: + raise AEAEnforceError( + "Cannot update the dialogue with message number {}".format( + message_id + ) + ) + else: # first message from self + _, dialogue = dialogues.create( + counterparty=counterparty, performative=performative, **contents + ) + + for idx, dialogue_message in enumerate(messages[1:]): + ( + performative, + contents, + message_id, + is_incoming, + target, + ) = self._extract_message_fields(dialogue_message, idx + 1, is_incoming) + if is_incoming: # messages from the opponent + dialogue_reference = self._non_initial_incoming_message_dialogue_reference( + dialogue + ) + message = self.build_incoming_message( + message_type=dialogues.message_class, + dialogue_reference=dialogue_reference, + message_id=message_id, + target=target, + performative=performative, + to=self.skill.skill_context.agent_address, + sender=counterparty, + **contents, + ) + dialogue = cast(Dialogue, dialogues.update(message)) + if dialogue is None: + raise AEAEnforceError( + "Cannot update the dialogue with message number {}".format( + message_id + ) + ) + else: # messages from self + dialogue.reply(performative=performative, target=target, **contents) + + return dialogue + + @classmethod + def setup(cls) -> None: + """Set up the skill test case.""" + identity = Identity("test_agent_name", "test_agent_address") + + cls._multiplexer = AsyncMultiplexer() + cls._multiplexer._out_queue = ( # pylint: disable=protected-access + asyncio.Queue() + ) + cls._outbox = OutBox(cast(Multiplexer, cls._multiplexer)) + + agent_context = AgentContext( + identity=identity, + connection_status=cls._multiplexer.connection_status, + outbox=cls._outbox, + decision_maker_message_queue=Queue(), + decision_maker_handler_context=SimpleNamespace(), + task_manager=TaskManager(), + default_connection=None, + default_routing={}, + search_service_address="dummy_search_service_address", + decision_maker_address="dummy_decision_maker_address", + ) + + cls._skill = Skill.from_dir(str(cls.path_to_skill), agent_context) diff --git a/benchmark/run_from_branch.sh b/benchmark/run_from_branch.sh index f5bc0a4d35..160350178a 100755 --- a/benchmark/run_from_branch.sh +++ b/benchmark/run_from_branch.sh @@ -12,7 +12,7 @@ pip install pipenv # this is to install benchmark dependencies pipenv install --dev --skip-lock # this is to install the AEA in the Pipenv virtual env -pipenv run pip install --upgrade aea[all]=="0.6.2" +pipenv run pip install --upgrade aea[all]=="0.6.3" chmod +x benchmark/checks/run_benchmark.sh echo "Start the experiments." diff --git a/deploy-image/docker-env.sh b/deploy-image/docker-env.sh index 7279162c26..201386f939 100755 --- a/deploy-image/docker-env.sh +++ b/deploy-image/docker-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Swap the following lines if you want to work with 'latest' -DOCKER_IMAGE_TAG=aea-deploy:0.6.2 +DOCKER_IMAGE_TAG=aea-deploy:0.6.3 # DOCKER_IMAGE_TAG=aea-deploy:latest DOCKER_BUILD_CONTEXT_DIR=.. diff --git a/develop-image/docker-env.sh b/develop-image/docker-env.sh index e9010daaca..6e01045dd3 100755 --- a/develop-image/docker-env.sh +++ b/develop-image/docker-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Swap the following lines if you want to work with 'latest' -DOCKER_IMAGE_TAG=aea-develop:0.6.2 +DOCKER_IMAGE_TAG=aea-develop:0.6.3 # DOCKER_IMAGE_TAG=aea-develop:latest DOCKER_BUILD_CONTEXT_DIR=.. diff --git a/docs/agent-vs-aea.md b/docs/agent-vs-aea.md index 4921a09331..5765197812 100644 --- a/docs/agent-vs-aea.md +++ b/docs/agent-vs-aea.md @@ -127,7 +127,7 @@ We use the input and output text files to send an envelope to our agent and rece ``` python # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = ( - b"my_agent,other_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + b"my_agent,other_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) @@ -254,7 +254,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = ( - b"my_agent,other_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + b"my_agent,other_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) diff --git a/docs/api/aea.md b/docs/api/aea.md index e8f662516e..8b96ff7ee8 100644 --- a/docs/api/aea.md +++ b/docs/api/aea.md @@ -7,7 +7,7 @@ This module contains the implementation of an autonomous economic agent (AEA). ## AEA Objects ```python -class AEA(Agent, WithLogger) +class AEA(Agent) ``` This class implements an autonomous economic agent. @@ -30,7 +30,7 @@ Instantiate the agent. - `resources`: the resources (protocols and skills) of the agent. - `loop`: the event loop to run the connections. - `period`: period to call agent's act -- `exeution_timeout`: amount of time to limit single act/handle to execute. +- `execution_timeout`: amount of time to limit single act/handle to execute. - `max_reactions`: the processing rate of envelopes per tick (i.e. single loop). - `decision_maker_handler_class`: the class implementing the decision maker handler to be used. - `skill_exception_policy`: the skill exception policy enum diff --git a/docs/api/aea_builder.md b/docs/api/aea_builder.md index 55c78bd993..00708c68a3 100644 --- a/docs/api/aea_builder.md +++ b/docs/api/aea_builder.md @@ -140,11 +140,20 @@ version field. the merged PyPI dependencies + +#### install`_`dependencies + +```python + | install_dependencies() -> None +``` + +Install extra dependencies for components. + ## AEABuilder Objects ```python -class AEABuilder() +class AEABuilder(WithLogger) ``` This class helps to build an AEA. @@ -711,6 +720,15 @@ Remove protocol. the AEABuilder + +#### install`_`pypi`_`dependencies + +```python + | install_pypi_dependencies() -> None +``` + +Install components extra dependecies. + #### build @@ -815,11 +833,11 @@ AEABuilder instance Return path to aea-config file for the given aea project path. - -#### make`_`logger + +#### make`_`component`_`logger ```python -make_logger(configuration: ComponentConfiguration, agent_name: str) -> Optional[logging.Logger] +make_component_logger(configuration: ComponentConfiguration, agent_name: str) -> Optional[logging.Logger] ``` Make the logger for a component. diff --git a/docs/api/agent.md b/docs/api/agent.md index a3a96b151d..c8af2cdc8b 100644 --- a/docs/api/agent.md +++ b/docs/api/agent.md @@ -7,7 +7,7 @@ This module contains the implementation of a generic agent. ## Agent Objects ```python -class Agent(AbstractAgent) +class Agent(AbstractAgent, WithLogger) ``` This class provides an abstract base class for a generic agent. @@ -16,7 +16,7 @@ This class provides an abstract base class for a generic agent. #### `__`init`__` ```python - | __init__(identity: Identity, connections: List[Connection], loop: Optional[AbstractEventLoop] = None, period: float = 1.0, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None) -> None + | __init__(identity: Identity, connections: List[Connection], loop: Optional[AbstractEventLoop] = None, period: float = 1.0, loop_mode: Optional[str] = None, runtime_mode: Optional[str] = None, logger: Logger = _default_logger) -> None ``` Instantiate the agent. diff --git a/docs/api/configurations/base.md b/docs/api/configurations/base.md index 02f7c60415..18f6b46361 100644 --- a/docs/api/configurations/base.md +++ b/docs/api/configurations/base.md @@ -3,17 +3,157 @@ Classes to handle AEA configurations. + +## PyPIPackageName Objects + +```python +class PyPIPackageName(RegexConstrainedString) +``` + +A PyPI Package name. + + +## GitRef Objects + +```python +class GitRef(RegexConstrainedString) +``` + +A Git reference. + +It can be a branch name, a commit hash or a tag. + -#### Dependency +## Dependency Objects + +```python +class Dependency() +``` + +This class represents a PyPI dependency. + +It contains the following information: +- version: a version specifier(s) (e.g. '==0.1.0'). +- index: the PyPI index where to download the package from (default: https://pypi.org) +- git: the URL to the Git repository (e.g. https://github.com/fetchai/agents-aea.git) +- ref: either the branch name, the tag, the commit number or a Git reference (default: 'master'.) -A dependency is a dictionary with the following (optional) keys: - - version: a version specifier(s) (e.g. '==0.1.0'). - - index: the PyPI index where to download the package from (default: https://pypi.org) - - git: the URL to the Git repository (e.g. https://github.com/fetchai/agents-aea.git) - - ref: either the branch name, the tag, the commit number or a Git reference (default: 'master'.) If the 'git' field is set, the 'version' field will be ignored. These fields will be forwarded to the 'pip' command. + +#### `__`init`__` + +```python + | __init__(name: Union[PyPIPackageName, str], version: Union[str, SpecifierSet] = "", index: Optional[Union[str, Url]] = None, git: Optional[Union[str, Url]] = None, ref: Optional[Union[GitRef, str]] = None) +``` + +Initialize a PyPI dependency. + +**Arguments**: + +- `name`: the package name. +- `version`: the specifier set object +- `index`: the URL to the PyPI server. +- `git`: the URL to a git repository. +- `ref`: the Git reference (branch/commit/tag). + + +#### name + +```python + | @property + | name() -> str +``` + +Get the name. + + +#### version + +```python + | @property + | version() -> str +``` + +Get the version. + + +#### index + +```python + | @property + | index() -> Optional[str] +``` + +Get the index. + + +#### git + +```python + | @property + | git() -> Optional[str] +``` + +Get the git. + + +#### ref + +```python + | @property + | ref() -> Optional[str] +``` + +Get the ref. + + +#### from`_`json + +```python + | @classmethod + | from_json(cls, obj: Dict[str, Dict[str, str]]) -> "Dependency" +``` + +Parse a dependency object from a dictionary. + + +#### to`_`json + +```python + | to_json() -> Dict[str, Dict[str, str]] +``` + +Transform the object to JSON. + + +#### get`_`pip`_`install`_`args + +```python + | get_pip_install_args() -> List[str] +``` + +Get 'pip install' arguments. + + +#### `__`str`__` + +```python + | __str__() -> str +``` + +Get the string representation. + + +#### `__`eq`__` + +```python + | __eq__(other) +``` + +Compare with another object. + #### Dependencies @@ -23,6 +163,41 @@ The package name must satisfy +#### dependencies`_`from`_`json + +```python +dependencies_from_json(obj: Dict[str, Dict]) -> Dependencies +``` + +Parse a JSON object to get an instance of Dependencies. + +**Arguments**: + +- `obj`: a dictionary whose keys are package names and values are dictionary with package specifications. + +**Returns**: + +a Dependencies object. + + +#### dependencies`_`to`_`json + +```python +dependencies_to_json(dependencies: Dependencies) -> Dict[str, Dict] +``` + +Transform a Dependencies object into a JSON object. + +**Arguments**: + +- `dependencies`: an instance of "Dependencies" type. + +**Returns**: + +a dictionary whose keys are package names and +values are the JSON version of a Dependency object. + ## PackageVersion Objects @@ -148,6 +323,19 @@ Enum of component types supported. Get package type for component type. + +#### plurals + +```python + | @staticmethod + | plurals() -> Collection[str] +``` + +Get the collection of type names, plural. + +>>> ComponentType.plurals() +['protocols', 'connections', 'skills', 'contracts'] + #### to`_`plural @@ -467,6 +655,24 @@ Check if the other public id has the same author and name of this. Return the same public id, but with latest version. + +#### is`_`valid`_`str + +```python + | @classmethod + | is_valid_str(cls, public_id_string: str) -> bool +``` + +Check if a string is a public id. + +**Arguments**: + +- `public_id_string`: the public id in string format. + +**Returns**: + +bool indicating validity + #### from`_`str @@ -767,6 +973,15 @@ Get the hash. Get the string representation. + +#### `__`repr`__` + +```python + | __repr__() +``` + +Get the object representation in string. + #### `__`eq`__` @@ -981,16 +1196,6 @@ Class to represent an agent component configuration. Set component configuration. - -#### pypi`_`dependencies - -```python - | @property - | pypi_dependencies() -> Dependencies -``` - -Get PyPI dependencies. - #### component`_`type diff --git a/docs/api/configurations/loader.md b/docs/api/configurations/loader.md index 954ba2685b..434ddbdf91 100644 --- a/docs/api/configurations/loader.md +++ b/docs/api/configurations/loader.md @@ -141,7 +141,7 @@ the configuration object. | validate(json_data: Dict) -> None ``` -Validate a JSON object. +Validate a JSON object against the right JSON schema. **Arguments**: @@ -213,6 +213,31 @@ Load agent configuration from configuration json data. AgentConfig instance + +#### validate`_`component`_`configuration + +```python + | @staticmethod + | validate_component_configuration(component_id: ComponentId, configuration: Dict) -> None +``` + +Validate the component configuration of an agent configuration file. + +This check is to detect inconsistencies in the specified fields. + +**Arguments**: + +- `component_id`: the component id. +- `configuration`: the configuration dictionary. + +**Returns**: + +None + +**Raises**: + +- `ValueError`: if the configuration is not valid. + ## ConfigLoaders Objects diff --git a/docs/api/configurations/project.md b/docs/api/configurations/project.md index b98b950b04..1c9445e1c4 100644 --- a/docs/api/configurations/project.md +++ b/docs/api/configurations/project.md @@ -29,7 +29,7 @@ Init project with public_id and project's path. | load(cls, working_dir: str, public_id: PublicId) -> "Project" ``` -Load project with given pubblic_id to working_dir. +Load project with given public_id to working_dir. #### remove @@ -53,10 +53,10 @@ Agent alias representation. #### `__`init`__` ```python - | __init__(project: Project, agent_name: str, config: List[Dict], agent: AEA) + | __init__(project: Project, agent_name: str, config: List[Dict], agent: AEA, builder: AEABuilder) ``` -Init agent alias with project, config, name, agent. +Init agent alias with project, config, name, agent, builder. #### remove`_`from`_`project diff --git a/docs/api/connections/base.md b/docs/api/connections/base.md index 0d6b04372f..89cd6d2dd4 100644 --- a/docs/api/connections/base.md +++ b/docs/api/connections/base.md @@ -25,7 +25,7 @@ Abstract definition of a connection. #### `__`init`__` ```python - | __init__(configuration: ConnectionConfig, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs) + | __init__(configuration: ConnectionConfig, identity: Optional[Identity] = None, crypto_store: Optional[CryptoStore] = None, restricted_to_protocols: Optional[Set[PublicId]] = None, excluded_protocols: Optional[Set[PublicId]] = None, **kwargs, ,) ``` Initialize the connection. @@ -208,7 +208,7 @@ the connection object. ```python | @classmethod - | from_config(cls, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, **kwargs) -> "Connection" + | from_config(cls, configuration: ConnectionConfig, identity: Identity, crypto_store: CryptoStore, **kwargs, ,) -> "Connection" ``` Load a connection from a configuration. diff --git a/docs/api/crypto/base.md b/docs/api/crypto/base.md index 2b5d26b2d6..cc11f5f86e 100644 --- a/docs/api/crypto/base.md +++ b/docs/api/crypto/base.md @@ -310,6 +310,21 @@ Get the hash of a message. the hash of the message. + +#### is`_`valid`_`address + +```python + | @classmethod + | @abstractmethod + | is_valid_address(cls, address: Address) -> bool +``` + +Check if the address is valid. + +**Arguments**: + +- `address`: the address to validate + ## LedgerApi Objects diff --git a/docs/api/crypto/cosmos.md b/docs/api/crypto/cosmos.md index 1b9ddd5be4..b184a972e1 100644 --- a/docs/api/crypto/cosmos.md +++ b/docs/api/crypto/cosmos.md @@ -127,6 +127,20 @@ Get the hash of a message. the hash of the message. + +#### is`_`valid`_`address + +```python + | @classmethod + | is_valid_address(cls, address: Address) -> bool +``` + +Check if the address is valid. + +**Arguments**: + +- `address`: the address to validate + ## CosmosCrypto Objects @@ -423,7 +437,7 @@ the unsigned CosmWasm HandleMsg | @staticmethod | @try_decorator( | "Encountered exception when trying to execute wasm transaction: {}", - | logger_method=logger.warning, + | logger_method=_default_logger.warning, | ) | try_execute_wasm_transaction(tx_signed: Any, signed_tx_filename: str = "tx.signed") -> Optional[str] ``` @@ -445,7 +459,7 @@ the transaction digest | @staticmethod | @try_decorator( | "Encountered exception when trying to execute wasm query: {}", - | logger_method=logger.warning, + | logger_method=_default_logger.warning, | ) | try_execute_wasm_query(contract_address: Address, query_msg: Any) -> Optional[str] ``` diff --git a/docs/api/crypto/ethereum.md b/docs/api/crypto/ethereum.md index c6f1dc926e..7f291a091b 100644 --- a/docs/api/crypto/ethereum.md +++ b/docs/api/crypto/ethereum.md @@ -438,6 +438,20 @@ Attempts to update the transaction with a gas estimate. the transaction (potentially updated) + +#### is`_`valid`_`address + +```python + | @classmethod + | is_valid_address(cls, address: Address) -> bool +``` + +Check if the address is valid. + +**Arguments**: + +- `address`: the address to validate + ## EthereumFaucetApi Objects diff --git a/docs/api/decision_maker/base.md b/docs/api/decision_maker/base.md index 65ecb1c0d5..b21e754473 100644 --- a/docs/api/decision_maker/base.md +++ b/docs/api/decision_maker/base.md @@ -313,7 +313,7 @@ internal message ## DecisionMakerHandler Objects ```python -class DecisionMakerHandler(ABC) +class DecisionMakerHandler(WithLogger, ABC) ``` This class implements the decision maker. @@ -331,6 +331,7 @@ Initialize the decision maker handler. - `identity`: the identity - `wallet`: the wallet +- `logger`: the logger - `kwargs`: the key word arguments @@ -405,7 +406,7 @@ None ## DecisionMaker Objects ```python -class DecisionMaker() +class DecisionMaker(WithLogger) ``` This class implements the decision maker. diff --git a/docs/api/helpers/base.md b/docs/api/helpers/base.md index bf6420d777..e888cfada1 100644 --- a/docs/api/helpers/base.md +++ b/docs/api/helpers/base.md @@ -3,58 +3,6 @@ Miscellaneous helpers. - -#### yaml`_`load - -```python -@_ordered_loading -yaml_load(*args, **kwargs) -> Dict[str, Any] -``` - -Load a yaml from a file pointer in an ordered way. - -**Returns**: - -the yaml - - -#### yaml`_`load`_`all - -```python -@_ordered_loading -yaml_load_all(*args, **kwargs) -> List[Dict[str, Any]] -``` - -Load a multi-paged yaml from a file pointer in an ordered way. - -**Returns**: - -the yaml - - -#### yaml`_`dump - -```python -@_ordered_dumping -yaml_dump(*args, **kwargs) -> None -``` - -Dump multi-paged yaml data to a yaml file in an ordered way. - -:return None - - -#### yaml`_`dump`_`all - -```python -@_ordered_dumping -yaml_dump_all(*args, **kwargs) -> None -``` - -Dump multi-paged yaml data to a yaml file in an ordered way. - -:return None - #### locate @@ -295,3 +243,24 @@ It does side-effects to the first dictionary. None + +#### find`_`topological`_`order + +```python +find_topological_order(adjacency_list: Dict[T, Set[T]]) -> List[T] +``` + +Compute the topological order of a graph (using Kahn's algorithm). + +**Arguments**: + +- `adjacency_list`: the adjacency list of the graph. + +**Returns**: + +the topological order for the graph (as a sequence of nodes) + +**Raises**: + +- `ValueError`: if the graph contains a cycle. + diff --git a/docs/api/helpers/dialogue/base.md b/docs/api/helpers/dialogue/base.md index 45cebfd2dd..5246772559 100644 --- a/docs/api/helpers/dialogue/base.md +++ b/docs/api/helpers/dialogue/base.md @@ -395,6 +395,20 @@ Get the dialogue rules. the rules + +#### message`_`class + +```python + | @property + | message_class() -> Type[Message] +``` + +Get the message class. + +**Returns**: + +the message class + #### is`_`self`_`initiated @@ -469,7 +483,7 @@ True if empty, False otherwise #### reply ```python - | reply(performative: Message.Performative, target_message: Optional[Message] = None, **kwargs, ,) -> Message + | reply(performative: Message.Performative, target_message: Optional[Message] = None, target: Optional[int] = None, **kwargs, ,) -> Message ``` Reply to the 'target_message' in this dialogue with a message with 'performative', and contents from kwargs. @@ -479,6 +493,7 @@ Note if no target_message is provided, the last message in the dialogue will be **Arguments**: - `target_message`: the message to reply to. +- `target`: the id of the message to reply to. - `performative`: the performative of the reply message. - `kwargs`: the content of the reply message. @@ -620,6 +635,34 @@ Get the dialogue statistics. dialogue stats object + +#### message`_`class + +```python + | @property + | message_class() -> Type[Message] +``` + +Get the message class. + +**Returns**: + +the message class + + +#### dialogue`_`class + +```python + | @property + | dialogue_class() -> Type[Dialogue] +``` + +Get the dialogue class. + +**Returns**: + +the dialogue class + #### get`_`dialogues`_`with`_`counterparty @@ -641,7 +684,8 @@ The dialogues with the counterparty. #### new`_`self`_`initiated`_`dialogue`_`reference ```python - | new_self_initiated_dialogue_reference() -> Tuple[str, str] + | @classmethod + | new_self_initiated_dialogue_reference(cls) -> Tuple[str, str] ``` Return a dialogue label for a new self initiated dialogue. diff --git a/docs/api/helpers/logging.md b/docs/api/helpers/logging.md index 9269100ea3..39338adf68 100644 --- a/docs/api/helpers/logging.md +++ b/docs/api/helpers/logging.md @@ -3,6 +3,15 @@ Logging helpers. + +#### get`_`logger + +```python +get_logger(module_path: str, agent_name: str) -> Logger +``` + +Get the logger based on a module path and agent name. + ## AgentLoggerAdapter Objects diff --git a/docs/api/helpers/multiaddr/base.md b/docs/api/helpers/multiaddr/base.md index 371ee50517..35956a134a 100644 --- a/docs/api/helpers/multiaddr/base.md +++ b/docs/api/helpers/multiaddr/base.md @@ -16,7 +16,7 @@ Protocol Labs' Multiaddress representation of a network address. #### `__`init`__` ```python - | __init__(host: str, port: int, public_key: str) + | __init__(host: str, port: int, public_key: Optional[str] = None, multihash_id: Optional[str] = None) ``` Initialize a multiaddress. @@ -24,8 +24,9 @@ Initialize a multiaddress. **Arguments**: - `host`: ip host of the address -- `host`: port number of the address -- `host`: hex encoded public key. Must conform to Bitcoin EC encoding standard for Secp256k1 +- `port`: port number of the address +- `public_key`: hex encoded public key. Must conform to Bitcoin EC encoding standard for Secp256k1 +- `multihash_id`: a multihash of the public key #### compute`_`peerid @@ -48,6 +49,20 @@ libp2p PeerID from Bitcoin EC encoded Secp256k1 public key. the peer id. + +#### from`_`string + +```python + | @classmethod + | from_string(cls, maddr: str) -> "MultiAddr" +``` + +Construct a MultiAddr object from its string format + +**Arguments**: + +- `maddr`: multiaddress string + #### public`_`key @@ -68,6 +83,26 @@ Get the public key. Get the peer id. + +#### host + +```python + | @property + | host() -> str +``` + +Get the peer host. + + +#### port + +```python + | @property + | port() -> int +``` + +Get the peer port. + #### format diff --git a/docs/api/helpers/search/models.md b/docs/api/helpers/search/models.md index 4a520db72e..b291442541 100644 --- a/docs/api/helpers/search/models.md +++ b/docs/api/helpers/search/models.md @@ -59,6 +59,15 @@ Get the distance to another location. Compare equality of two locations. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the data model. + ## AttributeInconsistencyException Objects @@ -105,6 +114,15 @@ Initialize an attribute. Compare with another object. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the data model. + ## DataModel Objects @@ -137,6 +155,15 @@ Initialize a data model. Compare with another object. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the data model. + #### generate`_`data`_`model @@ -210,6 +237,15 @@ Compare with another object. Create an iterator. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the description. + #### encode @@ -412,6 +448,15 @@ True if the value satisfy the constraint, False otherwise. Check equality with another object. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the constraint type. + ## ConstraintExpr Objects @@ -801,6 +846,15 @@ Check whether the constraint expression is valid wrt a data model. Compare with another object. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the constraint. + ## Query Objects @@ -880,6 +934,15 @@ Check whether the` object is valid. Compare with another object. + +#### `__`str`__` + +```python + | __str__() +``` + +Get the string representation of the constraint. + #### encode diff --git a/docs/api/helpers/yaml_utils.md b/docs/api/helpers/yaml_utils.md new file mode 100644 index 0000000000..73851b287a --- /dev/null +++ b/docs/api/helpers/yaml_utils.md @@ -0,0 +1,128 @@ + +# aea.helpers.yaml`_`utils + +Helper functions related to YAML loading/dumping. + + +## `_`AEAYamlLoader Objects + +```python +class _AEAYamlLoader(yaml.SafeLoader) +``` + +Custom yaml.SafeLoader for the AEA framework. + +It extends the default SafeLoader in two ways: +- loads YAML configurations while *remembering the order of the fields*; +- resolves the environment variables at loading time. + +This class is for internal usage only; please use +the public functions of the module 'yaml_load' and 'yaml_load_all'. + + +#### `__`init`__` + +```python + | __init__(*args, **kwargs) +``` + +Initialize the AEAYamlLoader. + +It adds a YAML Loader constructor to use 'OderedDict' to load the files. + + +## `_`AEAYamlDumper Objects + +```python +class _AEAYamlDumper(yaml.SafeDumper) +``` + +Custom yaml.SafeDumper for the AEA framework. + +It extends the default SafeDumper so to dump +YAML configurations while *following the order of the fields*. + +This class is for internal usage only; please use +the public functions of the module 'yaml_dump' and 'yaml_dump_all'. + + +#### `__`init`__` + +```python + | __init__(*args, **kwargs) +``` + +Initialize the AEAYamlDumper. + +It adds a YAML Dumper representer to use 'OderedDict' to dump the files. + + +#### yaml`_`load + +```python +yaml_load(stream: TextIO) -> Dict[str, Any] +``` + +Load a yaml from a file pointer in an ordered way. + +**Arguments**: + +- `stream`: file pointer to the input file. + +**Returns**: + +the dictionary object with the YAML file content. + + +#### yaml`_`load`_`all + +```python +yaml_load_all(stream: TextIO) -> List[Dict[str, Any]] +``` + +Load a multi-paged yaml from a file pointer in an ordered way. + +**Arguments**: + +- `stream`: file pointer to the input file. + +**Returns**: + +the list of dictionary objects with the (multi-paged) YAML file content. + + +#### yaml`_`dump + +```python +yaml_dump(data: Dict, stream: Optional[TextIO] = None) -> None +``` + +Dump YAML data to a yaml file in an ordered way. + +**Arguments**: + +- `data`: the data to write. +- `stream`: (optional) the file to write on. + +**Returns**: + +None + + +#### yaml`_`dump`_`all + +```python +yaml_dump_all(data: Sequence[Dict], stream: Optional[TextIO] = None) -> None +``` + +Dump YAML data to a yaml file in an ordered way. + +**Arguments**: + +- `data`: the data to write. +- `stream`: (optional) the file to write on. + +**Returns**: + +None + diff --git a/docs/api/mail/base.md b/docs/api/mail/base.md index 285d595974..77aff63feb 100644 --- a/docs/api/mail/base.md +++ b/docs/api/mail/base.md @@ -481,6 +481,26 @@ Get the connection id from an envelope context, if set. connection id + +#### is`_`sender`_`public`_`id + +```python + | @property + | is_sender_public_id() +``` + +Check if sender is a public id. + + +#### is`_`to`_`public`_`id + +```python + | @property + | is_to_public_id() +``` + +Check if to is a public id. + #### `__`eq`__` diff --git a/docs/api/manager.md b/docs/api/manager.md index 2c9a7aca94..16d3d62ed0 100644 --- a/docs/api/manager.md +++ b/docs/api/manager.md @@ -363,3 +363,12 @@ Return details about agent alias definition. AgentAlias + +#### install`_`pypi`_`dependencies + +```python + | install_pypi_dependencies() -> None +``` + +Install dependencies for every project has at least one agent alias. + diff --git a/docs/api/multiplexer.md b/docs/api/multiplexer.md index 3349f0916d..10c749f70b 100644 --- a/docs/api/multiplexer.md +++ b/docs/api/multiplexer.md @@ -74,7 +74,7 @@ This class can handle multiple connections at once. #### `__`init`__` ```python - | __init__(connections: Optional[Sequence[Connection]] = None, default_connection_index: int = 0, loop: Optional[AbstractEventLoop] = None, exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, threaded: bool = False) + | __init__(connections: Optional[Sequence[Connection]] = None, default_connection_index: int = 0, loop: Optional[AbstractEventLoop] = None, exception_policy: ExceptionPolicyEnum = ExceptionPolicyEnum.propagate, threaded: bool = False, agent_name: str = "standalone") ``` Initialize the connection multiplexer. diff --git a/docs/api/protocols/generator/base.md b/docs/api/protocols/generator/base.md index 666c4eb5b2..9c29732e61 100644 --- a/docs/api/protocols/generator/base.md +++ b/docs/api/protocols/generator/base.md @@ -51,7 +51,7 @@ None #### generate`_`full`_`mode ```python - | generate_full_mode() -> None + | generate_full_mode() -> Optional[str] ``` Run the generator in "full" mode: @@ -64,13 +64,13 @@ e) applies isort formatting **Returns**: -None +optional warning message #### generate ```python - | generate(protobuf_only: bool = False) -> None + | generate(protobuf_only: bool = False) -> Optional[str] ``` Run the generator. If in "full" mode (protobuf_only is False), it: @@ -89,5 +89,5 @@ If in "protobuf only" mode (protobuf_only is True), it only does a) and b). **Returns**: -None +optional warning message. diff --git a/docs/api/registries/base.md b/docs/api/registries/base.md index ea7799d533..71862594e5 100644 --- a/docs/api/registries/base.md +++ b/docs/api/registries/base.md @@ -16,11 +16,15 @@ This class implements an abstract registry. #### `__`init`__` ```python - | __init__() + | __init__(agent_name: str = "standalone") ``` Initialize the registry. +**Arguments**: + +- `agent_name`: the name of the agent + #### register @@ -240,11 +244,15 @@ This class implements a simple dictionary-based registry for agent components. #### `__`init`__` ```python - | __init__() -> None + | __init__(**kwargs) -> None ``` Instantiate the registry. +**Arguments**: + +- `kwargs`: kwargs + **Returns**: None @@ -368,11 +376,15 @@ This class implements a generic registry for skill components. #### `__`init`__` ```python - | __init__() -> None + | __init__(**kwargs) -> None ``` Instantiate the registry. +**Arguments**: + +- `kwargs`: kwargs + **Returns**: None @@ -507,11 +519,15 @@ This class implements the handlers registry. #### `__`init`__` ```python - | __init__() -> None + | __init__(**kwargs) -> None ``` Instantiate the registry. +**Arguments**: + +- `kwargs`: kwargs + **Returns**: None diff --git a/docs/api/registries/filter.md b/docs/api/registries/filter.md index 859c5646d3..dee47ece22 100644 --- a/docs/api/registries/filter.md +++ b/docs/api/registries/filter.md @@ -7,7 +7,7 @@ This module contains registries. ## Filter Objects ```python -class Filter() +class Filter(WithLogger) ``` This class implements the filter of an AEA. diff --git a/docs/api/registries/resources.md b/docs/api/registries/resources.md index 45b48633fc..50b5cb0759 100644 --- a/docs/api/registries/resources.md +++ b/docs/api/registries/resources.md @@ -16,13 +16,23 @@ This class implements the object that holds the resources of an AEA. #### `__`init`__` ```python - | __init__() -> None + | __init__(agent_name: str = "standalone") -> None ``` Instantiate the resources. :return None + +#### agent`_`name + +```python + | @property + | agent_name() -> str +``` + +Get the agent name. + #### component`_`registry diff --git a/docs/api/runtime.md b/docs/api/runtime.md index 9e18f7cfcf..a2443364e6 100644 --- a/docs/api/runtime.md +++ b/docs/api/runtime.md @@ -45,7 +45,7 @@ Runtime states. ## BaseRuntime Objects ```python -class BaseRuntime(Runnable) +class BaseRuntime(Runnable, WithLogger) ``` Abstract runtime class to create implementations. @@ -54,7 +54,7 @@ Abstract runtime class to create implementations. #### `__`init`__` ```python - | __init__(agent: AbstractAgent, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, threaded=False) -> None + | __init__(agent: AbstractAgent, loop_mode: Optional[str] = None, loop: Optional[AbstractEventLoop] = None, threaded: bool = False) -> None ``` Init runtime. diff --git a/docs/api/test_tools/generic.md b/docs/api/test_tools/generic.md index 25e521cfc1..83f1f083db 100644 --- a/docs/api/test_tools/generic.md +++ b/docs/api/test_tools/generic.md @@ -36,14 +36,14 @@ Read an envelope from a file. envelope - -#### force`_`set`_`config + +#### nested`_`set`_`config ```python -force_set_config(dotted_path: str, value: Any) -> None +nested_set_config(dotted_path: str, value: Any, author: str = DEFAULT_AUTHOR) -> None ``` -Set an AEA config without validation. +Set an AEA config with nested values. Run from agent's directory. @@ -59,6 +59,7 @@ Allowed dotted_path: - `dotted_path`: dotted path to a setting. - `value`: a value to assign. Must be of yaml serializable type. +- `author`: the author name, used to parse the dotted path. **Returns**: diff --git a/docs/api/helpers/test_cases.md b/docs/api/test_tools/test_cases.md similarity index 96% rename from docs/api/helpers/test_cases.md rename to docs/api/test_tools/test_cases.md index 3fe8546cd7..352853dcf9 100644 --- a/docs/api/helpers/test_cases.md +++ b/docs/api/test_tools/test_cases.md @@ -54,12 +54,12 @@ Run from agent's directory. Result - -#### force`_`set`_`config + +#### nested`_`set`_`config ```python | @classmethod - | force_set_config(cls, dotted_path: str, value: Any) -> None + | nested_set_config(cls, dotted_path: str, value: Any) -> None ``` Force set config. @@ -145,7 +145,7 @@ None. ```python | @classmethod - | create_agents(cls, *agents_names: str) -> None + | create_agents(cls, *agents_names: str, *, is_local: bool = True, is_empty: bool = False) -> None ``` Create agents in current working directory. @@ -153,6 +153,8 @@ Create agents in current working directory. **Arguments**: - `agents_names`: str agent names. +- `is_local`: a flag for local folder add True by default. +- `empty`: optional boolean flag for skip adding default dependencies. **Returns**: @@ -163,7 +165,7 @@ None ```python | @classmethod - | fetch_agent(cls, public_id: str, agent_name: str) -> None + | fetch_agent(cls, public_id: str, agent_name: str, is_local: bool = True) -> None ``` Create agents in current working directory. @@ -172,6 +174,7 @@ Create agents in current working directory. - `public_id`: str public id - `agents_name`: str agent name. +- `is_local`: a flag for local folder add True by default. **Returns**: diff --git a/docs/api/test_tools/test_skill.md b/docs/api/test_tools/test_skill.md new file mode 100644 index 0000000000..313a4a32bc --- /dev/null +++ b/docs/api/test_tools/test_skill.md @@ -0,0 +1,192 @@ + +# aea.test`_`tools.test`_`skill + +This module contains test case classes based on pytest for AEA skill testing. + + +## BaseSkillTestCase Objects + +```python +class BaseSkillTestCase() +``` + +A class to test a skill. + + +#### skill + +```python + | @property + | skill() -> Skill +``` + +Get the skill. + + +#### get`_`quantity`_`in`_`outbox + +```python + | get_quantity_in_outbox() -> int +``` + +Get the quantity of envelopes in the outbox. + + +#### get`_`message`_`from`_`outbox + +```python + | get_message_from_outbox() -> Optional[Message] +``` + +Get message from outbox. + + +#### get`_`quantity`_`in`_`decision`_`maker`_`inbox + +```python + | get_quantity_in_decision_maker_inbox() -> int +``` + +Get the quantity of messages in the decision maker inbox. + + +#### get`_`message`_`from`_`decision`_`maker`_`inbox + +```python + | get_message_from_decision_maker_inbox() -> Optional[Message] +``` + +Get message from decision maker inbox. + + +#### assert`_`quantity`_`in`_`outbox + +```python + | assert_quantity_in_outbox(expected_quantity) -> None +``` + +Assert the quantity of messages in the outbox. + + +#### assert`_`quantity`_`in`_`decision`_`making`_`queue + +```python + | assert_quantity_in_decision_making_queue(expected_quantity) -> None +``` + +Assert the quantity of messages in the decision maker queue. + + +#### message`_`has`_`attributes + +```python + | @staticmethod + | message_has_attributes(actual_message: Message, message_type: Type[Message], **kwargs, ,) -> Tuple[bool, str] +``` + +Evaluates whether a message's attributes match the expected attributes provided. + +**Arguments**: + +- `actual_message`: the actual message +- `message_type`: the expected message type +- `kwargs`: other expected message attributes + +**Returns**: + +boolean result of the evaluation and accompanied message + + +#### build`_`incoming`_`message + +```python + | build_incoming_message(message_type: Type[Message], performative: Message.Performative, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Address = COUNTERPARTY_NAME, **kwargs, ,) -> Message +``` + +Quickly create an incoming message with the provided attributes. + +For any attribute not provided, the corresponding default value in message is used. + +**Arguments**: + +- `message_type`: the type of the message +- `dialogue_reference`: the dialogue_reference +- `message_id`: the message_id +- `target`: the target +- `performative`: the performative +- `to`: the 'to' address +- `sender`: the 'sender' address +- `kwargs`: other attributes + +**Returns**: + +the created incoming message + + +#### build`_`incoming`_`message`_`for`_`skill`_`dialogue + +```python + | build_incoming_message_for_skill_dialogue(dialogue: Dialogue, performative: Message.Performative, message_type: Optional[Type[Message]] = None, dialogue_reference: Optional[Tuple[str, str]] = None, message_id: Optional[int] = None, target: Optional[int] = None, to: Optional[Address] = None, sender: Optional[Address] = None, **kwargs, ,) -> Message +``` + +Quickly create an incoming message with the provided attributes for a dialogue. + +For any attribute not provided, a value based on the dialogue is used. +These values are shown in parantheses in the list of parameters below. + +NOTE: This method must be used with care. The dialogue provided is part of the skill +which is being tested. Because for any unspecified attribute, a "correct" value is used, +the test will be, by design, insured to pass on these values. + +**Arguments**: + +- `dialogue`: the dialogue to which the incoming message is intended +- `performative`: the performative of the message +- `message_type`: (the message_class of the provided dialogue) the type of the message +- `dialogue_reference`: (the dialogue_reference of the provided dialogue) the dialogue reference of the message +- `message_id`: (the id of the last message in the provided dialogue + 1) the id of the message +- `target`: (the id of the last message in the provided dialogue) the target of the message +- `to`: (the agent address associated with this skill) the receiver of the message +- `sender`: (the counterperty in the provided dialogue) the sender of the message +- `kwargs`: other attributes + +**Returns**: + +the created incoming message + + +#### prepare`_`skill`_`dialogue + +```python + | prepare_skill_dialogue(dialogues: Dialogues, messages: Tuple[DialogueMessage, ...], counterparty: Address = COUNTERPARTY_NAME) -> Dialogue +``` + +Quickly create a dialogue. + +The 'messages' argument is a tuple of DialogueMessages. +For every DialogueMessage (performative, contents, is_incoming, target): +- if 'is_incoming' is not provided: for the first message it is assumed False (outgoing), +for any other message, it is the opposite of the one preceding it. +- if 'target' is not provided: for the first message it is assumed 0, +for any other message, it is the index of the message before it in the tuple of messages + 1. + +**Arguments**: + +- `dialogues`: a dialogues class +- `counterparty`: the message_id +- `messages`: the dialogue_reference + +**Returns**: + +the created incoming message + + +#### setup + +```python + | @classmethod + | setup(cls) -> None +``` + +Set up the skill test case. + diff --git a/docs/aries-cloud-agent-demo.md b/docs/aries-cloud-agent-demo.md index 74e487d125..cc60d2788b 100644 --- a/docs/aries-cloud-agent-demo.md +++ b/docs/aries-cloud-agent-demo.md @@ -180,7 +180,7 @@ Now you can create **Alice_AEA** and **Faber_AEA** in terminals 3 and 4 respecti In the third terminal, fetch **Alice_AEA** and move into its project folder: ``` bash -aea fetch fetchai/aries_alice:0.11.0 +aea fetch fetchai/aries_alice:0.12.0 cd aries_alice ``` @@ -191,11 +191,11 @@ The following steps create **Alice_AEA** from scratch: ``` bash aea create aries_alice cd aries_alice -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/http_client:0.9.0 -aea add connection fetchai/webhook:0.7.0 -aea add skill fetchai/aries_alice:0.8.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/http_client:0.10.0 +aea add connection fetchai/webhook:0.8.0 +aea add skill fetchai/aries_alice:0.9.0 ```

@@ -265,7 +265,7 @@ Once you see a message of the form `My libp2p addresses: ['SOME_ADDRESS']` take In the fourth terminal, fetch **Faber_AEA** and move into its project folder: ``` bash -aea fetch fetchai/aries_faber:0.11.0 +aea fetch fetchai/aries_faber:0.12.0 cd aries_faber ``` @@ -276,11 +276,11 @@ The following steps create **Faber_AEA** from scratch: ``` bash aea create aries_faber cd aries_faber -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/http_client:0.9.0 -aea add connection fetchai/webhook:0.7.0 -aea add skill fetchai/aries_faber:0.7.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/http_client:0.10.0 +aea add connection fetchai/webhook:0.8.0 +aea add skill fetchai/aries_faber:0.8.0 ```

diff --git a/docs/assets/acn-tiers.png b/docs/assets/acn-tiers.png index 92b7560f39..826f88c357 100644 Binary files a/docs/assets/acn-tiers.png and b/docs/assets/acn-tiers.png differ diff --git a/docs/assets/acn-trust-security.png b/docs/assets/acn-trust-security.png index fce4ed3366..33673dc2c8 100644 Binary files a/docs/assets/acn-trust-security.png and b/docs/assets/acn-trust-security.png differ diff --git a/docs/assets/dht.png b/docs/assets/dht.png index fc7044b120..88ea783fbc 100644 Binary files a/docs/assets/dht.png and b/docs/assets/dht.png differ diff --git a/docs/build-aea-programmatically.md b/docs/build-aea-programmatically.md index 5c162b7651..69fb919340 100644 --- a/docs/build-aea-programmatically.md +++ b/docs/build-aea-programmatically.md @@ -48,7 +48,7 @@ We will use the stub connection to pass envelopes in and out of the AEA. Ensure ``` ## Initialise the AEA -We use the `AEABuilder` to readily build an AEA. By default, the `AEABuilder` adds the `fetchai/default:0.6.0` protocol, the `fetchai/stub:0.10.0` connection and the `fetchai/error:0.6.0` skill. +We use the `AEABuilder` to readily build an AEA. By default, the `AEABuilder` adds the `fetchai/default:0.7.0` protocol, the `fetchai/stub:0.11.0` connection and the `fetchai/error:0.7.0` skill. ``` python # Instantiate the builder and build the AEA # By default, the default protocol, error skill and stub connection are added @@ -121,7 +121,7 @@ We run the AEA from a different thread so that we can still use the main thread We use the input and output text files to send an envelope to our AEA and receive a response (from the echo skill) ``` python # Create a message inside an envelope and get the stub connection to pass it on to the echo skill - message_text = b"my_aea,other_agent,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello," + message_text = b"my_aea,other_agent,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) print(b"input message: " + message_text) @@ -147,8 +147,8 @@ Finally stop our AEA and wait for it to finish ## Running the AEA If you now run this python script file, you should see this output: - input message: my_aea,other_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello - output message: other_agent,my_aea,fetchai/default:0.6.0,...\x05hello + input message: my_aea,other_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello + output message: other_agent,my_aea,fetchai/default:0.7.0,...\x05hello ## Entire code listing @@ -239,7 +239,7 @@ def run(): time.sleep(4) # Create a message inside an envelope and get the stub connection to pass it on to the echo skill - message_text = b"my_aea,other_agent,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello," + message_text = b"my_aea,other_agent,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) print(b"input message: " + message_text) diff --git a/docs/car-park-skills.md b/docs/car-park-skills.md index b7fed86aa3..c18432465f 100644 --- a/docs/car-park-skills.md +++ b/docs/car-park-skills.md @@ -55,7 +55,7 @@ Follow the Preliminaries and @@ -89,7 +89,7 @@ default_routing: Then, fetch the car data client AEA: ``` bash -aea fetch fetchai/car_data_buyer:0.13.0 +aea fetch fetchai/car_data_buyer:0.14.0 cd car_data_buyer aea install ``` @@ -101,19 +101,19 @@ The following steps create the car data client from scratch: ``` bash aea create car_data_buyer cd car_data_buyer -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/carpark_client:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/carpark_client:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `car_data_buyer/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

diff --git a/docs/cli-gui.md b/docs/cli-gui.md index 7d554b8ff8..6a60ce3c94 100644 --- a/docs/cli-gui.md +++ b/docs/cli-gui.md @@ -14,7 +14,8 @@ pip install aea[cli_gui] ## Starting the GUI -Go to the directory in which you will create new AEAs. If you followed the quick start guide, this will be `my_aea`. + +Go to the directory in which you will create new AEAs. If you followed the quick start guide, this will be `my_aea`. It is important to start the GUI not from within an agent project but its parent directory instead. Start the local web-server. ``` bash diff --git a/docs/cli-vs-programmatic-aeas.md b/docs/cli-vs-programmatic-aeas.md index b4a3513300..55c8eddecb 100644 --- a/docs/cli-vs-programmatic-aeas.md +++ b/docs/cli-vs-programmatic-aeas.md @@ -28,7 +28,7 @@ If you want to create the weather station AEA step by step you can follow this g Fetch the weather station AEA with the following command : ``` bash -aea fetch fetchai/weather_station:0.13.0 +aea fetch fetchai/weather_station:0.14.0 cd weather_station ``` @@ -124,8 +124,8 @@ def run(): # specify the default routing for some protocols default_routing = { - PublicId.from_str("fetchai/ledger_api:0.4.0"): LedgerConnection.connection_id, - PublicId.from_str("fetchai/oef_search:0.7.0"): SOEFConnection.connection_id, + PublicId.from_str("fetchai/ledger_api:0.5.0"): LedgerConnection.connection_id, + PublicId.from_str("fetchai/oef_search:0.8.0"): SOEFConnection.connection_id, } default_connection = P2PLibp2pConnection.connection_id @@ -192,7 +192,7 @@ def run(): api_key=API_KEY, soef_addr=SOEF_ADDR, soef_port=SOEF_PORT, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.7.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.8.0")}, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection(configuration=configuration, identity=identity) diff --git a/docs/config.md b/docs/config.md index 1173780dbf..65dabf54d7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -21,13 +21,13 @@ aea_version: '>=0.6.0, <0.7.0' # AEA framework version(s) compa fingerprint: {} # Fingerprint of AEA project components. fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: # The list of connection public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX) -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 contracts: [] # The list of contract public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: # The list of protocol public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: # The list of skill public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/error:0.6.0 -default_connection: fetchai/p2p_libp2p:0.10.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). +- fetchai/error:0.7.0 +default_connection: fetchai/p2p_libp2p:0.11.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). default_ledger: fetchai # The default ledger identifier the AEA project uses (must satisfy LEDGER_ID_REGEX) logging_config: # The logging configurations the AEA project uses disable_existing_loggers: false diff --git a/docs/connect-a-frontend.md b/docs/connect-a-frontend.md index 6c1559b9bc..cc21116dc2 100644 --- a/docs/connect-a-frontend.md +++ b/docs/connect-a-frontend.md @@ -3,7 +3,7 @@ This demo discusses the options we have to connect a front-end to the AEA. The f How to connect frontend to your AEA ## Case 1 -The first option we have is to create a `Connection` that will handle the incoming requests from the rest API. In this scenario, the rest API communicates with the AEA and requests are handled by the `HTTP Server` Connection package. The rest API should send CRUD requests to the `HTTP Server` Connection (`fetchai/http_server:0.9.0`) which translates these into Envelopes to be consumed by the correct skill. +The first option we have is to create a `Connection` that will handle the incoming requests from the rest API. In this scenario, the rest API communicates with the AEA and requests are handled by the `HTTP Server` Connection package. The rest API should send CRUD requests to the `HTTP Server` Connection (`fetchai/http_server:0.10.0`) which translates these into Envelopes to be consumed by the correct skill. ## Case 2 -The other option we have is to create a stand-alone `Multiplexer` with a `P2P` connection (`fetchai/p2p_libp2p:0.10.0`). In this scenario, the front-end needs to incorporate a Multiplexer with an `P2P` Connection. Then the
Agent Communication Network can be used to send Envelopes from the AEA to the front-end. \ No newline at end of file +The other option we have is to create a stand-alone `Multiplexer` with a `P2P` connection (`fetchai/p2p_libp2p:0.11.0`). In this scenario, the front-end needs to incorporate a Multiplexer with an `P2P` Connection. Then the Agent Communication Network can be used to send Envelopes from the AEA to the front-end. \ No newline at end of file diff --git a/docs/contract.md b/docs/contract.md index fdb0297a25..3bc608de6e 100644 --- a/docs/contract.md +++ b/docs/contract.md @@ -1,4 +1,4 @@ -`Contracts` wrap smart contracts for Fetch.ai and third-party decentralized ledgers. In particular, they provide wrappers around the API or ABI of a smart contract and its byte code. They implement a translation between framework messages (in the `fetchai/contract_api:0.5.0` protocol) and the implementation specifics of the ABI. +`Contracts` wrap smart contracts for Fetch.ai and third-party decentralized ledgers. In particular, they provide wrappers around the API or ABI of a smart contract and its byte code. They implement a translation between framework messages (in the `fetchai/contract_api:0.6.0` protocol) and the implementation specifics of the ABI. Contracts usually implement four types of methods: @@ -16,32 +16,32 @@ The smart contract wrapped in a AEA contract package might be a third-party smar Interacting with contracts in almost all cases requires network access. Therefore, the framework executes contract related logic in a Connection. -In particular, the `fetchai/ledger:0.6.0` connection can be used to execute contract related logic. The skills communicate with the `fetchai/ledger:0.6.0` connection via the `fetchai/contract_api:0.5.0` protocol. This protocol implements a request-response pattern to serve the four types of methods listed above: +In particular, the `fetchai/ledger:0.7.0` connection can be used to execute contract related logic. The skills communicate with the `fetchai/ledger:0.7.0` connection via the `fetchai/contract_api:0.6.0` protocol. This protocol implements a request-response pattern to serve the four types of methods listed above: -- the `get_deploy_transaction` message is used to request a deploy transaction for a specific contract. For instance, to request a deploy transaction for the deployment of the smart contract wrapped in the `fetchai/erc1155:0.10.0` package, we send the following message to the `fetchai/ledger:0.6.0`: +- the `get_deploy_transaction` message is used to request a deploy transaction for a specific contract. For instance, to request a deploy transaction for the deployment of the smart contract wrapped in the `fetchai/erc1155:0.11.0` package, we send the following message to the `fetchai/ledger:0.7.0`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( {"deployer_address": self.context.agent_address} ), ) ``` -This message will be handled by the `fetchai/ledger:0.6.0` connection and then a `raw_transaction` message will be returned with the matching raw transaction. To send this transaction to the ledger for processing, we first sign the message with the decision maker and then send the signed transaction to the `fetchai/ledger:0.6.0` connection using the `fetchai/ledger_api:0.4.0` protocol. +This message will be handled by the `fetchai/ledger:0.7.0` connection and then a `raw_transaction` message will be returned with the matching raw transaction. To send this transaction to the ledger for processing, we first sign the message with the decision maker and then send the signed transaction to the `fetchai/ledger:0.7.0` connection using the `fetchai/ledger_api:0.5.0` protocol. -- the `get_raw_transaction` message is used to request any transaction for a specific contract which changes state in the contract. For instance, to request a transaction for the creation of token in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.10.0` package, we send the following message to the `fetchai/ledger:0.6.0`: +- the `get_raw_transaction` message is used to request any transaction for a specific contract which changes state in the contract. For instance, to request a transaction for the creation of token in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.11.0` package, we send the following message to the `fetchai/ledger:0.7.0`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=strategy.contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -52,16 +52,16 @@ contract_api_msg = ContractApiMessage( ), ) ``` -This message will be handled by the `fetchai/ledger:0.6.0` connection and then a `raw_transaction` message will be returned with the matching raw transaction. For this to be executed correctly, the `fetchai/erc1155:0.10.0` contract package needs to implement the `get_create_batch_transaction` method with the specified key word arguments. Similarly to above, to send this transaction to the ledger for processing, we first sign the message with the decision maker and then send the signed transaction to the `fetchai/ledger:0.6.0` connection using the `fetchai/ledger_api:0.4.0` protocol. +This message will be handled by the `fetchai/ledger:0.7.0` connection and then a `raw_transaction` message will be returned with the matching raw transaction. For this to be executed correctly, the `fetchai/erc1155:0.11.0` contract package needs to implement the `get_create_batch_transaction` method with the specified key word arguments. Similarly to above, to send this transaction to the ledger for processing, we first sign the message with the decision maker and then send the signed transaction to the `fetchai/ledger:0.7.0` connection using the `fetchai/ledger_api:0.5.0` protocol. -- the `get_raw_message` message is used to request any contract method call for a specific contract which does not change state in the contract. For instance, to request a call to get a hash from some input data in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.10.0` package, we send the following message to the `fetchai/ledger:0.6.0`: +- the `get_raw_message` message is used to request any contract method call for a specific contract which does not change state in the contract. For instance, to request a call to get a hash from some input data in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.11.0` package, we send the following message to the `fetchai/ledger:0.7.0`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=strategy.contract_address, callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( @@ -77,17 +77,17 @@ contract_api_msg = ContractApiMessage( ), ) ``` -This message will be handled by the `fetchai/ledger:0.6.0` connection and then a `raw_message` message will be returned with the matching raw message. For this to be executed correctly, the `fetchai/erc1155:0.10.0` contract package needs to implement the `get_hash_single` method with the specified key word arguments. We can then send the raw message to the `fetchai/ledger:0.6.0` connection using the `fetchai/ledger_api:0.4.0` protocol. In this case, signing is not required. +This message will be handled by the `fetchai/ledger:0.7.0` connection and then a `raw_message` message will be returned with the matching raw message. For this to be executed correctly, the `fetchai/erc1155:0.11.0` contract package needs to implement the `get_hash_single` method with the specified key word arguments. We can then send the raw message to the `fetchai/ledger:0.7.0` connection using the `fetchai/ledger_api:0.5.0` protocol. In this case, signing is not required. -- the `get_state` message is used to request any contract method call to query state in the deployed contract. For instance, to request a call to get the balances in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.10.0` package, we send the following message to the `fetchai/ledger:0.6.0`: +- the `get_state` message is used to request any contract method call to query state in the deployed contract. For instance, to request a call to get the balances in the deployed `erc1155` smart contract wrapped in the `fetchai/erc1155:0.11.0` package, we send the following message to the `fetchai/ledger:0.7.0`: ``` python contract_api_msg = ContractApiMessage( performative=ContractApiMessage.Performative.GET_STATE, dialogue_reference=contract_api_dialogues.new_self_initiated_dialogue_reference(), ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=strategy.contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( @@ -95,7 +95,7 @@ contract_api_msg = ContractApiMessage( ), ) ``` -This message will be handled by the `fetchai/ledger:0.6.0` connection and then a `state` message will be returned with the matching state. For this to be executed correctly, the `fetchai/erc1155:0.10.0` contract package needs to implement the `get_balance` method with the specified key word arguments. We can then send the raw message to the `fetchai/ledger:0.6.0` connection using the `fetchai/ledger_api:0.4.0` protocol. In this case, signing is not required. +This message will be handled by the `fetchai/ledger:0.7.0` connection and then a `state` message will be returned with the matching state. For this to be executed correctly, the `fetchai/erc1155:0.11.0` contract package needs to implement the `get_balance` method with the specified key word arguments. We can then send the raw message to the `fetchai/ledger:0.7.0` connection using the `fetchai/ledger_api:0.5.0` protocol. In this case, signing is not required. ## Developing your own @@ -168,6 +168,6 @@ class MyContract(Contract): tx = cls._try_estimate_gas(ledger_api, tx) return tx ``` -Above, we implement a method to create a transaction, in this case a transaction to create a batch of tokens. The method will be called by the framework, specifically the `fetchai/ledger:0.6.0` connection once it receives a message (see bullet point 2 above). The method first gets the latest transaction nonce of the `deployer_address`, then constracts the contract instance, then uses the instance to build the transaction and finally updates the gas on the transaction. +Above, we implement a method to create a transaction, in this case a transaction to create a batch of tokens. The method will be called by the framework, specifically the `fetchai/ledger:0.7.0` connection once it receives a message (see bullet point 2 above). The method first gets the latest transaction nonce of the `deployer_address`, then constracts the contract instance, then uses the instance to build the transaction and finally updates the gas on the transaction. -It helps to look at existing contract packages, like `fetchai/erc1155:0.10.0`, and skills using them, like `fetchai/erc1155_client:0.11.0` and `fetchai/erc1155_deploy:0.14.0`, for inspiration and guidance. \ No newline at end of file +It helps to look at existing contract packages, like `fetchai/erc1155:0.11.0`, and skills using them, like `fetchai/erc1155_client:0.11.0` and `fetchai/erc1155_deploy:0.15.0`, for inspiration and guidance. \ No newline at end of file diff --git a/docs/core-components-1.md b/docs/core-components-1.md index e549fc63b4..7a6e26268a 100644 --- a/docs/core-components-1.md +++ b/docs/core-components-1.md @@ -32,7 +32,7 @@ An `Envelope` is the core object * `Dialogues`, which define rules over `Message` sequences. -The framework provides one default `Protocol`, called `default` (current version `fetchai/default:0.6.0`). This `Protocol` provides a bare-bones implementation for an AEA `Protocol` which includes a `DefaultMessage` class and associated `DefaultSerializer` and `DefaultDialogue` classes. +The framework provides one default `Protocol`, called `default` (current version `fetchai/default:0.7.0`). This `Protocol` provides a bare-bones implementation for an AEA `Protocol` which includes a `DefaultMessage` class and associated `DefaultSerializer` and `DefaultDialogue` classes. Additional `Protocols` - i.e. a new type of interaction - can be added as packages and generated with the protocol generator. For more details on `Protocols` also read the `Protocol` guide here. @@ -42,7 +42,7 @@ Protocol specific `Messages`, wrapped in `Envelopes`, are sent and received to o A `Connection` wraps an SDK or API and provides an interface to network, ledgers and other services. Where necessary, a `Connection` is responsible for translating between the framework specific `Envelope` with its contained `Message` and the external service or third-party protocol (e.g. `HTTP`). -The framework provides one default `Connection`, called `stub` (current version `fetchai/stub:0.10.0`). It implements an I/O reader and writer to send `Messages` to the agent from a local file. +The framework provides one default `Connection`, called `stub` (current version `fetchai/stub:0.11.0`). It implements an I/O reader and writer to send `Messages` to the agent from a local file. Additional `Connections` can be added as packages. For more details on `Connections` also read the `Connection` guide here. diff --git a/docs/debug.md b/docs/debug.md new file mode 100644 index 0000000000..9d0df2c80d --- /dev/null +++ b/docs/debug.md @@ -0,0 +1,17 @@ +There are multiple ways in which to configure your AEA for debugging during development. We focus on the standard Python approach here. + +## Using `pdb` stdlib + +You can add a debugger anywhere in your code: + +``` python +import pdb; pdb.set_trace() +``` + +Then simply run you aea with the `--skip-consistency-check` mode: + +``` bash +aea -s run +``` + +For more guidance on how to use `pdb` check out the documentation. diff --git a/docs/deployment.md b/docs/deployment.md index e8fea42914..23737b3370 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -28,7 +28,7 @@ Finally, we run it: docker run -it aea-deploy:latest ``` -This will run the `fetchai/my_first_aea:0.12.0` demo project. You can edit `entrypoint.sh` to run whatever project you would like. +This will run the `fetchai/my_first_aea:0.13.0` demo project. You can edit `entrypoint.sh` to run whatever project you would like. ## Deployment diff --git a/docs/erc1155-skills.md b/docs/erc1155-skills.md index 1c2cd8fd5a..78ab31ec31 100644 --- a/docs/erc1155-skills.md +++ b/docs/erc1155-skills.md @@ -26,7 +26,7 @@ with a one-step atomic swap functionality. That means the trade between the two Fetch the AEA that will deploy the contract. ``` bash -aea fetch fetchai/erc1155_deployer:0.14.0 +aea fetch fetchai/erc1155_deployer:0.15.0 cd erc1155_deployer aea install ``` @@ -39,20 +39,20 @@ Create the AEA that will deploy the contract. ``` bash aea create erc1155_deployer cd erc1155_deployer -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/erc1155_deploy:0.14.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/erc1155_deploy:0.15.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` Then update the agent config (`aea-config.yaml`) with the default routing: ``` yaml default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` And change the default ledger: @@ -81,7 +81,7 @@ aea add-key cosmos cosmos_private_key.txt --connection In another terminal, fetch the AEA that will get some tokens from the deployer. ``` bash -aea fetch fetchai/erc1155_client:0.14.0 +aea fetch fetchai/erc1155_client:0.15.0 cd erc1155_client aea install ``` @@ -94,20 +94,20 @@ Create the AEA that will get some tokens from the deployer. ``` bash aea create erc1155_client cd erc1155_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/erc1155_client:0.13.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/erc1155_client:0.14.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` Then update the agent config (`aea-config.yaml`) with the default routing: ``` yaml default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` And change the default ledger: @@ -178,7 +178,7 @@ At some point you should see the log output: registering service on SOEF. ``` -Then, update the configuration of the buyer AEA's p2p connection (in `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) replace the following: +Then, update the configuration of the client AEA's p2p connection (in `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) replace the following: ``` yaml config: diff --git a/docs/generic-skills-step-by-step.md b/docs/generic-skills-step-by-step.md index 2e9c7318ab..65d2862edb 100644 --- a/docs/generic-skills-step-by-step.md +++ b/docs/generic-skills-step-by-step.md @@ -41,16 +41,16 @@ Follow the Preliminaries and Preliminaries and @@ -93,7 +93,7 @@ default_routing: Then, fetch the buyer AEA: ``` bash -aea fetch fetchai/generic_buyer:0.10.0 --alias my_buyer_aea +aea fetch fetchai/generic_buyer:0.11.0 --alias my_buyer_aea cd my_buyer_aea aea install ``` @@ -105,19 +105,19 @@ The following steps create the buyer from scratch: ``` bash aea create my_buyer_aea cd my_buyer_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/generic_buyer:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/generic_buyer:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `my_buyer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

diff --git a/docs/gym-skill.md b/docs/gym-skill.md index fe54a9dfee..b171dcc511 100644 --- a/docs/gym-skill.md +++ b/docs/gym-skill.md @@ -19,7 +19,7 @@ Follow the
Preliminaries and Open Economic Framework (OEF) - a decentralized communication and search & discovery system for agents - and using Fetch.ai's blockchain as a financial settlement and commitment layer. Third-party blockchains, such as Ethereum, may also allow AEA integration. + + +## Why build with the AEA Framework? + +The AEA framework provides the developer with a number of features which cannot be found in this combination anywhere else: + +* the peer-to-peer agent communication network allows your AEAs to interact with all other AEAs over the public internet +* the search and discovery system sOEF allows your AEAs find other agents +* the AEA registry enables code sharing and re-use +* the framework's crypto and ledger APIs make it possible for AEAs to interact with blockchains +* reusability of code for interacting with blockchains is enabled via the contract packages + ## Next steps @@ -36,7 +50,7 @@ To learn more about some of the distinctive characteristics of agent-oriented de If you would like to develop an AEA in a language different to Python then check out our language agnostic AEA definition. -AEAs achieve their goals with the help of the Open Economic Framework (OEF) - a decentralized communication and search & discovery system for agents - and using Fetch.ai's blockchain as a financial settlement and commitment layer. Third-party blockchains, such as Ethereum, may also allow AEA integration. +If you would run a demo, check out the demo guides. ## Help us improve diff --git a/docs/language-agnostic-definition.md b/docs/language-agnostic-definition.md index 7d93b5429d..00a7e50e22 100644 --- a/docs/language-agnostic-definition.md +++ b/docs/language-agnostic-definition.md @@ -50,7 +50,7 @@ The format for the above fields, except `message`, is specified below. For those

This section is incomplete, and will be updated soon!

-
  • It SHOULD implement the `fetchai/default:0.6.0` protocol which satisfies the following protobuf schema: +
  • It SHOULD implement the `fetchai/default:0.7.0` protocol which satisfies the following protobuf schema: ``` proto syntax = "proto3"; @@ -100,7 +100,7 @@ message DefaultMessage{
  • It MUST have an identity in the form of, at a minimum, an address derived from a public key and its associated private key (where the eliptic curve must be of type SECP256k1).
  • -
  • It SHOULD implement handling of errors using the `fetchai/default:0.6.0` protocol. The protobuf schema is given above. +
  • It SHOULD implement handling of errors using the `fetchai/default:0.7.0` protocol. The protobuf schema is given above.
  • It MUST implement the following principles when handling messages:
      diff --git a/docs/logging.md b/docs/logging.md index db47eac014..178edca72a 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -22,13 +22,13 @@ aea_version: 0.6.0 fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: -- fetchai/error:0.6.0 -default_connection: fetchai/stub:0.10.0 +- fetchai/error:0.7.0 +default_connection: fetchai/stub:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false diff --git a/docs/ml-skills.md b/docs/ml-skills.md index cf399e6cda..1b070137f7 100644 --- a/docs/ml-skills.md +++ b/docs/ml-skills.md @@ -62,7 +62,7 @@ Follow the Preliminaries and @@ -96,7 +96,7 @@ default_routing: Then, fetch the model trainer AEA: ``` bash -aea fetch fetchai/ml_model_trainer:0.13.0 +aea fetch fetchai/ml_model_trainer:0.14.0 cd ml_model_trainer aea install ``` @@ -108,19 +108,19 @@ The following steps create the model trainer from scratch: ``` bash aea create ml_model_trainer cd ml_model_trainer -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/ml_train:0.12.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/ml_train:0.13.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea install ``` In `ml_model_trainer/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      diff --git a/docs/multi-agent-manager.md b/docs/multi-agent-manager.md index f23c3ac4a5..bc698ca162 100644 --- a/docs/multi-agent-manager.md +++ b/docs/multi-agent-manager.md @@ -21,8 +21,8 @@ We first add a couple of finished AEA project: ``` python from aea.configurations.base import PublicId -weather_client_id = PublicId.from_str("fetchai/weather_client:0.13.0") -weather_station_id = PublicId.from_str("fetchai/weather_station:0.13.0") +weather_client_id = PublicId.from_str("fetchai/weather_client:0.14.0") +weather_station_id = PublicId.from_str("fetchai/weather_station:0.14.0") manager.add_project(weather_client_id) manager.add_project(weather_station_id) ``` diff --git a/docs/multiplexer-standalone.md b/docs/multiplexer-standalone.md index e8b3ed436e..c092f6cbdd 100644 --- a/docs/multiplexer-standalone.md +++ b/docs/multiplexer-standalone.md @@ -61,7 +61,7 @@ We use the input and output text files to send an envelope to our agent and rece ``` python # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( - "multiplexer,some_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + "multiplexer,some_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: write_with_lock(f, message_text) @@ -159,7 +159,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( - "multiplexer,some_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + "multiplexer,some_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: write_with_lock(f, message_text) diff --git a/docs/oef-ledger.md b/docs/oef-ledger.md index 9020561da9..2aa34eb684 100644 --- a/docs/oef-ledger.md +++ b/docs/oef-ledger.md @@ -53,7 +53,7 @@ When it is live you will see the sentence 'A thing of beauty is a joy forever... To view the `OEF search and communication node` logs for debugging, navigate to `data/oef-logs`. -To connect to an `OEF search and communication node` an AEA uses the `OEFConnection` connection package (`fetchai/oef:0.10.0`). +To connect to an `OEF search and communication node` an AEA uses the `OEFConnection` connection package (`fetchai/oef:0.11.0`). If you experience any problems launching the `OEF search and communication node` then consult
      this guide. diff --git a/docs/orm-integration.md b/docs/orm-integration.md index 4a879de4ce..df6c4cb776 100644 --- a/docs/orm-integration.md +++ b/docs/orm-integration.md @@ -59,7 +59,7 @@ A demo to run a scenario with a true ledger transaction on Fetch.ai `testnet` ne First, fetch the seller AEA, which will provide data: ``` bash -aea fetch fetchai/thermometer_aea:0.11.0 --alias my_thermometer_aea +aea fetch fetchai/thermometer_aea:0.12.0 --alias my_thermometer_aea cd my_thermometer_aea aea install ``` @@ -71,19 +71,19 @@ The following steps create the seller from scratch: ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `my_thermometer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      @@ -94,7 +94,7 @@ default_routing: In another terminal, fetch the AEA that will query the seller AEA. ``` bash -aea fetch fetchai/thermometer_client:0.11.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.12.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` @@ -106,19 +106,19 @@ The following steps create the car data client from scratch: ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer_client:0.11.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer_client:0.12.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `my_buyer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      @@ -214,7 +214,7 @@ aea install Before being able to modify a package we need to eject it from vendor: ``` bash -aea eject skill fetchai/thermometer:0.12.0 +aea eject skill fetchai/thermometer:0.13.0 ``` This will move the package to your `skills` directory and reset the version to `0.1.0` and the author to your author handle. diff --git a/docs/p2p-connection.md b/docs/p2p-connection.md index 1e8163ba4b..3e256f75af 100644 --- a/docs/p2p-connection.md +++ b/docs/p2p-connection.md @@ -1,4 +1,4 @@ -The `fetchai/p2p_libp2p:0.10.0` connection allows AEAs to create a peer-to-peer communication network. In particular, the connection creates an overlay network which maps agents' public keys to IP addresses. +The `fetchai/p2p_libp2p:0.11.0` connection allows AEAs to create a peer-to-peer communication network. In particular, the connection creates an overlay network which maps agents' public keys to IP addresses. ## Local demo @@ -9,9 +9,9 @@ Create one AEA as follows: ``` bash aea create my_genesis_aea cd my_genesis_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 -aea run --connections fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 +aea run --connections fetchai/p2p_libp2p:0.11.0 ``` ### Create and run another AEA @@ -21,8 +21,8 @@ Create a second AEA: ``` bash aea create my_other_aea cd my_other_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` Provide the AEA with the information it needs to find the genesis by replacing the following block in `vendor/fetchai/connnections/p2p_libp2p/connection.yaml`: @@ -40,7 +40,7 @@ Here `MULTI_ADDRESSES` needs to be replaced with the list of multi addresses dis Run the AEA: ``` bash -aea run --connections fetchai/p2p_libp2p:0.10.0 +aea run --connections fetchai/p2p_libp2p:0.11.0 ``` You can inspect the `libp2p_node.log` log files of the AEA to see how they discover each other. @@ -71,7 +71,7 @@ config: ## Configuring the `connection.yaml` entries: -To learn more about how to configure your `fetchai/p2p_libp2p:0.10.0` connection consult the `README.md` supplied with the connection package. +To learn more about how to configure your `fetchai/p2p_libp2p:0.11.0` connection consult the `README.md` supplied with the connection package. ## Running Go peer standalone diff --git a/docs/package-imports.md b/docs/package-imports.md index b30e656e0d..3fc862cf89 100644 --- a/docs/package-imports.md +++ b/docs/package-imports.md @@ -47,7 +47,7 @@ The `aea-config.yaml` is the top level configuration file of an AEA. It defines For the AEA to use a package, the `public_id` for the package must be listed in the `aea-config.yaml` file, e.g. ``` yaml connections: -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 ``` The above shows a part of the `aea-config.yaml`. If you see the connections, you will see that we follow a pattern of `author/name_package:version` to identify each package, also referred to as `public_id`. Here the `author` is the author of the package. diff --git a/docs/protocol-generator.md b/docs/protocol-generator.md index d53e099118..cfc193b085 100644 --- a/docs/protocol-generator.md +++ b/docs/protocol-generator.md @@ -102,21 +102,21 @@ A multi type denotes an "or" separated set of sub-types, e.g. `pt:union[pt:str, An optional type for a content denotes that the content's existence is optional, but if it is present, its type must match `pt:optional[...]`'s sub-type. -| Type | Code | Format | Example | In Python | -| ---------------------------| --------------| --------------------------------------------------------------------------------------------------------------|------------------------------------------|------------------------------------| -| Custom types1 | `` | `ct:RegExp(^[A-Z][a-zA-Z0-9]*$)` | `ct:DataModel` | Custom Class | -| Primitive types | `` | `pt:bytes` | `pt:bytes` | `bytes` | -| | | `pt:int` | `pt:int` | `int` | -| | | `pt:float` | `pt:float` | `float` | -| | | `pt:bool` | `pt:bool` | `bool` | -| | | `pt:str` | `pt:str` | `str` | -| Primitive collection types | `` | `pt:set[]` | `pt:set[pt:str]` | `FrozenSet[str]` | -| | | `pt:list[]` | `pt:list[pt:int]` | `Tuple[int, ...]`* | -| Primitive mapping types2 | `` | `pt:dict[, ]` | `pt:dict[pt:str, pt:bool]` | `Dict[str, bool]` | -| Multi types | `` | `pt:union[///, ..., ///]` | `pt:union[ct:DataModel, pt:set[pt:str]]` | `Union[DataModel, FrozenSet[str]]` | -| Optional types | `` | `pt:optional[////]` | `pt:optional[pt:bool]` | `Optional[bool]` | - -*This is how variable length tuples containing elements of the same type are declared in Python*; see here +| Type | Code | Format | Example | In Python | +| ------------------------------------| --------| --------------------------------------------------------------|------------------------------------------|------------------------------------| +| Custom types1 | `` | `ct:RegExp(^[A-Z][a-zA-Z0-9]*$)` | `ct:DataModel` | Custom Class | +| Primitive types | `` | `pt:bytes` | `pt:bytes` | `bytes` | +| | | `pt:int` | `pt:int` | `int` | +| | | `pt:float` | `pt:float` | `float` | +| | | `pt:bool` | `pt:bool` | `bool` | +| | | `pt:str` | `pt:str` | `str` | +| Primitive collection types | `` | `pt:set[]` | `pt:set[pt:str]` | `FrozenSet[str]` | +| | | `pt:list[]` | `pt:list[pt:int]` | `Tuple[int, ...]`* | +| Primitive mapping types2 | `` | `pt:dict[, ]` | `pt:dict[pt:str, pt:bool]` | `Dict[str, bool]` | +| Multi types | `` | `pt:union[///, ..., ///]` | `pt:union[ct:DataModel, pt:set[pt:str]]` | `Union[DataModel, FrozenSet[str]]` | +| Optional types | `` | `pt:optional[////]` | `pt:optional[pt:bool]` | `Optional[bool]` | + +* This is how variable length tuples containing elements of the same type are declared in Python; see here. ### Protocol Buffer Schema diff --git a/docs/protocol.md b/docs/protocol.md index 8108395dd0..c6af5ab77f 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -66,9 +66,9 @@ The developer can generate custom protocols with the SOEF search node to register and unregister their own services and search for services registered by other agents. +The `fetchai/oef_search:0.8.0` protocol is used by AEAs to interact with an SOEF search node to register and unregister their own services and search for services registered by other agents. -The `fetchai/oef_search:0.7.0` protocol definition includes an `OefSearchMessage` with the following message types: +The `fetchai/oef_search:0.8.0` protocol definition includes an `OefSearchMessage` with the following message types: ``` python class Performative(Enum): @@ -231,7 +231,7 @@ oef_msg = OefSearchMessage( ) ``` -* The SOEF search node will respond with a message, say `msg` of type `OefSearchMessage`, of performative `OefSearchMessage.Performative.SEARCH_RESULT`. To access the tuple of agents which match the query, simply use `msg.agents`. In particular, this will return the agent addresses matching the query. The agent address can then be used to send a message to the agent utilising the P2P agent communication network and any protocol other than `fetchai/oef_search:0.7.0`. +* The SOEF search node will respond with a message, say `msg` of type `OefSearchMessage`, of performative `OefSearchMessage.Performative.SEARCH_RESULT`. To access the tuple of agents which match the query, simply use `msg.agents`. In particular, this will return the agent addresses matching the query. The agent address can then be used to send a message to the agent utilising the P2P agent communication network and any protocol other than `fetchai/oef_search:0.8.0`. * If the SOEF search node encounters any errors with the messages you send, it will return an `OefSearchMessage` of performative `OefSearchMessage.Performative.OEF_ERROR` and indicate the error operation encountered: ``` python @@ -246,11 +246,11 @@ class OefErrorOperation(Enum): OTHER = 10000 ``` -## `fetchai/fipa:0.7.0` protocol +## `fetchai/fipa:0.8.0` protocol This protocol provides classes and functions necessary for communication between AEAs via a variant of the FIPA Agent Communication Language. -The `fetchai/fipa:0.7.0` protocol definition includes a `FipaMessage` with the following performatives: +The `fetchai/fipa:0.8.0` protocol definition includes a `FipaMessage` with the following performatives: ``` python class Performative(Enum): @@ -283,9 +283,9 @@ def __init__( ) ``` -The `fetchai/fipa:0.7.0` protocol also defines a `FipaDialogue` class which specifies the valid reply structure and provides other helper methods to maintain dialogues. +The `fetchai/fipa:0.8.0` protocol also defines a `FipaDialogue` class which specifies the valid reply structure and provides other helper methods to maintain dialogues. -For examples of the usage of the `fetchai/fipa:0.7.0` protocol check out the generic skills step by step guide. +For examples of the usage of the `fetchai/fipa:0.8.0` protocol check out the generic skills step by step guide. ### Fipa dialogue diff --git a/docs/questions-and-answers.md b/docs/questions-and-answers.md index 8a99afb531..9f3a2a6f48 100644 --- a/docs/questions-and-answers.md +++ b/docs/questions-and-answers.md @@ -72,7 +72,7 @@ You can find more details about the CLI commands here
      When a new AEA is created, is the `vendor` folder populated with some default packages? -All AEA projects by default hold the `fetchai/stub:0.10.0` connection, the `fetchai/default:0.6.0` protocol and the `fetchai/error:0.6.0` skill. These (as all other packages installed from the registry) are placed in the vendor's folder. +All AEA projects by default hold the `fetchai/stub:0.11.0` connection, the `fetchai/default:0.7.0` protocol and the `fetchai/error:0.7.0` skill. These (as all other packages installed from the registry) are placed in the vendor's folder.

      You can find more details about the file structure
      here
      diff --git a/docs/quickstart.md b/docs/quickstart.md index 6c7729d9d5..8591396aab 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -64,7 +64,9 @@ sudo apt-get install python3.7-dev ## Setup author name -You can now setup your author name: +AEAs are composed from components. The components can be developed by anyone and are available on the AEA registry. To use the registry we need to register an author name. + +You can setup your author name using the `init` command: ``` bash aea init ``` @@ -86,7 +88,7 @@ Confirm password: / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.6.2 +v0.6.3 AEA configurations successfully initialized: {'author': 'fetchai'} ``` @@ -98,10 +100,12 @@ AEA configurations successfully initialized: {'author': 'fetchai'} ## Echo skill demo -The echo skill is a simple demo that introduces you to the main business logic components of an AEA. The fastest way to create your first AEA is to fetch it! +The echo skill demo is a simple demo that introduces you to the main business logic components of an AEA. The echo skill simply echoes received messages back to the sender. + +The fastest way to create your first AEA is to fetch it! ``` bash -aea fetch fetchai/my_first_aea:0.12.0 +aea fetch fetchai/my_first_aea:0.13.0 cd my_first_aea ``` @@ -121,9 +125,9 @@ cd my_first_aea
      Second, add the echo skill to the project. ``` bash -aea add skill fetchai/echo:0.8.0 +aea add skill fetchai/echo:0.9.0 ``` -This copies the `fetchai/echo:0.8.0` skill code containing the "behaviours", and "handlers" into the project, ready to run. The identifier of the skill `fetchai/echo:0.8.0` consists of the name of the author of the skill, followed by the skill name and its version. +This copies the `fetchai/echo:0.9.0` skill code containing the "behaviours", and "handlers" into the project, ready to run. The identifier of the skill `fetchai/echo:0.9.0` consists of the name of the author of the skill, followed by the skill name and its version. ## Communication via envelopes and messages @@ -149,12 +153,12 @@ TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE, For example: ``` bash -recipient_aea,sender_aea,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello, +recipient_aea,sender_aea,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello, ``` ## Run the AEA -Run the AEA with the default `fetchai/stub:0.10.0` connection. +Run the AEA with the default `fetchai/stub:0.11.0` connection. ``` bash aea run @@ -163,7 +167,7 @@ aea run or ``` bash -aea run --connections fetchai/stub:0.10.0 +aea run --connections fetchai/stub:0.11.0 ``` You will see the echo skill running in the terminal window. @@ -175,7 +179,7 @@ You will see the echo skill running in the terminal window. / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.6.2 +v0.6.3 Starting AEA 'my_first_aea' in 'async' mode ... info: Echo Handler: setup method called. @@ -207,12 +211,12 @@ info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` -
      CLI interact command +
      Manual approach Optionally, from a different terminal and same directory (i.e. the `my_first_aea` project), we send the AEA a message wrapped in an envelope via the input file. ``` bash -echo 'my_first_aea,sender_aea,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello,' >> input_file +echo 'my_first_aea,sender_aea,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello,' >> input_file ``` You will see the `Echo Handler` dealing with the envelope and responding with the same message to the `output_file`, and also decoding the Base64 encrypted message in this case. @@ -224,7 +228,7 @@ info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` -Note, due to the dialogue reference having to be incremented, you can only send the above envelope once! +Note, due to the dialogue reference having to be incremented, you can only send the above envelope once! This approach does not work in conjunction with the `aea interact` command.
      diff --git a/docs/simple-oef-usage.md b/docs/simple-oef-usage.md index db1a22de30..aee8e881ec 100644 --- a/docs/simple-oef-usage.md +++ b/docs/simple-oef-usage.md @@ -1,12 +1,12 @@ You can use the SOEF in the agent framework by using the SOEF connection as a package in your agent project. ## Add the SOEF package -Check out the CLI guide on details how to add a connection. You will want to add the `fetchai/soef:0.9.0` connection package. +Check out the CLI guide on details how to add a connection. You will want to add the `fetchai/soef:0.10.0` connection package. ## Register your agent and its services ### Register agent location -To register your agent's location, you have to send a message in the `fetchai/oef_search:0.7.0` protocol to the SOEF connection. +To register your agent's location, you have to send a message in the `fetchai/oef_search:0.8.0` protocol to the SOEF connection. First, define a data model for location data: ``` python diff --git a/docs/simple-oef.md b/docs/simple-oef.md index 9e26620e6c..9fb079466b 100644 --- a/docs/simple-oef.md +++ b/docs/simple-oef.md @@ -59,7 +59,7 @@ A genus is a coarse agent class. It is the roughest description of what an agent | `buyer` | Indicates the agent is a buyer _only_ and does not have value to deliver | | `viewer` |The agent is a view in the world, acting as a "camera" to view content | -The best way to use genus is to pick the *best fit* choice. If there isn't one for you, then do not specify it. If you feel that a high-level genus is missing, please make the suggestion in our Developer Slack (see here for the instructions on joining, or the "Further Information" section below). +The best way to use genus is to pick the *best fit* choice. If there isn't one for you, then do not specify it. If you feel that a high-level genus is missing, please make the suggestion in our Developer Slack (see here for the instructions on joining, or the "Further Information" section below). #### Architectures diff --git a/docs/skill-guide.md b/docs/skill-guide.md index 28bbc38848..b009f27a4d 100644 --- a/docs/skill-guide.md +++ b/docs/skill-guide.md @@ -47,7 +47,7 @@ class MySearchBehaviour(TickerBehaviour): search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) - agent_location = Location(longitude=location["longitude"], latitude=location["latitude"]) + agent_location = Location(latitude=location["latitude"], longitude=location["longitude"]) radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) close_to_my_service = Constraint( @@ -108,7 +108,7 @@ class MySearchBehaviour(TickerBehaviour): Searches are proactive and, as such, well placed in a `Behaviour`. Specifically, we subclass the `TickerBehaviour` as it allows us to repeatedly search at a defined tick interval. -We place this code in `my_aea/skills/my_search/behaviours.py`. +We place this code in `my_aea/skills/my_search/behaviours.py`. Ensure you replace the `fetchai` author in this line `from packages.fetchai.skills.my_search.dialogues import OefSearchDialogues` with your author handle (run `aea init` to set or check the author name). ## Step 3: Develop a Handler @@ -259,7 +259,7 @@ Note, how the handler simply reacts to incoming events (i.e. messages). It could Also note, how we have access to other objects in the skill via `self.context`, the `SkillContext`. -We place this code in `my_aea/skills/my_search/handlers.py`. +We place this code in `my_aea/skills/my_search/handlers.py`. Ensure you replace the `fetchai` author in this line `from packages.fetchai.skills.my_search.dialogues import (` with your author handle (run `aea init` to set or check the author name). ## Step 4: Add dialogues model @@ -329,7 +329,7 @@ fingerprint: {} fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: my_search_behaviour: @@ -373,32 +373,32 @@ Ensure, you use the correct author name to reference your skill (here we use `fe Our AEA does not have the oef protocol yet so let's add it. ``` bash -aea add protocol fetchai/oef_search:0.7.0 +aea add protocol fetchai/oef_search:0.8.0 ``` This adds the protocol to our AEA and makes it available on the path `packages.fetchai.protocols...`. We also need to add the soef and p2p connections and install the AEA's dependencies: ``` bash -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` Finally, in the `aea-config.yaml` add the following lines: ``` yaml default_routing: - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` This will ensure that search requests are processed by the correct connection. ## Step 8: Run a service provider AEA -In order to be able to find another AEA when searching, from a different terminal window, we fetch another finished AEA: +In order to be able to find another AEA when searching, from a different terminal window, we fetch another finished AEA and install its Python dependencies: ``` bash -aea fetch fetchai/simple_service_registration:0.13.0 && cd simple_service_registration +aea fetch fetchai/simple_service_registration:0.14.0 && cd simple_service_registration && aea install ``` This AEA will simply register a location service on the SOEF search node so we can search for it. @@ -583,7 +583,7 @@ class Strategy(Model): """ location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { - "location": Location(longitude=location["longitude"], latitude=location["latitude"]) + "location": Location(latitude=location["latitude"], longitude=location["longitude"]) } self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) assert ( @@ -693,7 +693,7 @@ from packages.fetchai.skills.simple_service_registration.dialogues import ( OefSearchDialogues, ) -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class OefSearchHandler(Handler): @@ -803,7 +803,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: service: @@ -841,7 +841,7 @@ aea add-key fetchai fetchai_private_key.txt aea add-key fetchai fetchai_private_key.txt --connection ``` -Then, update the configuration of the buyer AEA's p2p connection (in `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) replace the following: +Then, update the configuration of the search AEA's p2p connection (in `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) replace the following: ``` yaml config: diff --git a/docs/skill-testing.md b/docs/skill-testing.md new file mode 100644 index 0000000000..b321c61f4e --- /dev/null +++ b/docs/skill-testing.md @@ -0,0 +1,103 @@ +In this guide we describe some of the tools the framework offers for testing skills. + +## The `BaseSkillTestCase` class + +The framework offers a `BaseSkillTestCase` class which you can subclass and write your test cases with. + +Let us assume you want to test the `my_behaviour` behaviour of a `CustomSkill` skill you have developed. + +You can create a `TestMyBehaviour` class which inherits `BaseSkillTestCase` as below: + +``` python +from aea.test_tools.test_skill import BaseSkillTestCase + +class TestMyBehaviour(BaseSkillTestCase): + """Test my_behaviours of the custom skill.""" + + path_to_skill = Path("path_to_this_skill") +``` + +### Specifying Skill Path + +You must then specify the path to your skill directory via `path_to_skill` to allow the skill to be loaded and tested. This must be the directory in which `skill.yaml` of your skill resides. + +### Setting up Each Test + +You can add a `setup()` class method to set the environment up for each of your tests. This code will be executed before every test method. If you do include this method, you must call the `setup()` method of the `BaseSkillTestCase` class via `super().setup()`. + +``` python +@classmethod +def setup(cls): + """Setup the test class.""" + super().setup() + cls.my_behaviour = cast( + MyBehaviour, cls._skill.skill_context.behaviours.my_behaviour + ) +``` + +In the above, we make the `my_behaviour` behaviour object accessible for every test. + +### Skill and Skill Context + +The skill object itself is exposed via a property. So you can access the skill object by `self.skill` and by extension all of its attributes. This crucially includes the complete `skill_context`. This means that for example, all of the components of the skill (e.g. behaviours, handlers, models) can be accessed via the skill context. + +In the above code snippet, `my_behavior` is accessed and exposed as a class attribute. Note accessing the skill context is slightly different in the above because it is a class method. If this was a test method, you could access the behaviour via `self.skill.skill_context.behaviours.my_behaviour`. + +### Dummy Agent Context + +The loaded skill is also fed a dummy `agent_context` complete with an `identity`, `outbox`, `decision_maker_queue` and so on, to allow the skill to be properly loaded and have access to everything it requires to function. The `agent_context` object fed to the skill is shown below: + +``` python +_multiplexer = AsyncMultiplexer() +_multiplexer._out_queue = (asyncio.Queue()) + +agent_context = AgentContext( + identity=Identity("test_agent_name", "test_agent_address"), + connection_status=_multiplexer.connection_status, + outbox=OutBox(cast(Multiplexer, cls._multiplexer)), + decision_maker_message_queue=Queue(), + decision_maker_handler_context=SimpleNamespace(), + task_manager=TaskManager(), + default_connection=None, + default_routing={}, + search_service_address="dummy_search_service_address", + decision_maker_address="dummy_decision_maker_address", +) +``` + +### Some Useful Skill Attributes + +Some of the useful objects you can access in your test class for the loaded skill are below: + +* `self.skill.skill_context.agent_address`: this is the agent identity the skill uses and is set to `"test_agent_address"`. +* `self.skill.skill_context.search_service_address`: this is the address of the search service and is set to `"dummy_search_service_address"`. +* `self.skill.skill_context.skill_id`: this is the id of the skill. +* `self.skill.skill_context.decision_maker_address`: this is the address of the decision maker and is set to `"dummy_decision_maker_address"`. + +### Some Useful `BaseSkillTestCase` Methods + +There are a number of methods that `BaseSkillTestCase` offers to make testing skills easier. Some of these are mentioned below. For the rest, consult the API for `BaseSkillTestCase`: + +* `self.get_quantity_in_outbox()`: gives you the number of messages which are in the outbox. After running a part of the skill which is expected to send messages, you can use this method to assert the correct number of messages are indeed sent. +* `self.get_message_from_outbox()`: gives you the last message in the outbox. Together with the above, you can use this method to grab the last message sent by the skill code you tested and check this is indeed the expected message. +* `self.message_has_attributes(actual_message: Message, message_type: Type[Message], **kwargs,)`: you can use this method in tandem with the above method to check that a message has the attributes you expect it to have. You have to supply it with the actual message (e.g. using `self.get_message_from_outbox()`), specify its expected type (e.g. `FipaMessage`), and any other attribute you expect the message to have (e.g. `message_id` is 1) may be provided via keyword arguments. +* `self.build_incoming_message`: this is an especially useful method to test handlers. Since handlers handle incoming messages, you can create an incoming message using this method to feed it to the handler and test its execution. + +#### Checking Logger Output + +You can check the output of your skill's `logger` by mocking it using `unittest.mock` before executing a part of your skill as such: + +``` python +with mock.patch.object(self.my_behaviour.context.logger, "log") as mock_logger: + self.my_behaviour.act() + +mock_logger.assert_any_call(logging.INFO, "some_logger_message") +``` + +In the above, we mock the logger before running `my_behaviour`'s `act()` method and check that the string `"some_logger_message"` is indeed passed to the logger. + +## Next steps + +You can consult the `fetchai/generic_buyer` and `fetchai/generic_seller` skills and their associated tests here to study how `BaseSkillTestCase` can help you in testing your skills. + +You can also refer to the API to study the different methods `BaseSkillTestCase` makes available to make testing your skills easier. diff --git a/docs/skill.md b/docs/skill.md index debb1d34ee..200e4be502 100644 --- a/docs/skill.md +++ b/docs/skill.md @@ -262,7 +262,7 @@ handlers: models: {} dependencies: {} protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 ``` @@ -275,7 +275,7 @@ All AEAs have a default `error` skill that contains error handling code for a nu * Envelopes with decoding errors * Invalid messages with respect to the registered protocol -The error skill relies on the `fetchai/default:0.6.0` protocol which provides error codes for the above. +The error skill relies on the `fetchai/default:0.7.0` protocol which provides error codes for the above.
      diff --git a/docs/tac-skills-contract.md b/docs/tac-skills-contract.md index 75f3f7e628..74cabe2a63 100644 --- a/docs/tac-skills-contract.md +++ b/docs/tac-skills-contract.md @@ -101,7 +101,7 @@ Follow the Preliminaries and Preliminaries and @@ -134,8 +134,8 @@ default_routing: In a separate terminal, in the root directory, fetch at least two participants: ``` bash -aea fetch fetchai/tac_participant:0.11.0 --alias tac_participant_one -aea fetch fetchai/tac_participant:0.11.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.12.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.12.0 --alias tac_participant_two cd tac_participant_two aea install ``` @@ -152,41 +152,41 @@ aea create tac_participant_two Build participant one: ``` bash cd tac_participant_one -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_participation:0.9.0 -aea add skill fetchai/tac_negotiation:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_participation:0.10.0 +aea add skill fetchai/tac_negotiation:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger fetchai ``` In `tac_participant_one/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` Then, build participant two: ``` bash cd tac_participant_two -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_participation:0.9.0 -aea add skill fetchai/tac_negotiation:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_participation:0.10.0 +aea add skill fetchai/tac_negotiation:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger fetchai ``` In `tac_participant_two/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      @@ -220,9 +220,14 @@ aea run Once you see a message of the form `My libp2p addresses: ['SOME_ADDRESS']` take note of the address. -Then, update the configuration of the weather client AEA's p2p connection (in `vendor/fetchai/connections/p2p_libp2p/connection.yaml`) replace the following: +Then, update the configuration of the weather client AEA's p2p connection (in `aea-config.yaml`) add the following: ``` yaml +--- +name: p2p_libp2p +author: fetchai +version: 0.11.0 +type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: ['SOME_ADDRESS'] @@ -232,6 +237,11 @@ config: ``` ``` yaml +--- +name: p2p_libp2p +author: fetchai +version: 0.11.0 +type: connection config: delegate_uri: 127.0.0.1:11002 entry_peers: ['SOME_ADDRESS'] diff --git a/docs/thermometer-skills.md b/docs/thermometer-skills.md index bbaed90239..f6b1eb9913 100644 --- a/docs/thermometer-skills.md +++ b/docs/thermometer-skills.md @@ -62,8 +62,8 @@ A demo to run the thermometer scenario with a true ledger transaction This demo First, fetch the thermometer AEA: ``` bash -aea fetch fetchai/thermometer_aea:0.11.0 --alias my_thermometer_aea -cd thermometer_aea +aea fetch fetchai/thermometer_aea:0.12.0 --alias my_thermometer_aea +cd my_thermometer_aea aea install ``` @@ -74,19 +74,19 @@ The following steps create the thermometer AEA from scratch: ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `my_thermometer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      @@ -96,7 +96,7 @@ default_routing: Then, fetch the thermometer client AEA: ``` bash -aea fetch fetchai/thermometer_client:0.11.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.12.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` @@ -108,19 +108,19 @@ The following steps create the thermometer client from scratch: ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer_client:0.11.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer_client:0.12.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `my_thermometer_aea/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      diff --git a/docs/upgrading.md b/docs/upgrading.md index 1142a43044..1b25a197d0 100644 --- a/docs/upgrading.md +++ b/docs/upgrading.md @@ -1,5 +1,9 @@ This page provides some tipps of how to upgrade between versions. +## v0.6.2 to v0.6.3 + +A new `upgrade` command is introduced to upgrade agent projects and components to their latest versions on the registry. To use the command first upgrade the AEA PyPI package to the latest version, then enter your project and run `aea upgrade`. The project's vendor dependencies will be updated where possible. + ## v0.6.1 to v0.6.2 No public APIs have been changed. diff --git a/docs/wealth.md b/docs/wealth.md index a223f72b2f..cdf2a41e9a 100644 --- a/docs/wealth.md +++ b/docs/wealth.md @@ -1,7 +1,7 @@ To fund an AEA for testing on a test-net you need to request some test tokens from a faucet. -Add a private key to the agent: +Add a private key to the agent ``` bash aea generate-key fetchai aea add-key fetchai fetchai_private_key.txt @@ -12,6 +12,12 @@ aea generate-key ethereum aea add-key ethereum ethereum_private_key.txt ``` +
      +

      Note

      +

      If you already have keys in your project, the commands will prompt you for confirmation whether or not to replace the existing keys. +

      +
      + ## Using a faucet website First, print the address: @@ -35,6 +41,7 @@ aea get-wealth ethereum ``` ## Using the cli + Simply generate wealth via the cli: ``` bash aea generate-wealth fetchai @@ -44,4 +51,10 @@ or aea generate-wealth ethereum ``` +
      +

      Note

      +

      This approach can be unreliable for non-fetchai test nets. +

      +
      +
      diff --git a/docs/weather-skills.md b/docs/weather-skills.md index 3b1a2db8b7..1757c769ca 100644 --- a/docs/weather-skills.md +++ b/docs/weather-skills.md @@ -61,7 +61,7 @@ trusts the seller AEA to send the data upon successful payment. First, fetch the AEA that will provide weather measurements: ``` bash -aea fetch fetchai/weather_station:0.13.0 --alias my_weather_station +aea fetch fetchai/weather_station:0.14.0 --alias my_weather_station cd my_weather_station aea install ``` @@ -73,19 +73,19 @@ The following steps create the weather station from scratch: ``` bash aea create my_weather_station cd my_weather_station -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/weather_station:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/weather_station:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `weather_station/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      @@ -96,7 +96,7 @@ default_routing: In another terminal, fetch the AEA that will query the weather station: ``` bash -aea fetch fetchai/weather_client:0.13.0 --alias my_weather_client +aea fetch fetchai/weather_client:0.14.0 --alias my_weather_client cd my_weather_client aea install ``` @@ -108,19 +108,19 @@ The following steps create the weather client from scratch: ``` bash aea create my_weather_client cd my_weather_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/weather_client:0.11.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/weather_client:0.12.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` In `my_weather_client/aea-config.yaml` add ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ```

      diff --git a/examples/gym_ex/proxy/env.py b/examples/gym_ex/proxy/env.py index 79637dd6da..3b70533002 100755 --- a/examples/gym_ex/proxy/env.py +++ b/examples/gym_ex/proxy/env.py @@ -93,7 +93,7 @@ def __init__(self, gym_env: gym.Env) -> None: super().__init__() self._queue: Queue = Queue() self._action_counter: int = 0 - self.gym_address = "fetchai/gym:0.8.0" + self.gym_address = "fetchai/gym:0.9.0" self._agent = ProxyAgent( name="proxy", gym_env=gym_env, proxy_env_queue=self._queue ) diff --git a/mkdocs.yml b/mkdocs.yml index 7c02d4ccc0..880a40060f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,6 +55,7 @@ nav: - Use multiplexer stand-alone: 'multiplexer-standalone.md' - Create stand-alone transaction: 'standalone-transaction.md' - Create decision-maker transaction: 'decision-maker-transaction.md' + - Testing Skills: 'skill-testing.md' - Deployment: 'deployment.md' - Known limitations: 'known-limits.md' - Build an AEA programmatically: 'build-aea-programmatically.md' @@ -63,6 +64,7 @@ nav: - Upgrading versions: 'upgrading.md' - Modes of running an AEA: 'modes.md' - Multi agent manager: 'multi-agent-manager.md' + - Debugging: 'debug.md' - Use case components: - Generic skills: 'generic-skills.md' - Front-end intergration: 'connect-a-frontend.md' @@ -160,10 +162,10 @@ nav: - Search: - Generic: 'api/helpers/search/generic.md' - Models: 'api/helpers/search/models.md' - - Test Cases: 'api/helpers/test_cases.md' - Transaction: - Base: 'api/helpers/transaction/base.md' - Win32: 'api/helpers/win32.md' + - YamlUtils: 'api/helpers/yaml_utils.md' - Identity: 'api/identity/base.md' - Mail: 'api/mail/base.md' - Protocols: @@ -196,7 +198,10 @@ nav: - Error Skill: 'api/skills/error/handlers.md' - Behaviors: 'api/skills/behaviours.md' - Task: 'api/skills/tasks.md' - - Test Tools: 'api/test_tools/generic.md' + - Test Tools: + - Generic: 'api/test_tools/generic.md' + - Test Cases: 'api/test_tools/test_cases.md' + - Test Skill: 'api/test_tools/test_skill.md' - Glossary: 'glossary.md' - Q&A: 'questions-and-answers.md' diff --git a/packages/fetchai/agents/aries_alice/README.md b/packages/fetchai/agents/aries_alice/README.md new file mode 100644 index 0000000000..8f17b0438a --- /dev/null +++ b/packages/fetchai/agents/aries_alice/README.md @@ -0,0 +1,15 @@ +# Aries Alice + +This agent represents the Alice actor in
      this demo. + +## Description + +This agent is part of the Fetch.ai Aries demo. It simulates the Alice actor of the demo linked above. It uses its primary skill, the `fetchai/aries_alice` skill, to do the following: + +* Registers Alice on the `SOEF` service. +* On receiving an invitation details from Faber AEA (see `fetchai/aries_faber` agent), it connects with an underlying Aries Cloud Agent (ACA) instance and executes an `accept-invitation` command. + +## Links + +* AEA Aries Demo +* Hyperledger Demo diff --git a/packages/fetchai/agents/aries_alice/aea-config.yaml b/packages/fetchai/agents/aries_alice/aea-config.yaml index 9be95df03d..238cde0c38 100644 --- a/packages/fetchai/agents/aries_alice/aea-config.yaml +++ b/packages/fetchai/agents/aries_alice/aea-config.yaml @@ -1,26 +1,26 @@ agent_name: aries_alice author: fetchai -version: 0.11.0 +version: 0.12.0 description: An AEA representing Alice in the Aries demo. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/http_client:0.9.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 -- fetchai/webhook:0.7.0 +- fetchai/http_client:0.10.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 +- fetchai/webhook:0.8.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/http:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/http:0.7.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/aries_alice:0.8.0 -- fetchai/error:0.6.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/aries_alice:0.9.0 +- fetchai/error:0.7.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -28,5 +28,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/http:0.6.0: fetchai/http_client:0.9.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/http:0.7.0: fetchai/http_client:0.10.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/aries_faber/README.md b/packages/fetchai/agents/aries_faber/README.md new file mode 100644 index 0000000000..f45848656a --- /dev/null +++ b/packages/fetchai/agents/aries_faber/README.md @@ -0,0 +1,19 @@ +# Aries Faber + +This agent represents the Faber actor in this demo. + +## Description + +This agent is part of the Fetch.ai Aries demo. It simulates the Faber actor of the demo linked above. It uses its primary skill, the `fetchai/aries_faber` skill, to do the following: + * Register a decentralised ID on a ledger. + * Connects with an underlying Aries Cloud Agent (ACA) instance, and forwards the following instructions: + * Register schema definition + * Register credential definition + * Create an invitation + +It then sends the invitation detail to the Alice agent (see `fetchai/aries_alice` agent) it finds via the `SOEF` service. + +## Links + +* AEA Aries Demo +* Hyperledger Demo diff --git a/packages/fetchai/agents/aries_faber/aea-config.yaml b/packages/fetchai/agents/aries_faber/aea-config.yaml index ab87ebe399..d46ee79b31 100644 --- a/packages/fetchai/agents/aries_faber/aea-config.yaml +++ b/packages/fetchai/agents/aries_faber/aea-config.yaml @@ -1,26 +1,26 @@ agent_name: aries_faber author: fetchai -version: 0.11.0 +version: 0.12.0 description: An AEA representing Faber in the Aries demo. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/http_client:0.9.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 -- fetchai/webhook:0.7.0 +- fetchai/http_client:0.10.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 +- fetchai/webhook:0.8.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/http:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/http:0.7.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/aries_faber:0.7.0 -- fetchai/error:0.6.0 -default_connection: fetchai/http_client:0.9.0 +- fetchai/aries_faber:0.8.0 +- fetchai/error:0.7.0 +default_connection: fetchai/http_client:0.10.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -28,5 +28,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/http:0.6.0: fetchai/http_client:0.9.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/http:0.7.0: fetchai/http_client:0.10.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/car_data_buyer/README.md b/packages/fetchai/agents/car_data_buyer/README.md new file mode 100644 index 0000000000..6756753fd2 --- /dev/null +++ b/packages/fetchai/agents/car_data_buyer/README.md @@ -0,0 +1,13 @@ +# Car Park Client + +This agent purchases information on available car parking spaces in a vicinity. + +## Description + +This agent is part of the Fetch.ai car park demo. It uses its primary skill, the `fetchai/carpark_client` skill, to find an agent on the `SOEF` service that sells car park availability data in a vicinity. + +Once found, it requests this data, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. + +## Links + +* Car Park Demo diff --git a/packages/fetchai/agents/car_data_buyer/aea-config.yaml b/packages/fetchai/agents/car_data_buyer/aea-config.yaml index 10463565e5..a1ae162e63 100644 --- a/packages/fetchai/agents/car_data_buyer/aea-config.yaml +++ b/packages/fetchai/agents/car_data_buyer/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: car_data_buyer author: fetchai -version: 0.13.0 +version: 0.14.0 description: An agent which searches for an instance of a `car_detector` agent and attempts to purchase car park data from it. license: Apache-2.0 @@ -8,21 +8,21 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/carpark_client:0.12.0 -- fetchai/error:0.6.0 -- fetchai/generic_buyer:0.12.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/carpark_client:0.13.0 +- fetchai/error:0.7.0 +- fetchai/generic_buyer:0.13.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -30,5 +30,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/car_detector/README.md b/packages/fetchai/agents/car_detector/README.md new file mode 100644 index 0000000000..be5c2bebad --- /dev/null +++ b/packages/fetchai/agents/car_detector/README.md @@ -0,0 +1,13 @@ +# Car Park Detector + +This agent sells information on the number of car parking spaces available in a given vicinity. + +## Description + +This agent is part of the Fetch.ai car park demo. It uses its primary skill, the `fetchai/carpark_detection` skill, to register its 'carpark-availability-data selling' service on the `SOEF`. It can then be contacted by another agent (for example the `fetchai/carpark_client` agent) to provide its data. + +Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the data after receiving payment. + +## Links + +* Car Park Demo diff --git a/packages/fetchai/agents/car_detector/aea-config.yaml b/packages/fetchai/agents/car_detector/aea-config.yaml index 6351d2f41c..ad95599c0c 100644 --- a/packages/fetchai/agents/car_detector/aea-config.yaml +++ b/packages/fetchai/agents/car_detector/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: car_detector author: fetchai -version: 0.13.0 +version: 0.14.0 description: An agent which sells car park data to instances of `car_data_buyer` agents. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/carpark_detection:0.12.0 -- fetchai/error:0.6.0 -- fetchai/generic_seller:0.13.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/carpark_detection:0.13.0 +- fetchai/error:0.7.0 +- fetchai/generic_seller:0.14.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/erc1155_client/README.md b/packages/fetchai/agents/erc1155_client/README.md new file mode 100644 index 0000000000..a39124f5b5 --- /dev/null +++ b/packages/fetchai/agents/erc1155_client/README.md @@ -0,0 +1,13 @@ +# ERC1155 Client + +An agent that purchases data via a smart contract. + +## Description + +This agent uses its primary skill, the `fetchai/erc1155_client` skill, to find an agent selling data on the `SOEF` service. + + Once found, it requests specific data, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount via a deployed smart contract and receives the data. + +## Links + +* Contract Deployment Guide diff --git a/packages/fetchai/agents/erc1155_client/aea-config.yaml b/packages/fetchai/agents/erc1155_client/aea-config.yaml index a38ca8c0ae..c6acd99f47 100644 --- a/packages/fetchai/agents/erc1155_client/aea-config.yaml +++ b/packages/fetchai/agents/erc1155_client/aea-config.yaml @@ -1,29 +1,29 @@ agent_name: erc1155_client author: fetchai -version: 0.14.0 +version: 0.15.0 description: An AEA to interact with the ERC1155 deployer AEA license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/contract_api:0.5.0 -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 +- fetchai/contract_api:0.6.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 skills: -- fetchai/erc1155_client:0.13.0 -- fetchai/error:0.6.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/erc1155_client:0.14.0 +- fetchai/error:0.7.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: ethereum logging_config: disable_existing_loggers: false @@ -31,6 +31,6 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/erc1155_deployer/README.md b/packages/fetchai/agents/erc1155_deployer/README.md new file mode 100644 index 0000000000..03b2bf9caa --- /dev/null +++ b/packages/fetchai/agents/erc1155_deployer/README.md @@ -0,0 +1,13 @@ +# ERC1155 Deployer + +An agent that deploys a smart contract sells data using it. + +## Description + +This agent uses its primary skill, the `fetchai/erc1155_deploy` skill, to deploy a smart contract, create and mint tokens, and register its 'data-selling' service on the `SOEF`. It can then be contacted by another agent (for example the `fetchai/erc1155_client` agent) to provide specific data. + +Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the data after receiving payment via the deployed smart contract. + +## Links + +* Contract Deployment Guide diff --git a/packages/fetchai/agents/erc1155_deployer/aea-config.yaml b/packages/fetchai/agents/erc1155_deployer/aea-config.yaml index 0e8764b869..ba9feab22c 100644 --- a/packages/fetchai/agents/erc1155_deployer/aea-config.yaml +++ b/packages/fetchai/agents/erc1155_deployer/aea-config.yaml @@ -1,29 +1,29 @@ agent_name: erc1155_deployer author: fetchai -version: 0.14.0 +version: 0.15.0 description: An AEA to deploy and interact with an ERC1155 license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/contract_api:0.5.0 -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 +- fetchai/contract_api:0.6.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 skills: -- fetchai/erc1155_deploy:0.14.0 -- fetchai/error:0.6.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/erc1155_deploy:0.15.0 +- fetchai/error:0.7.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: ethereum logging_config: disable_existing_loggers: false @@ -31,6 +31,6 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/generic_buyer/README.md b/packages/fetchai/agents/generic_buyer/README.md new file mode 100644 index 0000000000..2193c18a3c --- /dev/null +++ b/packages/fetchai/agents/generic_buyer/README.md @@ -0,0 +1,14 @@ +# Generic Buyer + +A generic agent for buying data. + +## Description + +This agent uses its primary skill, the `fetchai/generic_buyer` skill, to find an agent selling data on the `SOEF` service. + +Once found, it requests specific data, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. + +## Links + +* Generic Skills +* Generic Skill Step by Step Guide diff --git a/packages/fetchai/agents/generic_buyer/aea-config.yaml b/packages/fetchai/agents/generic_buyer/aea-config.yaml index 2be2666c1f..9cd4ae49df 100644 --- a/packages/fetchai/agents/generic_buyer/aea-config.yaml +++ b/packages/fetchai/agents/generic_buyer/aea-config.yaml @@ -1,26 +1,26 @@ agent_name: generic_buyer author: fetchai -version: 0.10.0 +version: 0.11.0 description: The buyer AEA purchases the services offered by the seller AEA. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_buyer:0.12.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_buyer:0.13.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -28,5 +28,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/generic_seller/README.md b/packages/fetchai/agents/generic_seller/README.md new file mode 100644 index 0000000000..aa8be1c442 --- /dev/null +++ b/packages/fetchai/agents/generic_seller/README.md @@ -0,0 +1,14 @@ +# Generic Seller + +A generic agent for selling data. + +## Description + +This agent uses its primary skill, the `fetchai/generic_seller` skill, to register its 'data-selling' service on the `SOEF`. It can then be contacted by another agent (for example the `fetchai/generic_buyer` agent) to provide specific data. + +Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the data after receiving payment. + +## Links + +* Generic Skills +* Generic Skill Step by Step Guide diff --git a/packages/fetchai/agents/generic_seller/aea-config.yaml b/packages/fetchai/agents/generic_seller/aea-config.yaml index 4c9a7ad097..e3ba00d188 100644 --- a/packages/fetchai/agents/generic_seller/aea-config.yaml +++ b/packages/fetchai/agents/generic_seller/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: generic_seller author: fetchai -version: 0.10.0 +version: 0.11.0 description: The seller AEA sells the services specified in the `skill.yaml` file and delivers them upon payment to the buyer. license: Apache-2.0 @@ -8,20 +8,20 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_seller:0.13.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_seller:0.14.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/gym_aea/README.md b/packages/fetchai/agents/gym_aea/README.md new file mode 100644 index 0000000000..2de7c9c696 --- /dev/null +++ b/packages/fetchai/agents/gym_aea/README.md @@ -0,0 +1,15 @@ +# Gym + +This agent trains an RL algorithm using OpenAI Gym. + +## Description + +This agent is part of the Fetch.ai Gym demo. It uses its primary skill, the `fetchai/gym` skill, to train a reinforcement learning (RL) algorithm using OpenAI Gym. + +It demonstrate how an RL agent can be wrapped inside a skill by decoupling the RL agent from the gym environment, allowing them to run in separate execution environments. + +## Links + +* Gym Demo +* Gym Example +* OpenAI Gym \ No newline at end of file diff --git a/packages/fetchai/agents/gym_aea/aea-config.yaml b/packages/fetchai/agents/gym_aea/aea-config.yaml index 357713694e..26621f82c2 100644 --- a/packages/fetchai/agents/gym_aea/aea-config.yaml +++ b/packages/fetchai/agents/gym_aea/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: gym_aea author: fetchai -version: 0.11.0 +version: 0.12.0 description: The gym aea demos the interaction between a skill containing a RL agent and a gym connection. license: Apache-2.0 @@ -8,16 +8,16 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/gym:0.8.0 -- fetchai/stub:0.10.0 +- fetchai/gym:0.9.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/gym:0.6.0 +- fetchai/default:0.7.0 +- fetchai/gym:0.7.0 skills: -- fetchai/error:0.6.0 -- fetchai/gym:0.8.0 -default_connection: fetchai/gym:0.8.0 +- fetchai/error:0.7.0 +- fetchai/gym:0.9.0 +default_connection: fetchai/gym:0.9.0 default_ledger: fetchai logging_config: disable_existing_loggers: false diff --git a/packages/fetchai/agents/ml_data_provider/README.md b/packages/fetchai/agents/ml_data_provider/README.md new file mode 100644 index 0000000000..8601515d03 --- /dev/null +++ b/packages/fetchai/agents/ml_data_provider/README.md @@ -0,0 +1,15 @@ +# ML Data Provider + +This agent sells ML data for training. + +## Description + +This agent is part of the Fetch.ai ML skill demo. It uses its primary skill, the `fetchai/ml_data_provider` skill, to register its 'ML-data-selling' service on the `SOEF`. + +It can be contacted by another agent (for example the `fetchai/ml_train` agent) to provide specific data samples. + +Once such a request is made and the terms of trade are agreed by both agents, it delivers the data after receiving payment. + +## Links + +* ML Demo diff --git a/packages/fetchai/agents/ml_data_provider/aea-config.yaml b/packages/fetchai/agents/ml_data_provider/aea-config.yaml index 6edfe08859..63c5e846e8 100644 --- a/packages/fetchai/agents/ml_data_provider/aea-config.yaml +++ b/packages/fetchai/agents/ml_data_provider/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: ml_data_provider author: fetchai -version: 0.13.0 +version: 0.14.0 description: An agent that sells data. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/ledger_api:0.4.0 -- fetchai/ml_trade:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/ledger_api:0.5.0 +- fetchai/ml_trade:0.7.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_seller:0.13.0 -- fetchai/ml_data_provider:0.12.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_seller:0.14.0 +- fetchai/ml_data_provider:0.13.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/ml_model_trainer/README.md b/packages/fetchai/agents/ml_model_trainer/README.md new file mode 100644 index 0000000000..5ebc2a08f5 --- /dev/null +++ b/packages/fetchai/agents/ml_model_trainer/README.md @@ -0,0 +1,13 @@ +# ML Train + +This agent buys ML data for training. + +## Description + +This skill is part of the Fetch.ai ML skill demo. It uses its primary skill, the `fetchai/ml_train` skill, to find an agent selling ML data on the `SOEF` service (for example a `fetchai/ml_data_provider` agent). + +Once found, it requests specific data samples. If both parties agree with the terms of trade, it pays the proposed amount and trains an ML model using the data bought. + +## Links + +* ML Demo \ No newline at end of file diff --git a/packages/fetchai/agents/ml_model_trainer/aea-config.yaml b/packages/fetchai/agents/ml_model_trainer/aea-config.yaml index 70796a0f06..84c5b571f0 100644 --- a/packages/fetchai/agents/ml_model_trainer/aea-config.yaml +++ b/packages/fetchai/agents/ml_model_trainer/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: ml_model_trainer author: fetchai -version: 0.13.0 +version: 0.14.0 description: An agent buying data and training a model from it. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/ledger_api:0.4.0 -- fetchai/ml_trade:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/ledger_api:0.5.0 +- fetchai/ml_trade:0.7.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_buyer:0.12.0 -- fetchai/ml_train:0.12.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_buyer:0.13.0 +- fetchai/ml_train:0.13.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/my_first_aea/README.md b/packages/fetchai/agents/my_first_aea/README.md new file mode 100644 index 0000000000..54777073b9 --- /dev/null +++ b/packages/fetchai/agents/my_first_aea/README.md @@ -0,0 +1,12 @@ +# My First Agent + +This is the "Hello World" agent! + +## Description + +This agent uses its primary skill, the `fetchai/echo` skill, to sends back the contents of any message it receives. + +## Links + +* Quick Start +* Programmatically Build an AEA \ No newline at end of file diff --git a/packages/fetchai/agents/my_first_aea/aea-config.yaml b/packages/fetchai/agents/my_first_aea/aea-config.yaml index a855b3aa89..66ed9bba6c 100644 --- a/packages/fetchai/agents/my_first_aea/aea-config.yaml +++ b/packages/fetchai/agents/my_first_aea/aea-config.yaml @@ -1,20 +1,20 @@ agent_name: my_first_aea author: fetchai -version: 0.12.0 +version: 0.13.0 description: A simple agent to demo the echo skill. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: -- fetchai/echo:0.8.0 -- fetchai/error:0.6.0 -default_connection: fetchai/stub:0.10.0 +- fetchai/echo:0.9.0 +- fetchai/error:0.7.0 +default_connection: fetchai/stub:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false diff --git a/packages/fetchai/agents/simple_service_registration/README.md b/packages/fetchai/agents/simple_service_registration/README.md new file mode 100644 index 0000000000..a6a49d0a32 --- /dev/null +++ b/packages/fetchai/agents/simple_service_registration/README.md @@ -0,0 +1,11 @@ +# Simple Service Registration + +This agent registers and unregisters its service on the SOEF. + +## Description + +This agent is used in the "Guide on Writing a Skill" section of the documentation. On start, it registers its service on the `SOEF`, and on termination it unregisters it. + +## Links + +* Guide on Building a Skill diff --git a/packages/fetchai/agents/simple_service_registration/aea-config.yaml b/packages/fetchai/agents/simple_service_registration/aea-config.yaml index e58f388a4b..371ea34a37 100644 --- a/packages/fetchai/agents/simple_service_registration/aea-config.yaml +++ b/packages/fetchai/agents/simple_service_registration/aea-config.yaml @@ -1,24 +1,24 @@ agent_name: simple_service_registration author: fetchai -version: 0.13.0 +version: 0.14.0 description: A simple example of service registration. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: '' fingerprint_ignore_patterns: [] connections: -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/simple_service_registration:0.10.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/simple_service_registration:0.11.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -26,4 +26,4 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/tac_controller/README.md b/packages/fetchai/agents/tac_controller/README.md new file mode 100644 index 0000000000..b930623ea0 --- /dev/null +++ b/packages/fetchai/agents/tac_controller/README.md @@ -0,0 +1,13 @@ +# TAC Controller + +This is a controller agent for a Trading Agent Competition (TAC). + +## Description + +This agent is part of the Fetch.ai TAC demo. + +It uses its `fetchai/tac_control` skill to manage the progression of a competition across various stages. + +## Links + +* TAC Demo diff --git a/packages/fetchai/agents/tac_controller/aea-config.yaml b/packages/fetchai/agents/tac_controller/aea-config.yaml index 442993e0b4..214c0eb6a0 100644 --- a/packages/fetchai/agents/tac_controller/aea-config.yaml +++ b/packages/fetchai/agents/tac_controller/aea-config.yaml @@ -1,24 +1,24 @@ agent_name: tac_controller author: fetchai -version: 0.10.0 +version: 0.11.0 description: An AEA to manage an instance of the TAC (trading agent competition) license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/oef_search:0.7.0 -- fetchai/tac:0.7.0 +- fetchai/default:0.7.0 +- fetchai/oef_search:0.8.0 +- fetchai/tac:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/tac_control:0.8.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/tac_control:0.9.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -26,4 +26,4 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/tac_controller_contract/README.md b/packages/fetchai/agents/tac_controller_contract/README.md new file mode 100644 index 0000000000..c2385e5b16 --- /dev/null +++ b/packages/fetchai/agents/tac_controller_contract/README.md @@ -0,0 +1,13 @@ +# TAC Controller Contract + +This is a controller agent for a Trading Agent Competition (TAC) using smart contracts. + +## Description + +This agent is part of the Fetch.ai TAC demo. + +It uses its `fetchai/tac_control` skill to manage the progression of a competition across various stages, and uses its `fetchai/tac_control_contract` skill to create and maintain a smart contract (for example, deploying the contract, creating and minting tokens, etc). + +## Links + +* TAC Demo diff --git a/packages/fetchai/agents/tac_controller_contract/aea-config.yaml b/packages/fetchai/agents/tac_controller_contract/aea-config.yaml index 9846c1d80b..7b82391cda 100644 --- a/packages/fetchai/agents/tac_controller_contract/aea-config.yaml +++ b/packages/fetchai/agents/tac_controller_contract/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: tac_controller_contract author: fetchai -version: 0.11.0 +version: 0.12.0 description: An AEA to manage an instance of the TAC (trading agent competition) using an ERC1155 smart contract. license: Apache-2.0 @@ -8,24 +8,24 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/contract_api:0.5.0 -- fetchai/default:0.6.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 -- fetchai/tac:0.7.0 +- fetchai/contract_api:0.6.0 +- fetchai/default:0.7.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 +- fetchai/tac:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/tac_control:0.8.0 -- fetchai/tac_control_contract:0.9.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/tac_control:0.9.0 +- fetchai/tac_control_contract:0.10.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: ethereum logging_config: disable_existing_loggers: false @@ -33,22 +33,19 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 --- name: soef author: fetchai -version: 0.9.0 +version: 0.10.0 type: connection config: - api_key: TwiCIriSl0mLahw17pyqoA chain_identifier: ethereum - soef_addr: soef.fetch.ai - soef_port: 9002 --- name: tac_control author: fetchai -version: 0.8.0 +version: 0.9.0 type: skill is_abstract: true diff --git a/packages/fetchai/agents/tac_participant/README.md b/packages/fetchai/agents/tac_participant/README.md new file mode 100644 index 0000000000..ceb9e91505 --- /dev/null +++ b/packages/fetchai/agents/tac_participant/README.md @@ -0,0 +1,15 @@ +# TAC Participant + +This is an agent that participates in a Trading Agent Competition (TAC). + +## Description + +This agent is part of the Fetch.ai TAC demo. + +It uses its `fetchai/tac_participation` skill to find and register with a TAC by communicating with a controller agent. + +It then uses its `fetchai/tac_negotiation` skill to find trading partners, negotiate deals and exchange goods with them. + +## Links + +* TAC Demo diff --git a/packages/fetchai/agents/tac_participant/aea-config.yaml b/packages/fetchai/agents/tac_participant/aea-config.yaml index c5e96b4791..3e36946d77 100644 --- a/packages/fetchai/agents/tac_participant/aea-config.yaml +++ b/packages/fetchai/agents/tac_participant/aea-config.yaml @@ -1,28 +1,28 @@ agent_name: tac_participant author: fetchai -version: 0.11.0 +version: 0.12.0 description: An AEA to participate in the TAC (trading agent competition) license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/oef_search:0.7.0 -- fetchai/tac:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/oef_search:0.8.0 +- fetchai/tac:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/tac_negotiation:0.10.0 -- fetchai/tac_participation:0.9.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/tac_negotiation:0.11.0 +- fetchai/tac_participation:0.10.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -30,5 +30,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/tac_participant_contract/README.md b/packages/fetchai/agents/tac_participant_contract/README.md new file mode 100644 index 0000000000..28bc3c7f95 --- /dev/null +++ b/packages/fetchai/agents/tac_participant_contract/README.md @@ -0,0 +1,15 @@ +# TAC Participant Contract + +This is an agent that participates in a Trading Agent Competition (TAC) using a smart contract. + +## Description + +This agent is part of the Fetch.ai TAC demo. + +It uses its `fetchai/tac_participation` skill to find and register with a TAC by communicating with a controller agent. + +It then uses its `fetchai/tac_negotiation` skill to find trading partners, negotiate deals and exchange goods with them using a smart contract. + +## Links + +* TAC Demo diff --git a/packages/fetchai/agents/tac_participant_contract/aea-config.yaml b/packages/fetchai/agents/tac_participant_contract/aea-config.yaml index 32726f5f0d..c0175c0bb0 100644 --- a/packages/fetchai/agents/tac_participant_contract/aea-config.yaml +++ b/packages/fetchai/agents/tac_participant_contract/aea-config.yaml @@ -1,6 +1,6 @@ agent_name: tac_participant_contract author: fetchai -version: 0.1.0 +version: 0.2.0 description: An AEA to participate in the TAC (trading agent competition) using an ERC1155 smart contract. license: Apache-2.0 @@ -8,22 +8,22 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/oef_search:0.7.0 -- fetchai/tac:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/oef_search:0.8.0 +- fetchai/tac:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/tac_negotiation:0.10.0 -- fetchai/tac_participation:0.9.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/tac_negotiation:0.11.0 +- fetchai/tac_participation:0.10.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: ethereum logging_config: disable_existing_loggers: false @@ -31,15 +31,32 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 +--- +name: soef +author: fetchai +version: 0.10.0 +type: connection +config: + chain_identifier: ethereum --- name: tac_participation author: fetchai -version: 0.9.0 +version: 0.10.0 type: skill +models: + game: + args: + is_using_contract: true --- name: tac_negotiation author: fetchai -version: 0.10.0 +version: 0.11.0 type: skill +models: + strategy: + args: + is_contract_tx: true + ledger_id: ethereum diff --git a/packages/fetchai/agents/thermometer_aea/README.md b/packages/fetchai/agents/thermometer_aea/README.md new file mode 100644 index 0000000000..2200d07086 --- /dev/null +++ b/packages/fetchai/agents/thermometer_aea/README.md @@ -0,0 +1,15 @@ +# Thermometer + +This agent sells thermometer data. + +## Description + +This agent is part of the Fetch.ai thermometer demo. It uses its primary skill, the `fetchai/thermometer` skill, to register its 'thermometer-data-selling' service on the `SOEF`. + +It can be contacted by another agent (for example the `fetchai/thermometer_client` agent) to provide data from a thermometer reading. + +Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it reads data from a (real or fake) thermometer, and delivers it after receiving payment. + +## Links + +* Thermometer Demo diff --git a/packages/fetchai/agents/thermometer_aea/aea-config.yaml b/packages/fetchai/agents/thermometer_aea/aea-config.yaml index a3ed35fd38..02f1efb9c5 100644 --- a/packages/fetchai/agents/thermometer_aea/aea-config.yaml +++ b/packages/fetchai/agents/thermometer_aea/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: thermometer_aea author: fetchai -version: 0.11.0 +version: 0.12.0 description: An AEA to represent a thermometer and sell temperature data. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_seller:0.13.0 -- fetchai/thermometer:0.12.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_seller:0.14.0 +- fetchai/thermometer:0.13.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/thermometer_client/README.md b/packages/fetchai/agents/thermometer_client/README.md new file mode 100644 index 0000000000..4f6d651cb5 --- /dev/null +++ b/packages/fetchai/agents/thermometer_client/README.md @@ -0,0 +1,13 @@ +# Thermometer Client + +This agent buys thermometer data. + +## Description + +This agent is part of the Fetch.ai thermometer demo. It uses its primary skill, the `fetchai/thermometer_client` skill, to find an agent selling thermometer data on the `SOEF` service. + +Once found, it requests data from a thermometer reading, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. + +## Links + +* Thermometer Demo diff --git a/packages/fetchai/agents/thermometer_client/aea-config.yaml b/packages/fetchai/agents/thermometer_client/aea-config.yaml index 22a8eacc24..6f62d73a4d 100644 --- a/packages/fetchai/agents/thermometer_client/aea-config.yaml +++ b/packages/fetchai/agents/thermometer_client/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: thermometer_client author: fetchai -version: 0.11.0 +version: 0.12.0 description: An AEA that purchases thermometer data. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_buyer:0.12.0 -- fetchai/thermometer_client:0.11.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_buyer:0.13.0 +- fetchai/thermometer_client:0.12.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/weather_client/README.md b/packages/fetchai/agents/weather_client/README.md new file mode 100644 index 0000000000..b414d7f2e0 --- /dev/null +++ b/packages/fetchai/agents/weather_client/README.md @@ -0,0 +1,13 @@ +# Weather Client + +This agent buys dummy weather data. + +## Description + +This agent is part of the Fetch.ai weather demo. It uses its primary skill, the `fetchai/weather_client` skill, to find an agent selling weather data on the `SOEF` service. + +Once found, it requests weather data for specific dates, negotiates the price using the `fetchai/fipa` protocol, and if an agreement is reached, pays the proposed amount and receives the data. + +## Links + +* Weather Demo \ No newline at end of file diff --git a/packages/fetchai/agents/weather_client/aea-config.yaml b/packages/fetchai/agents/weather_client/aea-config.yaml index 44841b8f5f..9c48115aaf 100644 --- a/packages/fetchai/agents/weather_client/aea-config.yaml +++ b/packages/fetchai/agents/weather_client/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: weather_client author: fetchai -version: 0.13.0 +version: 0.14.0 description: This AEA purchases weather data from the weather station. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_buyer:0.12.0 -- fetchai/weather_client:0.11.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_buyer:0.13.0 +- fetchai/weather_client:0.12.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/agents/weather_station/README.md b/packages/fetchai/agents/weather_station/README.md new file mode 100644 index 0000000000..c2f0a5a964 --- /dev/null +++ b/packages/fetchai/agents/weather_station/README.md @@ -0,0 +1,15 @@ +# Weather Station + +This agent sells dummy weather data. + +## Description + +This agent is part of the Fetch.ai weather demo. It uses its primary skill, the `fetchai/weather_station` skill, to registers its 'weather-data-selling' service on the `SOEF`. This data comes from a database that is populated with dummy data from a weather station. + +It can be contacted by another agent (for example the `fetchai/weather_client` agent) to provide weather data for specific dates.. + +Once such a request is made, this agent negotiates the terms of trade using the `fetchai/fipa` protocol, and if an agreement is reached, it delivers the weather data after receiving payment. + +## Links + +* Weather Demo diff --git a/packages/fetchai/agents/weather_station/aea-config.yaml b/packages/fetchai/agents/weather_station/aea-config.yaml index aab1c30c5c..96572909ab 100644 --- a/packages/fetchai/agents/weather_station/aea-config.yaml +++ b/packages/fetchai/agents/weather_station/aea-config.yaml @@ -1,27 +1,27 @@ agent_name: weather_station author: fetchai -version: 0.13.0 +version: 0.14.0 description: This AEA represents a weather station selling weather data. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/ledger:0.6.0 -- fetchai/p2p_libp2p:0.10.0 -- fetchai/soef:0.9.0 -- fetchai/stub:0.10.0 +- fetchai/ledger:0.7.0 +- fetchai/p2p_libp2p:0.11.0 +- fetchai/soef:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/error:0.6.0 -- fetchai/generic_seller:0.13.0 -- fetchai/weather_station:0.12.0 -default_connection: fetchai/p2p_libp2p:0.10.0 +- fetchai/error:0.7.0 +- fetchai/generic_seller:0.14.0 +- fetchai/weather_station:0.13.0 +default_connection: fetchai/p2p_libp2p:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false @@ -29,5 +29,5 @@ logging_config: private_key_paths: {} registry_path: ../packages default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 diff --git a/packages/fetchai/connections/gym/README.md b/packages/fetchai/connections/gym/README.md index cbb2f0f0e7..cd4fc1ad55 100644 --- a/packages/fetchai/connections/gym/README.md +++ b/packages/fetchai/connections/gym/README.md @@ -6,4 +6,4 @@ The connection wraps a gym and allows the AEA to interact with the gym interface ## Usage -First, add the connection to your AEA project (`aea add connection fetchai/gym:0.8.0`). Then, update the `config` in `connection.yaml` by providing a dotted path to the gym module in the `env` field. +First, add the connection to your AEA project (`aea add connection fetchai/gym:0.9.0`). Then, update the `config` in `connection.yaml` by providing a dotted path to the gym module in the `env` field. diff --git a/packages/fetchai/connections/gym/connection.py b/packages/fetchai/connections/gym/connection.py index 395a566099..e467e49b4a 100644 --- a/packages/fetchai/connections/gym/connection.py +++ b/packages/fetchai/connections/gym/connection.py @@ -41,9 +41,9 @@ from packages.fetchai.protocols.gym.message import GymMessage -logger = logging.getLogger("aea.packages.fetchai.connections.gym") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.gym") -PUBLIC_ID = PublicId.from_str("fetchai/gym:0.8.0") +PUBLIC_ID = PublicId.from_str("fetchai/gym:0.9.0") class GymDialogues(BaseGymDialogues): @@ -90,7 +90,7 @@ def __init__(self, address: Address, gym_env: gym.Env): self._threaded_pool: ThreadPoolExecutor = ThreadPoolExecutor( self.THREAD_POOL_SIZE ) - self.logger: Union[logging.Logger, logging.LoggerAdapter] = logger + self.logger: Union[logging.Logger, logging.LoggerAdapter] = _default_logger self._dialogues = GymDialogues() def _get_message_and_dialogue( diff --git a/packages/fetchai/connections/gym/connection.yaml b/packages/fetchai/connections/gym/connection.yaml index cce17f13a0..2d715eb5e0 100644 --- a/packages/fetchai/connections/gym/connection.yaml +++ b/packages/fetchai/connections/gym/connection.yaml @@ -1,22 +1,22 @@ name: gym author: fetchai -version: 0.8.0 +version: 0.9.0 type: connection description: The gym connection wraps an OpenAI gym. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmPUxpn8smGVFexxhUu3Vy3q5xsJukHnYtQzUXyt965bmL + README.md: QmVVY1b19ZVmViLJhCkWY2x1bhTVh3pFAcMPgjza2we83Q __init__.py: QmWwxj1hGGZNteCvRtZxwtY9PuEKsrWsEmMWCKwiYCdvRR - connection.py: QmeBfJXcmyFzH84siPgKjTCrsSnTw7V1Jza1ZL9vUjpBS3 + connection.py: Qmce7CQCmjXeYsQSERezU2JyBKECVce9yWgB9dKBLYyj3U fingerprint_ignore_patterns: [] protocols: -- fetchai/gym:0.6.0 +- fetchai/gym:0.7.0 class_name: GymConnection config: env: '' excluded_protocols: [] restricted_to_protocols: -- fetchai/gym:0.6.0 +- fetchai/gym:0.7.0 dependencies: gym: {} diff --git a/packages/fetchai/connections/http_client/README.md b/packages/fetchai/connections/http_client/README.md index 0686f336e5..2565b30655 100644 --- a/packages/fetchai/connections/http_client/README.md +++ b/packages/fetchai/connections/http_client/README.md @@ -4,4 +4,4 @@ This connection wraps an HTTP client. It consumes messages from the AEA, transla ## Usage -First, add the connection to your AEA project (`aea add connection fetchai/http_client:0.9.0`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. +First, add the connection to your AEA project (`aea add connection fetchai/http_client:0.10.0`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. diff --git a/packages/fetchai/connections/http_client/connection.py b/packages/fetchai/connections/http_client/connection.py index 3c86c22d22..ce8697dc38 100644 --- a/packages/fetchai/connections/http_client/connection.py +++ b/packages/fetchai/connections/http_client/connection.py @@ -26,7 +26,7 @@ from asyncio.events import AbstractEventLoop from asyncio.tasks import Task from traceback import format_exc -from typing import Any, Optional, Set, Tuple, Union, cast +from typing import Any, Optional, Set, Tuple, Type, Union, cast import aiohttp from aiohttp.client_reqrep import ClientResponse @@ -37,8 +37,9 @@ from aea.exceptions import enforce from aea.mail.base import Envelope, EnvelopeContext, Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue +from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel -from packages.fetchai.protocols.http.dialogues import HttpDialogue +from packages.fetchai.protocols.http.dialogues import HttpDialogue as BaseHttpDialogue from packages.fetchai.protocols.http.dialogues import HttpDialogues as BaseHttpDialogues from packages.fetchai.protocols.http.message import HttpMessage @@ -47,17 +48,57 @@ NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 -PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.9.0") +PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.10.0") -logger = logging.getLogger("aea.packages.fetchai.connections.http_client") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.http_client") RequestId = str +class HttpDialogue(BaseHttpDialogue): + """The dialogue class maintains state of a dialogue and manages it.""" + + def __init__( + self, + dialogue_label: BaseDialogueLabel, + self_address: Address, + role: BaseDialogue.Role, + message_class: Type[HttpMessage] = HttpMessage, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param self_address: the address of the entity for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + + :return: None + """ + BaseHttpDialogue.__init__( + self, + dialogue_label=dialogue_label, + self_address=self_address, + role=role, + message_class=message_class, + ) + self._envelope_context = None # type: Optional[EnvelopeContext] + + @property + def envelope_context(self) -> Optional[EnvelopeContext]: + """Get envelope_context.""" + return self._envelope_context + + @envelope_context.setter + def envelope_context(self, envelope_context: Optional[EnvelopeContext]) -> None: + """Set envelope_context.""" + enforce(self._envelope_context is None, "envelope_context already set!") + self._envelope_context = envelope_context + + class HttpDialogues(BaseHttpDialogues): """The dialogues class keeps track of all http dialogues.""" - def __init__(self, **kwargs) -> None: + def __init__(self) -> None: """ Initialize dialogues. @@ -80,7 +121,7 @@ def role_from_first_message( # pylint: disable=unused-argument self, self_address=str(HTTPClientConnection.connection_id), role_from_first_message=role_from_first_message, - **kwargs, + dialogue_class=HttpDialogue, ) @@ -125,7 +166,7 @@ def __init__( self.is_stopped = True self._tasks: Set[Task] = set() - self.logger = logger + self.logger = _default_logger self.logger.info("Initialised the HTTP client channel") async def connect(self, loop: AbstractEventLoop) -> None: @@ -151,7 +192,9 @@ def _get_message_and_dialogue( :return: Tuple[MEssage, Optional[Dialogue]] """ message = cast(HttpMessage, envelope.message) - dialogue = cast(HttpDialogue, self._dialogues.update(message)) + dialogue = cast(Optional[HttpDialogue], self._dialogues.update(message)) + if dialogue is not None: + dialogue.envelope_context = envelope.context return message, dialogue async def _http_request_task(self, request_envelope: Envelope) -> None: @@ -181,7 +224,6 @@ async def _http_request_task(self, request_envelope: Envelope) -> None: timeout=self.DEFAULT_TIMEOUT, ) envelope = self.to_envelope( - self.connection_id, request_http_message, status_code=resp.status, headers=resp.headers, @@ -193,7 +235,6 @@ async def _http_request_task(self, request_envelope: Envelope) -> None: ) except Exception: # pragma: nocover # pylint: disable=broad-except envelope = self.to_envelope( - self.connection_id, request_http_message, status_code=self.DEFAULT_EXCEPTION_CODE, headers={}, @@ -307,7 +348,6 @@ async def get_message(self) -> Union["Envelope", None]: @staticmethod def to_envelope( - connection_id: PublicId, http_request_message: HttpMessage, status_code: int, headers: dict, @@ -318,7 +358,6 @@ def to_envelope( """ Convert an HTTP response object (from the 'requests' library) into an Envelope containing an HttpMessage (from the 'http' Protocol). - :param connection_id: the connection id :param http_request_message: the message of the http request envelop :param status_code: the http status code, int :param headers: dict of http response headers @@ -327,7 +366,6 @@ def to_envelope( :return: Envelope with http response data. """ - context = EnvelopeContext(connection_id=connection_id) http_message = dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_request_message, @@ -341,7 +379,7 @@ def to_envelope( to=http_message.to, sender=http_message.sender, protocol_id=http_message.protocol_id, - context=context, + context=dialogue.envelope_context, message=http_message, ) return envelope diff --git a/packages/fetchai/connections/http_client/connection.yaml b/packages/fetchai/connections/http_client/connection.yaml index dd5d64ad4c..fd6413199d 100644 --- a/packages/fetchai/connections/http_client/connection.yaml +++ b/packages/fetchai/connections/http_client/connection.yaml @@ -1,25 +1,25 @@ name: http_client author: fetchai -version: 0.9.0 +version: 0.10.0 type: connection description: The HTTP_client connection that wraps a web-based client connecting to a RESTful API specification. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmV5yDm9hsPKzhW3xA8GgViryhbwGEcRSqxwtrEzFm1fuQ + README.md: QmYFN1K5v8t5Jn7jBWFQC1dULB3AK8jNiCeZgBChobRZT8 __init__.py: QmPdKAks8A6XKAgZiopJzPZYXJumTeUqChd8UorqmLQQPU - connection.py: QmPWU31DeNecH9Ch62hFZ5a1peEk5p7UXYj2qRgm3TBLFD + connection.py: QmRYrhEHdfjcct7KEzDZvbh2GAAhApFCYBf8uaS1HCkFvG fingerprint_ignore_patterns: [] protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 class_name: HTTPClientConnection config: host: 127.0.0.1 port: 8000 excluded_protocols: [] restricted_to_protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 dependencies: aiohttp: - version: '>=3.6.2,<3.7' + version: <3.7,>=3.6.2 diff --git a/packages/fetchai/connections/http_server/README.md b/packages/fetchai/connections/http_server/README.md index 20aee30556..5bb793d36e 100644 --- a/packages/fetchai/connections/http_server/README.md +++ b/packages/fetchai/connections/http_server/README.md @@ -4,4 +4,4 @@ This connection wraps an HTTP server. It consumes requests from clients, transla ## Usage -First, add the connection to your AEA project (`aea add connection fetchai/http_server:0.9.0`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. Optionally, provide a path to an [OpenAPI spec](https://swagger.io/docs/specification/about/) for request validation. \ No newline at end of file +First, add the connection to your AEA project (`aea add connection fetchai/http_server:0.10.0`). Then, update the `config` in `connection.yaml` by providing a `host` and `port` of the server. Optionally, provide a path to an [OpenAPI spec](https://swagger.io/docs/specification/about/) for request validation. \ No newline at end of file diff --git a/packages/fetchai/connections/http_server/connection.py b/packages/fetchai/connections/http_server/connection.py index c906ca6960..6dd5b86102 100644 --- a/packages/fetchai/connections/http_server/connection.py +++ b/packages/fetchai/connections/http_server/connection.py @@ -66,7 +66,7 @@ _default_logger = logging.getLogger("aea.packages.fetchai.connections.http_server") RequestId = DialogueLabel -PUBLIC_ID = PublicId.from_str("fetchai/http_server:0.9.0") +PUBLIC_ID = PublicId.from_str("fetchai/http_server:0.10.0") class HttpDialogues(BaseHttpDialogues): diff --git a/packages/fetchai/connections/http_server/connection.yaml b/packages/fetchai/connections/http_server/connection.yaml index d9db65c7dd..7aa3f5451f 100644 --- a/packages/fetchai/connections/http_server/connection.yaml +++ b/packages/fetchai/connections/http_server/connection.yaml @@ -1,18 +1,18 @@ name: http_server author: fetchai -version: 0.9.0 +version: 0.10.0 type: connection description: The HTTP server connection that wraps http server implementing a RESTful API specification. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmUeXiba13kjgsW8EMYvWnMYb6GzSirTPyr3L2dLU4J9gT + README.md: QmNq1yYrHzeudna7vebSaHtvnCeRp4XvhvTiZfW1A54Ums __init__.py: Qmb6JEAkJeb5JweqrSGiGoQp1vGXqddjGgb9WMkm2phTgA - connection.py: QmPjqaEpWNRM412ZbxCN2dp3qUM1jTEYmhABbYUDejTm6w + connection.py: QmfGG7pydLZLTGWoKvRmP4ERyfjawdYgb54jV1DQdRPNNP fingerprint_ignore_patterns: [] protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 class_name: HTTPServerConnection config: api_spec_path: '' @@ -20,10 +20,10 @@ config: port: 8000 excluded_protocols: [] restricted_to_protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 dependencies: aiohttp: - version: '>=3.6.2,<3.7' + version: <3.7,>=3.6.2 openapi-core: version: ==0.13.2 openapi-spec-validator: diff --git a/packages/fetchai/connections/ledger/README.md b/packages/fetchai/connections/ledger/README.md index 0da1b56662..dfb439ecac 100644 --- a/packages/fetchai/connections/ledger/README.md +++ b/packages/fetchai/connections/ledger/README.md @@ -2,10 +2,10 @@ The ledger connection wraps the APIs needed to interact with multiple ledgers, including smart contracts deployed on those ledgers. -The AEA communicates with the ledger connection via the `fetchai/ledger_api:0.4.0` and `fetchai/contract_api:0.5.0` protocols. +The AEA communicates with the ledger connection via the `fetchai/ledger_api:0.5.0` and `fetchai/contract_api:0.6.0` protocols. The connection uses the ledger apis registered in the ledger api registry. ## Usage -First, add the connection to your AEA project (`aea add connection fetchai/ledger:0.6.0`). Optionally, update the `ledger_apis` in `config` of `connection.yaml`. +First, add the connection to your AEA project (`aea add connection fetchai/ledger:0.7.0`). Optionally, update the `ledger_apis` in `config` of `connection.yaml`. diff --git a/packages/fetchai/connections/ledger/base.py b/packages/fetchai/connections/ledger/base.py index b26b5f8bf9..ac220e62a3 100644 --- a/packages/fetchai/connections/ledger/base.py +++ b/packages/fetchai/connections/ledger/base.py @@ -33,7 +33,7 @@ from aea.protocols.dialogue.base import Dialogue, Dialogues -CONNECTION_ID = PublicId.from_str("fetchai/ledger:0.6.0") +CONNECTION_ID = PublicId.from_str("fetchai/ledger:0.7.0") class RequestDispatcher(ABC): diff --git a/packages/fetchai/connections/ledger/connection.py b/packages/fetchai/connections/ledger/connection.py index d3f02c51f4..84f6aef5b4 100644 --- a/packages/fetchai/connections/ledger/connection.py +++ b/packages/fetchai/connections/ledger/connection.py @@ -183,5 +183,6 @@ def _handle_done_task(self, task: asyncio.Future) -> Optional[Envelope]: sender=request.to, protocol_id=response_message.protocol_id, message=response_message, + context=request.context, ) return response_envelope diff --git a/packages/fetchai/connections/ledger/connection.yaml b/packages/fetchai/connections/ledger/connection.yaml index e5e6fb9093..a3e13400a0 100644 --- a/packages/fetchai/connections/ledger/connection.yaml +++ b/packages/fetchai/connections/ledger/connection.yaml @@ -1,21 +1,21 @@ name: ledger author: fetchai -version: 0.6.0 +version: 0.7.0 type: connection description: A connection to interact with any ledger API and contract API. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmY6YCoxyUXSwQzFvNPKd3nfHaoMsjCLCZAvTGSGwZnZbP + README.md: QmbS5JT9mxTx6MVUM5YVQmYyJQzEVYnJDsYC1zrW22oXSA __init__.py: QmZvYZ5ECcWwqiNGh8qNTg735wu51HqaLxTSifUxkQ4KGj - base.py: QmdWmK8S9dX1U6jiRotdFrEfAbPhLUDvPj8yPiCwsJnUDZ - connection.py: Qmdibf99GzdFjFCcoTX7AiiBVWznKvPZWvur8cngSRNdye + base.py: QmYbJveprgzvP9Qa1SCCmDGjkRoRweppbXyRvWEHK2U823 + connection.py: Qmc9XVMhHh3DDsWAWRt7TVgqRe1utQChMUWW8zZUehTKU7 contract_dispatcher.py: QmbwomSmrddSY4wREL7ywHF2p9qQ3daCiv9VoYf9cbBR61 ledger_dispatcher.py: QmfFNHQQdGiE33Cef5vRHMnXJmLVr6mQGR3iENx5ShDnQk fingerprint_ignore_patterns: [] protocols: -- fetchai/contract_api:0.5.0 -- fetchai/ledger_api:0.4.0 +- fetchai/contract_api:0.6.0 +- fetchai/ledger_api:0.5.0 class_name: LedgerConnection config: ledger_apis: @@ -26,6 +26,6 @@ config: address: https://rest-agent-land.fetch.ai:443 excluded_protocols: [] restricted_to_protocols: -- fetchai/contract_api:0.5.0 -- fetchai/ledger_api:0.4.0 +- fetchai/contract_api:0.6.0 +- fetchai/ledger_api:0.5.0 dependencies: {} diff --git a/packages/fetchai/connections/local/connection.py b/packages/fetchai/connections/local/connection.py index db08d23349..aec601bbf1 100644 --- a/packages/fetchai/connections/local/connection.py +++ b/packages/fetchai/connections/local/connection.py @@ -23,17 +23,22 @@ from asyncio import AbstractEventLoop, Queue from collections import defaultdict from threading import Thread -from typing import Dict, List, Optional, Tuple, cast +from typing import Dict, List, Optional, Tuple, Type, cast from aea.common import Address from aea.configurations.base import ProtocolId, PublicId from aea.connections.base import Connection, ConnectionStates +from aea.exceptions import enforce from aea.helpers.search.models import Description -from aea.mail.base import Envelope, Message +from aea.mail.base import Envelope, EnvelopeContext +from aea.protocols.base import Message from aea.protocols.default.message import DefaultMessage from aea.protocols.dialogue.base import Dialogue as BaseDialogue +from aea.protocols.dialogue.base import DialogueLabel as BaseDialogueLabel -from packages.fetchai.protocols.oef_search.dialogues import OefSearchDialogue +from packages.fetchai.protocols.oef_search.dialogues import ( + OefSearchDialogue as BaseOefSearchDialogue, +) from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogues as BaseOefSearchDialogues, ) @@ -47,13 +52,49 @@ RESPONSE_TARGET = MESSAGE_ID RESPONSE_MESSAGE_ID = MESSAGE_ID + 1 STUB_DIALOGUE_ID = 0 -PUBLIC_ID = PublicId.from_str("fetchai/local:0.9.0") +PUBLIC_ID = PublicId.from_str("fetchai/local:0.10.0") + + +class OefSearchDialogue(BaseOefSearchDialogue): + """The dialogue class maintains state of a dialogue and manages it.""" + + def __init__( + self, + dialogue_label: BaseDialogueLabel, + self_address: Address, + role: BaseDialogue.Role, + message_class: Type[OefSearchMessage] = OefSearchMessage, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param self_address: the address of the entity for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + + :return: None + """ + BaseOefSearchDialogue.__init__( + self, dialogue_label=dialogue_label, self_address=self_address, role=role + ) + self._envelope_context = None # type: Optional[EnvelopeContext] + + @property + def envelope_context(self) -> Optional[EnvelopeContext]: + """Get envelope_context.""" + return self._envelope_context + + @envelope_context.setter + def envelope_context(self, envelope_context: Optional[EnvelopeContext]) -> None: + """Set envelope_context.""" + enforce(self._envelope_context is None, "envelope_context already set!") + self._envelope_context = envelope_context class OefSearchDialogues(BaseOefSearchDialogues): """The dialogues class keeps track of all dialogues.""" - def __init__(self, **kwargs) -> None: + def __init__(self) -> None: """ Initialize dialogues. @@ -76,7 +117,7 @@ def role_from_first_message( # pylint: disable=unused-argument self, self_address=str(OEFLocalConnection.connection_id), role_from_first_message=role_from_first_message, - **kwargs, + dialogue_class=OefSearchDialogue, ) @@ -181,9 +222,12 @@ async def _handle_envelope(self, envelope: Envelope) -> None: :param envelope: the envelope :return: None """ - if envelope.protocol_id == ProtocolId.from_str("fetchai/oef_search:0.7.0"): + if envelope.protocol_id == ProtocolId.from_str("fetchai/oef_search:0.8.0"): await self._handle_oef_message(envelope) else: + OEFLocalConnection._ensure_valid_envelope_for_external_comms( # pylint: disable=protected-access + envelope + ) await self._handle_agent_message(envelope) async def _handle_oef_message(self, envelope: Envelope) -> None: @@ -282,6 +326,7 @@ async def _unregister_service( sender=msg.sender, protocol_id=msg.protocol_id, message=msg, + context=dialogue.envelope_context, ) await self._send(envelope) else: @@ -320,7 +365,11 @@ async def _search_services( ) envelope = Envelope( - to=msg.to, sender=msg.sender, protocol_id=msg.protocol_id, message=msg, + to=msg.to, + sender=msg.sender, + protocol_id=msg.protocol_id, + message=msg, + context=dialogue.envelope_context, ) await self._send(envelope) @@ -337,7 +386,9 @@ def _get_message_and_dialogue( if self._dialogues is None: # pragma: nocover raise ValueError("Call connect before!") message = cast(OefSearchMessage, envelope.message) - dialogue = cast(OefSearchDialogue, self._dialogues.update(message)) + dialogue = cast(Optional[OefSearchDialogue], self._dialogues.update(message)) + if dialogue is not None: + dialogue.envelope_context = envelope.context return message, dialogue async def _send(self, envelope: Envelope): diff --git a/packages/fetchai/connections/local/connection.yaml b/packages/fetchai/connections/local/connection.yaml index 4f86525679..bff14c1dbe 100644 --- a/packages/fetchai/connections/local/connection.yaml +++ b/packages/fetchai/connections/local/connection.yaml @@ -1,6 +1,6 @@ name: local author: fetchai -version: 0.9.0 +version: 0.10.0 type: connection description: The local connection provides a stub for an OEF node. license: Apache-2.0 @@ -8,10 +8,10 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: README.md: QmbK7MtyAVqh2LmSh9TY6yBZqfWaAXURP4rQGATyP2hTKC __init__.py: QmeeoX5E38Ecrb1rLdeFyyxReHLrcJoETnBcPbcNWVbiKG - connection.py: QmUVta4sBNBna7bmhpa1aV8nfHrUQ6e8GsV5758GJT5BT9 + connection.py: QmbhmhzTKpHAd6Ea5yiueuboihMwdJ2xH1b1yYUj97Cfxd fingerprint_ignore_patterns: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 class_name: OEFLocalConnection config: {} excluded_protocols: [] diff --git a/packages/fetchai/connections/oef/README.md b/packages/fetchai/connections/oef/README.md index 3f6b09fce2..590dc8bceb 100644 --- a/packages/fetchai/connections/oef/README.md +++ b/packages/fetchai/connections/oef/README.md @@ -4,4 +4,4 @@ Connection to interact with an OEF node (https://fetchai.github.io/oef-sdk-pytho ## Usage -Register/unregister services, perform searches using `fetchai/oef_search:0.7.0` protocol and send messages of any protocol to other agents connected to the same node. \ No newline at end of file +Register/unregister services, perform searches using `fetchai/oef_search:0.8.0` protocol and send messages of any protocol to other agents connected to the same node. \ No newline at end of file diff --git a/packages/fetchai/connections/oef/connection.py b/packages/fetchai/connections/oef/connection.py index b99a6ff66f..ed7f050cd4 100644 --- a/packages/fetchai/connections/oef/connection.py +++ b/packages/fetchai/connections/oef/connection.py @@ -23,6 +23,7 @@ from asyncio import AbstractEventLoop, CancelledError from concurrent.futures.thread import ThreadPoolExecutor from itertools import cycle +from logging import Logger from typing import Dict, List, Optional, Set, Type, cast import oef @@ -50,7 +51,7 @@ from packages.fetchai.protocols.oef_search.message import OefSearchMessage -logger = logging.getLogger("aea.packages.fetchai.connections.oef") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.oef") TARGET = 0 MESSAGE_ID = 1 @@ -59,7 +60,7 @@ STUB_MESSAGE_ID = 0 STUB_DIALOGUE_ID = 0 DEFAULT_OEF = "oef" -PUBLIC_ID = PublicId.from_str("fetchai/oef:0.10.0") +PUBLIC_ID = PublicId.from_str("fetchai/oef:0.11.0") class OefSearchDialogue(BaseOefSearchDialogue): @@ -142,6 +143,7 @@ def __init__( oef_addr: str, oef_port: int, excluded_protocols: Optional[Set[str]] = None, + logger: Logger = _default_logger, ): """ Initialize. @@ -411,7 +413,7 @@ def send(self, envelope: Envelope) -> None: ) raise ValueError("Cannot send message.") - if envelope.protocol_id == PublicId.from_str("fetchai/oef_search:0.7.0"): + if envelope.protocol_id == PublicId.from_str("fetchai/oef_search:0.8.0"): self.send_oef_message(envelope) else: self.send_default_message(envelope) @@ -489,7 +491,7 @@ async def connect( # pylint: disable=invalid-overridden-method,arguments-differ ) -> None: """Connect channel.""" await self._set_loop_and_queue() - self.core.__init__(loop=self._loop, logger=logger) + self.core.__init__(loop=self._loop, logger=_default_logger) if self.CONNECT_ATTEMPTS_LIMIT != 0: # pragma: nocover gen = range(self.CONNECT_ATTEMPTS_LIMIT) @@ -556,7 +558,7 @@ def __init__(self, **kwargs): raise ValueError("addr and port must be set!") # pragma: nocover self.oef_addr = addr self.oef_port = port - self.channel = OEFChannel(self.address, self.oef_addr, self.oef_port) # type: ignore + self.channel = OEFChannel(self.address, self.oef_addr, self.oef_port, logger=self.logger) # type: ignore self._connection_check_task = None # type: Optional[asyncio.Future] async def connect(self) -> None: diff --git a/packages/fetchai/connections/oef/connection.yaml b/packages/fetchai/connections/oef/connection.yaml index d788e1ffa1..022e06b0f3 100644 --- a/packages/fetchai/connections/oef/connection.yaml +++ b/packages/fetchai/connections/oef/connection.yaml @@ -1,20 +1,20 @@ name: oef author: fetchai -version: 0.10.0 +version: 0.11.0 type: connection description: The oef connection provides a wrapper around the OEF SDK for connection with the OEF search and communication node. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: Qma1rPtWWsKVgrutuRKqtP4uCzhs6ZDRbwLFaH5DF75KZs + README.md: QmW6uQWyRnVh6nzz5Sdr6gk2ySShApPDC5GY73KgGPmEJt __init__.py: QmUAen8tmoBHuCerjA3FSGKJRLG6JYyUS3chuWzPxKYzez - connection.py: QmNgAS6quG3NJtrTBQa6d4g4s1oMuFpXUiPb5FY2d85tX9 - object_translator.py: QmZbRaJgFC1RKDA8hox6xTpqmrpTbp8SyYQoKarTmS5HwG + connection.py: QmTLTfWnd4WpJprX9JUu5KWztJ4nqfA847r3AsDR7w2ZEi + object_translator.py: QmZNGxNywRZJTfcd2GYzbxkrXqUFRFEs8wnL3NeBW78poe fingerprint_ignore_patterns: [] protocols: -- fetchai/default:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/oef_search:0.8.0 class_name: OEFConnection config: addr: 127.0.0.1 diff --git a/packages/fetchai/connections/oef/object_translator.py b/packages/fetchai/connections/oef/object_translator.py index ddbf63fe26..e41eac582d 100644 --- a/packages/fetchai/connections/oef/object_translator.py +++ b/packages/fetchai/connections/oef/object_translator.py @@ -53,7 +53,7 @@ ) -logger = logging.getLogger("aea.packages.fetchai.connections.oef") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.oef") class OEFObjectTranslator: @@ -73,7 +73,9 @@ def to_oef_description(cls, desc: Description) -> OEFDescription: loggers_by_key = {} for key, value in desc.values.items(): if isinstance(value, Location): - oef_location = OEFLocation(value.latitude, value.longitude) + oef_location = OEFLocation( + latitude=value.latitude, longitude=value.longitude + ) location_keys.add(key) new_values[key] = oef_location else: @@ -127,7 +129,7 @@ def to_oef_query(cls, query: Query) -> OEFQuery: @classmethod def to_oef_location(cls, location: Location) -> OEFLocation: """From our location to OEF location.""" - return OEFLocation(location.latitude, location.longitude) # type: ignore + return OEFLocation(latitude=location.latitude, longitude=location.longitude) # type: ignore @classmethod def to_oef_constraint_expr( @@ -192,7 +194,9 @@ def from_oef_description(cls, oef_desc: OEFDescription) -> Description: new_values = {} for key, value in oef_desc.values.items(): if isinstance(value, OEFLocation): - new_values[key] = Location(value.latitude, value.longitude) + new_values[key] = Location( + latitude=value.latitude, longitude=value.longitude + ) else: new_values[key] = value @@ -234,7 +238,9 @@ def from_oef_query(cls, oef_query: OEFQuery) -> Query: @classmethod def from_oef_location(cls, oef_location: OEFLocation) -> Location: """From oef location to our location.""" - return Location(oef_location.latitude, oef_location.longitude) + return Location( + latitude=oef_location.latitude, longitude=oef_location.longitude + ) @classmethod def from_oef_constraint_expr( diff --git a/packages/fetchai/connections/p2p_libp2p/README.md b/packages/fetchai/connections/p2p_libp2p/README.md index 036c8c2d17..6ef78553e6 100644 --- a/packages/fetchai/connections/p2p_libp2p/README.md +++ b/packages/fetchai/connections/p2p_libp2p/README.md @@ -6,7 +6,7 @@ The DHT provides proper messages delivery by mapping agents addresses to their l ## Usage -First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p:0.10.0`. +First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p:0.11.0`. Next, ensure that the connection is properly configured by setting: @@ -15,4 +15,4 @@ Next, ensure that the connection is properly configured by setting: - `entry_peers` to a list of multiaddresses of already deployed nodes to join their network, should be empty for genesis node - `delegate_uri` to the ip address and port number for the delegate service, leave empty to disable the service -If the delegate service is enabled, then other AEAs can connect to the peer node using the `fetchai/p2p_libp2p_client:0.7.0` connection. +If the delegate service is enabled, then other AEAs can connect to the peer node using the `fetchai/p2p_libp2p_client:0.8.0` connection. diff --git a/packages/fetchai/connections/p2p_libp2p/connection.py b/packages/fetchai/connections/p2p_libp2p/connection.py index 67107f20ac..cb8fe015b2 100644 --- a/packages/fetchai/connections/p2p_libp2p/connection.py +++ b/packages/fetchai/connections/p2p_libp2p/connection.py @@ -26,8 +26,10 @@ import subprocess # nosec import tempfile from asyncio import AbstractEventLoop, CancelledError +from ipaddress import ip_address from pathlib import Path from random import randint +from socket import gethostbyname from typing import IO, List, Optional, Sequence, cast from aea.common import Address @@ -38,6 +40,7 @@ from aea.crypto.registries import make_crypto from aea.exceptions import AEAException from aea.helpers.async_utils import AwaitableProc +from aea.helpers.multiaddr.base import MultiAddr from aea.helpers.pipe import IPCChannel, make_ipc_channel from aea.mail.base import Envelope @@ -61,12 +64,27 @@ # TOFIX(LR) not sure is needed LIBP2P = "libp2p" -PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p:0.10.0") - -MultiAddr = str +PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p:0.11.0") SUPPORTED_LEDGER_IDS = ["fetchai", "cosmos", "ethereum"] +LIBP2P_SUCCESS_MESSAGE = "Peer running in " + + +def _ip_all_private_or_all_public(addrs: List[str]) -> bool: + if len(addrs) == 0: + return True + + is_private = ip_address(gethostbyname(addrs[0])).is_private + is_loopback = ip_address(gethostbyname(addrs[0])).is_loopback + + for addr in addrs: + if ip_address(gethostbyname(addr)).is_private != is_private: + return False # pragma: nocover + if ip_address(gethostbyname(addr)).is_loopback != is_loopback: + return False + return True + async def _golang_module_build_async( path: str, @@ -355,7 +373,7 @@ async def start(self) -> None: self.logger.info("Successfully connected to libp2p node!") self.multiaddrs = self.get_libp2p_node_multiaddrs() - self.logger.info("My libp2p addresses: {}".format(self.multiaddrs)) + self.describe_configuration() async def write(self, data: bytes) -> None: """ @@ -377,7 +395,25 @@ async def read(self) -> Optional[bytes]: raise ValueError("pipe is not set.") # pragma: nocover return await self.pipe.read() - # TOFIX(LR) hack, need to import multihash library and compute multiaddr from uri and public key + def describe_configuration(self) -> None: + """Print a message discribing the libp2p node configuration""" + msg = LIBP2P_SUCCESS_MESSAGE + + if self.public_uri is not None: + msg += "full DHT mode with " + if self.delegate_uri is not None: + msg += "delegate service reachable at '{}:{}' and relay service enabled. ".format( + self.public_uri.host, self.delegate_uri.port + ) + else: + msg += "relay service enabled. " + + msg += "To join its network use multiaddr '{}'.".format(self.multiaddrs[0]) + else: + msg += "relayed mode and cannot be used as entry peer." + + self.logger.info(msg) + def get_libp2p_node_multiaddrs(self) -> Sequence[MultiAddr]: """ Get the node's multiaddresses. @@ -401,8 +437,8 @@ def get_libp2p_node_multiaddrs(self) -> Sequence[MultiAddr]: continue if found: elem = line.strip() - if elem != LIST_END: - multiaddrs.append(MultiAddr(elem)) + if elem != LIST_END and len(elem) != 0: + multiaddrs.append(MultiAddr.from_string(elem)) else: found = False return multiaddrs @@ -512,22 +548,20 @@ def __init__(self, **kwargs): if libp2p_delegate_uri is not None: delegate_uri = Uri(libp2p_delegate_uri) - entry_peers = [MultiAddr(maddr) for maddr in libp2p_entry_peers] - # TOFIX(LR) Make sure that this node is reachable in the case where - # fetchai's public dht nodes are used as entry peer and public - # uri is provided. - # Otherwise, it may impact the proper functioning of the dht + entry_peers = [ + MultiAddr.from_string(str(maddr)) for maddr in libp2p_entry_peers + ] if public_uri is None: # node will be run as a ClientDHT # requires entry peers to use as relay if entry_peers is None or len(entry_peers) == 0: raise ValueError( - "At least one Entry Peer should be provided when node can not be publically reachable" + "At least one Entry Peer should be provided when node is run in relayed mode" ) if delegate_uri is not None: # pragma: no cover self.logger.warning( - "Ignoring Delegate Uri configuration as node can not be publically reachable" + "Ignoring Delegate Uri configuration as node is run in relayed mode" ) else: # node will be run as a full NodeDHT @@ -536,6 +570,14 @@ def __init__(self, **kwargs): "Local Uri must be set when Public Uri is provided. " "Hint: they are the same for local host/network deployment" ) + # check if node's public host and entry peers hosts are either + # both private or both public + if not _ip_all_private_or_all_public( + [public_uri.host] + [maddr.host for maddr in entry_peers] + ): + raise ValueError( # pragma: nocover + "Node's public ip and entry peers ip addresses are not in the same ip address space (private/public)" + ) # libp2p local node self.logger.debug("Public key used by libp2p node: {}".format(key.public_key)) @@ -648,6 +690,7 @@ async def send(self, envelope: Envelope): :return: None """ + self._ensure_valid_envelope_for_external_comms(envelope) await self.node.write(envelope.encode()) async def _receive_from_node(self) -> None: diff --git a/packages/fetchai/connections/p2p_libp2p/connection.yaml b/packages/fetchai/connections/p2p_libp2p/connection.yaml index 323a12b3e1..f53f6617b2 100644 --- a/packages/fetchai/connections/p2p_libp2p/connection.yaml +++ b/packages/fetchai/connections/p2p_libp2p/connection.yaml @@ -1,6 +1,6 @@ name: p2p_libp2p author: fetchai -version: 0.10.0 +version: 0.11.0 type: connection description: The p2p libp2p connection implements an interface to standalone golang go-libp2p node that can exchange aea envelopes with other agents connected to the @@ -8,14 +8,14 @@ description: The p2p libp2p connection implements an interface to standalone gol license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmWefvnDUHPCCZqe8BseAj6EuJWc4RrKKRL8Y8bT5ip3hy + README.md: QmVk1toobXuNqKTctprHnfoWMRneUtNbDp9arZpCt41zTv __init__.py: QmYQuLNyQ8WTjgRYAoKAzoJEb7ocKXvM2hTyK4hsGch5D6 aea/api.go: QmYRSRJL5jUTU8eBUFDot5inhmpZPZLykCRM32LVQHauVS aea/envelope.pb.go: QmRfUNGpCeVJfsW3H1MzCN4pwDWgumfyWufVFp6xvUjjug aea/envelope.proto: QmSC8EGCKiNFR2vf5bSWymSzYDFMipQW9aQVMwPzQoKb4n aea/pipe_unix.go: QmSzbAexrSUSA9ZE6VT6MmcWF5MhEcmceQjAbnfRoK7Ruv aea/pipe_windows.go: QmdYpfjgdgFbA6Pothg65NYM45ubfpZeKr8cVQoqbFzgUK - connection.py: QmXR4qF92gJDMvphLX14GwnPbGdYYWr3f7oM6dWjMScgzy + connection.py: QmcqQsSfKycioVUQKMT7J4Vjmi1vAtQmnXNmQ7VJ7DSYNP dht/dhtclient/dhtclient.go: QmasA3GrgswTnUJoffBzeeqxeT3GjLu6foN6PHJhWNpMMa dht/dhtclient/dhtclient_test.go: QmPfnHSHXtbaW5VYuq1QsKQWey64pUEvLEaKKkT9eAcmws dht/dhtclient/options.go: QmPorj38wNrxGrzsbFe5wwLmiHzxbTJ2VsgvSd8tLDYS8s @@ -35,6 +35,7 @@ class_name: P2PLibp2pConnection config: delegate_uri: 127.0.0.1:11000 entry_peers: [] + ledger_id: fetchai local_uri: 127.0.0.1:9000 log_file: libp2p_node.log public_uri: 127.0.0.1:9000 diff --git a/packages/fetchai/connections/p2p_libp2p_client/README.md b/packages/fetchai/connections/p2p_libp2p_client/README.md index 73dbde954c..73bea70df7 100644 --- a/packages/fetchai/connections/p2p_libp2p_client/README.md +++ b/packages/fetchai/connections/p2p_libp2p_client/README.md @@ -7,7 +7,7 @@ It allows for using the DHT without having to deploy a node by delegating its co ## Usage -First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p_client:0.7.0`. +First, add the connection to your AEA project: `aea add connection fetchai/p2p_libp2p_client:0.8.0`. Next, ensure that the connection is properly configured by setting: diff --git a/packages/fetchai/connections/p2p_libp2p_client/connection.py b/packages/fetchai/connections/p2p_libp2p_client/connection.py index 07b795f36e..26cfa0724d 100644 --- a/packages/fetchai/connections/p2p_libp2p_client/connection.py +++ b/packages/fetchai/connections/p2p_libp2p_client/connection.py @@ -35,9 +35,11 @@ from aea.mail.base import Envelope -logger = logging.getLogger("aea.packages.fetchai.connections.p2p_libp2p_client") +_default_logger = logging.getLogger( + "aea.packages.fetchai.connections.p2p_libp2p_client" +) -PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p_client:0.7.0") +PUBLIC_ID = PublicId.from_str("fetchai/p2p_libp2p_client:0.8.0") SUPPORTED_LEDGER_IDS = ["fetchai", "cosmos", "ethereum"] @@ -129,7 +131,7 @@ def __init__(self, **kwargs): # client connection id self.key = key - logger.debug("Public key used by libp2p client: {}".format(key.public_key)) + self.logger.debug("Public key used by libp2p client: {}".format(key.public_key)) # delegate uris self.delegate_uris = [Uri(node_uri) for node_uri in nodes_uris] @@ -141,7 +143,7 @@ def __init__(self, **kwargs): # select a delegate index = random.randint(0, len(self.delegate_uris) - 1) # nosec self.node_uri = self.delegate_uris[index] - logger.debug("Node to use as delegate: {}".format(self.node_uri)) + self.logger.debug("Node to use as delegate: {}".format(self.node_uri)) # tcp connection self._reader = None # type: Optional[asyncio.StreamReader] @@ -252,6 +254,7 @@ async def send(self, envelope: Envelope): :return: None """ + self._ensure_valid_envelope_for_external_comms(envelope) await self._send(envelope.encode()) async def _process_messages(self) -> None: diff --git a/packages/fetchai/connections/p2p_libp2p_client/connection.yaml b/packages/fetchai/connections/p2p_libp2p_client/connection.yaml index dbe1904eb8..b3650682ab 100644 --- a/packages/fetchai/connections/p2p_libp2p_client/connection.yaml +++ b/packages/fetchai/connections/p2p_libp2p_client/connection.yaml @@ -1,6 +1,6 @@ name: p2p_libp2p_client author: fetchai -version: 0.7.0 +version: 0.8.0 type: connection description: The libp2p client connection implements a tcp connection to a running libp2p node as a traffic delegate to send/receive envelopes to/from agents in the @@ -8,13 +8,14 @@ description: The libp2p client connection implements a tcp connection to a runni license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmUp9UrXSqEYeRxrEsGDhGPTjFrwKmuT3bjHJfGrfP8yjk + README.md: QmSeZsfj2D2eqctCAeEn1hvKewPLEk3cKtduiTozGUzAAd __init__.py: QmT1FEHkPGMHV5oiVEfQHHr25N2qdZxydSNRJabJvYiTgf - connection.py: QmSrJKGLtRogfMr6ZMGsZHeUfwkTAPDY49Hccw5QJzJ7K2 + connection.py: QmNdyKcUpZW8LnqEcYLbQepFegLQGSM5KAHwV9WyWqtXvp fingerprint_ignore_patterns: [] protocols: [] class_name: P2PLibp2pClientConnection config: + ledger_id: fetchai nodes: - uri: agents-p2p-dht.sandbox.fetch-ai.com:11000 - uri: agents-p2p-dht.sandbox.fetch-ai.com:11001 diff --git a/packages/fetchai/connections/p2p_stub/README.md b/packages/fetchai/connections/p2p_stub/README.md index 5116ec9244..eddbb17441 100644 --- a/packages/fetchai/connections/p2p_stub/README.md +++ b/packages/fetchai/connections/p2p_stub/README.md @@ -4,6 +4,6 @@ Simple file based connection to perform interaction between multiple local agent ## Usage -First, add the connection to your AEA project: `aea add connection fetchai/p2p_stub:0.7.0`. +First, add the connection to your AEA project: `aea add connection fetchai/p2p_stub:0.8.0`. Optionally, in the `connection.yaml` file under `config` set the `namespace_dir` to the desired file path. The `p2p_stub` connection reads encoded envelopes from its input file and writes encoded envelopes to its output file. Multiple agents can be pointed to the same `namespace_dir` and are then able to exchange envelopes via the file system. diff --git a/packages/fetchai/connections/p2p_stub/connection.py b/packages/fetchai/connections/p2p_stub/connection.py index b24e1ceebf..61f4087ece 100644 --- a/packages/fetchai/connections/p2p_stub/connection.py +++ b/packages/fetchai/connections/p2p_stub/connection.py @@ -29,7 +29,7 @@ from aea.mail.base import Envelope -PUBLIC_ID = PublicId.from_str("fetchai/p2p_stub:0.7.0") +PUBLIC_ID = PublicId.from_str("fetchai/p2p_stub:0.8.0") class P2PStubConnection(StubConnection): @@ -74,6 +74,7 @@ async def send(self, envelope: Envelope): """ if self.loop is None: raise ValueError("Loop not initialized.") # pragma: nocover + self._ensure_valid_envelope_for_external_comms(envelope) target_file = Path(os.path.join(self.namespace, "{}.in".format(envelope.to))) with open(target_file, "ab") as file: diff --git a/packages/fetchai/connections/p2p_stub/connection.yaml b/packages/fetchai/connections/p2p_stub/connection.yaml index 94f70002ba..7bdab3f2b8 100644 --- a/packages/fetchai/connections/p2p_stub/connection.yaml +++ b/packages/fetchai/connections/p2p_stub/connection.yaml @@ -1,15 +1,15 @@ name: p2p_stub author: fetchai -version: 0.7.0 +version: 0.8.0 type: connection description: The stub p2p connection implements a local p2p connection allowing agents to communicate with each other through files created in the namespace directory. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmW9vG1qAt8hjycMMK6LUFNaDp52KnakgjbRrTCMCRTnNW + README.md: QmRQoKLF2PJcdYK4bhu28xLbzTTW6KSmgfVc6zny97AnFH __init__.py: QmW9XFKGsea4u3fupkFMcQutgsjqusCMBMyTcTmLLmQ4tR - connection.py: QmbeuHyjBW7PjEuZXGr9BsLTmV5WEe6YE9sNYszX7iirek + connection.py: QmaGhDBVKjeYm8fmHYYgFXD8mpZNVXDv4bmPtrnPcMtLP4 fingerprint_ignore_patterns: [] protocols: [] class_name: P2PStubConnection diff --git a/packages/fetchai/connections/soef/README.md b/packages/fetchai/connections/soef/README.md index 10cdfc89b8..d0915f191d 100644 --- a/packages/fetchai/connections/soef/README.md +++ b/packages/fetchai/connections/soef/README.md @@ -4,6 +4,6 @@ The SOEF connection is used to connect to an SOEF node. The SOEF provides OEF se ## Usage -First, add the connection to your AEA project: `aea add connection fetchai/soef:0.9.0`. Then ensure the `config` in `connection.yaml` matches your need. In particular, make sure `chain_identifier` matches your `default_ledger`. +First, add the connection to your AEA project: `aea add connection fetchai/soef:0.10.0`. Then ensure the `config` in `connection.yaml` matches your need. In particular, make sure `chain_identifier` matches your `default_ledger`. -To register/unregister services and perform searches use the `fetchai/oef_search:0.7.0` protocol \ No newline at end of file +To register/unregister services and perform searches use the `fetchai/oef_search:0.8.0` protocol \ No newline at end of file diff --git a/packages/fetchai/connections/soef/connection.py b/packages/fetchai/connections/soef/connection.py index ab150d35a5..e8d0069078 100644 --- a/packages/fetchai/connections/soef/connection.py +++ b/packages/fetchai/connections/soef/connection.py @@ -21,6 +21,7 @@ import asyncio import copy import logging +import re import urllib from asyncio import CancelledError from concurrent.futures._base import CancelledError as ConcurrentCancelledError @@ -65,7 +66,7 @@ _default_logger = logging.getLogger("aea.packages.fetchai.connections.soef") -PUBLIC_ID = PublicId.from_str("fetchai/soef:0.9.0") +PUBLIC_ID = PublicId.from_str("fetchai/soef:0.10.0") NOT_SPECIFIED = object() @@ -202,12 +203,11 @@ def role_from_first_message( # pylint: disable=unused-argument class SOEFChannel: """The OEFChannel connects the OEF Agent with the connection.""" - DEFAULT_CHAIN_IDENTIFIER = "fetchai_cosmos" + DEFAULT_CHAIN_IDENTIFIER = "fetchai_v2_testnet_stable" SUPPORTED_CHAIN_IDENTIFIERS = [ - "fetchai", - "fetchai_cosmos", - "ethereum", + re.compile("ethereum"), + re.compile("^fetchai(_[a-z0-9_]*)?$"), ] DEFAULT_PERSONALITY_PIECES = ["architecture,agentframework"] @@ -237,12 +237,11 @@ def __init__( :param restricted_to_protocols: the protocol ids restricted to :param chain_identifier: supported chain id """ - if ( - chain_identifier is not None - and chain_identifier not in self.SUPPORTED_CHAIN_IDENTIFIERS + if chain_identifier is not None and not any( + regex.match(chain_identifier) for regex in self.SUPPORTED_CHAIN_IDENTIFIERS ): raise ValueError( - f"Unsupported chain_identifier. Valida are {', '.join(self.SUPPORTED_CHAIN_IDENTIFIERS)}" + f"Unsupported chain_identifier. Valid identifier regular expressions are {', '.join([reg.pattern for reg in self.SUPPORTED_CHAIN_IDENTIFIERS])}" ) self.address = address @@ -1115,7 +1114,7 @@ def __init__(self, **kwargs): if kwargs.get("configuration") is None: # pragma: nocover kwargs["excluded_protocols"] = kwargs.get("excluded_protocols") or [] kwargs["restricted_to_protocols"] = kwargs.get("excluded_protocols") or [ - PublicId.from_str("fetchai/oef_search:0.7.0") + PublicId.from_str("fetchai/oef_search:0.8.0") ] super().__init__(**kwargs) diff --git a/packages/fetchai/connections/soef/connection.yaml b/packages/fetchai/connections/soef/connection.yaml index 83c44646c6..26db2bca90 100644 --- a/packages/fetchai/connections/soef/connection.yaml +++ b/packages/fetchai/connections/soef/connection.yaml @@ -1,25 +1,25 @@ name: soef author: fetchai -version: 0.9.0 +version: 0.10.0 type: connection description: The soef connection provides a connection api to the simple OEF. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmS9zorTodmaRyaxocELEwEqHEPowKoG9wgSAak7s59rZD + README.md: QmT6qSQHjBR8vsau6STwrNe7FjfrMt8a4dY5fkEowBYa4i __init__.py: Qmd5VBGFJHXFe1H45XoUh5mMSYBwvLSViJuGFeMgbPdQts - connection.py: QmaH2heEFqpA7tECzQKrzcZKuXHpBQ3oKofKj2b2dtz5zk + connection.py: QmcMhHBfiGWRubtM68mvBMnHbnfXWRsuKSmEW4N5M6FNUa fingerprint_ignore_patterns: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 class_name: SOEFConnection config: api_key: TwiCIriSl0mLahw17pyqoA - chain_identifier: fetchai_cosmos + chain_identifier: fetchai_v2_testnet_stable soef_addr: soef.fetch.ai soef_port: 9002 excluded_protocols: [] restricted_to_protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 dependencies: defusedxml: {} diff --git a/packages/fetchai/connections/tcp/README.md b/packages/fetchai/connections/tcp/README.md index 1b933ee4af..d624a909bd 100644 --- a/packages/fetchai/connections/tcp/README.md +++ b/packages/fetchai/connections/tcp/README.md @@ -4,4 +4,4 @@ A simple TCP client/server connection to use the TCP protocol for sending and re ## Usage -Add the connection to your AEA project: `aea add connection fetchai/tcp:0.8.0`. +Add the connection to your AEA project: `aea add connection fetchai/tcp:0.9.0`. diff --git a/packages/fetchai/connections/tcp/base.py b/packages/fetchai/connections/tcp/base.py index 48be8f5012..0183f9aa3e 100644 --- a/packages/fetchai/connections/tcp/base.py +++ b/packages/fetchai/connections/tcp/base.py @@ -29,9 +29,9 @@ from aea.mail.base import Envelope -logger = logging.getLogger("aea.packages.fetchai.connections.tcp") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.tcp") -PUBLIC_ID = PublicId.from_str("fetchai/tcp:0.8.0") +PUBLIC_ID = PublicId.from_str("fetchai/tcp:0.9.0") class TCPConnection(Connection, ABC): @@ -134,6 +134,7 @@ async def send(self, envelope: Envelope) -> None: :param envelope: the envelope to send. :return: None. """ + self._ensure_valid_envelope_for_external_comms(envelope) writer = self.select_writer_from_envelope(envelope) if writer is not None: data = envelope.encode() diff --git a/packages/fetchai/connections/tcp/connection.yaml b/packages/fetchai/connections/tcp/connection.yaml index 92863a30ee..efe9411835 100644 --- a/packages/fetchai/connections/tcp/connection.yaml +++ b/packages/fetchai/connections/tcp/connection.yaml @@ -1,17 +1,17 @@ name: tcp author: fetchai -version: 0.8.0 +version: 0.9.0 type: connection description: The tcp connection implements a tcp server and client. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: Qmf1JEZWHRcP8jecLrQ4tGxCdmYLEzhnRJqpZqJmpfNDxZ + README.md: QmcNrbfgCwfDUbiRLJuQVRdRb6PKcz3Pz81LxsLkq6SWdw __init__.py: QmTxAtQ9ffraStxxLAkvmWxyGhoV3jE16Sw6SJ9xzTthLb - base.py: QmT8ZwAGWNAibUvnucaM2jiPu6ixPfaG14YKuXSaRp6b2b + base.py: Qme9w4MnmgUg8eoYtXsFCjJ9PVhTGQywyjzPDZRs8FvDjj connection.py: QmcQnyUagAhE7UsSBxiBSqsuF4mTMdU26LZLhUhdq5QygR - tcp_client.py: QmfTTuC41Wp8dRBeNJWXKa6ryu98TRT8H3oSCFmvfypCEy - tcp_server.py: QmZ8tNfGzqNepjjspMfjWdwsGX2Afj56nSEdyAJfdHcHqS + tcp_client.py: QmWGuFbVA5syCvoFpL2rpYePVDAkmEi7Lt5DT6D1pz2p1f + tcp_server.py: QmSSszevVoh4LZJoeWBpM25ebbYt4okP2uor1aXYFxcc3g fingerprint_ignore_patterns: [] protocols: [] class_name: TCPClientConnection diff --git a/packages/fetchai/connections/tcp/tcp_client.py b/packages/fetchai/connections/tcp/tcp_client.py index 4d4446d27d..d95530d1fc 100644 --- a/packages/fetchai/connections/tcp/tcp_client.py +++ b/packages/fetchai/connections/tcp/tcp_client.py @@ -35,7 +35,7 @@ from packages.fetchai.connections.tcp.base import TCPConnection -logger = logging.getLogger("aea.packages.fetchai.connections.tcp.tcp_client") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.tcp.tcp_client") STUB_DIALOGUE_ID = 0 diff --git a/packages/fetchai/connections/tcp/tcp_server.py b/packages/fetchai/connections/tcp/tcp_server.py index 515d3d3c71..962c1aa346 100644 --- a/packages/fetchai/connections/tcp/tcp_server.py +++ b/packages/fetchai/connections/tcp/tcp_server.py @@ -31,7 +31,7 @@ from packages.fetchai.connections.tcp.base import TCPConnection -logger = logging.getLogger("aea.packages.fetchai.connections.tcp.tcp_server") +_default_logger = logging.getLogger("aea.packages.fetchai.connections.tcp.tcp_server") STUB_DIALOGUE_ID = 0 diff --git a/packages/fetchai/connections/webhook/README.md b/packages/fetchai/connections/webhook/README.md index 562a56a87f..1d5808ad4f 100644 --- a/packages/fetchai/connections/webhook/README.md +++ b/packages/fetchai/connections/webhook/README.md @@ -4,4 +4,4 @@ An HTTP webhook connection which registers a webhook and waits for incoming requ ## Usage -First, add the connection to your AEA project: `aea add connection fetchai/webhook:0.7.0`. Then ensure the `config` in `connection.yaml` matches your need. In particular, set `webhook_address`, `webhook_port` and `webhook_url_path` appropriately. +First, add the connection to your AEA project: `aea add connection fetchai/webhook:0.8.0`. Then ensure the `config` in `connection.yaml` matches your need. In particular, set `webhook_address`, `webhook_port` and `webhook_url_path` appropriately. diff --git a/packages/fetchai/connections/webhook/connection.py b/packages/fetchai/connections/webhook/connection.py index a3b923a14d..525a31a369 100644 --- a/packages/fetchai/connections/webhook/connection.py +++ b/packages/fetchai/connections/webhook/connection.py @@ -29,7 +29,7 @@ from aea.common import Address from aea.configurations.base import PublicId from aea.connections.base import Connection, ConnectionStates -from aea.mail.base import Envelope, EnvelopeContext, URI +from aea.mail.base import Envelope, EnvelopeContext from aea.protocols.base import Message from aea.protocols.dialogue.base import Dialogue as BaseDialogue @@ -42,7 +42,7 @@ NOT_FOUND = 404 REQUEST_TIMEOUT = 408 SERVER_ERROR = 500 -PUBLIC_ID = PublicId.from_str("fetchai/webhook:0.7.0") +PUBLIC_ID = PublicId.from_str("fetchai/webhook:0.8.0") _default_logger = logging.getLogger("aea.packages.fetchai.connections.webhook") @@ -197,7 +197,7 @@ async def to_envelope(self, request: web.Request) -> Envelope: payload_bytes = await request.read() version = str(request.version[0]) + "." + str(request.version[1]) - context = EnvelopeContext(uri=URI("aea/mail/base.py")) + context = EnvelopeContext(connection_id=WebhookConnection.connection_id) http_message, _ = self._dialogues.create( counterparty=self.agent_address, performative=HttpMessage.Performative.REQUEST, diff --git a/packages/fetchai/connections/webhook/connection.yaml b/packages/fetchai/connections/webhook/connection.yaml index f89110fe3f..ec7cecc9bf 100644 --- a/packages/fetchai/connections/webhook/connection.yaml +++ b/packages/fetchai/connections/webhook/connection.yaml @@ -1,17 +1,17 @@ name: webhook author: fetchai -version: 0.7.0 +version: 0.8.0 type: connection description: The webhook connection that wraps a webhook functionality. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmX8P6hQej8CDXxbALfad9199k63JQAY7YQ23cDr9fQirG + README.md: QmWo3KYEYD81V9wiT13dv54aWVB9Q6bHoeNaBSgUKGLbgA __init__.py: QmWUKSmXaBgGMvKgdmzKmMjCx43BnrfW6og2n3afNoAALq - connection.py: QmRxk2Kf7vufXvQA6chasUzAQaqQKoojWd1EHNLxhb8UoJ + connection.py: QmWgBibWAQz45vzzAxCyL8g5F9WA8o19DCWcXUkcfYzdcw fingerprint_ignore_patterns: [] protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 class_name: WebhookConnection config: webhook_address: 127.0.0.1 @@ -19,7 +19,7 @@ config: webhook_url_path: /some/url/path excluded_protocols: [] restricted_to_protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 dependencies: aiohttp: version: ==3.6.2 diff --git a/packages/fetchai/contracts/erc1155/contract.py b/packages/fetchai/contracts/erc1155/contract.py index 9d8b9852c8..a963fae707 100644 --- a/packages/fetchai/contracts/erc1155/contract.py +++ b/packages/fetchai/contracts/erc1155/contract.py @@ -27,6 +27,7 @@ from vyper.utils import keccak256 from aea.common import Address +from aea.configurations.base import PublicId from aea.contracts.base import Contract from aea.crypto.base import LedgerApi from aea.crypto.cosmos import CosmosApi @@ -34,9 +35,11 @@ from aea.crypto.fetchai import FetchAIApi -logger = logging.getLogger("aea.packages.fetchai.contracts.erc1155.contract") +_default_logger = logging.getLogger("aea.packages.fetchai.contracts.erc1155.contract") MAX_UINT_256 = 2 ^ 256 - 1 +PUBLIC_ID = PublicId.from_str("fetchai/erc1155:0.11.0") + class ERC1155Contract(Contract): """The ERC1155 contract class which acts as a bridge between AEA framework and ERC1155 ABI.""" @@ -541,33 +544,37 @@ def get_hash_single( :param ledger_api: the ledger API :return: the transaction hash in a dict """ - instance = cls.get_instance(ledger_api, contract_address) - from_address_hash = instance.functions.getAddress(from_address).call() - to_address_hash = instance.functions.getAddress(to_address).call() - value_eth_wei = ledger_api.api.toWei(value, "ether") - tx_hash = cls._get_hash_single( - _from=from_address_hash, - _to=to_address_hash, - _id=token_id, - _from_value=from_supply, - _to_value=to_supply, - _value_eth_wei=value_eth_wei, - _nonce=trade_nonce, - ) - if ( - tx_hash - != instance.functions.getSingleHash( - from_address, - to_address, - token_id, - from_supply, - to_supply, - value_eth_wei, - trade_nonce, - ).call() - ): - raise ValueError("On-chain and off-chain hash computation do not agree!") - return tx_hash + if ledger_api.identifier == EthereumApi.identifier: + instance = cls.get_instance(ledger_api, contract_address) + from_address_hash = instance.functions.getAddress(from_address).call() + to_address_hash = instance.functions.getAddress(to_address).call() + value_eth_wei = ledger_api.api.toWei(value, "ether") + tx_hash = cls._get_hash_single( + _from=from_address_hash, + _to=to_address_hash, + _id=token_id, + _from_value=from_supply, + _to_value=to_supply, + _value_eth_wei=value_eth_wei, + _nonce=trade_nonce, + ) + if ( + tx_hash + != instance.functions.getSingleHash( + from_address, + to_address, + token_id, + from_supply, + to_supply, + value_eth_wei, + trade_nonce, + ).call() + ): + raise ValueError( + "On-chain and off-chain hash computation do not agree!" + ) + return tx_hash + raise NotImplementedError @staticmethod def _get_hash_single( @@ -632,33 +639,37 @@ def get_hash_batch( :param trade_nonce: the trade nonce :return: the transaction hash in a dict """ - instance = cls.get_instance(ledger_api, contract_address) - from_address_hash = instance.functions.getAddress(from_address).call() - to_address_hash = instance.functions.getAddress(to_address).call() - value_eth_wei = ledger_api.api.toWei(value, "ether") - tx_hash = cls._get_hash_batch( - _from=from_address_hash, - _to=to_address_hash, - _ids=token_ids, - _from_values=from_supplies, - _to_values=to_supplies, - _value_eth_wei=value_eth_wei, - _nonce=trade_nonce, - ) - if ( - tx_hash - != instance.functions.getHash( - from_address, - to_address, - token_ids, - from_supplies, - to_supplies, - value_eth_wei, - trade_nonce, - ).call() - ): - raise ValueError("On-chain and off-chain hash computation do not agree!") - return tx_hash + if ledger_api.identifier == EthereumApi.identifier: + instance = cls.get_instance(ledger_api, contract_address) + from_address_hash = instance.functions.getAddress(from_address).call() + to_address_hash = instance.functions.getAddress(to_address).call() + value_eth_wei = ledger_api.api.toWei(value, "ether") + tx_hash = cls._get_hash_batch( + _from=from_address_hash, + _to=to_address_hash, + _ids=token_ids, + _from_values=from_supplies, + _to_values=to_supplies, + _value_eth_wei=value_eth_wei, + _nonce=trade_nonce, + ) + if ( + tx_hash + != instance.functions.getHash( + from_address, + to_address, + token_ids, + from_supplies, + to_supplies, + value_eth_wei, + trade_nonce, + ).call() + ): + raise ValueError( + "On-chain and off-chain hash computation do not agree!" + ) + return tx_hash + raise NotImplementedError @staticmethod def _get_hash_batch( @@ -724,11 +735,13 @@ def generate_trade_nonce( :param agent_address: the address to use :return: the generated trade nonce """ - instance = cls.get_instance(ledger_api, contract_address) - trade_nonce = random.randrange(0, MAX_UINT_256) # nosec - while instance.functions.is_nonce_used(agent_address, trade_nonce).call(): + if ledger_api.identifier == EthereumApi.identifier: + instance = cls.get_instance(ledger_api, contract_address) trade_nonce = random.randrange(0, MAX_UINT_256) # nosec - return {"trade_nonce": trade_nonce} + while instance.functions.is_nonce_used(agent_address, trade_nonce).call(): + trade_nonce = random.randrange(0, MAX_UINT_256) # nosec + return {"trade_nonce": trade_nonce} + raise NotImplementedError @staticmethod def _try_estimate_gas(ledger_api: LedgerApi, tx: Dict[str, Any]) -> Dict[str, Any]: @@ -742,10 +755,12 @@ def _try_estimate_gas(ledger_api: LedgerApi, tx: Dict[str, Any]) -> Dict[str, An try: # try estimate the gas and update the transaction dict gas_estimate = ledger_api.api.eth.estimateGas(transaction=tx) - logger.debug("[ERC1155Contract]: gas estimate: {}".format(gas_estimate)) + _default_logger.debug( + "[ERC1155Contract]: gas estimate: {}".format(gas_estimate) + ) tx["gas"] = gas_estimate except Exception as e: # pylint: disable=broad-except - logger.debug( + _default_logger.debug( "[ERC1155Contract]: Error when trying to estimate gas: {}".format(e) ) return tx diff --git a/packages/fetchai/contracts/erc1155/contract.yaml b/packages/fetchai/contracts/erc1155/contract.yaml index b482f4aa6c..c9f86588a9 100644 --- a/packages/fetchai/contracts/erc1155/contract.yaml +++ b/packages/fetchai/contracts/erc1155/contract.yaml @@ -1,6 +1,6 @@ name: erc1155 author: fetchai -version: 0.10.0 +version: 0.11.0 type: contract description: The erc1155 contract implements an ERC1155 contract package. license: Apache-2.0 @@ -10,7 +10,7 @@ fingerprint: build/Migrations.json: QmfFYYWoq1L1Ni6YPBWWoRPvCZKBLZ7qzN3UDX537mCeuE build/erc1155.json: Qma5n7au2NDCg1nLwYfYnmFNwWChFuXtu65w5DV7wAZRvw build/erc1155.wasm: Qmc9gthbdwRSywinTHKjRVQdFzrKTxUuLDx2ryNfQp1xqf - contract.py: QmeSSDsCXpXiaj3ZHJGPE7QCFcfw8EgP6kCr8CNg6SnN4Q + contract.py: QmfYL2igohC264LdKDHSXwTam3nME5GWDbErVjSMN2cgif contracts/Migrations.sol: QmbW34mYrj3uLteyHf3S46pnp9bnwovtCXHbdBHfzMkSZx contracts/erc1155.vy: QmXwob8G1uX7fDvtuuKW139LALWtQmGw2vvaTRBVAWRxTx migrations/1_initial_migration.js: QmcxaWKQ2yPkQBmnpXmcuxPZQUMuUudmPmX3We8Z9vtAf7 diff --git a/packages/fetchai/protocols/contract_api/README.md b/packages/fetchai/protocols/contract_api/README.md index 532ccff9b0..5c18c01dcb 100644 --- a/packages/fetchai/protocols/contract_api/README.md +++ b/packages/fetchai/protocols/contract_api/README.md @@ -10,7 +10,7 @@ This is a protocol for contract APIs' requests and responses. --- name: contract_api author: fetchai -version: 0.5.0 +version: 0.6.0 description: A protocol for contract APIs requests and responses. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/contract_api/message.py b/packages/fetchai/protocols/contract_api/message.py index eb6202f927..8f7298b1f4 100644 --- a/packages/fetchai/protocols/contract_api/message.py +++ b/packages/fetchai/protocols/contract_api/message.py @@ -36,7 +36,9 @@ from packages.fetchai.protocols.contract_api.custom_types import State as CustomState -logger = logging.getLogger("aea.packages.fetchai.protocols.contract_api.message") +_default_logger = logging.getLogger( + "aea.packages.fetchai.protocols.contract_api.message" +) DEFAULT_BODY_SIZE = 4 @@ -44,7 +46,7 @@ class ContractApiMessage(Message): """A protocol for contract APIs requests and responses.""" - protocol_id = ProtocolId.from_str("fetchai/contract_api:0.5.0") + protocol_id = ProtocolId.from_str("fetchai/contract_api:0.6.0") Kwargs = CustomKwargs @@ -447,7 +449,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/contract_api/protocol.yaml b/packages/fetchai/protocols/contract_api/protocol.yaml index 2f91f73b4c..7d9ef6e078 100644 --- a/packages/fetchai/protocols/contract_api/protocol.yaml +++ b/packages/fetchai/protocols/contract_api/protocol.yaml @@ -1,18 +1,18 @@ name: contract_api author: fetchai -version: 0.5.0 +version: 0.6.0 type: protocol description: A protocol for contract APIs requests and responses. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmPK5NcjfCiw3wnnaM18hdgHUTyU9YfxVgs9wsBZdrfhhF + README.md: QmVeL8oxzxhNfcNT9ssWHRCRDHaxenGmkbAbbYtKDicGtw __init__.py: QmcZFuqoBkEx1fYytpLr7142vtXf9qh8cpeRYVZcaWdmrD contract_api.proto: QmNwngtcYFSuqL8yeTGVXmrHjfebCybdUa9BnTDKXn8odk contract_api_pb2.py: QmVT6Fv53KyFhshNFEo38seHypd7Y62psBaF8NszV8iRHK custom_types.py: QmcMtzozPhcL2H9hDmnUd9bHDE3ihy7HQgvGKkhqxdAXf4 dialogues.py: QmTjXH8JUtziUFDawKsSTYE5dxn1n1FmMPeWexyxiPYd6k - message.py: QmNodRNJKx6N2ToPRdVFark7e3S3j4VZU4c5rbTKa9Y5pw + message.py: QmWqJTFiycWzCtSGBMshbt8EMeEvp54PQ6vxTUFKeM1wCJ serialization.py: QmQzS931wTQNt758wvnB81aD16hUMQ19WVK6f1p1XuEwUp fingerprint_ignore_patterns: [] dependencies: diff --git a/packages/fetchai/protocols/fipa/README.md b/packages/fetchai/protocols/fipa/README.md index fd4f8577de..2da8b314c1 100644 --- a/packages/fetchai/protocols/fipa/README.md +++ b/packages/fetchai/protocols/fipa/README.md @@ -10,7 +10,7 @@ This is a protocol for two agents to negotiate over a fixed set of resources. --- name: fipa author: fetchai -version: 0.7.0 +version: 0.8.0 description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/fipa/message.py b/packages/fetchai/protocols/fipa/message.py index 84fb9195d4..6d43a2a647 100644 --- a/packages/fetchai/protocols/fipa/message.py +++ b/packages/fetchai/protocols/fipa/message.py @@ -32,7 +32,7 @@ from packages.fetchai.protocols.fipa.custom_types import Query as CustomQuery -logger = logging.getLogger("aea.packages.fetchai.protocols.fipa.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.fipa.message") DEFAULT_BODY_SIZE = 4 @@ -40,7 +40,7 @@ class FipaMessage(Message): """A protocol for FIPA ACL.""" - protocol_id = ProtocolId.from_str("fetchai/fipa:0.7.0") + protocol_id = ProtocolId.from_str("fetchai/fipa:0.8.0") Description = CustomDescription @@ -299,7 +299,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/fipa/protocol.yaml b/packages/fetchai/protocols/fipa/protocol.yaml index 20f15ddbad..ea20f3d715 100644 --- a/packages/fetchai/protocols/fipa/protocol.yaml +++ b/packages/fetchai/protocols/fipa/protocol.yaml @@ -1,18 +1,18 @@ name: fipa author: fetchai -version: 0.7.0 +version: 0.8.0 type: protocol description: A protocol for FIPA ACL. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmZAX3MEPFvQSmDqu1mLoEe3gWxfkH2VDFHN3dB99A4Z5d + README.md: QmNkfdMXEkJ8L1UXnrgGTFBxYJKYdjaf7nnsrHHW7PYw6T __init__.py: QmR6pcWX14FsQip4eYJRNeiQdrNMPj6y4m6Tsgd6hd7yU6 custom_types.py: Qmf72KRbkNsxxAHwMtkmJc5TRL23fU7AuzJAdSTftckwJQ dialogues.py: QmWaciW35ZTVeTeLWeyp3hjehKkWB5ZY7Di8N8cDH8Mjwb fipa.proto: QmP7JqnuQSQ9BDcKkscrTydKEX4wFBoyFaY1bkzGkamcit fipa_pb2.py: QmZMkefJLrb3zJKoimb6a9tdpxDBhc8rR2ghimqg7gZ471 - message.py: Qmc32ZmhrmT7FX6EVPwR899BkN8HMivtRHpnkQWkRThXYU + message.py: QmRM7yKj2bJCq3bgqFGotU1YVnX6FLP2VYj9vTLNuiTjG2 serialization.py: QmQMb8F8hJm1gkJFSPPCtDAoxSX3bFkshtzRgDWfWB8ynd fingerprint_ignore_patterns: [] dependencies: diff --git a/packages/fetchai/protocols/gym/README.md b/packages/fetchai/protocols/gym/README.md index 791f5a109b..a86005a4d3 100644 --- a/packages/fetchai/protocols/gym/README.md +++ b/packages/fetchai/protocols/gym/README.md @@ -10,7 +10,7 @@ This is a protocol for interacting with a gym connection. --- name: gym author: fetchai -version: 0.6.0 +version: 0.7.0 description: A protocol for interacting with a gym connection. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/gym/message.py b/packages/fetchai/protocols/gym/message.py index a9ef1b03fe..a597e22c5b 100644 --- a/packages/fetchai/protocols/gym/message.py +++ b/packages/fetchai/protocols/gym/message.py @@ -29,7 +29,7 @@ from packages.fetchai.protocols.gym.custom_types import AnyObject as CustomAnyObject -logger = logging.getLogger("aea.packages.fetchai.protocols.gym.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.gym.message") DEFAULT_BODY_SIZE = 4 @@ -37,7 +37,7 @@ class GymMessage(Message): """A protocol for interacting with a gym connection.""" - protocol_id = ProtocolId.from_str("fetchai/gym:0.6.0") + protocol_id = ProtocolId.from_str("fetchai/gym:0.7.0") AnyObject = CustomAnyObject @@ -292,7 +292,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/gym/protocol.yaml b/packages/fetchai/protocols/gym/protocol.yaml index 4184eea25e..3fcf977885 100644 --- a/packages/fetchai/protocols/gym/protocol.yaml +++ b/packages/fetchai/protocols/gym/protocol.yaml @@ -1,18 +1,18 @@ name: gym author: fetchai -version: 0.6.0 +version: 0.7.0 type: protocol description: A protocol for interacting with a gym connection. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmReTgjgH8mszRS1obqdxXDeA6f8sgkmfXHqZ1FSFeeZgX + README.md: QmSAvsmfUwUjfHiH9NUoK13ivQLndKUNVTcJqbRyvwWdoQ __init__.py: QmQvogZ6FVrp15UX2GZ2YKqZASS9gamA72MGt79oieE2tq custom_types.py: QmT3VKT86xZKR11RR1vQ3myRpmVZNdzY6ELd8HG9U2ngwa dialogues.py: QmdCzcFfyPF43U2SoxwshG5p4hd6dK49m6GYKduDHbnNPo gym.proto: QmQGF9Xz4Z93wmhdKoztzxjo5pS4SsAWe2TQdvZCLuzdGC gym_pb2.py: QmSTz7xrL8ryqzR1Sgu1NpR6PmW7GUhBGnN2qYc8m8NCcN - message.py: QmW1FwzkVpqcVmEWjwuX79ZYPuxwrmvfJEqtMcQjRC19AS + message.py: Qme6webfcfiVa3sCb9WpV6mGVLudS9YTx4tWmEbnwr1qLx serialization.py: QmPNsgeGkagzQuAyq97fcGXA2LoPwiuq8X1tcfVXoLwnSV fingerprint_ignore_patterns: [] dependencies: diff --git a/packages/fetchai/protocols/http/README.md b/packages/fetchai/protocols/http/README.md index a0997cc18a..225870facf 100644 --- a/packages/fetchai/protocols/http/README.md +++ b/packages/fetchai/protocols/http/README.md @@ -10,7 +10,7 @@ This is a protocol for interacting with a client/server via HTTP requests and re --- name: http author: fetchai -version: 0.6.0 +version: 0.7.0 description: A protocol for HTTP requests and responses. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/http/message.py b/packages/fetchai/protocols/http/message.py index 94ea11e63b..3dc9b5e469 100644 --- a/packages/fetchai/protocols/http/message.py +++ b/packages/fetchai/protocols/http/message.py @@ -27,7 +27,7 @@ from aea.protocols.base import Message -logger = logging.getLogger("aea.packages.fetchai.protocols.http.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.http.message") DEFAULT_BODY_SIZE = 4 @@ -35,7 +35,7 @@ class HttpMessage(Message): """A protocol for HTTP requests and responses.""" - protocol_id = ProtocolId.from_str("fetchai/http:0.6.0") + protocol_id = ProtocolId.from_str("fetchai/http:0.7.0") class Performative(Message.Performative): """Performatives for the http protocol.""" @@ -278,7 +278,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/http/protocol.yaml b/packages/fetchai/protocols/http/protocol.yaml index 87cb1647ff..b7aa67fa50 100644 --- a/packages/fetchai/protocols/http/protocol.yaml +++ b/packages/fetchai/protocols/http/protocol.yaml @@ -1,17 +1,17 @@ name: http author: fetchai -version: 0.6.0 +version: 0.7.0 type: protocol description: A protocol for HTTP requests and responses. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmYCacuyAELFigc3AvDAD5vm4iGZjan6QEk3dbksGvtFcM + README.md: QmagZXt6bzR1iYQtLfHFwHELAZaLnk4uy3w2LU94ozPqDN __init__.py: QmWzgWYrnS7PhjYrrx2mykLoaCbb7rDnVRcDqifsRukTy4 dialogues.py: QmdwTehjCppcxyDid8m6zuHY5YwprUhato88R9Zdm9aXaM http.proto: QmdTUTvvxGxMxSTB67AXjMUSDLdsxBYiSuJNVxHuLKB1jS http_pb2.py: QmYYKqdwiueq54EveL9WXn216FXLSQ6XGJJHoiJxwJjzHC - message.py: QmPB7CZ8hzvoN4s1sDLPiWdYfhKPVKQDM2jMPZtbW5TCAJ + message.py: QmQ9V7Q36bxz14y8YhUJgGSBCABrEfwzYt4YaGzK1DakTw serialization.py: QmTq34k2PD6Ybhk8x1EboY3UcNp8r3H6Tb3egZsWJN9nFv fingerprint_ignore_patterns: [] dependencies: diff --git a/packages/fetchai/protocols/ledger_api/README.md b/packages/fetchai/protocols/ledger_api/README.md index 8d45d848d3..d28f413665 100644 --- a/packages/fetchai/protocols/ledger_api/README.md +++ b/packages/fetchai/protocols/ledger_api/README.md @@ -10,7 +10,7 @@ This is a protocol for interacting with ledger APIs. --- name: ledger_api author: fetchai -version: 0.4.0 +version: 0.5.0 description: A protocol for ledger APIs requests and responses. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/ledger_api/message.py b/packages/fetchai/protocols/ledger_api/message.py index 414cde154a..787b256cb4 100644 --- a/packages/fetchai/protocols/ledger_api/message.py +++ b/packages/fetchai/protocols/ledger_api/message.py @@ -41,7 +41,7 @@ ) -logger = logging.getLogger("aea.packages.fetchai.protocols.ledger_api.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.ledger_api.message") DEFAULT_BODY_SIZE = 4 @@ -49,7 +49,7 @@ class LedgerApiMessage(Message): """A protocol for ledger APIs requests and responses.""" - protocol_id = ProtocolId.from_str("fetchai/ledger_api:0.4.0") + protocol_id = ProtocolId.from_str("fetchai/ledger_api:0.5.0") RawTransaction = CustomRawTransaction @@ -394,7 +394,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/ledger_api/protocol.yaml b/packages/fetchai/protocols/ledger_api/protocol.yaml index 30e6f0e21e..bd265aa141 100644 --- a/packages/fetchai/protocols/ledger_api/protocol.yaml +++ b/packages/fetchai/protocols/ledger_api/protocol.yaml @@ -1,18 +1,18 @@ name: ledger_api author: fetchai -version: 0.4.0 +version: 0.5.0 type: protocol description: A protocol for ledger APIs requests and responses. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmRxa7K9cDbKyBMK5yWuoAPc3dKB1jinUv78dkbPJfstjM + README.md: QmQV5DJ4kZDNbRPRvAZkGYCmKK5SWdUxUDWGuMWyUY2SYH __init__.py: QmX6ta6j6ust7qhVk1kZygzZK3gTTg7hSCBbSMKUxJgWgG custom_types.py: QmWRrvFStMhVJy8P2WD6qjDgk14ZnxErN7XymxUtof7HQo dialogues.py: QmRtWkAfR9WTvygMJ36R758RzdY2mGQs2fgtHCfjxmeaHy ledger_api.proto: QmfLcv7jJcGJ1gAdCMqsyxJcRud7RaTWteSXHL5NvGuViP ledger_api_pb2.py: QmQhM848REJTDKDoiqxkTniChW8bNNm66EtwMRkvVdbMry - message.py: QmQz3Kf3JqQ8Zv7Y2TM8HQ3vHASn6EPc2zfdF9EfXZERNi + message.py: QmTZ6meh1Rcqi7mXDSWYQs4PyuEuEZSLrgvMYq7nyBLgE4 serialization.py: QmRuTqH9t9JtsnpWX5wpC438DdRxWKiqAB2u9fvQ2oy1GE fingerprint_ignore_patterns: [] dependencies: diff --git a/packages/fetchai/protocols/ml_trade/README.md b/packages/fetchai/protocols/ml_trade/README.md index 0a2dbf8448..d0dd7c0e9f 100644 --- a/packages/fetchai/protocols/ml_trade/README.md +++ b/packages/fetchai/protocols/ml_trade/README.md @@ -10,7 +10,7 @@ This is a protocol for trading data for training and prediction purposes. --- name: ml_trade author: fetchai -version: 0.6.0 +version: 0.7.0 description: A protocol for trading data for training and prediction purposes. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/ml_trade/message.py b/packages/fetchai/protocols/ml_trade/message.py index b4cd6b46a3..b5fa56f23f 100644 --- a/packages/fetchai/protocols/ml_trade/message.py +++ b/packages/fetchai/protocols/ml_trade/message.py @@ -32,7 +32,7 @@ from packages.fetchai.protocols.ml_trade.custom_types import Query as CustomQuery -logger = logging.getLogger("aea.packages.fetchai.protocols.ml_trade.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.ml_trade.message") DEFAULT_BODY_SIZE = 4 @@ -40,7 +40,7 @@ class MlTradeMessage(Message): """A protocol for trading data for training and prediction purposes.""" - protocol_id = ProtocolId.from_str("fetchai/ml_trade:0.6.0") + protocol_id = ProtocolId.from_str("fetchai/ml_trade:0.7.0") Description = CustomDescription @@ -251,7 +251,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/ml_trade/protocol.yaml b/packages/fetchai/protocols/ml_trade/protocol.yaml index fc84c1f186..ee329cb2a4 100644 --- a/packages/fetchai/protocols/ml_trade/protocol.yaml +++ b/packages/fetchai/protocols/ml_trade/protocol.yaml @@ -1,16 +1,16 @@ name: ml_trade author: fetchai -version: 0.6.0 +version: 0.7.0 type: protocol description: A protocol for trading data for training and prediction purposes. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmdL1CDvD1fQH112oqefGxiTB4RXRxRYr9vfZMXEZDS1Dj + README.md: QmfBAHFLBtsAukMR7QuBdSB64St11r9kYPP3rP4G4jEuoH __init__.py: QmcCS9uUQTTS2w85dTNiN5rQ14wyBhmBkr7pPPPcbLphcn custom_types.py: QmPa6mxbN8WShsniQxJACfzAPRjGzYLbUFGoVU4N9DewUw dialogues.py: QmVvP34aKWEtHrKmccNMvEdDnx5B7xpE5aEGzr6GU2u8UK - message.py: Qmb1Z2wXMR1SVGFVezRaPogyhnmvrGmdY9ZEU4pr2vRKiq + message.py: QmPHXQv2ta71kW9Q4sy6zj1zVTqGSF4KjFG1VZsAxNNADo ml_trade.proto: QmeB21MQduEGQCrtiYZQzPpRqHL4CWEkvvcaKZ9GsfE8f6 ml_trade_pb2.py: QmZVvugPysR1og6kWCJkvo3af2s9pQRHfuj4BptE7gU1EU serialization.py: QmTamQzo8ZNM6T7QnsA7qNzs2uJ7CHTUczzCsHwU9Q6Z5K diff --git a/packages/fetchai/protocols/oef_search/README.md b/packages/fetchai/protocols/oef_search/README.md index 4972b642e2..947c3ea319 100644 --- a/packages/fetchai/protocols/oef_search/README.md +++ b/packages/fetchai/protocols/oef_search/README.md @@ -11,7 +11,7 @@ It allows for registering of agents and services, and searching of agents and se --- name: oef_search author: fetchai -version: 0.7.0 +version: 0.8.0 description: A protocol for interacting with an OEF search service. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' diff --git a/packages/fetchai/protocols/oef_search/message.py b/packages/fetchai/protocols/oef_search/message.py index 75bb56b7a7..0374e09dc8 100644 --- a/packages/fetchai/protocols/oef_search/message.py +++ b/packages/fetchai/protocols/oef_search/message.py @@ -38,7 +38,7 @@ from packages.fetchai.protocols.oef_search.custom_types import Query as CustomQuery -logger = logging.getLogger("aea.packages.fetchai.protocols.oef_search.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.oef_search.message") DEFAULT_BODY_SIZE = 4 @@ -46,7 +46,7 @@ class OefSearchMessage(Message): """A protocol for interacting with an OEF search service.""" - protocol_id = ProtocolId.from_str("fetchai/oef_search:0.7.0") + protocol_id = ProtocolId.from_str("fetchai/oef_search:0.8.0") AgentsInfo = CustomAgentsInfo @@ -296,7 +296,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/oef_search/protocol.yaml b/packages/fetchai/protocols/oef_search/protocol.yaml index ebf61fc6c5..5a7aa6aab6 100644 --- a/packages/fetchai/protocols/oef_search/protocol.yaml +++ b/packages/fetchai/protocols/oef_search/protocol.yaml @@ -1,16 +1,16 @@ name: oef_search author: fetchai -version: 0.7.0 +version: 0.8.0 type: protocol description: A protocol for interacting with an OEF search service. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmUYKYTTvPQWDHgB2N8LLKquwgGEA8vEoDYroCEEcGSugy + README.md: QmcBVa7T4QJoUoYtr5LP1oz25xQwaX7HJQTLgDnJR1VicW __init__.py: Qmdr5ks5X4YtnpH6yKUcNu9uouyv3EGmrKFhyvNH7ZBjvT custom_types.py: QmYAkKYj9gGHaij7uTejoJe9KRhNcsU4sJC1utMfhUYhg3 dialogues.py: QmQPLnW3jAs6tLLmhkX4C7texGRHM9bfdjs83dUH5TkJ4v - message.py: QmRTyiB6yaN1rwvZ412dMNdTwRGy3U3HSC4xN8k5La3Nr1 + message.py: QmZfLEzhveXqvWLuwQUoAxCarRgahsxkkHVtYEhv3LLKLw oef_search.proto: QmNU8WsxT6XNFFneKKeDaZkNn3CEFDfZQkmKv9TyhyxzDB oef_search_pb2.py: QmSAFT1xxYRzJU6h1aFVDuYcx672sZ2vzV6c2ico7f4BLK serialization.py: QmU3ipyvogbpkFuQki6xqscdiPahDVYw4sBaPHaH3LVvwJ diff --git a/packages/fetchai/protocols/tac/README.md b/packages/fetchai/protocols/tac/README.md index 4a7397b04e..f2f238f461 100644 --- a/packages/fetchai/protocols/tac/README.md +++ b/packages/fetchai/protocols/tac/README.md @@ -10,7 +10,7 @@ This is a protocol for participating in a Trading Agent Competition (TAC). --- name: tac author: fetchai -version: 0.7.0 +version: 0.8.0 description: The tac protocol implements the messages an AEA needs to participate in the TAC. license: Apache-2.0 diff --git a/packages/fetchai/protocols/tac/message.py b/packages/fetchai/protocols/tac/message.py index c06c9e6a50..cf6d759a69 100644 --- a/packages/fetchai/protocols/tac/message.py +++ b/packages/fetchai/protocols/tac/message.py @@ -29,7 +29,7 @@ from packages.fetchai.protocols.tac.custom_types import ErrorCode as CustomErrorCode -logger = logging.getLogger("aea.packages.fetchai.protocols.tac.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.tac.message") DEFAULT_BODY_SIZE = 4 @@ -37,7 +37,7 @@ class TacMessage(Message): """The tac protocol implements the messages an AEA needs to participate in the TAC.""" - protocol_id = ProtocolId.from_str("fetchai/tac:0.7.0") + protocol_id = ProtocolId.from_str("fetchai/tac:0.8.0") ErrorCode = CustomErrorCode @@ -741,7 +741,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/packages/fetchai/protocols/tac/protocol.yaml b/packages/fetchai/protocols/tac/protocol.yaml index 7b320ff557..9ee1d4ab62 100644 --- a/packages/fetchai/protocols/tac/protocol.yaml +++ b/packages/fetchai/protocols/tac/protocol.yaml @@ -1,17 +1,17 @@ name: tac author: fetchai -version: 0.7.0 +version: 0.8.0 type: protocol description: The tac protocol implements the messages an AEA needs to participate in the TAC. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmSZJ6wsgqjYN6cqWQKShj2xpruYMCxbK1m6TKk4V4Aopn + README.md: QmY5eUTJnZewbXAKa6TyvXFw1KHTtuVtFK3CsEHwmtktx7 __init__.py: QmSAC7PGra9fig8RhhF1j3XEVpgie9UZNNYPc2AB9Kx9xJ custom_types.py: QmXQATfnvuCpt4FicF4QcqCcLj9PQNsSHjCBvVQknWpyaN dialogues.py: QmQvuivrjhVFu7pjSFfv6FrWwVeBC7p8Hm3P4gjCnVx5Ym - message.py: QmWD79nj4kcMPN9xuDkgSDXnwgDnT6XE1WcdfBKsKKPqTP + message.py: QmZyNfMH3tfhfCCsAMRpxQkkGcqjezzdSqTSkvsbVA6fWy serialization.py: QmTk2Jp19dQ4SJdjSHAr8wbxw4rQSMheSuf1XzXG8CaoB4 tac.proto: QmdpPZNhUW593qVNVoSTWZgd9R69bmBbw6Y9xjzYpvuDvV tac_pb2.py: QmUwW3kixKwD2o1RRdq4NoNoihPb5BXKKRngWXztq32fea diff --git a/packages/fetchai/skills/aries_alice/skill.yaml b/packages/fetchai/skills/aries_alice/skill.yaml index ac08ddc5a1..90afb606d5 100644 --- a/packages/fetchai/skills/aries_alice/skill.yaml +++ b/packages/fetchai/skills/aries_alice/skill.yaml @@ -1,6 +1,6 @@ name: aries_alice author: fetchai -version: 0.8.0 +version: 0.9.0 type: skill description: The aries_alice skill implements the alice player in the aries cloud agent demo @@ -12,13 +12,13 @@ fingerprint: behaviours.py: QmTdJbrP1N3W1M6CLDyPFc6erfcrhnbRJtDNN6ZuY9eRUt dialogues.py: QmbCkYZ9mQK6az3qWVMuYmpy5bapq2szYigUbVzE2GgiJi handlers.py: QmTtqM5oWBEAZcHdTuwuPtbiZZGWontMNEtmDxcccS2u2Y - strategy.py: QmS6MYMF3TdZSqRNugUAoHcWcirsQ9kK2do28i5abKz4oM + strategy.py: QmPUPJvc9idv1KeJE67PKze4E6TpSzjkvFjPG1FRMPAhaL fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/http:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/http:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: alice: @@ -57,3 +57,4 @@ models: value: intro_alice class_name: AliceStrategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/aries_alice/strategy.py b/packages/fetchai/skills/aries_alice/strategy.py index af3158d7a6..672ca13bfe 100644 --- a/packages/fetchai/skills/aries_alice/strategy.py +++ b/packages/fetchai/skills/aries_alice/strategy.py @@ -66,7 +66,9 @@ def __init__(self, **kwargs) -> None: # search location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { - "location": Location(location["longitude"], location["latitude"]) + "location": Location( + latitude=location["latitude"], longitude=location["longitude"] + ) } self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( diff --git a/packages/fetchai/skills/aries_faber/skill.yaml b/packages/fetchai/skills/aries_faber/skill.yaml index 97410686ed..a0ac4d52ed 100644 --- a/packages/fetchai/skills/aries_faber/skill.yaml +++ b/packages/fetchai/skills/aries_faber/skill.yaml @@ -1,6 +1,6 @@ name: aries_faber author: fetchai -version: 0.7.0 +version: 0.8.0 type: skill description: The aries_faber skill implements the faber player in the aries cloud agent demo @@ -12,13 +12,13 @@ fingerprint: behaviours.py: QmQUmnhHmEFjGp9SvkLqcBrmCe2CSFngzqTHNadqJXQMgw dialogues.py: Qmeynv4h5ArYBJ2wkQurW7VXdDP1VXNbg5GiADkgMPFqj3 handlers.py: QmXpCVgMTLyewXPmXMmPHUpnZhQZ35GFAhZtcpujAd3WZ8 - strategy.py: QmPPL4aZhf2jtCHjQyuzh9gyWiNYRt9V29UYvPvp9Lbqhw + strategy.py: QmdcUrzKHX2CAe1zXRpoFBzj4vSmqGjuhh8MaRmBSpx8gi fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/http:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/http:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: faber: @@ -57,3 +57,4 @@ models: search_radius: 5.0 class_name: FaberStrategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/aries_faber/strategy.py b/packages/fetchai/skills/aries_faber/strategy.py index c7fd743f3c..06c2309c4b 100644 --- a/packages/fetchai/skills/aries_faber/strategy.py +++ b/packages/fetchai/skills/aries_faber/strategy.py @@ -72,7 +72,9 @@ def __init__(self, **kwargs) -> None: # Search self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) - self._agent_location = Location(location["longitude"], location["latitude"]) + self._agent_location = Location( + latitude=location["latitude"], longitude=location["longitude"] + ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) super().__init__(**kwargs) diff --git a/packages/fetchai/skills/carpark_client/skill.yaml b/packages/fetchai/skills/carpark_client/skill.yaml index 97a1234d50..fbd6b83a4e 100644 --- a/packages/fetchai/skills/carpark_client/skill.yaml +++ b/packages/fetchai/skills/carpark_client/skill.yaml @@ -1,6 +1,6 @@ name: carpark_client author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The carpark client skill implements the functionality to run a client for carpark data. @@ -16,12 +16,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_buyer:0.12.0 +- fetchai/generic_buyer:0.13.0 behaviours: search: args: @@ -75,3 +75,4 @@ models: service_id: car_park_service class_name: Strategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/carpark_detection/database.py b/packages/fetchai/skills/carpark_detection/database.py index 245d4c0155..752ea779e5 100644 --- a/packages/fetchai/skills/carpark_detection/database.py +++ b/packages/fetchai/skills/carpark_detection/database.py @@ -28,7 +28,7 @@ import skimage # type: ignore -_logger = logging.getLogger( +_default_logger = logging.getLogger( "aea.packages.fetchai.skills.carpark_detection.detection_database" ) @@ -61,7 +61,7 @@ def __init__( if create_if_not_present: self.initialise_backend() - self.logger = logger if logger is not None else _logger + self.logger = logger if logger is not None else _default_logger def is_db_exits(self): """Return true if database exixts and is set up.""" diff --git a/packages/fetchai/skills/carpark_detection/skill.yaml b/packages/fetchai/skills/carpark_detection/skill.yaml index 4d5e40c87a..0080c53ecb 100644 --- a/packages/fetchai/skills/carpark_detection/skill.yaml +++ b/packages/fetchai/skills/carpark_detection/skill.yaml @@ -1,6 +1,6 @@ name: carpark_detection author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The carpark detection skill implements the detection and trading functionality for a carpark agent. @@ -10,7 +10,7 @@ fingerprint: README.md: QmcwNhv5N8m4ZtWvXY5eMDeL5ciivryDZPtGWXMFfTbYR7 __init__.py: QmQoECB7dpCDCG3xCnBsoMy6oqgSdu69CzRcAcuZuyapnQ behaviours.py: QmTNboU3YH8DehWnpZmoiDUCncpNmqoSVt1Yp4j7NsgY2S - database.py: QmPQqzgsmFKBSMoYbuEpEMnikmkEadaT9VrD8ics5Het2b + database.py: Qma4Ydj8EQtWr4W9EgUvZs5AkZ5tKHjSgHaxQDfzrPm27N dialogues.py: QmPXfUWDxnHDaHQqsgtVhJ2v9dEgGWLtvEHKFvvFcDXGms handlers.py: QmbkmEP9K4Qu2MsRtnkdx3PGNbSW46qi48bCHVCUJHpcQF strategy.py: QmUCmsvvKqRsM4nFuhsUUTfCnZ6zPffXytD3PjMjFdqHdU @@ -18,12 +18,12 @@ fingerprint_ignore_patterns: - temp_files_placeholder/* contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_seller:0.13.0 +- fetchai/generic_seller:0.14.0 behaviours: service_registration: args: @@ -71,3 +71,4 @@ models: class_name: Strategy dependencies: scikit-image: {} +is_abstract: false diff --git a/packages/fetchai/skills/echo/skill.yaml b/packages/fetchai/skills/echo/skill.yaml index 977f02e359..a2e60c681c 100644 --- a/packages/fetchai/skills/echo/skill.yaml +++ b/packages/fetchai/skills/echo/skill.yaml @@ -1,6 +1,6 @@ name: echo author: fetchai -version: 0.8.0 +version: 0.9.0 type: skill description: The echo skill implements simple echo functionality. license: Apache-2.0 @@ -14,7 +14,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: [] behaviours: echo: @@ -30,3 +30,4 @@ models: args: {} class_name: DefaultDialogues dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/erc1155_client/behaviours.py b/packages/fetchai/skills/erc1155_client/behaviours.py index fd336e7fad..5033ca8889 100644 --- a/packages/fetchai/skills/erc1155_client/behaviours.py +++ b/packages/fetchai/skills/erc1155_client/behaviours.py @@ -33,7 +33,7 @@ DEFAULT_SEARCH_INTERVAL = 5.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class SearchBehaviour(TickerBehaviour): diff --git a/packages/fetchai/skills/erc1155_client/handlers.py b/packages/fetchai/skills/erc1155_client/handlers.py index b0466bf9c5..aca31aa5d5 100644 --- a/packages/fetchai/skills/erc1155_client/handlers.py +++ b/packages/fetchai/skills/erc1155_client/handlers.py @@ -48,7 +48,7 @@ from packages.fetchai.skills.erc1155_client.strategy import Strategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class FipaHandler(Handler): @@ -154,7 +154,7 @@ def _handle_propose( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=fipa_msg.proposal.values["contract_address"], callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( diff --git a/packages/fetchai/skills/erc1155_client/skill.yaml b/packages/fetchai/skills/erc1155_client/skill.yaml index 8825d73db8..590403c248 100644 --- a/packages/fetchai/skills/erc1155_client/skill.yaml +++ b/packages/fetchai/skills/erc1155_client/skill.yaml @@ -1,6 +1,6 @@ name: erc1155_client author: fetchai -version: 0.13.0 +version: 0.14.0 type: skill description: The erc1155 client interacts with the erc1155 deployer to conduct an atomic swap. @@ -9,20 +9,20 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: README.md: QmZLsx2wJE762rqDqfeqLMg9RBczvALxy3H2U9kfRvgK3s __init__.py: QmRXXJsv5bfvb7qsyxQtVzXwn6PMLJKkbm6kg4DNkT1NtW - behaviours.py: QmZwfSsHuCquNXfWaj2y4x5iTS7hhborRkkcaSCf83ELNo + behaviours.py: QmTWQLqGHrZ8zG9cPnpXRjG6xnLUno1oGGw6nR297Rbync dialogues.py: QmPb2odXbXxuY5Ygm9mfCufM2mtMZ23oapsAzsWHC2x2k4 - handlers.py: QmNPXo4j4tZDSGBfufz6xocH6yJiCAfqw2bo7L2nhBSTiL - strategy.py: QmNroEjE9CZ5VKdCK3ojeEzknPdum2A2XjoYAGQRuFSjZS + handlers.py: QmPxNz2wvcYi4wrBawdDMniFtbPqWpbt99mBSJjijuRaTT + strategy.py: QmWm4KWhcqt2i4C8SnyHfDyjVK1HrbyiHTWN4thkfGzcrB fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/contract_api:0.5.0 -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 +- fetchai/contract_api:0.6.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 skills: [] behaviours: search: @@ -77,3 +77,4 @@ models: search_radius: 5.0 class_name: Strategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/erc1155_client/strategy.py b/packages/fetchai/skills/erc1155_client/strategy.py index c4a9e5ddd9..5904d79c32 100644 --- a/packages/fetchai/skills/erc1155_client/strategy.py +++ b/packages/fetchai/skills/erc1155_client/strategy.py @@ -47,7 +47,9 @@ def __init__(self, **kwargs) -> None: """ self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) - self._agent_location = Location(location["longitude"], location["latitude"]) + self._agent_location = Location( + latitude=location["latitude"], longitude=location["longitude"] + ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._ledger_id = kwargs.pop("ledger_id", DEFAULT_LEDGER_ID) diff --git a/packages/fetchai/skills/erc1155_deploy/README.md b/packages/fetchai/skills/erc1155_deploy/README.md index 5548e37d69..73f66238a1 100644 --- a/packages/fetchai/skills/erc1155_deploy/README.md +++ b/packages/fetchai/skills/erc1155_deploy/README.md @@ -1,4 +1,4 @@ -# ERC1155 Client +# ERC1155 Deploy ## Description diff --git a/packages/fetchai/skills/erc1155_deploy/behaviours.py b/packages/fetchai/skills/erc1155_deploy/behaviours.py index 397b93405c..8e95711f01 100644 --- a/packages/fetchai/skills/erc1155_deploy/behaviours.py +++ b/packages/fetchai/skills/erc1155_deploy/behaviours.py @@ -36,7 +36,7 @@ DEFAULT_SERVICES_INTERVAL = 30.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class ServiceRegistrationBehaviour(TickerBehaviour): @@ -131,7 +131,7 @@ def _request_contract_deploy_transaction(self) -> None: counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs( {"deployer_address": self.context.agent_address} @@ -157,7 +157,7 @@ def _request_token_create_transaction(self) -> None: counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=strategy.contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -187,7 +187,7 @@ def _request_token_mint_transaction(self) -> None: counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=strategy.contract_address, callable="get_mint_batch_transaction", kwargs=ContractApiMessage.Kwargs( diff --git a/packages/fetchai/skills/erc1155_deploy/handlers.py b/packages/fetchai/skills/erc1155_deploy/handlers.py index 2caf6908fc..8066c226d5 100644 --- a/packages/fetchai/skills/erc1155_deploy/handlers.py +++ b/packages/fetchai/skills/erc1155_deploy/handlers.py @@ -48,7 +48,7 @@ from packages.fetchai.skills.erc1155_deploy.strategy import Strategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class FipaHandler(Handler): @@ -172,7 +172,7 @@ def _handle_accept_w_inform( counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=strategy.contract_address, callable="get_atomic_swap_single_transaction", kwargs=ContractApiMessage.Kwargs( diff --git a/packages/fetchai/skills/erc1155_deploy/skill.yaml b/packages/fetchai/skills/erc1155_deploy/skill.yaml index 4b1a68f86f..dc799d7de1 100644 --- a/packages/fetchai/skills/erc1155_deploy/skill.yaml +++ b/packages/fetchai/skills/erc1155_deploy/skill.yaml @@ -1,28 +1,28 @@ name: erc1155_deploy author: fetchai -version: 0.14.0 +version: 0.15.0 type: skill description: The ERC1155 deploy skill has the ability to deploy and interact with the smart contract. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - README.md: QmZ7swVkRpmJbJZ2NoLb9CWwH8p3QjnjRJg71UbAXdrKyx + README.md: QmZYqboojbibXnPAXqDifnHkVrmGjLJV3HBCJWeRzRpKMh __init__.py: Qmbm3ZtGpfdvvzqykfRqbaReAK9a16mcyK7qweSfeN5pq1 - behaviours.py: QmTxNwRPPSavQQFSnQLkEX6c49qJVg9cFfJuFv8HBpgvKo + behaviours.py: Qmf2wxPHGU7PHwSnf3B9v7nK49u1at3JMkeGQiKkhP2fsD dialogues.py: QmcCbdxFM4SX3MgXDxxbsC66gp8QK3C4W2czniQ5oDNc7G - handlers.py: QmUP1hkd8pGwFu9xK2GgempWwtQgh1BAfDvQPkJGCcKsoD - strategy.py: QmaHiqKdShWi1ZAUeLpmKAKYN7Np9wZ3SnKYadMStqWdZn + handlers.py: Qmc3aHkjwoyveijSqFSjuQFoMYvGDNh5pQkRTGyPJbfmWL + strategy.py: QmP3tbQasCnfJ2sYE3NqhfWrid4b9RiYNUzFYcntEVcFWd fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/contract_api:0.5.0 -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 +- fetchai/contract_api:0.6.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 skills: [] behaviours: service_registration: @@ -93,3 +93,4 @@ models: dependencies: vyper: version: ==0.1.0b12 +is_abstract: false diff --git a/packages/fetchai/skills/erc1155_deploy/strategy.py b/packages/fetchai/skills/erc1155_deploy/strategy.py index c47f57ac4d..cf1daff573 100644 --- a/packages/fetchai/skills/erc1155_deploy/strategy.py +++ b/packages/fetchai/skills/erc1155_deploy/strategy.py @@ -84,7 +84,9 @@ def __init__(self, **kwargs) -> None: location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { - "location": Location(location["longitude"], location["latitude"]) + "location": Location( + latitude=location["latitude"], longitude=location["longitude"] + ) } self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( diff --git a/packages/fetchai/skills/generic_buyer/behaviours.py b/packages/fetchai/skills/generic_buyer/behaviours.py index 5a8a0c2c8f..ba7dc11e0a 100644 --- a/packages/fetchai/skills/generic_buyer/behaviours.py +++ b/packages/fetchai/skills/generic_buyer/behaviours.py @@ -33,7 +33,7 @@ DEFAULT_SEARCH_INTERVAL = 5.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class GenericSearchBehaviour(TickerBehaviour): diff --git a/packages/fetchai/skills/generic_buyer/handlers.py b/packages/fetchai/skills/generic_buyer/handlers.py index dc2318533f..8648a163cf 100644 --- a/packages/fetchai/skills/generic_buyer/handlers.py +++ b/packages/fetchai/skills/generic_buyer/handlers.py @@ -45,7 +45,7 @@ from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class GenericFipaHandler(Handler): @@ -198,7 +198,9 @@ def _handle_match_accept( if strategy.is_ledger_tx: transfer_address = fipa_msg.info.get("address", None) if transfer_address is not None and isinstance(transfer_address, str): - fipa_dialogue.terms.counterparty_address = transfer_address + fipa_dialogue.terms.counterparty_address = ( # pragma: nocover + transfer_address + ) ledger_api_dialogues = cast( LedgerApiDialogues, self.context.ledger_api_dialogues ) diff --git a/packages/fetchai/skills/generic_buyer/skill.yaml b/packages/fetchai/skills/generic_buyer/skill.yaml index 786d8ef236..ab5afeff4c 100644 --- a/packages/fetchai/skills/generic_buyer/skill.yaml +++ b/packages/fetchai/skills/generic_buyer/skill.yaml @@ -1,6 +1,6 @@ name: generic_buyer author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 @@ -8,17 +8,17 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: README.md: QmTR91jm7WfJpmabisy74NR5mc35YXjDU1zQAUKZeHRw8L __init__.py: QmaEDrNJBeHCJpbdFckRUhLSBqCXQ6umdipTMpYhqSKxSG - behaviours.py: QmX7Km1haWpc8bXm24kD7d5F14me9sSfAo5X3JZtBhGUph + behaviours.py: QmejRk9gBDRqrbnTirmqmsR8mH6KXxPMk6Ex6KsjYHB2aL dialogues.py: QmY3uJpB2UpWsa9SipZQzMjYr7WtSycuF5YS3SLyCuh78N - handlers.py: QmTHw6Eg4zga1yzeskxu3qUKYcj56kLeogpJSYUwsvs29o - strategy.py: QmchU21Gb1pmNoGmFTDass7zhNE8rCvWGNh1UYFK5DzhoK + handlers.py: QmZaDk7TrKXUeUvT7zEgYiNn4fiUZxGuFYw6TVfReCSDDG + strategy.py: Qmd8ojtvdQ1W62PpY7KM3epFzhYVzQBEhc3kPLuXjps5eN fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: search: diff --git a/packages/fetchai/skills/generic_buyer/strategy.py b/packages/fetchai/skills/generic_buyer/strategy.py index d0966aa86e..960ee8fd1f 100644 --- a/packages/fetchai/skills/generic_buyer/strategy.py +++ b/packages/fetchai/skills/generic_buyer/strategy.py @@ -73,7 +73,7 @@ def __init__(self, **kwargs) -> None: self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = Location( - longitude=location["longitude"], latitude=location["latitude"] + latitude=location["latitude"], longitude=location["longitude"] ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) diff --git a/packages/fetchai/skills/generic_seller/behaviours.py b/packages/fetchai/skills/generic_seller/behaviours.py index c60d770500..e8bbd11557 100644 --- a/packages/fetchai/skills/generic_seller/behaviours.py +++ b/packages/fetchai/skills/generic_seller/behaviours.py @@ -33,7 +33,7 @@ DEFAULT_SERVICES_INTERVAL = 60.0 -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class GenericServiceRegistrationBehaviour(TickerBehaviour): diff --git a/packages/fetchai/skills/generic_seller/handlers.py b/packages/fetchai/skills/generic_seller/handlers.py index db4547380b..50ee01ad76 100644 --- a/packages/fetchai/skills/generic_seller/handlers.py +++ b/packages/fetchai/skills/generic_seller/handlers.py @@ -43,7 +43,7 @@ from packages.fetchai.skills.generic_seller.strategy import GenericStrategy -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class GenericFipaHandler(Handler): diff --git a/packages/fetchai/skills/generic_seller/skill.yaml b/packages/fetchai/skills/generic_seller/skill.yaml index 2a435a6efc..e2168157d1 100644 --- a/packages/fetchai/skills/generic_seller/skill.yaml +++ b/packages/fetchai/skills/generic_seller/skill.yaml @@ -1,6 +1,6 @@ name: generic_seller author: fetchai -version: 0.13.0 +version: 0.14.0 type: skill description: The weather station skill implements the functionality to sell weather data. @@ -9,17 +9,17 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: README.md: QmPb5kHYZyhUN87EKmuahyGqDGgqVdGPyfC1KpGC3xfmcP __init__.py: QmbfkeFnZVKppLEHpBrTXUXBwg2dpPABJWSLND8Lf1cmpG - behaviours.py: QmdHumzBTxWg2PgWzwNVF4a5eG1dhGXzDwrY9eq4uevHqs + behaviours.py: QmbZuzaKRw6rx5jthJV5cd4uwapNDEH6544TeLphZxPoqY dialogues.py: QmfBWHVuKTzXu8vV5AkgWF6ZqMTZ3bs7SHEykoKXdmqvnH - handlers.py: QmdCtELzeNVBtVozwMLqMeVDuk2JdS8CG5D3xssBWEjTrk - strategy.py: QmUrUJzDoKFk6mhoz5ui58pXxVjTkPfEMnSdM8UezTjy3S + handlers.py: QmQNoMFPtkqN4hzsxGXSifkK41BqrM6QRpHcQhmyduXbsM + strategy.py: QmYvr6ph4ZVSXAv837kPF4Pi3u9ExXtHnaUaiZFMtji2cr fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: service_registration: diff --git a/packages/fetchai/skills/generic_seller/strategy.py b/packages/fetchai/skills/generic_seller/strategy.py index a22aa9e183..c1eaeeaa73 100644 --- a/packages/fetchai/skills/generic_seller/strategy.py +++ b/packages/fetchai/skills/generic_seller/strategy.py @@ -75,7 +75,7 @@ def __init__(self, **kwargs) -> None: location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( - longitude=location["longitude"], latitude=location["latitude"] + latitude=location["latitude"], longitude=location["longitude"] ) } self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) @@ -103,7 +103,7 @@ def __init__(self, **kwargs) -> None: ) if self._has_data_source: - self._data_for_sale = self.collect_from_data_source() + self._data_for_sale = self.collect_from_data_source() # pragma: nocover else: self._data_for_sale = data_for_sale self._sale_quantity = len(data_for_sale) @@ -190,7 +190,7 @@ def generate_proposal_terms_and_data( # pylint: disable=unused-argument client=counterparty_address, ) else: - tx_nonce = uuid.uuid4().hex + tx_nonce = uuid.uuid4().hex # pragma: nocover proposal = Description( { "ledger_id": self.ledger_id, diff --git a/packages/fetchai/skills/gym/helpers.py b/packages/fetchai/skills/gym/helpers.py index 6347f05b1d..6166a0bebe 100644 --- a/packages/fetchai/skills/gym/helpers.py +++ b/packages/fetchai/skills/gym/helpers.py @@ -60,7 +60,7 @@ def __init__(self, skill_context: SkillContext) -> None: self._is_rl_agent_trained = False self._step_count = 0 self._active_dialogue = None # type: Optional[GymDialogue] - self.gym_address = "fetchai/gym:0.8.0" + self.gym_address = "fetchai/gym:0.9.0" @property def gym_dialogues(self) -> GymDialogues: diff --git a/packages/fetchai/skills/gym/skill.yaml b/packages/fetchai/skills/gym/skill.yaml index a045eb7c63..c0b075fa9d 100644 --- a/packages/fetchai/skills/gym/skill.yaml +++ b/packages/fetchai/skills/gym/skill.yaml @@ -1,6 +1,6 @@ name: gym author: fetchai -version: 0.8.0 +version: 0.9.0 type: skill description: The gym skill wraps an RL agent. license: Apache-2.0 @@ -10,13 +10,13 @@ fingerprint: __init__.py: QmTf1GCgHxu7qq4HvUNYiBwuGEL1DcsHQuWH7N7TB5TtoC dialogues.py: QmTETCiWFK7vu8PMM9AaY64xHZhHivafmX33n4FXVKSub6 handlers.py: QmagqBdQaGuGbVS5dFqxFtEww2e1YF2NprBNPtt5pVA2hZ - helpers.py: QmeYUyhPUeR6umfUGwMuE9nH6QCPhwkBpGxRG6P3s1avHu + helpers.py: QmQTSG8MeSPBWbBR5wuLR2L4jvAX1xEJZLJfv2LYGvsMj5 rl_agent.py: QmQMrewAKFZn7EmmHajm1wn1NJHvVUgxosaGR5zapWt13s tasks.py: QmVf9K7zCkKYduTnS7nS3d8FvUbEVy7s7a26yaCC8Q3rgd fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/gym:0.6.0 +- fetchai/gym:0.7.0 skills: [] behaviours: {} handlers: @@ -33,3 +33,4 @@ models: class_name: GymDialogues dependencies: gym: {} +is_abstract: false diff --git a/packages/fetchai/skills/http_echo/skill.yaml b/packages/fetchai/skills/http_echo/skill.yaml index d9ee0961c5..92a9f725ec 100644 --- a/packages/fetchai/skills/http_echo/skill.yaml +++ b/packages/fetchai/skills/http_echo/skill.yaml @@ -1,6 +1,6 @@ name: http_echo author: fetchai -version: 0.7.0 +version: 0.8.0 type: skill description: The http echo skill prints out the content of received http messages and responds with success. @@ -14,7 +14,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/http:0.6.0 +- fetchai/http:0.7.0 skills: [] behaviours: {} handlers: @@ -29,3 +29,4 @@ models: args: {} class_name: HttpDialogues dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/ml_data_provider/skill.yaml b/packages/fetchai/skills/ml_data_provider/skill.yaml index ae4d9a6241..bd81223e25 100644 --- a/packages/fetchai/skills/ml_data_provider/skill.yaml +++ b/packages/fetchai/skills/ml_data_provider/skill.yaml @@ -1,6 +1,6 @@ name: ml_data_provider author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The ml data provider skill implements a provider for Machine Learning datasets in order to monetize data. @@ -12,16 +12,16 @@ fingerprint: behaviours.py: QmWgXU9qgahXwMKNqLLfDiGNYJozSXv2SVMkoPDQncC7ok dialogues.py: QmUExSjdSxrtTDzMB8tZ5J9tFrgd78LhdMuvptpViSPfAW handlers.py: QmbmS4C1GdumnVkA5dc7czWRkjb9HGzY7YeATrnuMVU2X9 - strategy.py: QmaCViuN7q33drEtnJLSFjvBnr4Q6GF9LrJCCdv9AVukxk + strategy.py: QmZd2L6byxxh6HNpRNk4qaCiUhxmodid757JYXnb8NWLDq fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/ledger_api:0.4.0 -- fetchai/ml_trade:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/ledger_api:0.5.0 +- fetchai/ml_trade:0.7.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_seller:0.13.0 +- fetchai/generic_seller:0.14.0 behaviours: service_registration: args: @@ -70,3 +70,4 @@ dependencies: numpy: {} tensorflow: version: ==1.14.0 +is_abstract: false diff --git a/packages/fetchai/skills/ml_data_provider/strategy.py b/packages/fetchai/skills/ml_data_provider/strategy.py index 25bf63464d..5778244359 100644 --- a/packages/fetchai/skills/ml_data_provider/strategy.py +++ b/packages/fetchai/skills/ml_data_provider/strategy.py @@ -62,7 +62,9 @@ def __init__(self, **kwargs) -> None: location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { - "location": Location(location["longitude"], location["latitude"]) + "location": Location( + latitude=location["latitude"], longitude=location["longitude"] + ) } self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) enforce( diff --git a/packages/fetchai/skills/ml_train/handlers.py b/packages/fetchai/skills/ml_train/handlers.py index 2b94f46624..fa51737ced 100644 --- a/packages/fetchai/skills/ml_train/handlers.py +++ b/packages/fetchai/skills/ml_train/handlers.py @@ -48,7 +48,7 @@ DUMMY_DIGEST = "dummy_digest" -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class MlTradeHandler(Handler): diff --git a/packages/fetchai/skills/ml_train/skill.yaml b/packages/fetchai/skills/ml_train/skill.yaml index 1d84f5e241..26c8fb32c1 100644 --- a/packages/fetchai/skills/ml_train/skill.yaml +++ b/packages/fetchai/skills/ml_train/skill.yaml @@ -1,6 +1,6 @@ name: ml_train author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The ml train and predict skill implements a simple skill which buys training data, trains a model and sells predictions. @@ -11,20 +11,20 @@ fingerprint: __init__.py: QmbQigh7SV7dD2hLTGv3k9tnvpYWN1otG5yjiM7F3bbGEQ behaviours.py: QmQiBzKV5rEFpMQbSjfjzAJ7SqwwGmso6TozWkjdytucLR dialogues.py: QmPVJzrAKhfhG8n2JLhvycFYRReBFBj362LxH6s885rSQM - handlers.py: QmSZR738THX7bCaXkuuoPDgwF3fo6DSD3hDt7EY2pRGxBm + handlers.py: QmNpytwjcyJSFiJjETKu14MbVVNsthFpYpi1CpGfqsurzQ ml_model.py: QmTfshn6dFnz9gKXZt7aJJczRH14bN7nk6TybwFpzkEPnk model.json: QmdV2tGrRY6VQ5VLgUa4yqAhPDG6X8tYsWecypq8nox9Td - strategy.py: QmR98NbFLJntoDfMMdps4XVZqXyoWNU97PF8WbxwpsxXxJ + strategy.py: QmNnxTUoGY1VGFkJreafPcPiRJh2Cqn6tkStDKAGkAHrL8 tasks.py: QmahJRCf6V61FsqrKgMMUyJ8F7PRd6C2bjunZg2XtM9fpF fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/ledger_api:0.4.0 -- fetchai/ml_trade:0.6.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/ledger_api:0.5.0 +- fetchai/ml_trade:0.7.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_buyer:0.12.0 +- fetchai/generic_buyer:0.13.0 behaviours: search: args: @@ -84,3 +84,4 @@ dependencies: numpy: {} tensorflow: version: ==1.14.0 +is_abstract: false diff --git a/packages/fetchai/skills/ml_train/strategy.py b/packages/fetchai/skills/ml_train/strategy.py index 4eb44a3826..2364196e46 100644 --- a/packages/fetchai/skills/ml_train/strategy.py +++ b/packages/fetchai/skills/ml_train/strategy.py @@ -62,7 +62,9 @@ def __init__(self, **kwargs) -> None: self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) - self._agent_location = Location(location["longitude"], location["latitude"]) + self._agent_location = Location( + latitude=location["latitude"], longitude=location["longitude"] + ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) super().__init__(**kwargs) diff --git a/packages/fetchai/skills/simple_service_registration/handlers.py b/packages/fetchai/skills/simple_service_registration/handlers.py index 2a09d52d4e..5d06cc83ad 100644 --- a/packages/fetchai/skills/simple_service_registration/handlers.py +++ b/packages/fetchai/skills/simple_service_registration/handlers.py @@ -32,7 +32,7 @@ ) -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class OefSearchHandler(Handler): diff --git a/packages/fetchai/skills/simple_service_registration/skill.yaml b/packages/fetchai/skills/simple_service_registration/skill.yaml index 83bb6ec2e4..cbbc0186c9 100644 --- a/packages/fetchai/skills/simple_service_registration/skill.yaml +++ b/packages/fetchai/skills/simple_service_registration/skill.yaml @@ -1,6 +1,6 @@ name: simple_service_registration author: fetchai -version: 0.10.0 +version: 0.11.0 type: skill description: The simple service registration skills is a skill to register a service. license: Apache-2.0 @@ -10,12 +10,12 @@ fingerprint: __init__.py: QmNkZAetyctaZCUf6ACxP5onGWsSxu2hjSNoFmJ3ta6Lta behaviours.py: QmbAQq8xchbxFu7QT3RofR1VX1ExQGUBemCQuNjc5bUnVA dialogues.py: QmX8L6qMd4X6LHLyPmiXaQL2LA5Ca9Q6B77qYdfvfJ3aen - handlers.py: QmSjcePSvevn3msjeyTsrE9fzsJARCMyrM2TWGPCfQuAEi - strategy.py: QmSBEPT151kejVVjQG4fuyHMPK1PZ4KQkY2fV4p9MKLAXb + handlers.py: QmYNHtjuLNqNxuuMZmdGHCNAyJKvwaw3a2DPo8n4jPrZKD + strategy.py: QmdiJNDkTvhEC2yxEt4P8WYniLbjPGSSQFWjmjvhgxMtx5 fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: service: @@ -40,3 +40,4 @@ models: value: generic_service class_name: Strategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/simple_service_registration/strategy.py b/packages/fetchai/skills/simple_service_registration/strategy.py index ca6fa6d145..7fbb82d9ed 100644 --- a/packages/fetchai/skills/simple_service_registration/strategy.py +++ b/packages/fetchai/skills/simple_service_registration/strategy.py @@ -45,7 +45,7 @@ def __init__(self, **kwargs) -> None: location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { "location": Location( - longitude=location["longitude"], latitude=location["latitude"] + latitude=location["latitude"], longitude=location["longitude"] ) } self._set_service_data = kwargs.pop("service_data", DEFAULT_SERVICE_DATA) diff --git a/packages/fetchai/skills/tac_control/game.py b/packages/fetchai/skills/tac_control/game.py index e211937816..df2c4a4bae 100644 --- a/packages/fetchai/skills/tac_control/game.py +++ b/packages/fetchai/skills/tac_control/game.py @@ -169,6 +169,10 @@ def _check_consistency(self): enforce(len(self.agent_addr_to_name) >= 2, "Must have at least two agents.") enforce(len(self.good_id_to_name) >= 2, "Must have at least two goods.") enforce(len(self.currency_id_to_name) == 1, "Must have exactly one currency.") + enforce( + next(iter(self.currency_id_to_name)) not in self.good_id_to_name, + "Currency id and good ids cannot overlap.", + ) class Initialization: diff --git a/packages/fetchai/skills/tac_control/handlers.py b/packages/fetchai/skills/tac_control/handlers.py index 8dd75f55a5..61c4a355ed 100644 --- a/packages/fetchai/skills/tac_control/handlers.py +++ b/packages/fetchai/skills/tac_control/handlers.py @@ -297,7 +297,9 @@ def _handle_invalid_transaction( ) -> None: """Handle an invalid transaction.""" self.context.logger.info( - "handling invalid transaction: {}".format(tac_msg.transaction_id) + "handling invalid transaction: {}, tac_msg={}".format( + tac_msg.transaction_id, tac_msg + ) ) error_msg = tac_dialogue.reply( performative=TacMessage.Performative.TAC_ERROR, diff --git a/packages/fetchai/skills/tac_control/helpers.py b/packages/fetchai/skills/tac_control/helpers.py index c88450652a..4e910c05cc 100644 --- a/packages/fetchai/skills/tac_control/helpers.py +++ b/packages/fetchai/skills/tac_control/helpers.py @@ -35,28 +35,30 @@ FT_ID = 2 -def generate_good_ids(nb_goods: int) -> List[int]: +def generate_good_ids(nb_goods: int, starting_index: int) -> List[int]: """ Generate ids for things. :param nb_goods: the number of things. - :param contract: the instance of the contract + :param starting_index: the index to start creating from. """ - good_ids = ERC1155Contract.generate_token_ids(FT_ID, nb_goods) + good_ids = ERC1155Contract.generate_token_ids(FT_ID, nb_goods, starting_index) enforce( len(good_ids) == nb_goods, "Length of good ids and number of goods must match." ) return good_ids -def generate_currency_ids(nb_currencies: int) -> List[int]: +def generate_currency_ids(nb_currencies: int, starting_index: int) -> List[int]: """ Generate currency ids. :param nb_currencies: the number of currencies. - :param contract: the instance of the contract. + :param starting_index: the index to start creating from. """ - currency_ids = ERC1155Contract.generate_token_ids(FT_ID, nb_currencies) + currency_ids = ERC1155Contract.generate_token_ids( + FT_ID, nb_currencies, starting_index + ) enforce( len(currency_ids) == nb_currencies, "Length of currency ids and number of currencies must match.", @@ -65,7 +67,7 @@ def generate_currency_ids(nb_currencies: int) -> List[int]: def generate_currency_id_to_name( - nb_currencies: int, currency_ids: List[int] + nb_currencies: int, currency_ids: List[int], starting_index: int = 0 ) -> Dict[str, str]: """ Generate a dictionary mapping good ids to names. @@ -80,7 +82,7 @@ def generate_currency_id_to_name( "Length of currency_ids does not match nb_currencies.", ) else: - currency_ids = generate_currency_ids(nb_currencies) + currency_ids = generate_currency_ids(nb_currencies, starting_index) currency_id_to_name = { str(currency_id): "{}_{}".format(FT_NAME, currency_id) for currency_id in currency_ids @@ -88,7 +90,9 @@ def generate_currency_id_to_name( return currency_id_to_name -def generate_good_id_to_name(nb_goods: int, good_ids: List[int]) -> Dict[str, str]: +def generate_good_id_to_name( + nb_goods: int, good_ids: List[int], starting_index: int = 0 +) -> Dict[str, str]: """ Generate a dictionary mapping good ids to names. @@ -101,7 +105,7 @@ def generate_good_id_to_name(nb_goods: int, good_ids: List[int]) -> Dict[str, st len(good_ids) == nb_goods, "Length of good_ids does not match nb_goods." ) else: - good_ids = generate_good_ids(nb_goods) + good_ids = generate_good_ids(nb_goods, starting_index) good_id_to_name = { str(good_id): "{}_{}".format(FT_NAME, good_id) for good_id in good_ids } diff --git a/packages/fetchai/skills/tac_control/parameters.py b/packages/fetchai/skills/tac_control/parameters.py index df7a105677..d903d1ab20 100644 --- a/packages/fetchai/skills/tac_control/parameters.py +++ b/packages/fetchai/skills/tac_control/parameters.py @@ -112,7 +112,8 @@ def __init__(self, **kwargs): self._agent_location = { "location": Location( - self._location["longitude"], self._location["latitude"] + latitude=self._location["latitude"], + longitude=self._location["longitude"], ) } self._set_service_data = self._service_data @@ -125,7 +126,9 @@ def __init__(self, **kwargs): self._currency_id_to_name = generate_currency_id_to_name( self.nb_currencies, self.currency_ids ) - self._good_id_to_name = generate_good_id_to_name(self.nb_goods, self.good_ids) + self._good_id_to_name = generate_good_id_to_name( + self.nb_goods, self.good_ids, starting_index=1 + ) self._registration_end_time = ( self._registration_start_time + datetime.timedelta(seconds=self._registration_timeout) diff --git a/packages/fetchai/skills/tac_control/skill.yaml b/packages/fetchai/skills/tac_control/skill.yaml index 45fe2a38f5..4c76d11367 100644 --- a/packages/fetchai/skills/tac_control/skill.yaml +++ b/packages/fetchai/skills/tac_control/skill.yaml @@ -1,6 +1,6 @@ name: tac_control author: fetchai -version: 0.8.0 +version: 0.9.0 type: skill description: The tac control skill implements the logic for an AEA to control an instance of the TAC. @@ -11,16 +11,16 @@ fingerprint: __init__.py: Qme9YfgfPXymvupw1EHMJWGUSMTT6JQZxk2qaeKE76pgyN behaviours.py: QmPtGBEFGEdQCzeXC3ZkJuNTrSe217JupP1wtP8DMnm6cK dialogues.py: QmcVYVTZ95UKBnXzmKyGLo5LwSVcGYUTrifosQMSpzNnCX - game.py: QmU3jm1yqhtnW1XK9EGZAMx9iWeCiX8Cdvb55kQQKDXav1 - handlers.py: QmSjwPKEN83C2gQS4WVH8uaHMv5ze7EJ9S8ugmRcKnSwnW - helpers.py: QmQXrtqV3h4oU5cDwWKhtNiTTAGLNZGhjoVnDvGj7mGwpe - parameters.py: QmefuFt5DPCBciu4z17UJWcXxtb4vwA879pNwh4DoMKPUm + game.py: QmWebcVRJvNYEi21hryWL1DXFexRE2bS3SiPjWkiFM9onH + handlers.py: QmVHZLJYva9WaXNFrzqh2HTsrWTLxqYdVrFiWPK4D3h7EP + helpers.py: QmbXAH6yUue1SJSpaXRVpmbACr6mSHbqf5kFc3AHhtedJD + parameters.py: QmdNCew6YnAc4WoDnefR2xaTENmB8416AxqjUa3jMT6KTi fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/oef_search:0.7.0 -- fetchai/tac:0.7.0 +- fetchai/oef_search:0.8.0 +- fetchai/tac:0.8.0 skills: [] behaviours: tac: @@ -74,3 +74,4 @@ models: class_name: TacDialogues dependencies: numpy: {} +is_abstract: false diff --git a/packages/fetchai/skills/tac_control_contract/behaviours.py b/packages/fetchai/skills/tac_control_contract/behaviours.py index 5bf5178685..5690c1ad1d 100644 --- a/packages/fetchai/skills/tac_control_contract/behaviours.py +++ b/packages/fetchai/skills/tac_control_contract/behaviours.py @@ -34,7 +34,7 @@ from packages.fetchai.skills.tac_control_contract.parameters import Parameters -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" class TacBehaviour(BaseTacBehaviour): @@ -67,7 +67,7 @@ def _request_contract_deploy_transaction(self) -> None: counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=parameters.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", callable=ContractApiDialogue.Callable.GET_DEPLOY_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( {"deployer_address": self.context.agent_address} @@ -162,7 +162,7 @@ def _request_create_items_transaction(self, game: Game) -> None: counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=parameters.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=parameters.contract_address, callable=ContractApiDialogue.Callable.GET_CREATE_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( @@ -209,7 +209,7 @@ def _request_mint_items_transaction(self, game: Game) -> None: counterparty=LEDGER_API_ADDRESS, performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=parameters.ledger_id, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=parameters.contract_address, callable=ContractApiDialogue.Callable.GET_MINT_BATCH_TRANSACTION.value, kwargs=ContractApiMessage.Kwargs( diff --git a/packages/fetchai/skills/tac_control_contract/handlers.py b/packages/fetchai/skills/tac_control_contract/handlers.py index 2794aa4dec..2cfc3503ea 100644 --- a/packages/fetchai/skills/tac_control_contract/handlers.py +++ b/packages/fetchai/skills/tac_control_contract/handlers.py @@ -45,7 +45,7 @@ from packages.fetchai.skills.tac_control_contract.parameters import Parameters -LEDGER_API_ADDRESS = "fetchai/ledger:0.6.0" +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" TacHandler = BaseTacHandler diff --git a/packages/fetchai/skills/tac_control_contract/skill.yaml b/packages/fetchai/skills/tac_control_contract/skill.yaml index 7057bc6e20..1b72a47549 100644 --- a/packages/fetchai/skills/tac_control_contract/skill.yaml +++ b/packages/fetchai/skills/tac_control_contract/skill.yaml @@ -1,6 +1,6 @@ name: tac_control_contract author: fetchai -version: 0.9.0 +version: 0.10.0 type: skill description: The tac control skill implements the logic for an AEA to control an instance of the TAC. @@ -9,23 +9,23 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: README.md: QmUiw6Dt97mXLzSizbnMNTsfLy3edGXKdYTNR5bke6sFsm __init__.py: QmW9WBy1sNYVKpymGnpJY2pW5MEqGgVga2kBFUT9S34Yt5 - behaviours.py: QmSfxtgDawQai52wzoYp58KHEVjXPsFiHQpyv4cYCcBo28 + behaviours.py: QmXpRURhxKtDBeDBfZr2ZBxVxriVSnAZss8dLWuAEEi8EM dialogues.py: QmWvhTeUnDimhQQTzuSfJJ6d2ViqgBNooCMfRiVzvzApw3 game.py: QmQwskD5DBVNv1ouRpqGNLb3zQ5krLUcR6XXHUcJ5EVc8L - handlers.py: QmUevEFLkj94XQDPKqkWxRRRZJnY3XQBZFKXDK69xpwabB + handlers.py: QmYeA8eruEeVmhRpE6T7tjWreW9KWtVdtRS9FSTbKk9hq9 helpers.py: QmX3iv37Z5CmGupCtikc4muTpJyQ9zvjeLbNKDLF7iPhSZ parameters.py: QmVMNP52fPxkzEbXPhXtQXspnaj2f4ErZNcNdPAqv4cvwq fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/contract_api:0.5.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 -- fetchai/tac:0.7.0 +- fetchai/contract_api:0.6.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 +- fetchai/tac:0.8.0 skills: -- fetchai/tac_control:0.8.0 +- fetchai/tac_control:0.9.0 behaviours: tac: args: {} @@ -98,3 +98,4 @@ dependencies: numpy: {} vyper: version: ==0.1.0b12 +is_abstract: false diff --git a/packages/fetchai/skills/tac_negotiation/dialogues.py b/packages/fetchai/skills/tac_negotiation/dialogues.py index 493185e91d..d4669b9bee 100644 --- a/packages/fetchai/skills/tac_negotiation/dialogues.py +++ b/packages/fetchai/skills/tac_negotiation/dialogues.py @@ -27,6 +27,8 @@ from aea.common import Address from aea.exceptions import enforce +from aea.helpers.search.models import Description +from aea.helpers.transaction.base import Terms from aea.protocols.base import Message from aea.protocols.default.dialogues import DefaultDialogue as BaseDefaultDialogue from aea.protocols.default.dialogues import DefaultDialogues as BaseDefaultDialogues @@ -36,9 +38,23 @@ from aea.protocols.signing.message import SigningMessage from aea.skills.base import Model +from packages.fetchai.protocols.contract_api.dialogues import ( + ContractApiDialogue as BaseContractApiDialogue, +) +from packages.fetchai.protocols.contract_api.dialogues import ( + ContractApiDialogues as BaseContractApiDialogues, +) +from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.fipa.dialogues import FipaDialogue as BaseFipaDialogue from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.ledger_api.dialogues import ( + LedgerApiDialogue as BaseLedgerApiDialogue, +) +from packages.fetchai.protocols.ledger_api.dialogues import ( + LedgerApiDialogues as BaseLedgerApiDialogues, +) +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.dialogues import ( OefSearchDialogue as BaseOefSearchDialogue, ) @@ -52,39 +68,76 @@ ) -DefaultDialogue = BaseDefaultDialogue - - -class DefaultDialogues(Model, BaseDefaultDialogues): - """The dialogues class keeps track of all dialogues.""" +class FipaDialogue(BaseFipaDialogue): + """The dialogue class maintains state of a dialogue and manages it.""" - def __init__(self, **kwargs) -> None: + def __init__( + self, + dialogue_label: DialogueLabel, + self_address: Address, + role: Dialogue.Role, + message_class: Type[FipaMessage] = FipaMessage, + ) -> None: """ - Initialize dialogues. + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param self_address: the address of the entity for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for :return: None """ - Model.__init__(self, **kwargs) - - def role_from_first_message( # pylint: disable=unused-argument - message: Message, receiver_address: Address - ) -> Dialogue.Role: - """Infer the role of the agent from an incoming/outgoing first message + BaseFipaDialogue.__init__( + self, + dialogue_label=dialogue_label, + self_address=self_address, + role=role, + message_class=message_class, + ) + self._proposal = None # type: Optional[Description] + self._terms = None # type: Optional[Terms] + self._counterparty_signature = None # type: Optional[str] - :param message: an incoming/outgoing first message - :param receiver_address: the address of the receiving agent - :return: The role of the agent - """ - return DefaultDialogue.Role.AGENT + @property + def counterparty_signature(self) -> str: + """Get counterparty signature.""" + if self._counterparty_signature is None: + raise ValueError("counterparty_signature not set!") + return self._counterparty_signature - BaseDefaultDialogues.__init__( - self, - self_address=self.context.agent_address, - role_from_first_message=role_from_first_message, + @counterparty_signature.setter + def counterparty_signature(self, counterparty_signature: str) -> None: + """Set is_seller_search.""" + enforce( + self._counterparty_signature is None, "counterparty_signature already set!" ) + self._counterparty_signature = counterparty_signature + + @property + def proposal(self) -> Description: + """Get the proposal.""" + if self._proposal is None: + raise ValueError("Proposal not set!") + return self._proposal + + @proposal.setter + def proposal(self, proposal: Description) -> None: + """Set the proposal.""" + enforce(self._proposal is None, "Proposal already set!") + self._proposal = proposal + @property + def terms(self) -> Terms: + """Get the terms.""" + if self._terms is None: + raise ValueError("Terms not set!") + return self._terms -FipaDialogue = BaseFipaDialogue + @terms.setter + def terms(self, terms: Terms) -> None: + """Set the terms.""" + enforce(self._terms is None, "Terms already set!") + self._terms = terms class FipaDialogues(Model, BaseFipaDialogues): @@ -140,6 +193,190 @@ def role_from_first_message( ) +class ContractApiDialogue(BaseContractApiDialogue): + """The dialogue class maintains state of a dialogue and manages it.""" + + def __init__( + self, + dialogue_label: DialogueLabel, + self_address: Address, + role: Dialogue.Role, + message_class: Type[ContractApiMessage] = ContractApiMessage, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param self_address: the address of the entity for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + + :return: None + """ + BaseContractApiDialogue.__init__( + self, + dialogue_label=dialogue_label, + self_address=self_address, + role=role, + message_class=message_class, + ) + self._associated_fipa_dialogue: Optional[FipaDialogue] = None + + @property + def associated_fipa_dialogue(self) -> FipaDialogue: + """Get associated_fipa_dialogue.""" + if self._associated_fipa_dialogue is None: + raise ValueError("associated_fipa_dialogue not set!") + return self._associated_fipa_dialogue + + @associated_fipa_dialogue.setter + def associated_fipa_dialogue(self, associated_fipa_dialogue: FipaDialogue) -> None: + """Set associated_fipa_dialogue.""" + enforce( + self._associated_fipa_dialogue is None, + "associated_fipa_dialogue already set!", + ) + self._associated_fipa_dialogue = associated_fipa_dialogue + + +class ContractApiDialogues(Model, BaseContractApiDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + + def role_from_first_message( # pylint: disable=unused-argument + message: Message, receiver_address: Address + ) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :param receiver_address: the address of the receiving agent + :return: The role of the agent + """ + return ContractApiDialogue.Role.AGENT + + BaseContractApiDialogues.__init__( + self, + self_address=self.context.agent_address, + role_from_first_message=role_from_first_message, + dialogue_class=ContractApiDialogue, + ) + + +DefaultDialogue = BaseDefaultDialogue + + +class DefaultDialogues(Model, BaseDefaultDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + + def role_from_first_message( # pylint: disable=unused-argument + message: Message, receiver_address: Address + ) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :param receiver_address: the address of the receiving agent + :return: The role of the agent + """ + return DefaultDialogue.Role.AGENT + + BaseDefaultDialogues.__init__( + self, + self_address=self.context.agent_address, + role_from_first_message=role_from_first_message, + ) + + +class LedgerApiDialogue(BaseLedgerApiDialogue): + """The dialogue class maintains state of a dialogue and manages it.""" + + def __init__( + self, + dialogue_label: DialogueLabel, + self_address: Address, + role: Dialogue.Role, + message_class: Type[LedgerApiMessage] = LedgerApiMessage, + ) -> None: + """ + Initialize a dialogue. + + :param dialogue_label: the identifier of the dialogue + :param self_address: the address of the entity for whom this dialogue is maintained + :param role: the role of the agent this dialogue is maintained for + + :return: None + """ + BaseLedgerApiDialogue.__init__( + self, + dialogue_label=dialogue_label, + self_address=self_address, + role=role, + message_class=message_class, + ) + self._associated_signing_dialogue = None # type: Optional[SigningDialogue] + + @property + def associated_signing_dialogue(self) -> "SigningDialogue": + """Get the associated signing dialogue.""" + if self._associated_signing_dialogue is None: + raise ValueError("Associated signing dialogue not set!") + return self._associated_signing_dialogue + + @associated_signing_dialogue.setter + def associated_signing_dialogue( + self, associated_signing_dialogue: "SigningDialogue" + ) -> None: + """Set the associated signing dialogue.""" + enforce( + self._associated_signing_dialogue is None, + "Associated signing dialogue already set!", + ) + self._associated_signing_dialogue = associated_signing_dialogue + + +class LedgerApiDialogues(Model, BaseLedgerApiDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + Model.__init__(self, **kwargs) + + def role_from_first_message( # pylint: disable=unused-argument + message: Message, receiver_address: Address + ) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :param receiver_address: the address of the receiving agent + :return: The role of the agent + """ + return BaseLedgerApiDialogue.Role.AGENT + + BaseLedgerApiDialogues.__init__( + self, + self_address=self.context.agent_address, + role_from_first_message=role_from_first_message, + dialogue_class=LedgerApiDialogue, + ) + + class OefSearchDialogue(BaseOefSearchDialogue): """The dialogue class maintains state of a dialogue and manages it.""" @@ -239,24 +476,8 @@ def __init__( role=role, message_class=message_class, ) - self._counterparty_signature = None # type: Optional[str] self._associated_fipa_dialogue: Optional[FipaDialogue] = None - @property - def counterparty_signature(self) -> str: - """Get counterparty signature.""" - if self._counterparty_signature is None: - raise ValueError("counterparty_signature not set!") - return self._counterparty_signature - - @counterparty_signature.setter - def counterparty_signature(self, counterparty_signature: str) -> None: - """Set is_seller_search.""" - enforce( - self._counterparty_signature is None, "counterparty_signature already set!" - ) - self._counterparty_signature = counterparty_signature - @property def associated_fipa_dialogue(self) -> FipaDialogue: """Get associated_fipa_dialogue.""" diff --git a/packages/fetchai/skills/tac_negotiation/handlers.py b/packages/fetchai/skills/tac_negotiation/handlers.py index 016d018775..96bd3fdb8f 100644 --- a/packages/fetchai/skills/tac_negotiation/handlers.py +++ b/packages/fetchai/skills/tac_negotiation/handlers.py @@ -19,23 +19,29 @@ """This package contains a scaffold of a handler.""" -import pprint -import time from typing import Optional, Tuple, cast from aea.configurations.base import ProtocolId -from aea.helpers.search.models import Query +from aea.crypto.ledger_apis import LedgerApis +from aea.exceptions import enforce +from aea.helpers.transaction.base import RawMessage from aea.protocols.base import Message from aea.protocols.default.message import DefaultMessage from aea.protocols.signing.message import SigningMessage from aea.skills.base import Handler +from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.skills.tac_negotiation.dialogues import ( + ContractApiDialogue, + ContractApiDialogues, DefaultDialogues, FipaDialogue, FipaDialogues, + LedgerApiDialogue, + LedgerApiDialogues, OefSearchDialogue, OefSearchDialogues, SigningDialogue, @@ -45,6 +51,9 @@ from packages.fetchai.skills.tac_negotiation.transactions import Transactions +LEDGER_API_ADDRESS = "fetchai/ledger:0.7.0" + + class FipaNegotiationHandler(Handler): """This class implements the fipa negotiation handler.""" @@ -74,8 +83,13 @@ def handle(self, message: Message) -> None: self._handle_unidentified_dialogue(fipa_msg) return - self.context.logger.debug( - "handling FipaMessage of performative={}".format(fipa_msg.performative) + self.context.logger.info( + "received {} from {} (as {}), message={}".format( + fipa_msg.performative.value, + fipa_msg.sender[-5:], + fipa_dialogue.role, + fipa_msg, + ) ) if fipa_msg.performative == FipaMessage.Performative.CFP: self._on_cfp(fipa_msg, fipa_dialogue) @@ -128,29 +142,11 @@ def _on_cfp(self, cfp: FipaMessage, fipa_dialogue: FipaDialogue) -> None: :return: None """ - new_msg_id = cfp.message_id + 1 - query = cast(Query, cfp.query) strategy = cast(Strategy, self.context.strategy) - proposal_description = strategy.get_proposal_for_query( - query, cast(FipaDialogue.Role, fipa_dialogue.role) - ) - - if proposal_description is None: - self.context.logger.debug( - "sending to {} a Decline{}".format( - fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], - pprint.pformat( - { - "msg_id": new_msg_id, - "dialogue_reference": cfp.dialogue_reference, - "origin": fipa_dialogue.dialogue_label.dialogue_opponent_addr[ - -5: - ], - "target": cfp.target, - } - ), - ) - ) + proposal = strategy.get_proposal_for_query( + cfp.query, cast(FipaDialogue.Role, fipa_dialogue.role) + ) + if proposal is None: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=cfp, ) @@ -160,37 +156,25 @@ def _on_cfp(self, cfp: FipaMessage, fipa_dialogue: FipaDialogue) -> None: ) else: transactions = cast(Transactions, self.context.transactions) - signing_msg = transactions.generate_signing_message( - SigningMessage.Performative.SIGN_MESSAGE, - proposal_description, - fipa_dialogue, - cast(FipaDialogue.Role, fipa_dialogue.role), + fipa_msg = fipa_dialogue.reply( + performative=FipaMessage.Performative.PROPOSE, + target_message=cfp, + proposal=proposal, + ) + fipa_dialogue.terms = strategy.terms_from_proposal( + proposal, self.context.agent_address, + cfp.sender, + cast(FipaDialogue.Role, fipa_dialogue.role), ) transactions.add_pending_proposal( - fipa_dialogue.dialogue_label, new_msg_id, signing_msg - ) - self.context.logger.info( - "sending to {} a Propose {}".format( - fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], - pprint.pformat( - { - "msg_id": new_msg_id, - "dialogue_reference": cfp.dialogue_reference, - "origin": fipa_dialogue.dialogue_label.dialogue_opponent_addr[ - -5: - ], - "target": cfp.message_id, - "propose": proposal_description.values, - } - ), - ) + fipa_dialogue.dialogue_label, fipa_msg.message_id, fipa_dialogue.terms ) - fipa_msg = fipa_dialogue.reply( - performative=FipaMessage.Performative.PROPOSE, - target_message=cfp, - proposal=proposal_description, + self.context.logger.info( + "sending {} to {} (as {}), message={}".format( + fipa_msg.performative, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg ) + ) self.context.outbox.put_message(message=fipa_msg) def _on_propose(self, propose: FipaMessage, fipa_dialogue: FipaDialogue) -> None: @@ -201,38 +185,27 @@ def _on_propose(self, propose: FipaMessage, fipa_dialogue: FipaDialogue) -> None :param fipa_dialogue: the fipa_dialogue :return: None """ - new_msg_id = propose.message_id + 1 strategy = cast(Strategy, self.context.strategy) - proposal_description = propose.proposal - self.context.logger.debug("on Propose as {}.".format(fipa_dialogue.role)) - transactions = cast(Transactions, self.context.transactions) - signing_msg = transactions.generate_signing_message( - SigningMessage.Performative.SIGN_MESSAGE, - proposal_description, - fipa_dialogue, - cast(FipaDialogue.Role, fipa_dialogue.role), + fipa_dialogue.terms = strategy.terms_from_proposal( + propose.proposal, self.context.agent_address, + propose.sender, + cast(FipaDialogue.Role, fipa_dialogue.role), ) - + transactions = cast(Transactions, self.context.transactions) if strategy.is_profitable_transaction( - signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) + fipa_dialogue.terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ): - self.context.logger.info( - "accepting propose (as {}).".format(fipa_dialogue.role) + fipa_msg = fipa_dialogue.reply( + performative=FipaMessage.Performative.ACCEPT, target_message=propose, ) transactions.add_locked_tx( - signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) + fipa_dialogue.terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ) transactions.add_pending_initial_acceptance( - fipa_dialogue.dialogue_label, new_msg_id, signing_msg - ) - fipa_msg = fipa_dialogue.reply( - performative=FipaMessage.Performative.ACCEPT, target_message=propose, + fipa_dialogue.dialogue_label, fipa_msg.message_id, fipa_dialogue.terms ) else: - self.context.logger.info( - "declining propose (as {})".format(fipa_dialogue.role) - ) fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=propose, ) @@ -240,6 +213,11 @@ def _on_propose(self, propose: FipaMessage, fipa_dialogue: FipaDialogue) -> None fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) + self.context.logger.info( + "sending {} to {} (as {}), message={}".format( + fipa_msg.performative, fipa_msg.to[-5:], fipa_dialogue.role, fipa_msg + ) + ) self.context.outbox.put_message(message=fipa_msg) def _on_decline(self, decline: FipaMessage, fipa_dialogue: FipaDialogue) -> None: @@ -250,38 +228,29 @@ def _on_decline(self, decline: FipaMessage, fipa_dialogue: FipaDialogue) -> None :param fipa_dialogue: the fipa_dialogue :return: None """ - self.context.logger.debug( - "on_decline: msg_id={}, dialogue_reference={}, origin={}, target={}".format( - decline.message_id, - decline.dialogue_reference, - fipa_dialogue.dialogue_label.dialogue_opponent_addr, - decline.target, - ) - ) - target = decline.target fipa_dialogues = cast(FipaDialogues, self.context.fipa_dialogues) - if target == 1: + if decline.target == 1: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_CFP, fipa_dialogue.is_self_initiated ) - elif target == 2: + elif decline.target == 2: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_PROPOSE, fipa_dialogue.is_self_initiated ) transactions = cast(Transactions, self.context.transactions) - signing_msg = transactions.pop_pending_proposal( - fipa_dialogue.dialogue_label, target + terms = transactions.pop_pending_proposal( + fipa_dialogue.dialogue_label, decline.target ) - elif target == 3: + elif decline.target == 3: fipa_dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) transactions = cast(Transactions, self.context.transactions) - signing_msg = transactions.pop_pending_initial_acceptance( - fipa_dialogue.dialogue_label, target + terms = transactions.pop_pending_initial_acceptance( + fipa_dialogue.dialogue_label, decline.target ) - transactions.pop_locked_tx(signing_msg) + transactions.pop_locked_tx(terms) def _on_accept(self, accept: FipaMessage, fipa_dialogue: FipaDialogue) -> None: """ @@ -291,89 +260,64 @@ def _on_accept(self, accept: FipaMessage, fipa_dialogue: FipaDialogue) -> None: :param fipa_dialogue: the fipa_dialogue :return: None """ - self.context.logger.debug( - "on_accept: msg_id={}, dialogue_reference={}, origin={}, target={}".format( - accept.message_id, - accept.dialogue_reference, - fipa_dialogue.dialogue_label.dialogue_opponent_addr, - accept.target, - ) - ) transactions = cast(Transactions, self.context.transactions) - signing_msg = transactions.pop_pending_proposal( + terms = transactions.pop_pending_proposal( fipa_dialogue.dialogue_label, accept.target ) strategy = cast(Strategy, self.context.strategy) - if strategy.is_profitable_transaction( - signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) + terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ): - self.context.logger.info( - "locking the current state (as {}).".format(fipa_dialogue.role) - ) transactions.add_locked_tx( - signing_msg, role=cast(FipaDialogue.Role, fipa_dialogue.role) + terms, role=cast(FipaDialogue.Role, fipa_dialogue.role) ) if strategy.is_contract_tx: - pass - # contract = cast(ERC1155Contract, self.context.contracts.erc1155) # noqa: E800 - # if not contract.is_deployed: # noqa: E800 - # ledger_api = self.context.ledger_apis.get_api(strategy.ledger_id) # noqa: E800 - # contract_address = self.context.shared_state.get( # noqa: E800 - # "erc1155_contract_address", None # noqa: E800 - # ) # noqa: E800 - # enforce( # noqa: E800 - # contract_address is not None, # noqa: E800 - # "ERC1155Contract address not set!" # noqa: E800 - # ) # noqa: E800 - # tx_nonce = transaction_msg.skill_callback_info.get("tx_nonce", None) # noqa: E800 - # enforce(tx_nonce is not None, "tx_nonce must be provided") # noqa: E800 - # transaction_msg = contract.get_hash_batch_transaction_msg( # noqa: E800 - # from_address=accept.counterparty, # noqa: E800 - # to_address=self.context.agent_address, # must match self # noqa: E800 - # token_ids=[ # noqa: E800 - # int(key) # noqa: E800 - # for key in transaction_msg.terms.quantities_by_good_id.keys() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # int(key) # noqa: E800 - # for key in transaction_msg.terms.amount_by_currency_id.keys() # noqa: E800 - # ], # noqa: E800 - # from_supplies=[ # noqa: E800 - # quantity if quantity > 0 else 0 # noqa: E800 - # for quantity in transaction_msg.terms.quantities_by_good_id.values() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # value if value > 0 else 0 # noqa: E800 - # for value in transaction_msg.terms.amount_by_currency_id.values() # noqa: E800 - # ], # noqa: E800 - # to_supplies=[ # noqa: E800 - # -quantity if quantity < 0 else 0 # noqa: E800 - # for quantity in transaction_msg.terms.quantities_by_good_id.values() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # -value if value < 0 else 0 # noqa: E800 - # for value in transaction_msg.terms.amount_by_currency_id.values() # noqa: E800 - # ], # noqa: E800 - # value=0, # noqa: E800 - # trade_nonce=int(tx_nonce), # noqa: E800 - # ledger_api=self.context.ledger_apis.get_api(strategy.ledger_id), # noqa: E800 - # skill_callback_id=self.context.skill_id, # noqa: E800 - # skill_callback_info={ # noqa: E800 - # "dialogue_label": fipa_dialogue.dialogue_label.json # noqa: E800 - # }, # noqa: E800 - # ) # noqa: E800 + contract_api_dialogues = cast( + ContractApiDialogues, self.context.contract_api_dialogues + ) + contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( + counterparty=LEDGER_API_ADDRESS, + performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, + ledger_id=strategy.ledger_id, + contract_id=strategy.contract_id, + contract_address=strategy.contract_address, + callable="get_hash_batch", + kwargs=ContractApiMessage.Kwargs( + strategy.kwargs_from_terms(fipa_dialogue.terms) + ), + ) + contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) + contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue + self.context.logger.info( + "requesting batch transaction hash, sending {} to {}, message={}".format( + contract_api_msg.performative, + strategy.contract_id, + contract_api_msg, + ) + ) + self.context.outbox.put_message(message=contract_api_msg) else: + signing_dialogues = cast( + SigningDialogues, self.context.signing_dialogues + ) + raw_message = RawMessage( + ledger_id=terms.ledger_id, body=terms.sender_hash.encode("utf-8") + ) + signing_msg, signing_dialogue = signing_dialogues.create( + counterparty=self.context.decision_maker_address, + performative=SigningMessage.Performative.SIGN_MESSAGE, + terms=terms, + raw_message=raw_message, + ) + signing_dialogue = cast(SigningDialogue, signing_dialogue) + signing_dialogue.associated_fipa_dialogue = fipa_dialogue self.context.logger.info( - "sending signing_msg={} to decison maker following ACCEPT.".format( - signing_msg + "requesting signature, sending {} to decision_maker, message={}".format( + signing_msg.performative, signing_msg, ) ) self.context.decision_maker_message_queue.put(signing_msg) else: - self.context.logger.debug( - "decline the Accept (as {}).".format(fipa_dialogue.role) - ) fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.DECLINE, target_message=accept, ) @@ -381,6 +325,14 @@ def _on_accept(self, accept: FipaMessage, fipa_dialogue: FipaDialogue) -> None: dialogues.dialogue_stats.add_dialogue_endstate( FipaDialogue.EndState.DECLINED_ACCEPT, fipa_dialogue.is_self_initiated ) + self.context.logger.info( + "sending {} to {} (as {}), message={}".format( + fipa_msg.performative, + fipa_msg.to[-5:], + fipa_dialogue.role, + fipa_msg, + ) + ) self.context.outbox.put_message(message=fipa_msg) def _on_match_accept( @@ -393,98 +345,64 @@ def _on_match_accept( :param fipa_dialogue: the fipa_dialogue :return: None """ - self.context.logger.debug( - "on_match_accept: msg_id={}, dialogue_reference={}, origin={}, target={}".format( - match_accept.message_id, - match_accept.dialogue_reference, - fipa_dialogue.dialogue_label.dialogue_opponent_addr, - match_accept.target, + counterparty_signature = match_accept.info.get("signature") + if counterparty_signature is None: + self.context.logger.info( + f"{match_accept.performative} did not contain counterparty signature!" ) - ) - if match_accept.info.get("signature") is not None: + return + fipa_dialogue.counterparty_signature = counterparty_signature + strategy = cast(Strategy, self.context.strategy) + if strategy.is_contract_tx: + contract_api_dialogues = cast( + ContractApiDialogues, self.context.contract_api_dialogues + ) + contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( + counterparty=LEDGER_API_ADDRESS, + performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, + ledger_id=strategy.ledger_id, + contract_id=strategy.contract_id, + contract_address=strategy.contract_address, + callable="get_atomic_swap_batch_transaction", + kwargs=ContractApiMessage.Kwargs( + strategy.kwargs_from_terms( + fipa_dialogue.terms, signature=counterparty_signature, + ) + ), + ) + contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) + contract_api_dialogue.associated_fipa_dialogue = fipa_dialogue + self.context.logger.info( + "requesting batch atomic swap transaction, sending {} to {}, message={}".format( + contract_api_msg.performative, + strategy.contract_id, + contract_api_msg, + ) + ) + self.context.outbox.put_message(message=contract_api_msg) + else: transactions = cast(Transactions, self.context.transactions) - signing_msg = transactions.pop_pending_initial_acceptance( + terms = transactions.pop_pending_initial_acceptance( fipa_dialogue.dialogue_label, match_accept.target ) signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) - signing_dialogue = cast( - Optional[SigningDialogue], signing_dialogues.get_dialogue(signing_msg) + raw_message = RawMessage( + ledger_id=terms.ledger_id, body=terms.sender_hash.encode("utf-8") ) - if signing_dialogue is None: - raise ValueError("Could not recover dialogue.") - strategy = cast(Strategy, self.context.strategy) - counterparty_signature = match_accept.info.get("signature") - if counterparty_signature is None: - self.context.logger.warning("No signature from counterparty.") - return - signing_dialogue.counterparty_signature = counterparty_signature - if strategy.is_contract_tx: - pass - # contract = cast(ERC1155Contract, self.context.contracts.erc1155) # noqa: E800 - # if not contract.is_deployed: # noqa: E800 - # ledger_api = self.context.ledger_apis.get_api(strategy.ledger_id) # noqa: E800 - # contract_address = self.context.shared_state.get( # noqa: E800 - # "erc1155_contract_address", None # noqa: E800 - # ) # noqa: E800 - # enforce( # noqa: E800 - # contract_address is not None, # noqa: E800 - # "ERC1155Contract address not set!" # noqa: E800 - # ) # noqa: E800 - # contract.set_deployed_instance( # noqa: E800 - # ledger_api, cast(str, contract_address), # noqa: E800 - # ) # noqa: E800 - # strategy = cast(Strategy, self.context.strategy) # noqa: E800 - # tx_nonce = transaction_msg.skill_callback_info.get("tx_nonce", None) # noqa: E800 - # tx_signature = match_accept.info.get("tx_signature", None) # noqa: E800 - # enforce( # noqa: E800 - # tx_nonce is not None and tx_signature is not None, # noqa: E800 - # "tx_nonce or tx_signature not available" # noqa: E800 - # ) # noqa: E800 - # transaction_msg = contract.get_atomic_swap_batch_transaction_msg( # noqa: E800 - # from_address=self.context.agent_address, # noqa: E800 - # to_address=match_accept.counterparty, # noqa: E800 - # token_ids=[ # noqa: E800 - # int(key) # noqa: E800 - # for key in transaction_msg.terms.quantities_by_good_id.keys() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # int(key) # noqa: E800 - # for key in transaction_msg.terms.amount_by_currency_id.keys() # noqa: E800 - # ], # noqa: E800 - # from_supplies=[ # noqa: E800 - # -quantity if quantity < 0 else 0 # noqa: E800 - # for quantity in transaction_msg.terms.quantities_by_good_id.values() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # -value if value < 0 else 0 # noqa: E800 - # for value in transaction_msg.terms.amount_by_currency_id.values() # noqa: E800 - # ], # noqa: E800 - # to_supplies=[ # noqa: E800 - # quantity if quantity > 0 else 0 # noqa: E800 - # for quantity in transaction_msg.terms.quantities_by_good_id.values() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # value if value > 0 else 0 # noqa: E800 - # for value in transaction_msg.terms.amount_by_currency_id.values() # noqa: E800 - # ], # noqa: E800 - # value=0, # noqa: E800 - # trade_nonce=int(tx_nonce), # noqa: E800 - # ledger_api=self.context.ledger_apis.get_api(strategy.ledger_id), # noqa: E800 - # skill_callback_id=self.context.skill_id, # noqa: E800 - # signature=tx_signature, # noqa: E800 - # skill_callback_info={ # noqa: E800 - # "dialogue_label": dialogue.dialogue_label.json # noqa: E800 - # }, # noqa: E800 - # ) # noqa: E800 - else: - self.context.logger.info( - "sending signing_msg={} to decison maker following MATCH_ACCEPT.".format( - signing_msg - ) + signing_msg, signing_dialogue = signing_dialogues.create( + counterparty=self.context.decision_maker_address, + performative=SigningMessage.Performative.SIGN_MESSAGE, + terms=terms, + raw_message=raw_message, + ) + signing_dialogue = cast(SigningDialogue, signing_dialogue) + signing_dialogue.associated_fipa_dialogue = fipa_dialogue + self.context.logger.info( + "requesting signature, sending {} to decision_maker, message={}".format( + signing_msg.performative, signing_msg, ) - self.context.decision_maker_message_queue.put(signing_msg) - else: - self.context.logger.warning("match_accept did not contain signature!") + ) + self.context.decision_maker_message_queue.put(signing_msg) class SigningHandler(Handler): @@ -518,6 +436,11 @@ def handle(self, message: Message) -> None: self._handle_unidentified_dialogue(signing_msg) return + self.context.logger.info( + "received {} from decision_maker, message={}".format( + signing_msg.performative.value, signing_msg, + ) + ) # handle message if signing_msg.performative is SigningMessage.Performative.SIGNED_MESSAGE: self._handle_signed_message(signing_msg, signing_dialogue) @@ -558,19 +481,10 @@ def _handle_signed_message( :param signing_dialogue: the dialogue :return: None """ - strategy = cast(Strategy, self.context.strategy) - if strategy.is_contract_tx: - self.context.logger.warning( - "signed message handler only for non-contract case." - ) - return - self.context.logger.info("message signed by decision maker.") fipa_dialogue = signing_dialogue.associated_fipa_dialogue last_fipa_message = cast(FipaMessage, fipa_dialogue.last_incoming_message) - if ( - last_fipa_message is not None - and last_fipa_message.performative == FipaMessage.Performative.ACCEPT - ): + enforce(last_fipa_message is not None, "last message not recovered.") + if last_fipa_message.performative == FipaMessage.Performative.ACCEPT: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, target_message=last_fipa_message, @@ -578,38 +492,31 @@ def _handle_signed_message( ) self.context.outbox.put_message(message=fipa_msg) self.context.logger.info( - "sending match accept to {}.".format( - fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], + "sending {} to {} (as {}), message={}.".format( + fipa_msg.performative.value, + fipa_msg.to[-5:], + fipa_dialogue.role, + fipa_msg, ) ) elif ( - last_fipa_message is not None - and last_fipa_message.performative + last_fipa_message.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM ): - counterparty_signature = signing_dialogue.counterparty_signature - if counterparty_signature is not None: - last_signing_msg = cast( - Optional[SigningMessage], signing_dialogue.last_outgoing_message - ) - if last_signing_msg is None: - raise ValueError("Could not recover last signing message.") - tx_id = last_signing_msg.terms.sender_hash - if "transactions" not in self.context.shared_state.keys(): - self.context.shared_state["transactions"] = {} - self.context.shared_state["transactions"][tx_id] = { - "terms": last_signing_msg.terms, - "sender_signature": signing_msg.signed_message.body, - "counterparty_signature": counterparty_signature, - } - self.context.logger.info("sending transaction to controller.") - else: - self.context.logger.warning( - "transaction has no counterparty signature!" - ) + counterparty_signature = fipa_dialogue.counterparty_signature + tx_id = fipa_dialogue.terms.sender_hash + if "transactions" not in self.context.shared_state.keys(): + self.context.shared_state["transactions"] = {} + tx = { + "terms": fipa_dialogue.terms, + "sender_signature": signing_msg.signed_message.body, + "counterparty_signature": counterparty_signature, + } + self.context.shared_state["transactions"][tx_id] = tx + self.context.logger.info(f"sending transaction to controller, tx={tx}.") else: - self.context.logger.warning( - "last message should be of performative accept or match accept." + enforce( + False, "last message should be of performative accept or match accept." ) def _handle_signed_transaction( # pylint: disable=unused-argument @@ -628,82 +535,47 @@ def _handle_signed_transaction( # pylint: disable=unused-argument "signed transaction handler only for contract case." ) return - self.context.logger.info("transaction signed by decision maker.") fipa_dialogue = signing_dialogue.associated_fipa_dialogue last_fipa_message = cast(FipaMessage, fipa_dialogue.last_incoming_message) - if ( - last_fipa_message is not None - and last_fipa_message.performative == FipaMessage.Performative.ACCEPT - ): - self.context.logger.info( - "sending match accept to {}.".format( - fipa_dialogue.dialogue_label.dialogue_opponent_addr[-5:], - ) - ) + enforce(last_fipa_message is not None, "last message not recovered.") + if last_fipa_message.performative == FipaMessage.Performative.ACCEPT: fipa_msg = fipa_dialogue.reply( performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, target_message=last_fipa_message, - info={ - "tx_signature": signing_msg.signed_transaction, - "tx_id": signing_msg.dialogue_reference[0], - }, + info={"tx_signature": signing_msg.signed_transaction}, + ) + self.context.logger.info( + "sending {} to {} (as {}), message={}.".format( + fipa_msg.performative.value, + fipa_msg.to[-5:], + fipa_dialogue.role, + fipa_msg, + ) ) self.context.outbox.put_message(message=fipa_msg) elif ( - last_fipa_message is not None - and last_fipa_message.performative + last_fipa_message.performative == FipaMessage.Performative.MATCH_ACCEPT_W_INFORM ): - self.context.logger.info("sending atomic swap tx to ledger.") - tx_signed = signing_msg.signed_transaction - tx_digest = self.context.ledger_apis.get_api( - strategy.ledger_id - ).send_signed_transaction(tx_signed=tx_signed) - if tx_digest is None: - raise ValueError("Error when submitting tx.") - self.context.logger.info("tx_digest={}.".format(tx_digest)) - count = 0 - while ( - not self.context.ledger_apis.get_api( - strategy.ledger_id - ).is_transaction_settled(tx_digest) - and count < 20 - ): - self.context.logger.info( - "waiting for tx to confirm. Sleeping for 3 seconds ..." - ) - time.sleep(3.0) - count += 1 - tx_receipt = self.context.ledger_apis.get_api( - strategy.ledger_id - ).get_transaction_receipt(tx_digest=tx_digest) - if tx_receipt is None: - self.context.logger.info("failed to get tx receipt for atomic swap.") - elif tx_receipt.status != 1: - self.context.logger.info("failed to conduct atomic swap.") - else: - self.context.logger.info( - "successfully conducted atomic swap. Transaction digest: {}".format( - tx_digest - ) + ledger_api_dialogues = cast( + LedgerApiDialogues, self.context.ledger_api_dialogues + ) + ledger_api_msg, ledger_api_dialogue = ledger_api_dialogues.create( + counterparty=LEDGER_API_ADDRESS, + performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, + signed_transaction=signing_msg.signed_transaction, + ) + ledger_api_dialogue = cast(LedgerApiDialogue, ledger_api_dialogue) + ledger_api_dialogue.associated_signing_dialogue = signing_dialogue + self.context.logger.info( + "sending {} to ledger {}, message={}".format( + ledger_api_msg.performative, strategy.ledger_id, ledger_api_msg, ) - # contract = cast(ERC1155Contract, self.context.contracts.erc1155) # noqa: E800 - # result = contract.get_balances( # noqa: E800 - # address=self.context.agent_address, # noqa: E800 - # token_ids=[ # noqa: E800 - # int(key) # noqa: E800 - # for key in tx_message.terms.quantities_by_good_id.keys() # noqa: E800 - # ] # noqa: E800 - # + [ # noqa: E800 - # int(key) # noqa: E800 - # for key in tx_message.terms.amount_by_currency_id.keys() # noqa: E800 - # ], # noqa: E800 - # ) # noqa: E800 - result = 0 - self.context.logger.info("current balances: {}".format(result)) + ) + self.context.outbox.put_message(message=ledger_api_msg) else: - self.context.logger.warning( - "last message should be of performative accept or match accept." + enforce( + False, "last message should be of performative accept or match accept." ) def _handle_error( @@ -739,6 +611,161 @@ def _handle_invalid( ) +class LedgerApiHandler(Handler): + """Implement the ledger api handler.""" + + SUPPORTED_PROTOCOL = LedgerApiMessage.protocol_id # type: Optional[ProtocolId] + + def setup(self) -> None: + """Implement the setup for the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + ledger_api_msg = cast(LedgerApiMessage, message) + + # recover dialogue + ledger_api_dialogues = cast( + LedgerApiDialogues, self.context.ledger_api_dialogues + ) + ledger_api_dialogue = cast( + Optional[LedgerApiDialogue], ledger_api_dialogues.update(ledger_api_msg) + ) + if ledger_api_dialogue is None: + self._handle_unidentified_dialogue(ledger_api_msg) + return + + # handle message + if ledger_api_msg.performative is LedgerApiMessage.Performative.BALANCE: + self._handle_balance(ledger_api_msg) + elif ( + ledger_api_msg.performative + is LedgerApiMessage.Performative.TRANSACTION_DIGEST + ): + self._handle_transaction_digest(ledger_api_msg, ledger_api_dialogue) + elif ( + ledger_api_msg.performative + is LedgerApiMessage.Performative.TRANSACTION_RECEIPT + ): + self._handle_transaction_receipt(ledger_api_msg) + elif ledger_api_msg.performative == LedgerApiMessage.Performative.ERROR: + self._handle_error(ledger_api_msg, ledger_api_dialogue) + else: + self._handle_invalid(ledger_api_msg, ledger_api_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue(self, ledger_api_msg: LedgerApiMessage) -> None: + """ + Handle an unidentified dialogue. + + :param msg: the message + """ + self.context.logger.info( + "received invalid ledger_api message={}, unidentified dialogue.".format( + ledger_api_msg + ) + ) + + def _handle_balance(self, ledger_api_msg: LedgerApiMessage) -> None: + """ + Handle a message of balance performative. + + :param ledger_api_message: the ledger api message + """ + self.context.logger.info( + "starting balance on {} ledger={}.".format( + ledger_api_msg.ledger_id, ledger_api_msg.balance, + ) + ) + + def _handle_transaction_digest( + self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue + ) -> None: + """ + Handle a message of transaction_digest performative. + + :param ledger_api_message: the ledger api message + :param ledger_api_dialogue: the ledger api dialogue + """ + self.context.logger.info( + "transaction was successfully submitted. Transaction digest={}".format( + ledger_api_msg.transaction_digest + ) + ) + msg = ledger_api_dialogue.reply( + performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, + target_message=ledger_api_msg, + transaction_digest=ledger_api_msg.transaction_digest, + ) + self.context.outbox.put_message(message=msg) + self.context.logger.info("requesting transaction receipt.") + + def _handle_transaction_receipt(self, ledger_api_msg: LedgerApiMessage) -> None: + """ + Handle a message of transaction_receipt performative. + + :param ledger_api_message: the ledger api message + """ + is_transaction_successful = LedgerApis.is_transaction_settled( + ledger_api_msg.transaction_receipt.ledger_id, + ledger_api_msg.transaction_receipt.receipt, + ) + if is_transaction_successful: + self.context.logger.info( + "transaction was successfully settled. Transaction receipt={}".format( + ledger_api_msg.transaction_receipt + ) + ) + else: + self.context.logger.error( + "transaction failed. Transaction receipt={}".format( + ledger_api_msg.transaction_receipt + ) + ) + + def _handle_error( + self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue + ) -> None: + """ + Handle a message of error performative. + + :param ledger_api_message: the ledger api message + :param ledger_api_dialogue: the ledger api dialogue + """ + self.context.logger.info( + "received ledger_api error message={} in dialogue={}.".format( + ledger_api_msg, ledger_api_dialogue + ) + ) + + def _handle_invalid( + self, ledger_api_msg: LedgerApiMessage, ledger_api_dialogue: LedgerApiDialogue + ) -> None: + """ + Handle a message of invalid performative. + + :param ledger_api_message: the ledger api message + :param ledger_api_dialogue: the ledger api dialogue + """ + self.context.logger.warning( + "cannot handle ledger_api message of performative={} in dialogue={}.".format( + ledger_api_msg.performative, ledger_api_dialogue, + ) + ) + + class OefSearchHandler(Handler): """This class implements the oef search handler.""" @@ -891,3 +918,163 @@ def _handle_invalid( oef_search_msg.performative, oef_search_dialogue, ) ) + + +class ContractApiHandler(Handler): + """Implement the contract api handler.""" + + SUPPORTED_PROTOCOL = ContractApiMessage.protocol_id # type: Optional[ProtocolId] + + def setup(self) -> None: + """Implement the setup for the handler.""" + pass + + def handle(self, message: Message) -> None: + """ + Implement the reaction to a message. + + :param message: the message + :return: None + """ + contract_api_msg = cast(ContractApiMessage, message) + + # recover dialogue + contract_api_dialogues = cast( + ContractApiDialogues, self.context.contract_api_dialogues + ) + contract_api_dialogue = cast( + Optional[ContractApiDialogue], + contract_api_dialogues.update(contract_api_msg), + ) + if contract_api_dialogue is None: + self._handle_unidentified_dialogue(contract_api_msg) + return + + # handle message + if contract_api_msg.performative is ContractApiMessage.Performative.RAW_MESSAGE: + self._handle_raw_message(contract_api_msg, contract_api_dialogue) + elif ( + contract_api_msg.performative + is ContractApiMessage.Performative.RAW_TRANSACTION + ): + self._handle_raw_transaction(contract_api_msg, contract_api_dialogue) + elif contract_api_msg.performative == ContractApiMessage.Performative.ERROR: + self._handle_error(contract_api_msg, contract_api_dialogue) + else: + self._handle_invalid(contract_api_msg, contract_api_dialogue) + + def teardown(self) -> None: + """ + Implement the handler teardown. + + :return: None + """ + pass + + def _handle_unidentified_dialogue( + self, contract_api_msg: ContractApiMessage + ) -> None: + """ + Handle an unidentified dialogue. + + :param msg: the message + """ + self.context.logger.info( + "received invalid contract_api message={}, unidentified dialogue.".format( + contract_api_msg + ) + ) + + def _handle_raw_message( + self, + contract_api_msg: ContractApiMessage, + contract_api_dialogue: ContractApiDialogue, + ) -> None: + """ + Handle a message of raw_message performative. + + :param contract_api_message: the ledger api message + :param contract_api_dialogue: the ledger api dialogue + """ + self.context.logger.info("received raw message={}".format(contract_api_msg)) + signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) + signing_msg, signing_dialogue = signing_dialogues.create( + counterparty=self.context.decision_maker_address, + performative=SigningMessage.Performative.SIGN_MESSAGE, + raw_message=RawMessage( + contract_api_msg.raw_message.ledger_id, + contract_api_msg.raw_message.body, + is_deprecated_mode=True, + ), + terms=contract_api_dialogue.associated_fipa_dialogue.terms, + ) + signing_dialogue = cast(SigningDialogue, signing_dialogue) + signing_dialogue.associated_fipa_dialogue = ( + contract_api_dialogue.associated_fipa_dialogue + ) + self.context.decision_maker_message_queue.put_nowait(signing_msg) + self.context.logger.info( + "proposing the message to the decision maker. Waiting for confirmation ..." + ) + + def _handle_raw_transaction( + self, + contract_api_msg: ContractApiMessage, + contract_api_dialogue: ContractApiDialogue, + ) -> None: + """ + Handle a message of raw_transaction performative. + + :param contract_api_message: the ledger api message + :param contract_api_dialogue: the ledger api dialogue + """ + self.context.logger.info("received raw transaction={}".format(contract_api_msg)) + signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) + signing_msg, signing_dialogue = signing_dialogues.create( + counterparty=self.context.decision_maker_address, + performative=SigningMessage.Performative.SIGN_TRANSACTION, + raw_transaction=contract_api_msg.raw_transaction, + terms=contract_api_dialogue.associated_fipa_dialogue.terms, + ) + signing_dialogue = cast(SigningDialogue, signing_dialogue) + signing_dialogue.associated_fipa_dialogue = ( + contract_api_dialogue.associated_fipa_dialogue + ) + self.context.decision_maker_message_queue.put_nowait(signing_msg) + self.context.logger.info( + "proposing the transaction to the decision maker. Waiting for confirmation ..." + ) + + def _handle_error( + self, + contract_api_msg: ContractApiMessage, + contract_api_dialogue: ContractApiDialogue, + ) -> None: + """ + Handle a message of error performative. + + :param contract_api_message: the ledger api message + :param contract_api_dialogue: the ledger api dialogue + """ + self.context.logger.info( + "received ledger_api error message={} in dialogue={}.".format( + contract_api_msg, contract_api_dialogue + ) + ) + + def _handle_invalid( + self, + contract_api_msg: ContractApiMessage, + contract_api_dialogue: ContractApiDialogue, + ) -> None: + """ + Handle a message of invalid performative. + + :param contract_api_message: the ledger api message + :param contract_api_dialogue: the ledger api dialogue + """ + self.context.logger.warning( + "cannot handle contract_api message of performative={} in dialogue={}.".format( + contract_api_msg.performative, contract_api_dialogue, + ) + ) diff --git a/packages/fetchai/skills/tac_negotiation/skill.yaml b/packages/fetchai/skills/tac_negotiation/skill.yaml index ff609eb1bd..bb8227159f 100644 --- a/packages/fetchai/skills/tac_negotiation/skill.yaml +++ b/packages/fetchai/skills/tac_negotiation/skill.yaml @@ -1,6 +1,6 @@ name: tac_negotiation author: fetchai -version: 0.10.0 +version: 0.11.0 type: skill description: The tac negotiation skill implements the logic for an AEA to do fipa negotiation in the TAC. @@ -10,19 +10,19 @@ fingerprint: README.md: QmZucue4N3TX7BPe9CDZybfMQc1zpYbRKEVXAUGYRpcUfD __init__.py: QmcgZLvHebdfocqBmbu6gJp35khs6nbdbC649jzUyS86wy behaviours.py: QmcBCRFpM6ZtcuNZ12HUuwKgQKVdiAqQ6jDNMvzTGMpDdx - dialogues.py: QmdPAjBTpgrW9qP8SnAjKHWxBQRiGUqmgiiXMdrPKAJUKj - handlers.py: QmeSaLfQV3rzHuHPhzsBps9z4AbX1MNevTs2YTjnZaWuEA + dialogues.py: QmXnRTML2HBWTX3CQ1xJ41tpq2VsgkQY4BoboE3AoMpb99 + handlers.py: QmNcNs6sxdu4r97uGkGi8v5D82PgFzLwKHpMKhYZTd6pnu helpers.py: QmTJbGL8V6CLhbVhLekqKkHbu7cJMfBcv8DtWLSpkKP5tk - strategy.py: QmYcwqZ9ejjK2zxN8nKoc3kvBoGamTxwGQqXJrWV5s8STx - transactions.py: QmSFgjuvFMymkpCErkEFDiy5o2WCXPJXzVvmWyNjCFFT22 + strategy.py: QmTwwJPiutCXNvU4GYmLXpK4x3muVcAGTRehfLXUGKzRSJ + transactions.py: QmXdxq36sFqKUAiLri9QUPNhSa1ELN1EbAJKPWzHQVWtTW fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/fipa:0.7.0 -- fetchai/oef_search:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/tac_participation:0.9.0 +- fetchai/tac_participation:0.10.0 behaviours: clean_up: args: @@ -33,9 +33,15 @@ behaviours: search_interval: 5.0 class_name: GoodsRegisterAndSearchBehaviour handlers: + contract_api: + args: {} + class_name: ContractApiHandler fipa: args: {} class_name: FipaNegotiationHandler + ledger_api: + args: {} + class_name: LedgerApiHandler oef: args: {} class_name: OefSearchHandler @@ -43,12 +49,18 @@ handlers: args: {} class_name: SigningHandler models: + contract_api_dialogues: + args: {} + class_name: ContractApiDialogues default_dialogues: args: {} class_name: DefaultDialogues fipa_dialogues: args: {} class_name: FipaDialogues + ledger_api_dialogues: + args: {} + class_name: LedgerApiDialogues oef_search_dialogues: args: {} class_name: OefSearchDialogues @@ -72,3 +84,4 @@ models: pending_transaction_timeout: 30 class_name: Transactions dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/tac_negotiation/strategy.py b/packages/fetchai/skills/tac_negotiation/strategy.py index 23cadbaa3d..9fb26b70db 100644 --- a/packages/fetchai/skills/tac_negotiation/strategy.py +++ b/packages/fetchai/skills/tac_negotiation/strategy.py @@ -22,9 +22,11 @@ import copy import random from enum import Enum -from typing import Dict, List, Optional, Tuple, cast +from typing import Any, Dict, List, Optional, Tuple, cast +from aea.common import Address from aea.decision_maker.default import OwnershipState, Preferences +from aea.exceptions import enforce from aea.helpers.search.generic import ( AGENT_LOCATION_MODEL, AGENT_REMOVE_SERVICE_MODEL, @@ -37,9 +39,10 @@ Location, Query, ) -from aea.protocols.signing.message import SigningMessage +from aea.helpers.transaction.base import Terms from aea.skills.base import Model +from packages.fetchai.contracts.erc1155.contract import PUBLIC_ID from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue from packages.fetchai.skills.tac_negotiation.helpers import ( build_goods_description, @@ -57,6 +60,7 @@ "constraint_type": "==", } DEFAULT_SEARCH_RADIUS = 5.0 +CONTRACT_ID = str(PUBLIC_ID) class Strategy(Model): @@ -92,7 +96,9 @@ def __init__(self, **kwargs) -> None: location = kwargs.pop("location", DEFAULT_LOCATION) self._agent_location = { - "location": Location(location["longitude"], location["latitude"]) + "location": Location( + latitude=location["latitude"], longitude=location["longitude"] + ) } service_key = kwargs.pop("service_key", DEFAULT_SERVICE_KEY) self._set_service_data = {"key": service_key, "value": self._register_as.value} @@ -105,6 +111,8 @@ def __init__(self, **kwargs) -> None: } self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) + self._contract_id = CONTRACT_ID + super().__init__(**kwargs) @property @@ -145,6 +153,20 @@ def ledger_id(self) -> str: """Get the ledger id.""" return self._ledger_id + @property + def contract_id(self) -> str: + """Get the contract id.""" + return self._contract_id + + @property + def contract_address(self) -> str: + """Get the contract address.""" + contract_address = self.context.shared_state.get( + "erc1155_contract_address", None + ) + enforce(contract_address is not None, "ERC1155Contract address not set!") + return contract_address + def get_location_description(self) -> Description: """ Get the location description. @@ -400,9 +422,7 @@ def _generate_candidate_proposals(self, is_seller: bool): proposals.append(proposal) return proposals - def is_profitable_transaction( - self, signing_msg: SigningMessage, role: FipaDialogue.Role - ) -> bool: + def is_profitable_transaction(self, terms: Terms, role: FipaDialogue.Role) -> bool: """ Check if a transaction is profitable. @@ -411,7 +431,7 @@ def is_profitable_transaction( - check if the transaction is consistent with the locks (enough money/holdings) - check that we gain score. - :param signing_msg: the signing_msg + :param terms: the terms :param role: the role of the agent (seller or buyer) :return: True if the transaction is good (as stated above), False otherwise. @@ -422,12 +442,87 @@ def is_profitable_transaction( ownership_state_after_locks = transactions.ownership_state_after_locks( is_seller ) - if not ownership_state_after_locks.is_affordable_transaction(signing_msg.terms): + if not ownership_state_after_locks.is_affordable_transaction(terms): return False preferences = cast( Preferences, self.context.decision_maker_handler_context.preferences ) proposal_delta_score = preferences.utility_diff_from_transaction( - ownership_state_after_locks, signing_msg.terms + ownership_state_after_locks, terms ) return proposal_delta_score >= 0 + + @staticmethod + def terms_from_proposal( + proposal: Description, + sender: Address, + counterparty: Address, + role: FipaDialogue.Role, + ) -> Terms: + """ + Get the terms from a proposal. + + :param proposal: the proposal + :param sender: the sender of the proposal + :param counterparty: the receiver of the proposal + :return: the terms + """ + is_seller = role == FipaDialogue.Role.SELLER + goods_component = copy.copy(proposal.values) + [ # pylint: disable=expression-not-assigned + goods_component.pop(key) + for key in ["fee", "price", "currency_id", "nonce", "ledger_id"] + ] + # switch signs based on whether seller or buyer role + amount = proposal.values["price"] if is_seller else -proposal.values["price"] + fee = proposal.values["fee"] + if is_seller: + for good_id in goods_component.keys(): + goods_component[good_id] = goods_component[good_id] * (-1) + amount_by_currency_id = {proposal.values["currency_id"]: amount} + fee_by_currency_id = {proposal.values["currency_id"]: fee} + nonce = proposal.values["nonce"] + ledger_id = proposal.values["ledger_id"] + terms = Terms( + ledger_id=ledger_id, + sender_address=sender, + counterparty_address=counterparty, + amount_by_currency_id=amount_by_currency_id, + quantities_by_good_id=goods_component, + is_sender_payable_tx_fee=not is_seller, + nonce=nonce, + fee_by_currency_id=fee_by_currency_id, + ) + return terms + + @staticmethod + def kwargs_from_terms( + terms: Terms, signature: Optional[str] = None, + ) -> Dict[str, Any]: + """ + Get the contract api message kwargs from the terms. + + :param terms: the terms + :param signature: the signature + :return: the kwargs + """ + all_tokens = {**terms.amount_by_currency_id, **terms.quantities_by_good_id} + token_ids = [int(key) for key in all_tokens.keys()] + from_supplies = [ + 0 if int(value) <= 0 else int(value) for value in all_tokens.values() + ] + to_supplies = [ + 0 if int(value) >= 0 else -int(value) for value in all_tokens.values() + ] + kwargs = { + "from_address": terms.sender_address, + "to_address": terms.counterparty_address, + "token_ids": token_ids, + "from_supplies": from_supplies, + "to_supplies": to_supplies, + "value": 0, + "trade_nonce": int(terms.nonce), + } + if signature is not None: + kwargs["signature"] = signature + return kwargs diff --git a/packages/fetchai/skills/tac_negotiation/transactions.py b/packages/fetchai/skills/tac_negotiation/transactions.py index 3b7cacb7b1..ae7d76c550 100644 --- a/packages/fetchai/skills/tac_negotiation/transactions.py +++ b/packages/fetchai/skills/tac_negotiation/transactions.py @@ -19,25 +19,17 @@ """This module contains a class to manage transactions.""" -import copy import datetime from collections import defaultdict, deque from typing import Deque, Dict, List, Tuple, cast -from aea.common import Address from aea.decision_maker.default import OwnershipState from aea.exceptions import enforce -from aea.helpers.search.models import Description -from aea.helpers.transaction.base import RawMessage, Terms +from aea.helpers.transaction.base import Terms from aea.protocols.dialogue.base import DialogueLabel -from aea.protocols.signing.message import SigningMessage from aea.skills.base import Model -from packages.fetchai.skills.tac_negotiation.dialogues import ( - FipaDialogue, - SigningDialogue, - SigningDialogues, -) +from packages.fetchai.skills.tac_negotiation.dialogues import FipaDialogue MessageId = int @@ -54,14 +46,14 @@ def __init__(self, **kwargs) -> None: super().__init__(**kwargs) self._pending_proposals = defaultdict( lambda: {} - ) # type: Dict[DialogueLabel, Dict[MessageId, SigningMessage]] + ) # type: Dict[DialogueLabel, Dict[MessageId, Terms]] self._pending_initial_acceptances = defaultdict( lambda: {} - ) # type: Dict[DialogueLabel, Dict[MessageId, SigningMessage]] + ) # type: Dict[DialogueLabel, Dict[MessageId, Terms]] - self._locked_txs = {} # type: Dict[str, SigningMessage] - self._locked_txs_as_buyer = {} # type: Dict[str, SigningMessage] - self._locked_txs_as_seller = {} # type: Dict[str, SigningMessage] + self._locked_txs = {} # type: Dict[str, Terms] + self._locked_txs_as_buyer = {} # type: Dict[str, Terms] + self._locked_txs_as_seller = {} # type: Dict[str, Terms] self._last_update_for_transactions = ( deque() @@ -69,16 +61,14 @@ def __init__(self, **kwargs) -> None: self._nonce = 0 @property - def pending_proposals( - self, - ) -> Dict[DialogueLabel, Dict[MessageId, SigningMessage]]: + def pending_proposals(self,) -> Dict[DialogueLabel, Dict[MessageId, Terms]]: """Get the pending proposals.""" return self._pending_proposals @property def pending_initial_acceptances( self, - ) -> Dict[DialogueLabel, Dict[MessageId, SigningMessage]]: + ) -> Dict[DialogueLabel, Dict[MessageId, Terms]]: """Get the pending initial acceptances.""" return self._pending_initial_acceptances @@ -87,68 +77,6 @@ def get_next_nonce(self) -> str: self._nonce += 1 return str(self._nonce) - def generate_signing_message( - self, - performative: SigningMessage.Performative, - proposal_description: Description, - fipa_dialogue: FipaDialogue, - role: FipaDialogue.Role, - agent_addr: Address, - ) -> SigningMessage: - """ - Generate the transaction message from the description and the dialogue. - - :param proposal_description: the description of the proposal - :param fipa_dialogue: the fipa dialogue - :param role: the role of the agent (seller or buyer) - :param agent_addr: the address of the agent - :return: a transaction message - """ - is_seller = role == FipaDialogue.Role.SELLER - - goods_component = copy.copy(proposal_description.values) - [ # pylint: disable=expression-not-assigned - goods_component.pop(key) - for key in ["fee", "price", "currency_id", "nonce", "ledger_id"] - ] - # switch signs based on whether seller or buyer role - amount = ( - proposal_description.values["price"] - if is_seller - else -proposal_description.values["price"] - ) - fee = proposal_description.values["fee"] - if is_seller: - for good_id in goods_component.keys(): - goods_component[good_id] = goods_component[good_id] * (-1) - amount_by_currency_id = {proposal_description.values["currency_id"]: amount} - fee_by_currency_id = {proposal_description.values["currency_id"]: fee} - nonce = proposal_description.values["nonce"] - ledger_id = proposal_description.values["ledger_id"] - terms = Terms( - ledger_id=ledger_id, - sender_address=agent_addr, - counterparty_address=fipa_dialogue.dialogue_label.dialogue_opponent_addr, - amount_by_currency_id=amount_by_currency_id, - is_sender_payable_tx_fee=not is_seller, - quantities_by_good_id=goods_component, - nonce=nonce, - fee_by_currency_id=fee_by_currency_id, - ) - signing_dialogues = cast(SigningDialogues, self.context.signing_dialogues) - raw_message = RawMessage( - ledger_id=ledger_id, body=terms.sender_hash.encode("utf-8") - ) - signing_msg, signing_dialogue = signing_dialogues.create( - counterparty=self.context.decision_maker_address, - performative=performative, - terms=terms, - raw_message=raw_message, - ) - signing_dialogue = cast(SigningDialogue, signing_dialogue) - signing_dialogue.associated_fipa_dialogue = fipa_dialogue - return cast(SigningMessage, signing_msg) - def update_confirmed_transactions(self) -> None: """ Update model wrt to confirmed transactions. @@ -200,17 +128,14 @@ def cleanup_pending_transactions(self) -> None: next_date, next_item = queue[0] def add_pending_proposal( - self, - dialogue_label: DialogueLabel, - proposal_id: int, - signing_msg: SigningMessage, + self, dialogue_label: DialogueLabel, proposal_id: int, terms: Terms, ) -> None: """ Add a proposal (in the form of a transaction) to the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal - :param signing_msg: the transaction message + :param terms: the terms :raise AEAEnforceError: if the pending proposal is already present. :return: None @@ -220,11 +145,11 @@ def add_pending_proposal( and proposal_id not in self._pending_proposals[dialogue_label], "Proposal is already in the list of pending proposals.", ) - self._pending_proposals[dialogue_label][proposal_id] = signing_msg + self._pending_proposals[dialogue_label][proposal_id] = terms def pop_pending_proposal( self, dialogue_label: DialogueLabel, proposal_id: int - ) -> SigningMessage: + ) -> Terms: """ Remove a proposal (in the form of a transaction) from the pending list. @@ -239,21 +164,18 @@ def pop_pending_proposal( and proposal_id in self._pending_proposals[dialogue_label], "Cannot find the proposal in the list of pending proposals.", ) - signing_msg = self._pending_proposals[dialogue_label].pop(proposal_id) - return signing_msg + terms = self._pending_proposals[dialogue_label].pop(proposal_id) + return terms def add_pending_initial_acceptance( - self, - dialogue_label: DialogueLabel, - proposal_id: int, - signing_msg: SigningMessage, + self, dialogue_label: DialogueLabel, proposal_id: int, terms: Terms, ) -> None: """ Add an acceptance (in the form of a transaction) to the pending list. :param dialogue_label: the dialogue label associated with the proposal :param proposal_id: the message id of the proposal - :param signing_msg: the transaction message + :param terms: the terms :raise AEAEnforceError: if the pending acceptance is already present. :return: None @@ -263,11 +185,11 @@ def add_pending_initial_acceptance( and proposal_id not in self._pending_initial_acceptances[dialogue_label], "Initial acceptance is already in the list of pending initial acceptances.", ) - self._pending_initial_acceptances[dialogue_label][proposal_id] = signing_msg + self._pending_initial_acceptances[dialogue_label][proposal_id] = terms def pop_pending_initial_acceptance( self, dialogue_label: DialogueLabel, proposal_id: int - ) -> SigningMessage: + ) -> Terms: """ Remove an acceptance (in the form of a transaction) from the pending list. @@ -282,8 +204,8 @@ def pop_pending_initial_acceptance( and proposal_id in self._pending_initial_acceptances[dialogue_label], "Cannot find the initial acceptance in the list of pending initial acceptances.", ) - signing_msg = self._pending_initial_acceptances[dialogue_label].pop(proposal_id) - return signing_msg + terms = self._pending_initial_acceptances[dialogue_label].pop(proposal_id) + return terms def _register_transaction_with_time(self, transaction_id: str) -> None: """ @@ -296,13 +218,11 @@ def _register_transaction_with_time(self, transaction_id: str) -> None: now = datetime.datetime.now() self._last_update_for_transactions.append((now, transaction_id)) - def add_locked_tx( - self, signing_msg: SigningMessage, role: FipaDialogue.Role - ) -> None: + def add_locked_tx(self, terms: Terms, role: FipaDialogue.Role) -> None: """ Add a lock (in the form of a transaction). - :param signing_msg: the transaction message + :param terms: the terms :param role: the role of the agent (seller or buyer) :raise AEAEnforceError: if the transaction is already present. @@ -310,36 +230,36 @@ def add_locked_tx( """ as_seller = role == FipaDialogue.Role.SELLER - transaction_id = signing_msg.terms.id + transaction_id = terms.id enforce( transaction_id not in self._locked_txs, "This transaction is already a locked transaction.", ) self._register_transaction_with_time(transaction_id) - self._locked_txs[transaction_id] = signing_msg + self._locked_txs[transaction_id] = terms if as_seller: - self._locked_txs_as_seller[transaction_id] = signing_msg + self._locked_txs_as_seller[transaction_id] = terms else: - self._locked_txs_as_buyer[transaction_id] = signing_msg + self._locked_txs_as_buyer[transaction_id] = terms - def pop_locked_tx(self, signing_msg: SigningMessage) -> SigningMessage: + def pop_locked_tx(self, terms: Terms) -> Terms: """ Remove a lock (in the form of a transaction). - :param signing_msg: the transaction message + :param terms: the terms :raise AEAEnforceError: if the transaction with the given transaction id has not been found. :return: the transaction """ - transaction_id = signing_msg.terms.id + transaction_id = terms.id enforce( transaction_id in self._locked_txs, "Cannot find this transaction in the list of locked transactions.", ) - signing_msg = self._locked_txs.pop(transaction_id) + terms = self._locked_txs.pop(transaction_id) self._locked_txs_as_buyer.pop(transaction_id, None) self._locked_txs_as_seller.pop(transaction_id, None) - return signing_msg + return terms def ownership_state_after_locks(self, is_seller: bool) -> OwnershipState: """ @@ -351,14 +271,13 @@ def ownership_state_after_locks(self, is_seller: bool) -> OwnershipState: :return: the agent state with the locks applied to current state """ - signing_msgs = ( + all_terms = ( list(self._locked_txs_as_seller.values()) if is_seller else list(self._locked_txs_as_buyer.values()) ) - terms = [signing_msg.terms for signing_msg in signing_msgs] ownership_state = cast( OwnershipState, self.context.decision_maker_handler_context.ownership_state ) - ownership_state_after_locks = ownership_state.apply_transactions(terms) + ownership_state_after_locks = ownership_state.apply_transactions(all_terms) return ownership_state_after_locks diff --git a/packages/fetchai/skills/tac_participation/game.py b/packages/fetchai/skills/tac_participation/game.py index 50a508125c..72ec59a7a7 100644 --- a/packages/fetchai/skills/tac_participation/game.py +++ b/packages/fetchai/skills/tac_participation/game.py @@ -190,7 +190,9 @@ def __init__(self, **kwargs): self._search_query = kwargs.pop("search_query", DEFAULT_SEARCH_QUERY) location = kwargs.pop("location", DEFAULT_LOCATION) - self._agent_location = Location(location["longitude"], location["latitude"]) + self._agent_location = Location( + latitude=location["latitude"], longitude=location["longitude"] + ) self._radius = kwargs.pop("search_radius", DEFAULT_SEARCH_RADIUS) self._ledger_id = kwargs.pop("ledger_id", DEFAULT_LEDGER_ID) diff --git a/packages/fetchai/skills/tac_participation/skill.yaml b/packages/fetchai/skills/tac_participation/skill.yaml index 018167aae6..2b0fb9eea2 100644 --- a/packages/fetchai/skills/tac_participation/skill.yaml +++ b/packages/fetchai/skills/tac_participation/skill.yaml @@ -1,6 +1,6 @@ name: tac_participation author: fetchai -version: 0.9.0 +version: 0.10.0 type: skill description: The tac participation skill implements the logic for an AEA to participate in the TAC. @@ -11,14 +11,14 @@ fingerprint: __init__.py: QmcVpVrbV54Aogmowu6AomDiVMrVMo9BUvwKt9V1bJpBwp behaviours.py: QmX3UbuLohnPSLM2W6LrWcZyo4zXCr1YN5Bznu61v27SZC dialogues.py: QmZrJ1d9mhtfkxRg5QfsKbbtVZFa6cKna4anRWHvzNTEdD - game.py: QmWkoofaEnbaNp2X63HCi6pnrpJ9b8i3t7bV36GqeuVNgo + game.py: QmY5dV1SLK2Y1mreasZJRDXMDnJ82Un9XjR6TD8KdEhaLF handlers.py: QmSseAwQqHsSmj2eBPPnC9eygNSSwL1kheRVsgbLqyeDvV fingerprint_ignore_patterns: [] contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/oef_search:0.7.0 -- fetchai/tac:0.7.0 +- fetchai/oef_search:0.8.0 +- fetchai/tac:0.8.0 skills: [] behaviours: tac_search: @@ -61,3 +61,4 @@ models: args: {} class_name: TacDialogues dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/thermometer/skill.yaml b/packages/fetchai/skills/thermometer/skill.yaml index cfd58afabc..2585d9ee3e 100644 --- a/packages/fetchai/skills/thermometer/skill.yaml +++ b/packages/fetchai/skills/thermometer/skill.yaml @@ -1,6 +1,6 @@ name: thermometer author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The thermometer skill implements the functionality to sell data. license: Apache-2.0 @@ -15,12 +15,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_seller:0.13.0 +- fetchai/generic_seller:0.14.0 behaviours: service_registration: args: @@ -69,3 +69,4 @@ models: dependencies: pyserial: {} temper-py: {} +is_abstract: false diff --git a/packages/fetchai/skills/thermometer_client/skill.yaml b/packages/fetchai/skills/thermometer_client/skill.yaml index cdcb6fe238..65d8f0771e 100644 --- a/packages/fetchai/skills/thermometer_client/skill.yaml +++ b/packages/fetchai/skills/thermometer_client/skill.yaml @@ -1,6 +1,6 @@ name: thermometer_client author: fetchai -version: 0.11.0 +version: 0.12.0 type: skill description: The thermometer client skill implements the skill to purchase temperature data. @@ -16,12 +16,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_buyer:0.12.0 +- fetchai/generic_buyer:0.13.0 behaviours: search: args: @@ -75,3 +75,4 @@ models: service_id: thermometer_data class_name: Strategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/weather_client/skill.yaml b/packages/fetchai/skills/weather_client/skill.yaml index 074dc33692..fb603cf822 100644 --- a/packages/fetchai/skills/weather_client/skill.yaml +++ b/packages/fetchai/skills/weather_client/skill.yaml @@ -1,6 +1,6 @@ name: weather_client author: fetchai -version: 0.11.0 +version: 0.12.0 type: skill description: The weather client skill implements the skill to purchase weather data. license: Apache-2.0 @@ -15,12 +15,12 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_buyer:0.12.0 +- fetchai/generic_buyer:0.13.0 behaviours: search: args: @@ -74,3 +74,4 @@ models: service_id: weather_data class_name: Strategy dependencies: {} +is_abstract: false diff --git a/packages/fetchai/skills/weather_station/dummy_weather_station_data.py b/packages/fetchai/skills/weather_station/dummy_weather_station_data.py index bfde1963c9..a097dbd4f9 100644 --- a/packages/fetchai/skills/weather_station/dummy_weather_station_data.py +++ b/packages/fetchai/skills/weather_station/dummy_weather_station_data.py @@ -28,7 +28,7 @@ from typing import Dict, Union -logger = logging.getLogger( +_default_logger = logging.getLogger( "aea.packages.fetchai.skills.weather_station.dummy_weather_station_data" ) @@ -64,7 +64,9 @@ cur.close() con.commit() if con is not None: - logger.debug("Weather station: I closed the db after checking it is populated!") + _default_logger.debug( + "Weather station: I closed the db after checking it is populated!" + ) con.close() @@ -107,7 +109,7 @@ def add_data(tagged_data: Dict[str, Union[int, datetime.datetime]]) -> None: tagged_data["wind_gust"], ), ) - logger.info("Wheather station: I added data in the db!") + _default_logger.info("Wheather station: I added data in the db!") cur_.close() con_.commit() con_.close() diff --git a/packages/fetchai/skills/weather_station/skill.yaml b/packages/fetchai/skills/weather_station/skill.yaml index fde55e17ea..aa62cc9a79 100644 --- a/packages/fetchai/skills/weather_station/skill.yaml +++ b/packages/fetchai/skills/weather_station/skill.yaml @@ -1,6 +1,6 @@ name: weather_station author: fetchai -version: 0.12.0 +version: 0.13.0 type: skill description: The weather station skill implements the functionality to sell weather data. @@ -12,19 +12,19 @@ fingerprint: behaviours.py: QmfPE6zrMmY2QARQt3gNZ2oiV3uAqvAQXSvU3XWnFDUQkG db_communication.py: QmSLm4jic8JbP2wz35WyWevc9H2ZxsEYfaBMWcEx4pzVcy dialogues.py: QmPXfUWDxnHDaHQqsgtVhJ2v9dEgGWLtvEHKFvvFcDXGms - dummy_weather_station_data.py: QmVurNWKjXvCZk62JdYLMvrhCuU3kUpBqcRGQs2E5KnrV7 + dummy_weather_station_data.py: QmNvUTxRnLQbVak2jWZ4JarhUxn1T2n3S3yERjqaKatHcU handlers.py: QmNujxh4FtecTar5coHTJyY3BnVnsseuARSpyTLUDmFmfX strategy.py: QmVDAXUA1YBGa2PSQKDekyf5j1qZfEfLvZpuLJ6MnURvS8 fingerprint_ignore_patterns: - '*.db' contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: -- fetchai/generic_seller:0.13.0 +- fetchai/generic_seller:0.14.0 behaviours: service_registration: args: @@ -73,3 +73,4 @@ models: unit_price: 10 class_name: Strategy dependencies: {} +is_abstract: false diff --git a/packages/hashes.csv b/packages/hashes.csv index 89725bf818..1c9c2a4ca4 100644 --- a/packages/hashes.csv +++ b/packages/hashes.csv @@ -1,73 +1,75 @@ -fetchai/agents/aries_alice,QmYHiYqWNEhmzhBNQCE6cUzRBtnmPbbC5yT47rSZLnoGgk -fetchai/agents/aries_faber,QmTSTfkJW1UtKtsRjboCQa7BGXrBPJsaKmAwZ5B1vgMwPp -fetchai/agents/car_data_buyer,QmNuEJZWSZrS4JSbuRHaRg82kQGjMJxYTrWFwHTBQFmE46 -fetchai/agents/car_detector,QmebrWVrc9DiQW5SMqz7M2MU7wjRb5qMK1VqhemUoErjVk -fetchai/agents/erc1155_client,QmbeW3bPTgxRtromSt8EArKZU1Mmx2pSP497Kyx2WYTCsy -fetchai/agents/erc1155_deployer,QmWGF4m36HZvWpa49AE6cQmWpxrWvrCu7PL5wKkGUQYKsK -fetchai/agents/generic_buyer,QmfMAT5Vk7vengDfMiH5D2iajAhmtWMUWM2CaVKz5ucxRG -fetchai/agents/generic_seller,Qmb9PnhRUkc2jBMpDhEeeuqhxHYu8v3ANxXXrat2z9gCU5 -fetchai/agents/gym_aea,QmU4dvEtznNgVpGjo9ptMf46tcuhVAu5tv8dgNGgzEak8p -fetchai/agents/ml_data_provider,QmeqfbEW6J11SKjrqnCRxjZCMr4VWvr9K1gJRd7ZCWP3uF -fetchai/agents/ml_model_trainer,QmWnBK6TGFro2VrtcwwBq1eztiT5y4DV92DdeyHPQqGzcY -fetchai/agents/my_first_aea,QmYqeuaG7mkxqPZTM2qUHP2bMm1A7VsSDLi6BfW6SaFbuZ -fetchai/agents/simple_service_registration,QmY4oz8n2zjJnAa3vHRRDuzpZi8Taf8qsScX7NJoXrTk3u -fetchai/agents/tac_controller,QmWVvJemSdE5N5iyMuDt5ZHh1FA32oijHxBqq91zaNkdY3 -fetchai/agents/tac_controller_contract,QmSZRM9QqFpZDosBuY1gCgX4Ho8icAMAnyh6VMti6UdwVE -fetchai/agents/tac_participant,QmRmswKxJ9mdQe6zFRqxr9U1ZpYTybQf3DKsrAySt39aVc -fetchai/agents/tac_participant_contract,QmVuA9fJAM2jrJH6PCfcq12mgp6999LFCktwkLNjhHhsLz -fetchai/agents/thermometer_aea,QmT1GNUCcB6xqnmB1vv3jTs4HqeYM6tLnnL4TSjv5raHpk -fetchai/agents/thermometer_client,QmdWMJCb7Pkz8F3emJ8wKhNWpnEUXHpBFQTGcmJyziu6kH -fetchai/agents/weather_client,Qmd5iiwyqEuw7Fg8NkdLsNjkuZQRatNRADpXVXwG13BvtP -fetchai/agents/weather_station,QmRAs7AY2R9RNi5M4gnSDiQzLb58z4nuCYAnG2UAnv3XC2 -fetchai/connections/gym,Qmack3EaCW6ARrHrtCnJmL3kR2xrHutJmgD8uoRVGFz6kH -fetchai/connections/http_client,QmaCa98anLuMAduNWtnApEwLWBcqdVkNBREBZ9T5VPSaUm -fetchai/connections/http_server,QmT6JV2sB1rv7XZgx1bEpMjfQJAtpq775D2n8yvzKzSXYK -fetchai/connections/ledger,QmPwbSbzK66j147Bjjxaqp3jRAR2ui59zthqFAqB5i7Uxy -fetchai/connections/local,QmNXMDuB7vfGrhv2Q4R8REDicSUgXfVPd7x1tAP3847jsr -fetchai/connections/oef,QmT6nbEYgjHPTwoXqV5FNeg7SUaraXGQGUhGeVGEhqXVDg -fetchai/connections/p2p_libp2p,QmbPgTY8am7PgLoP3ksaHyuwZzfFGXN4cBrvjV9byramEh -fetchai/connections/p2p_libp2p_client,QmSbfL2DT9L1oooKn2pme1tjivEzkzaiRqJ4zahB4Jb27b -fetchai/connections/p2p_stub,QmecGYtXsb7HA2dSWrw2ARNTPFqZsKXAbVroBo57cdUQSG +fetchai/agents/aries_alice,Qme6aMz6SBhAPrq2tq1CGTWPacM4xmzwN2xK26xXAxd8ND +fetchai/agents/aries_faber,QmQRe4ctBxjHJtpT59GZ5PZ1iWa4BUjZi3en6ngLaZe4aW +fetchai/agents/car_data_buyer,QmU3dPfJCFUWAkgJ3P7F4Hf18qenQ7xoAK1woMM5WQY9Fj +fetchai/agents/car_detector,QmSk6UYgPjtqi6nybSb2YhbANCgCwv5jxnDJERi97sXxje +fetchai/agents/erc1155_client,QmaDiuZyr1H9n5qc5Vm2HMznG8bbEKzqje26wBrSDn7vrZ +fetchai/agents/erc1155_deployer,QmQRrL3LvcW59yGXqtu3cEdZ4WAbpxdhZ3GEUen9pQtht1 +fetchai/agents/generic_buyer,QmReGdHpyZb2aw8Dh7qttKHPDyvLoC8BLYdwziBLpjucsb +fetchai/agents/generic_seller,QmSuGv6xdZWXMJuurMSpGVhrCW7qd6nuXSgrKuViTte5Lo +fetchai/agents/gym_aea,QmWQyAw2Vv3v6uM3UUnfMTi2cvtZHAzeSi3c8N7qPrVfPh +fetchai/agents/ml_data_provider,QmQwnsjMMV2ADK4m4CQEcZTcNEZ1SAwKn3H5mMZA1PJHnf +fetchai/agents/ml_model_trainer,QmbsAvQMdMPLAo3yfky81B14RPdeCdkR26gYGCDrDwcgjY +fetchai/agents/my_first_aea,QmX2SqJbYeUZ2qLfbhBE5j6N7JmjUYUvRUnmP955YRFfqC +fetchai/agents/simple_service_registration,QmRMLFKpiAkqD2v3CkdgFdZXSBmwv2BJuqgv7wHeB6KAQz +fetchai/agents/tac_controller,QmZUq11HRbxVM69TeMYnoWGBicTQPrkj3Sbf3V7sYtmBAY +fetchai/agents/tac_controller_contract,QmShNpy3JW9ZMLpqHfWABaw7LcF29rz8do7CMYcietxUJ8 +fetchai/agents/tac_participant,QmS9KddcwLTnNsyaefskLBDvHTtXkj2xPEWVG3pjXQnksP +fetchai/agents/tac_participant_contract,QmXrW2V1Z3dPyV7pqiDdeTkepnra6fhBt2rztcQPDCgvG3 +fetchai/agents/thermometer_aea,QmNN7XcjNp67QUEfaSypZsbainmBshAZSqD7itx28ef8g3 +fetchai/agents/thermometer_client,Qmdvvk9HreF5V4zoWLoM64A5n5HjzdxwKVLgTVcyxJHdda +fetchai/agents/weather_client,QmbF8UfJKiJh8hLXuTPoMfK4bgUe1AquMq7nGcgQAZqYn6 +fetchai/agents/weather_station,QmcgbSf97vYTvcV3SrMUf9pt1EnNTNS6EUYbBVnkbVNVKZ +fetchai/connections/gym,QmaVjWPpipys1swkQy64GitynFjyTNZMaVRex4k2RiLnyy +fetchai/connections/http_client,QmPL4gBPPkJgX2s3ZpwgvA4LU7zad159nzS3MTTREPLWC3 +fetchai/connections/http_server,Qmdc7MCZVmWk76k2pHB2zBwTJ9iokWpuZ8iqzzreBpVEgn +fetchai/connections/ledger,QmQz561g9MqeTviwbVRUKSY4m1rP5htevi23GKcMYfVwFb +fetchai/connections/local,QmVw7RDiP5uBtUKodcvvS3uvojPYQGHKMjavUJ2iE7aNB4 +fetchai/connections/oef,QmUnHLMteyQvJKLg1EA5FgwugHz5rjZZFboAL5KB3NgeU1 +fetchai/connections/p2p_libp2p,QmStTaRFFdh6nTm68A9C2XPh2wR1teBPM3k1y9mC9hVsiz +fetchai/connections/p2p_libp2p_client,QmV3Tp1d9sUMQpyvtYnsTQPudk1UMQrmaccWnPb5xGebss +fetchai/connections/p2p_stub,QmPstikxKerbMnnjUJrMsSHpEx2UXAhPDa6N5nzB3qeRJB fetchai/connections/scaffold,QmNUta43nLexAHaXLgmLQYEjntLXSV6MLNvc6Q2CTx7eBS -fetchai/connections/soef,QmaaC1Va79rkztC2GDEwDXEugkiDGqe9hPkPgYsHRdn7d7 -fetchai/connections/stub,QmQxzFLTBjPUMkpE1uBdkoyhb7iXY4k8VPzyykpjhuUcK1 -fetchai/connections/tcp,QmPADQqZJQ4cw1ecdMC2wR7NwFCdk7MYzvmyP62nbfGk7E -fetchai/connections/webhook,QmWcm3WGxcm5JvhuYRyauQp5wmzFAkV61e2aYpDvYmNvWd -fetchai/contracts/erc1155,QmWu22zW9AVMBVySwF413JMiT5dcWsLsU2gk1KvFYWAwPq +fetchai/connections/soef,QmcRKha58cJA15nGQYCamvt5rmAQgfFwsQYdWfJF5HvUSc +fetchai/connections/stub,QmUEv1bMiFqk3wSR3EnYpNDrufwmHckFdpWKwjtcRjdzjN +fetchai/connections/tcp,QmdiYVdNCHFvYWhNMGLhAnXweoB6DsiqdzG7i37FQApPew +fetchai/connections/webhook,QmRXxSvtwzvSdEXjY5UeUuoJBD4qzofMkQCzRxeoQUBmJH +fetchai/contracts/erc1155,QmbqeTU1TDRVHfqdX6VNf9sWdoqjRAcC1i25ZaCwZXjntY fetchai/contracts/scaffold,QmTKkXifQKq5sTVdVKeD9UGnDwdJoEAoJDjY6qUPt9Bsv2 -fetchai/protocols/contract_api,QmTHPWZuBy8kZzppuy2EJ5NrriiygY9RXP93fcrRUbaMNw -fetchai/protocols/default,QmNjse6y7aBhtT2WZh3t7JYuGb4dYd2BmJzcYK3BeX641t -fetchai/protocols/fipa,QmXdUfENAXFUntcybMjWvo816ccxG1Rf4DEv41TQVC85XM -fetchai/protocols/gym,QmTKp65qxtt55NxJohT5AK71kVeHwfATgPnRVffYQzL1ND -fetchai/protocols/http,QmePja5ZBEv6nN7FfnURzgPa445vK9GQ2rdBPugZMVbkWR -fetchai/protocols/ledger_api,QmS1zebL2Q6qPL8B6tD9iCLmXSCgAXSnCFmfeHAqGDdWa3 -fetchai/protocols/ml_trade,QmZoHGtvrAr5z4FrL7BTyKew2eQBudCJXwTpPciwrmcpuP -fetchai/protocols/oef_search,QmWiRygk9JHtCJ6FbSK49uwkCzsSu6wJVqc27iQEwGphFR +fetchai/protocols/contract_api,QmfHLj6VAmqCXzxZ11Vz7FLE3obni2UCLMnKxyqFA1iJCF +fetchai/protocols/default,QmWYLfSXcMkvwdAzKmdD3rweSRNVwX5UTgQaH8tECGBxv4 +fetchai/protocols/fipa,QmdiRToU57bqkzD23fiz7LmtkyCDaeunzGFRdoSV9ZFZLw +fetchai/protocols/gym,QmfDY4gVBraTejz9EXYQ2H4RN6aKkgFpQD1BVCpRMAs55S +fetchai/protocols/http,QmcjgaDxKk2tXijwdzfgokHVydF8htSmJQ8C7JTYoNfQiN +fetchai/protocols/ledger_api,QmTrEGA1sDotji4GBoW2e4foFXwpFQrFX5g9bC82VSnHBn +fetchai/protocols/ml_trade,QmeCUoGByR2C57mfCNQ9N3HaXZv1NvLxUSQ4osoZmqLHLx +fetchai/protocols/oef_search,QmeU9uYHmv7MEefVsNj2kbyYxhyGphTmPpgBZ2tAydpCmt fetchai/protocols/scaffold,QmaQJxMae5XaHKDc838pcGEKhhYSUyKHGWiMfU6zEcVHsy -fetchai/protocols/signing,Qmazm8bkwpPVV7BiJZx2Aha96AcmAxwiYbjgYGwvcgXGYi -fetchai/protocols/state_update,QmS1xuSYYgHkQq8Ww2btsS88rTXXjhchyStzUfcWYQKNda -fetchai/protocols/tac,QmYuMW1tNE5eLdKEo5T6uyTYmVmGuavQLT4xx3hDaGNLJJ -fetchai/skills/aries_alice,QmefZJ27H36RZYgA6eEcnbv8yNPyf3YYXnBm5E88C8xBLQ -fetchai/skills/aries_faber,QmYAQ8gVsokPsXHd7RDXb8KCkmH7XpB8JC7VMx3QrJX3Uh -fetchai/skills/carpark_client,QmYjZXvq2h2DCXZF2ba9W5cvJAvsRFvBrw7h7EwcvWThfW -fetchai/skills/carpark_detection,QmV5qPay1FMk3tsygygRL4ThsBRxFL5nZe7x5NrXFXpSJN -fetchai/skills/echo,QmYKGMNs5NU7ycWXRBQnKrfBcg5HPnUnH13K1YFnPLQgW6 -fetchai/skills/erc1155_client,Qmd6KJbis7Me39teeHA8y3JZHQrS1hjovMxhqiwViRZhgc -fetchai/skills/erc1155_deploy,QmZHDmFaxJmieLaCUJKBwp1EsTEAAxbFvFkfLjL4fVyhfK -fetchai/skills/error,QmTFECCHQHaSN3wP16EYgKeqgSgxBvfCRwX7F5uvxfkN3a -fetchai/skills/generic_buyer,QmXFFS6V4ZaNWNqVdx5TnaqU5DizQDd9aWjbE9ACqCZsEU -fetchai/skills/generic_seller,QmQivEfxdFGkWmaw9iMHJmEmgVv1TAAqihNT9CM8x3XzV2 -fetchai/skills/gym,QmUqB5EJafEGWX5xkTngAgXW2857RnCEk6St2HE5x9hgtZ -fetchai/skills/http_echo,QmQDMH3L5F5AzTDurwZkSd8S4LCyPVJhtrqrXe1KCa9NGk -fetchai/skills/ml_data_provider,QmaKyLjyvcj35bCHaKKo15MeF7k48hKPCfFbrtvXCogYwV -fetchai/skills/ml_train,QmNoV8PRFQj7WEj9We6sEtX9G1Qb8qESoZ2QRK4MybVNoG -fetchai/skills/scaffold,QmRVUvLnRRPjgNDYw2gNZFY8MFBhGez1CfvY7z3N8yFMux -fetchai/skills/simple_service_registration,QmVTVvRoxKsP9N97TRGrj92bgPcEYhKYfYtohVuQ8BNzbr -fetchai/skills/tac_control,QmQqF4n4M1kYBHUFAUXsWoEdGZBDwvjwBDmtot5bzWtRMF -fetchai/skills/tac_control_contract,QmQaYhgXfyZ3xP6USHWsVQ76hTQnuLxHUt2YGGXHEpUHqx -fetchai/skills/tac_negotiation,QmU6rghC95ESWaxM8gENDuRxuCXDgXQR3xkhrqFurQSEEo -fetchai/skills/tac_participation,QmdiCxur8qx2beRKz2HxGTF4GLzM6k7GEiecy67NvQ4iKt -fetchai/skills/thermometer,QmTud9hXxRq8wRzcf7hVeszou3crmAEZBaw5MjV8x4XtH1 -fetchai/skills/thermometer_client,QmXBkiJU23UVygpX5Zz34DaCNiKKGxkgJMs3RfLEPLSdTH -fetchai/skills/weather_client,QmWohsaia5um76sce6gXN9jMeuHve26v6dm4xWHdwRaGA3 -fetchai/skills/weather_station,QmdcFK1rBSMbNVLXLwFyNizCfa77xUCUkEeBpvJChQurDV +fetchai/protocols/signing,QmVCe2ZsT3U9stz23uAFFjctTJjy5XRAVnngJYeNANF9Ww +fetchai/protocols/state_update,Qmdo6GidjoBnZgwYtH9QjdDJeAsjGzxSxxxWtbpHJyx9XG +fetchai/protocols/t_protocol,Qmb7pLnen3VmikYN12Fq6mWgRLanSTnxLtYpcP1g2NYz74 +fetchai/protocols/t_protocol_no_ct,QmY5WNHZTDsQWqdRcW5TCBtvPd8j6pX7PgCBwEqfMMYufb +fetchai/protocols/tac,QmfAvonoD7oM7LBejz9QPbbhF9CbQXbQVkbaDhDwK5HUSS +fetchai/skills/aries_alice,Qmb1BzZ9s5uRxPiqj5gT47JN36LZqGpe3uPP8zv4EiLNDZ +fetchai/skills/aries_faber,QmWeJTouqVPJocuFJjDijwCdF1vNfvShFLPNdSSQQrxKD8 +fetchai/skills/carpark_client,Qmd1rUfLVA9MhJThhUCxeAMdcoRJAQZAmjiLZv58X6EQ2U +fetchai/skills/carpark_detection,QmYnJmmKzhghbV4uXphQfCg1S4TEQBLg6dsGzJ4i8fj1yY +fetchai/skills/echo,QmP1dwNe8WqnytCwU17bhpVnq8WkDGzryqMsjnbFAZUuu1 +fetchai/skills/erc1155_client,QmcvGurDx1BxExWqFc1gkJbo6LnVawNGeSS2TjpneqN1pa +fetchai/skills/erc1155_deploy,QmTeJscJ5eDUbr4i8iVW9ij2a78vM23MuEBDq7btExhcft +fetchai/skills/error,QmQdJfLrNxHxXMswvntbAqxJWwP5wWgaboWxMiT28pT3Rh +fetchai/skills/generic_buyer,QmdUqBJTij8pJxYBRBasvPE56ST9j6houRomxj6TB5pJgn +fetchai/skills/generic_seller,Qmaop7p743Wn911M11kgr3ek62MjC56iqihD26iByJ8L4m +fetchai/skills/gym,QmVZPhjB6PQbYH5f5XNFqsdf63aEBxpLtW5Zb7kRUc4S9L +fetchai/skills/http_echo,QmSrhSVBjR6EdYZ7d6pSqRexBk7jpqcfHqKs9xRegDLGqS +fetchai/skills/ml_data_provider,QmSRCvaVQs6MDnpNb7bk2zqks9sVGXvxC3bEkBTxTLhftZ +fetchai/skills/ml_train,QmRg6C7JSSZEheCyLkaMXVwgMkAcTtGHAjv6XWVcNVuQJw +fetchai/skills/scaffold,QmWXsefztjMfLAsmHGAdLLAvZJb1WxGPpDfx3cS1FWSEFT +fetchai/skills/simple_service_registration,QmREkUut9Xr2pwwkJ7EXxzpTC4M1ub9R18CwY9YA9J7QJb +fetchai/skills/tac_control,QmQ1PPbuKfw4JJsYWmhQdU17y5yVZTJ1pxg2T9qVKqJ6SH +fetchai/skills/tac_control_contract,QmWAEt7nH2sdzGec4LPByR7VrZLnN8n4ptoxgYP9Y9exhE +fetchai/skills/tac_negotiation,QmditL1KuEmHt9T727Um7ZXU7geJ7GAqMDwK4ztzL1JTWv +fetchai/skills/tac_participation,QmVExZCQgqsFAAYwLtNxFog7HoMuRVSS2hkMR9uukCCy5t +fetchai/skills/thermometer,QmNd289PBg5qyDLjQexNoN1Y2dQDhP3jsT65AKiMwJFvzN +fetchai/skills/thermometer_client,QmaPRBznjimpX18uRB7JbtEdU82cimMxXWDPnbk9T4VwQ6 +fetchai/skills/weather_client,QmewFx3nxrFqQpLutXY6fr8AMQtJfYoeK4uXA8sSHG883Z +fetchai/skills/weather_station,QmcMJ6uFxd172cyPze87FMRECRxSZNtgShfZzEfFjKpt97 diff --git a/scripts/generate_api_docs.py b/scripts/generate_api_docs.py index 90ad331f18..04051a4560 100755 --- a/scripts/generate_api_docs.py +++ b/scripts/generate_api_docs.py @@ -77,6 +77,7 @@ "aea.helpers.multiple_executor": "api/helpers/multiple_executor.md", "aea.helpers.pipe": "api/helpers/pipe.md", "aea.helpers.win32": "api/helpers/win32.md", + "aea.helpers.yaml_utils": "api/helpers/yaml_utils.md", "aea.identity.base": "api/identity/base.md", "aea.mail.base": "api/mail/base.md", "aea.protocols.base": "api/protocols/base.md", @@ -104,7 +105,8 @@ "aea.skills.tasks": "api/skills/tasks.md", "aea.skills.error.handlers": "api/skills/error/handlers.md", "aea.test_tools.generic": "api/test_tools/generic.md", - "aea.test_tools.test_cases": "api/helpers/test_cases.md", + "aea.test_tools.test_cases": "api/test_tools/test_cases.md", + "aea.test_tools.test_skill": "api/test_tools/test_skill.md", } diff --git a/scripts/generate_ipfs_hashes.py b/scripts/generate_ipfs_hashes.py index 04b6020faf..cda4bc8f90 100755 --- a/scripts/generate_ipfs_hashes.py +++ b/scripts/generate_ipfs_hashes.py @@ -53,7 +53,7 @@ _compute_fingerprint, ) from aea.configurations.loader import ConfigLoaders -from aea.helpers.base import yaml_dump, yaml_dump_all +from aea.helpers.yaml_utils import yaml_dump, yaml_dump_all AUTHOR = "fetchai" @@ -117,6 +117,8 @@ def package_type_and_path(package_path: Path) -> Tuple[PackageType, Path]: (PackageType.AGENT, TEST_PATH / "dummy_aea"), (PackageType.CONNECTION, TEST_PATH / "dummy_connection"), (PackageType.CONTRACT, TEST_PATH / "dummy_contract"), + (PackageType.PROTOCOL, TEST_PATH / "generator" / "t_protocol"), + (PackageType.PROTOCOL, TEST_PATH / "generator" / "t_protocol_no_ct"), (PackageType.SKILL, TEST_PATH / "dependencies_skill"), (PackageType.SKILL, TEST_PATH / "exception_skill"), (PackageType.SKILL, TEST_PATH / "dummy_skill"), diff --git a/scripts/update_package_versions.py b/scripts/update_package_versions.py index e0ef1d5010..9bebf5eae7 100644 --- a/scripts/update_package_versions.py +++ b/scripts/update_package_versions.py @@ -59,6 +59,7 @@ "agents": "aea-config.yaml", } PUBLIC_ID_REGEX = PublicId.PUBLIC_ID_REGEX[1:-1] +TEST_PROTOCOLS = ["t_protocol", "t_protocol_no_ct"] def get_protocol_specification_header_regex(public_id: PublicId) -> Pattern: @@ -259,6 +260,8 @@ def get_all_package_ids() -> Set[PackageId]: now_by_type = split_hashes_by_type(now) for type_, name_to_hashes in now_by_type.items(): for name, _ in name_to_hashes.items(): + if name in TEST_PROTOCOLS: + continue configuration_file_path = get_configuration_file_path(type_, name) public_id = get_public_id_from_yaml(configuration_file_path) package_id = PackageId(PackageType(type_[:-1]), public_id) @@ -354,7 +357,7 @@ def _sort_in_update_order(package_ids: Set[PackageId]) -> List[PackageId]: semver.VersionInfo.parse(x.public_id.version), x.public_id.author, x.public_id.name, - x.package_type, + x.package_type.value, ), reverse=True, ) diff --git a/scripts/whitelist.py b/scripts/whitelist.py index e16caa277f..0df987dfb3 100644 --- a/scripts/whitelist.py +++ b/scripts/whitelist.py @@ -73,6 +73,7 @@ _.get_raw_transaction # unused method (aea/contracts/base.py:147) _.get_raw_message # unused method (aea/contracts/base.py:163) MyScaffoldContract # unused class (aea/contracts/scaffold/contract.py:25) +_.is_valid_address # aea/crypto/cosmos.py:170: unused method 'is_valid_address' (60% confidence) _.get_init_transaction # unused method (aea/crypto/cosmos.py:437) _.get_handle_transaction # unused method (aea/crypto/cosmos.py:491) _.try_execute_wasm_query # unused method (aea/crypto/cosmos.py:571) @@ -190,6 +191,18 @@ receiver_address # unused variable (aea/decision_maker/default.py:99) create_with_message # unused method (aea/helpers/dialogue/base.py:1054) _.is_disconnecting # unused property (aea/multiplexer.py:67) +_.get_quantity_in_outbox # unused method (aea/test_tools/test_skills.py:52) +_.get_message_from_outbox # unused method (aea/test_tools/test_skills.py:56) +_.get_quantity_in_decision_maker_inbox # unused method (aea/test_tools/test_skills.py:69) +_.get_message_from_decision_maker_inbox # unused method (aea/test_tools/test_skills.py:73) +_.assert_quantity_in_outbox # unused method (aea/test_tools/test_skills.py:79) +_.assert_quantity_in_decision_making_queue # unused method (aea/test_tools/test_skills.py:86) +_.message_has_attributes # unused method (aea/test_tools/test_skills.py:69) +_.build_incoming_message_for_skill_dialogue # unused method (aea/test_tools/test_skills.py:155) +_.prepare_skill_dialogue # unused method (aea/test_tools/test_skills.py:290) +_.__defaults__ # unused attribute (aea/protocols/dialogue/base.py:49) +_.build_incoming_message_for_dialogue # unused method (aea/test_tools/test_skills.py:155) +_._has_message # unused method (aea/protocols/dialogue/base.py:491) MultiAgentManager # unused class (aea/manager.py:127) _.add_error_callback # unused method (aea/manager.py:202) _.start_manager # unused method (aea/manager.py:208) @@ -199,3 +212,4 @@ _.add_agent # unused method (aea/manager.py:291) _.start_all_agents # unused method (aea/manager.py:396) _.get_agent_alias # unused method (aea/manager.py:486) +_.DEFAULT_PYPI_INDEX_URL # unused variable (aea/configurations/base.py:85) diff --git a/setup.cfg b/setup.cfg index 37bfb84bfa..d76d45f0d9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -123,6 +123,9 @@ ignore_missing_imports = True [mypy-ecdsa.*] ignore_missing_imports = True +[mypy-urllib3.*] +ignore_missing_imports = True + # Per-module options for examples dir: [mypy-numpy] diff --git a/tests/common/utils.py b/tests/common/utils.py index f44ec39d37..0162a4768f 100644 --- a/tests/common/utils.py +++ b/tests/common/utils.py @@ -284,11 +284,11 @@ def run_in_thread(fn, timeout=10, on_exit=None, **kwargs): raise Exception("Thread was not stopped!") -def wait_for_condition(condition_checker, timeout=2, error_msg="Timeout"): +def wait_for_condition(condition_checker, timeout=2, error_msg="Timeout", period=0.001): """Wait for condition occures in selected timeout.""" start_time = time.time() while not condition_checker(): - time.sleep(0.0001) + time.sleep(period) if time.time() > start_time + timeout: raise TimeoutError(error_msg) diff --git a/tests/conftest.py b/tests/conftest.py index 6efe975b86..7d33572db7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -171,6 +171,7 @@ "local_uri": "127.0.0.1:9001", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9001", + "ledger_id": "cosmos", } NON_GENESIS_CONFIG_TWO = { "delegate_uri": "127.0.0.1:11002", @@ -178,6 +179,7 @@ "local_uri": "127.0.0.1:9002", "log_file": "libp2p_node.log", "public_uri": "127.0.0.1:9002", + "ledger_id": "cosmos", } PUBLIC_DHT_P2P_MADDR_1 = "/dns4/agents-p2p-dht.sandbox.fetch-ai.com/tcp/9000/p2p/16Uiu2HAkw1ypeQYQbRFV5hKUxGRHocwU5ohmVmCnyJNg36tnPFdx" PUBLIC_DHT_P2P_MADDR_2 = "/dns4/agents-p2p-dht.sandbox.fetch-ai.com/tcp/9001/p2p/16Uiu2HAmVWnopQAqq4pniYLw44VRvYxBUoRHqjz1Hh2SoCyjbyRW" @@ -198,7 +200,7 @@ UNKNOWN_SKILL_PUBLIC_ID = PublicId("unknown_author", "unknown_skill", "0.1.0") LOCAL_CONNECTION_PUBLIC_ID = PublicId("fetchai", "local", "0.1.0") P2P_CLIENT_CONNECTION_PUBLIC_ID = PublicId("fetchai", "p2p_client", "0.1.0") -HTTP_CLIENT_CONNECTION_PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.9.0") +HTTP_CLIENT_CONNECTION_PUBLIC_ID = PublicId.from_str("fetchai/http_client:0.10.0") HTTP_PROTOCOL_PUBLIC_ID = PublicId("fetchai", "http", "0.1.0") STUB_CONNECTION_PUBLIC_ID = DEFAULT_CONNECTION DUMMY_PROTOCOL_PUBLIC_ID = PublicId("dummy_author", "dummy", "0.1.0") diff --git a/tests/data/aea-config.example.yaml b/tests/data/aea-config.example.yaml index 7c690e3e7e..bdc72c1a48 100644 --- a/tests/data/aea-config.example.yaml +++ b/tests/data/aea-config.example.yaml @@ -7,16 +7,16 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.10.0 +- fetchai/oef:0.11.0 contracts: [] protocols: -- fetchai/oef_search:0.7.0 -- fetchai/default:0.6.0 -- fetchai/tac:0.7.0 -- fetchai/fipa:0.7.0 +- fetchai/oef_search:0.8.0 +- fetchai/default:0.7.0 +- fetchai/tac:0.8.0 +- fetchai/fipa:0.8.0 skills: -- fetchai/echo:0.8.0 -default_connection: fetchai/oef:0.10.0 +- fetchai/echo:0.9.0 +default_connection: fetchai/oef:0.11.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/data/aea-config.example_w_keys.yaml b/tests/data/aea-config.example_w_keys.yaml index bb2da28530..12871c2280 100644 --- a/tests/data/aea-config.example_w_keys.yaml +++ b/tests/data/aea-config.example_w_keys.yaml @@ -7,16 +7,16 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/oef:0.10.0 +- fetchai/oef:0.11.0 contracts: [] protocols: -- fetchai/oef_search:0.7.0 -- fetchai/default:0.6.0 -- fetchai/tac:0.7.0 -- fetchai/fipa:0.7.0 +- fetchai/oef_search:0.8.0 +- fetchai/default:0.7.0 +- fetchai/tac:0.8.0 +- fetchai/fipa:0.8.0 skills: -- fetchai/echo:0.8.0 -default_connection: fetchai/oef:0.10.0 +- fetchai/echo:0.9.0 +default_connection: fetchai/oef:0.11.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/data/dependencies_skill/skill.yaml b/tests/data/dependencies_skill/skill.yaml index b8005667c3..513b7f99d8 100644 --- a/tests/data/dependencies_skill/skill.yaml +++ b/tests/data/dependencies_skill/skill.yaml @@ -10,7 +10,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: [] behaviours: {} handlers: {} @@ -35,3 +35,4 @@ dependencies: git: https://github.com/a-random-username/a-repository.git index: https://test.pypi.org/simple ref: master +is_abstract: false diff --git a/tests/data/dummy_aea/aea-config.yaml b/tests/data/dummy_aea/aea-config.yaml index cea1bf386a..cab30e85f3 100644 --- a/tests/data/dummy_aea/aea-config.yaml +++ b/tests/data/dummy_aea/aea-config.yaml @@ -7,17 +7,17 @@ aea_version: '>=0.6.0, <0.7.0' fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/local:0.9.0 +- fetchai/local:0.10.0 contracts: -- fetchai/erc1155:0.10.0 +- fetchai/erc1155:0.11.0 protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/oef_search:0.8.0 skills: - dummy_author/dummy:0.1.0 -- fetchai/error:0.6.0 -default_connection: fetchai/local:0.9.0 +- fetchai/error:0.7.0 +default_connection: fetchai/local:0.10.0 default_ledger: cosmos logging_config: disable_existing_loggers: false diff --git a/tests/data/dummy_connection/connection.yaml b/tests/data/dummy_connection/connection.yaml index 606cb1eb8c..9af2fc2cf4 100644 --- a/tests/data/dummy_connection/connection.yaml +++ b/tests/data/dummy_connection/connection.yaml @@ -14,7 +14,7 @@ class_name: DummyConnection config: {} excluded_protocols: [] restricted_to_protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 dependencies: dep1: version: ==1.0.0 diff --git a/tests/data/dummy_skill/skill.yaml b/tests/data/dummy_skill/skill.yaml index 4e8dd1ef71..cc91b2e006 100644 --- a/tests/data/dummy_skill/skill.yaml +++ b/tests/data/dummy_skill/skill.yaml @@ -16,7 +16,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: [] behaviours: dummy: @@ -42,3 +42,4 @@ models: model_arg_2: '2' class_name: DummyModel dependencies: {} +is_abstract: false diff --git a/tests/data/exception_skill/skill.yaml b/tests/data/exception_skill/skill.yaml index ac22f939dd..88a3ddc924 100644 --- a/tests/data/exception_skill/skill.yaml +++ b/tests/data/exception_skill/skill.yaml @@ -21,3 +21,4 @@ behaviours: handlers: {} models: {} dependencies: {} +is_abstract: false diff --git a/tests/data/generator/t_protocol/message.py b/tests/data/generator/t_protocol/message.py index cd24500c6c..b6c80b9e37 100644 --- a/tests/data/generator/t_protocol/message.py +++ b/tests/data/generator/t_protocol/message.py @@ -29,7 +29,7 @@ from tests.data.generator.t_protocol.custom_types import DataModel as CustomDataModel -logger = logging.getLogger("aea.packages.fetchai.protocols.t_protocol.message") +_default_logger = logging.getLogger("aea.packages.fetchai.protocols.t_protocol.message") DEFAULT_BODY_SIZE = 4 @@ -1165,7 +1165,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/tests/data/generator/t_protocol/protocol.yaml b/tests/data/generator/t_protocol/protocol.yaml index 7c0d7f00a8..5bb413c7d6 100644 --- a/tests/data/generator/t_protocol/protocol.yaml +++ b/tests/data/generator/t_protocol/protocol.yaml @@ -6,11 +6,11 @@ description: A protocol for testing purposes. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - __init__.py: QmTwLir2v2eYMkDeUomf9uL1hrQhjzVTTqrQwamGG5iwn4 - custom_types.py: QmVboXL98TzRtreCRMrCF3U9xjRGRkGJjNgDBTAm5GXyLq - dialogues.py: QmVUoaW4CNgjZNy6PzmQrSYTCdeWQERsQR5maXhD9cd6HF - message.py: QmS3NcTMgbDx1ZkXkzwfiML26LU9FPfgjg2XznpL8wueQR - serialization.py: QmcS33k6rHgCCkhBuQ5kiXVKFMxxEzcZManshPD51MEHbw + __init__.py: QmQy21g5sVYfmy4vSYuEFyPnobM4SA1dEouz5deXNssPWx + custom_types.py: QmWg8HFav8w9tfZfMrTG5Uo7QpexvYKKkhpGPD18233pLw + dialogues.py: QmdpXJCUP6wV6StDxByraFGveMcTSjjFhDju74S4QVQpxf + message.py: QmaTFAmfVGAoxmQ78J3q5KhNAk7ErwKfzsaMZik4bxdjEx + serialization.py: QmYkdw251GKKCw6ZmPp4JmE6mXQ3jUhTgcfNKqsdCJbyfU t_protocol.proto: QmRuYvnojwkyZLzeECH3snomgoMJTB3m48yJiLq8LYsVb8 t_protocol_pb2.py: QmXrSgBBJCj8hbGCynKrvdkSDohQzHLPBA2vi5hDHmaGid fingerprint_ignore_patterns: [] diff --git a/tests/data/generator/t_protocol_no_ct/message.py b/tests/data/generator/t_protocol_no_ct/message.py index 3d71c4dfb7..9f8b76acb7 100644 --- a/tests/data/generator/t_protocol_no_ct/message.py +++ b/tests/data/generator/t_protocol_no_ct/message.py @@ -27,7 +27,9 @@ from aea.protocols.base import Message -logger = logging.getLogger("aea.packages.fetchai.protocols.t_protocol_no_ct.message") +_default_logger = logging.getLogger( + "aea.packages.fetchai.protocols.t_protocol_no_ct.message" +) DEFAULT_BODY_SIZE = 4 @@ -1125,7 +1127,7 @@ def _is_consistent(self) -> bool: ), ) except (AEAEnforceError, ValueError, KeyError) as e: - logger.error(str(e)) + _default_logger.error(str(e)) return False return True diff --git a/tests/data/generator/t_protocol_no_ct/protocol.yaml b/tests/data/generator/t_protocol_no_ct/protocol.yaml index 0633e33a05..40d7723965 100644 --- a/tests/data/generator/t_protocol_no_ct/protocol.yaml +++ b/tests/data/generator/t_protocol_no_ct/protocol.yaml @@ -6,10 +6,10 @@ description: A protocol for testing purposes. license: Apache-2.0 aea_version: '>=0.6.0, <0.7.0' fingerprint: - __init__.py: QmRGHGRoZHGCXQ29v3q93Nt6J5TuhggYvUvZoQfrM6c3yp - dialogues.py: QmUjFTMYk2C69dcYfsKorTQWJbaUGEz8orCQnkZtFbR6Je - message.py: Qma11dAGwWr3oYSWAmRrwLJj9mLvyLVD66svTyHwU3HVNB - serialization.py: Qmc3tJ5vk1AbtkF5BrPUeuyrnvVUTrfuUMF9MgDfkiiMkB + __init__.py: QmaaZ7Je2PRTkcnqy8oLR58yBDVpcRQ4BcaRe3sd3fug3Z + dialogues.py: QmPHhh9wkKDG7Fiy9E2WkkggYULFhLrySihJpoBw3mRn2o + message.py: QmT5Goi3nLkkRRgqzL7fugSQG2Tpvrmm22jXNT7fn3L7ax + serialization.py: QmfMNm543uhKp7MwPcEvBMoCyi54phLZ4AHpC6S1S2y8Uv t_protocol_no_ct.proto: QmeZWVLhb6EUGr5AgVwgf2YTEZTSuCskpmxCwAE3sDU9sY t_protocol_no_ct_pb2.py: QmYtjyYTv1fQrwTS2x5ZkrNB8bpgH2vpPUJsUV29B7E4d9 fingerprint_ignore_patterns: [] diff --git a/tests/data/gym-connection.yaml b/tests/data/gym-connection.yaml index 83da790575..bb4fc0ec10 100644 --- a/tests/data/gym-connection.yaml +++ b/tests/data/gym-connection.yaml @@ -9,8 +9,8 @@ fingerprint: aea_version: '>=0.6.0, <0.7.0' description: "The gym connection wraps an OpenAI gym." class_name: GymConnection -protocols: ["fetchai/gym:0.6.0"] -restricted_to_protocols: ["fetchai/gym:0.6.0"] +protocols: ["fetchai/gym:0.7.0"] +restricted_to_protocols: ["fetchai/gym:0.7.0"] excluded_protocols: [] config: env: 'gyms.env.BanditNArmedRandom' diff --git a/tests/data/hashes.csv b/tests/data/hashes.csv index 5a2e72f81a..45c2413f06 100644 --- a/tests/data/hashes.csv +++ b/tests/data/hashes.csv @@ -1,6 +1,6 @@ -dummy_author/agents/dummy_aea,QmYEVtxkYh889JfQy5umXqM7KBNVfjDunV1SWf1Kd9zDfm -dummy_author/skills/dummy_skill,QmTBZg36AhCipACz8x7eJsA5CHKqUQN1SwT3n8zX6n9XNF -fetchai/connections/dummy_connection,QmWegP8qsY6Ngdh9xorMDCSA1UBjt4CrrPeVWQqyVfHvob +dummy_author/agents/dummy_aea,QmdvumQvcNv2yzfZ6MagWJYtUFLoE9q9rAmQwoCGYbtH3w +dummy_author/skills/dummy_skill,QmbNGnFv7RuFbZ3Gy5VyRiEiN8bKCPSkzNzRhogXQwUpFd +fetchai/connections/dummy_connection,QmV4RsMfJ3P858RNgXVp9VSQoE6QT78b5WxQ35rRjCy5jR fetchai/contracts/dummy_contract,QmPMs9VDGZGF8xJ8XBYLVb1xK5XAgiaJr5Gwcq7Vr3TUyu -fetchai/skills/dependencies_skill,Qmdn8AArsVSXPut57BXBjkctMYsFYeNnwUuRNe6cuTpbMu -fetchai/skills/exception_skill,QmT2RiM95EVbXTD31zU6pKKoERkrCLuyxpAJfkm3dTsTp2 +fetchai/skills/dependencies_skill,QmRKwjiPFptKnSB772aD9a5xCDgd956rEJEYo48pNk8ZmZ +fetchai/skills/exception_skill,QmQidMBMHE3pyTXuFpPvLBe6y3vQF3bmquGr3yymnaG3mr diff --git a/tests/test_aea.py b/tests/test_aea.py index afaca49536..daa2dc7364 100644 --- a/tests/test_aea.py +++ b/tests/test_aea.py @@ -160,10 +160,12 @@ def test_react(): builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) - local_connection_id = PublicId.from_str("fetchai/local:0.9.0") + local_connection_id = PublicId.from_str("fetchai/local:0.10.0") builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) - agent = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.9.0")]) + agent = builder.build( + connection_ids=[PublicId.from_str("fetchai/local:0.10.0")] + ) # This is a temporary workaround to feed the local node to the OEF Local connection # TODO remove it. local_connection = agent.resources.get_connection(local_connection_id) @@ -214,10 +216,10 @@ def test_handle(): builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) - local_connection_id = PublicId.from_str("fetchai/local:0.9.0") + local_connection_id = PublicId.from_str("fetchai/local:0.10.0") builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) - aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.9.0")]) + aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.10.0")]) # This is a temporary workaround to feed the local node to the OEF Local connection # TODO remove it. local_connection = aea.resources.get_connection(local_connection_id) @@ -312,10 +314,10 @@ def test_initialize_aea_programmatically(): builder.add_connection( Path(ROOT_DIR, "packages", "fetchai", "connections", "local") ) - local_connection_id = PublicId.from_str("fetchai/local:0.9.0") + local_connection_id = PublicId.from_str("fetchai/local:0.10.0") builder.set_default_connection(local_connection_id) builder.add_skill(Path(CUR_PATH, "data", "dummy_skill")) - aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.9.0")]) + aea = builder.build(connection_ids=[PublicId.from_str("fetchai/local:0.10.0")]) local_connection = aea.resources.get_connection(local_connection_id) local_connection._local_node = node diff --git a/tests/test_aea_builder.py b/tests/test_aea_builder.py index f99aa5fbec..adafbe5c7c 100644 --- a/tests/test_aea_builder.py +++ b/tests/test_aea_builder.py @@ -17,10 +17,11 @@ # # ------------------------------------------------------------------------------ - """This module contains tests for aea/aea_builder.py.""" import os import re +import sys +from importlib import import_module from pathlib import Path from textwrap import dedent, indent from typing import Collection @@ -37,6 +38,7 @@ ComponentType, ConnectionConfig, DEFAULT_AEA_CONFIG_FILE, + Dependency, ProtocolConfig, PublicId, SkillConfig, @@ -47,6 +49,8 @@ from aea.exceptions import AEAEnforceError, AEAException from aea.helpers.base import cd from aea.helpers.exception_policy import ExceptionPolicyEnum +from aea.helpers.install_dependency import run_install_subprocess +from aea.helpers.yaml_utils import yaml_load_all from aea.protocols.base import Protocol from aea.protocols.default import DefaultMessage from aea.registries.resources import Resources @@ -96,7 +100,7 @@ def test_add_package_already_existing(): builder.add_component(ComponentType.PROTOCOL, fipa_package_path) expected_message = re.escape( - "Component 'fetchai/fipa:0.7.0' of type 'protocol' already added." + "Component 'fetchai/fipa:0.8.0' of type 'protocol' already added." ) with pytest.raises(AEAException, match=expected_message): builder.add_component(ComponentType.PROTOCOL, fipa_package_path) @@ -106,12 +110,12 @@ def test_when_package_has_missing_dependency(): """Test the case when the builder tries to load the packages, but fails because of a missing dependency.""" builder = AEABuilder() expected_message = re.escape( - "Package 'fetchai/oef:0.10.0' of type 'connection' cannot be added. " - "Missing dependencies: ['(protocol, fetchai/oef_search:0.7.0)']" + "Package 'fetchai/oef:0.11.0' of type 'connection' cannot be added. " + "Missing dependencies: ['(protocol, fetchai/oef_search:0.8.0)']" ) with pytest.raises(AEAException, match=expected_message): - # connection "fetchai/oef:0.10.0" requires - # "fetchai/oef_search:0.7.0" and "fetchai/fipa:0.7.0" protocols. + # connection "fetchai/oef:0.11.0" requires + # "fetchai/oef_search:0.8.0" and "fetchai/fipa:0.8.0" protocols. builder.add_component( ComponentType.CONNECTION, Path(ROOT_DIR) / "packages" / "fetchai" / "connections" / "oef", @@ -375,7 +379,7 @@ def test_remove_skill(): builder.set_name("aea_1") builder.add_private_key("fetchai") - skill = Skill.from_dir(dummy_skill_path, Mock()) + skill = Skill.from_dir(dummy_skill_path, Mock(agent_name="name")) num_deps = len(builder._package_dependency_manager.all_dependencies) builder.add_component_instance(skill) assert len(builder._package_dependency_manager.all_dependencies) == num_deps + 1 @@ -434,13 +438,17 @@ def test_component_add_bad_dep(): builder.set_name("aea_1") builder.add_private_key("fetchai") connection = _make_dummy_connection() - connection.configuration._pypi_dependencies = {"something": {"version": "==0.1.0"}} + connection.configuration.pypi_dependencies = { + "something": Dependency("something", "==0.1.0") + } builder.add_component_instance(connection) a_protocol = Protocol( ProtocolConfig("a_protocol", "author", "0.1.0"), DefaultMessage ) - a_protocol.configuration._pypi_dependencies = {"something": {"version": "==0.2.0"}} + a_protocol.configuration.pypi_dependencies = { + "something": Dependency("something", "==0.2.0") + } with pytest.raises( AEAException, match=r"Conflict on package something: specifier set .*" ): @@ -490,11 +498,11 @@ def test_load_abstract_component(): builder.set_name("aea_1") builder.add_private_key("fetchai") - skill = Skill.from_dir(dummy_skill_path, Mock()) + skill = Skill.from_dir(dummy_skill_path, Mock(agent_name="name")) skill.configuration.is_abstract = True builder.add_component_instance(skill) builder._load_and_add_components( - ComponentType.SKILL, Resources(), "aea_1", agent_context=Mock() + ComponentType.SKILL, Resources(), "aea_1", agent_context=Mock(agent_name="name") ) @@ -508,7 +516,8 @@ def test_find_import_order(): def _new_load(*args, **kwargs): skill_config = _old_load(*args, **kwargs) - skill_config.skills = [Mock()] + # add loop + skill_config.skills = [skill_config.public_id] return skill_config with patch("aea.aea_builder.load_component_configuration", _new_load): @@ -568,7 +577,7 @@ def _add_stub_connection_config(self): --- name: stub author: fetchai - version: 0.10.0 + version: 0.11.0 type: connection config: input_file: "{self.expected_input_file}" @@ -588,7 +597,7 @@ def test_from_project(self): aea = builder.build() assert aea.name == self.agent_name stub_connection = aea.resources.get_connection( - PublicId.from_str("fetchai/stub:0.10.0") + PublicId.from_str("fetchai/stub:0.11.0") ) assert stub_connection.configuration.config == dict( input_file=self.expected_input_file, output_file=self.expected_output_file @@ -650,6 +659,13 @@ def test_from_project(self): dummy_model = dummy_skill.models["dummy"] assert dummy_model.config == {"model_arg_1": 42, "model_arg_2": "2"} + def test_from_json(self): + """Test load project from json file with path specified.""" + with open(Path(self._get_cwd(), DEFAULT_AEA_CONFIG_FILE), "r") as fp: + json_config = yaml_load_all(fp) + + AEABuilder.from_config_json(json_config, Path(self._get_cwd())) + class TestFromAEAProjectMakeSkillAbstract(AEATestCase): """Test builder set from project dir, to make a skill 'abstract'.""" @@ -719,3 +735,50 @@ def test_from_project(self): ): with cd(self._get_cwd()): AEABuilder.from_aea_project(Path(self._get_cwd())) + + +class TestExtraDeps(AEATestCaseEmpty): + """Test builder set from project dir.""" + + def test_check_dependencies_correct(self): + """Test dependencies properly listed.""" + self.run_cli_command( + "add", "--local", "connection", "fetchai/http_client", cwd=self._get_cwd() + ) + builder = AEABuilder.from_aea_project(Path(self._get_cwd())) + assert "aiohttp" in builder._package_dependency_manager.pypi_dependencies + + def test_install_dependency(self): + """Test dependencies installed.""" + package_name = "async_generator" + dependency = Dependency(package_name, "==1.10") + sys.modules.pop(package_name, None) + run_install_subprocess( + [sys.executable, "-m", "pip", "uninstall", package_name, "-y"] + ) + try: + import_module(package_name) + + raise Exception("should not be raised") + except ModuleNotFoundError: + pass + + builder = AEABuilder.from_aea_project(Path(self._get_cwd())) + with patch( + "aea.aea_builder._DependenciesManager.pypi_dependencies", + {"package_name": dependency}, + ): + builder.install_pypi_dependencies() + + import_module(package_name) + + sys.modules.pop(package_name) + run_install_subprocess( + [sys.executable, "-m", "pip", "uninstall", package_name, "-y"] + ) + try: + import_module(package_name) + + raise Exception("should not be raised") + except ModuleNotFoundError: + pass diff --git a/tests/test_cli/test_add/test_connection.py b/tests/test_cli/test_add/test_connection.py index 6af59ceec9..9898e6ad3a 100644 --- a/tests/test_cli/test_add/test_connection.py +++ b/tests/test_cli/test_add/test_connection.py @@ -57,7 +57,7 @@ def setup_class(cls): cls.connection_name = "http_client" cls.connection_author = "fetchai" cls.connection_version = "0.3.0" - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) @@ -120,9 +120,7 @@ def test_error_message_connection_already_existing(self): The expected message is: 'A connection with id '{connection_id}' already exists. Aborting...' """ - s = "A connection with id '{}/{}' already exists. Aborting...".format( - self.connection_author, self.connection_name - ) + s = f"A connection with id '{self.connection_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod @@ -148,7 +146,7 @@ def setup_class(cls): cls.connection_name = "http_client" cls.connection_author = "fetchai" cls.connection_version = "0.3.0" - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) @@ -207,9 +205,7 @@ def test_error_message_connection_already_existing(self): The expected message is: 'A connection with id '{connection_id}' already exists. Aborting...' """ - s = "A connection with id '{}' already exists. Aborting...".format( - self.connection_author + "/" + self.connection_name - ) + s = f"A connection with id '{self.connection_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod @@ -345,7 +341,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" cls.connection_name = "http_client" # copy the 'packages' directory in the parent of the agent folder. @@ -413,7 +409,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" cls.connection_name = "http_client" # copy the 'packages' directory in the parent of the agent folder. @@ -476,11 +472,14 @@ def teardown_class(cls): class TestAddConnectionFromRemoteRegistry(AEATestCaseEmpty): """Test case for add connection from Registry command.""" + IS_LOCAL = False + IS_EMPTY = True + @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_connection_from_remote_registry_positive(self): """Test add connection from Registry positive result.""" - self.add_item("connection", "fetchai/local:0.4.0", local=False) + self.add_item("connection", "fetchai/local:0.9.0", local=self.IS_LOCAL) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "connections") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add/test_contract.py b/tests/test_cli/test_add/test_contract.py index 18662e80fd..0f8b59d379 100644 --- a/tests/test_cli/test_add/test_contract.py +++ b/tests/test_cli/test_add/test_contract.py @@ -58,11 +58,14 @@ def test_add_contract_positive(self, *mocks): class TestAddContractFromRemoteRegistry(AEATestCaseEmpty): """Test case for add contract from Registry command.""" + IS_LOCAL = False + IS_EMPTY = True + @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_contract_from_remote_registry_positive(self): """Test add contract from Registry positive result.""" - self.add_item("contract", "fetchai/erc1155:0.6.0", local=False) + self.add_item("contract", "fetchai/erc1155:0.10.0", local=self.IS_LOCAL) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add/test_protocol.py b/tests/test_cli/test_add/test_protocol.py index b89aeb174b..89475a5a61 100644 --- a/tests/test_cli/test_add/test_protocol.py +++ b/tests/test_cli/test_add/test_protocol.py @@ -54,7 +54,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = PublicId.from_str("fetchai/gym:0.6.0") + cls.protocol_id = PublicId.from_str("fetchai/gym:0.7.0") cls.protocol_name = cls.protocol_id.name cls.protocol_author = cls.protocol_id.author cls.protocol_version = cls.protocol_id.version @@ -97,9 +97,7 @@ def test_error_message_protocol_already_existing(self): The expected message is: 'A protocol with id '{protocol_id}' already exists. Aborting...' """ - s = "A protocol with id '{}' already exists. Aborting...".format( - self.protocol_author + "/" + self.protocol_name - ) + s = f"A protocol with id '{self.protocol_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod @@ -122,7 +120,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = PublicId.from_str("fetchai/gym:0.6.0") + cls.protocol_id = PublicId.from_str("fetchai/gym:0.7.0") cls.protocol_name = cls.protocol_id.name cls.protocol_author = cls.protocol_id.author cls.protocol_version = cls.protocol_id.version @@ -182,9 +180,7 @@ def test_error_message_protocol_already_existing(self): The expected message is: 'A protocol with id '{protocol_id}' already exists. Aborting...' """ - s = "A protocol with id '{}' already exists. Aborting...".format( - self.protocol_author + "/" + self.protocol_name - ) + s = f"A protocol with id '{self.protocol_id}' already exists. Aborting..." assert self.result.exception.message == s @unittest.mock.patch("aea.cli.add.get_package_path", return_value="dest/path") @@ -334,7 +330,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = "fetchai/gym:0.6.0" + cls.protocol_id = "fetchai/gym:0.7.0" # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) @@ -400,7 +396,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = "fetchai/gym:0.6.0" + cls.protocol_id = "fetchai/gym:0.7.0" cls.protocol_name = "gym" # copy the 'packages' directory in the parent of the agent folder. @@ -457,11 +453,14 @@ def teardown_class(cls): class TestAddProtocolFromRemoteRegistry(AEATestCaseEmpty): """Test case for add protocol from Registry command.""" + IS_LOCAL = False + IS_EMPTY = True + @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_protocol_from_remote_registry_positive(self): """Test add protocol from Registry positive result.""" - self.add_item("protocol", "fetchai/fipa:0.4.0", local=False) + self.add_item("protocol", "fetchai/fipa:0.7.0", local=self.IS_LOCAL) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "protocols") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add/test_skill.py b/tests/test_cli/test_add/test_skill.py index cf0c630731..ca890af333 100644 --- a/tests/test_cli/test_add/test_skill.py +++ b/tests/test_cli/test_add/test_skill.py @@ -17,6 +17,7 @@ # # ------------------------------------------------------------------------------ + """This test module contains the tests for the `aea add skill` sub-command.""" import os @@ -60,7 +61,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = PublicId.from_str("fetchai/error:0.6.0") + cls.skill_id = PublicId.from_str("fetchai/error:0.7.0") cls.skill_name = cls.skill_id.name cls.skill_author = cls.skill_id.author cls.skill_version = cls.skill_id.version @@ -99,9 +100,7 @@ def test_error_message_skill_already_existing(self): The expected message is: 'A skill with id '{skill_id}' already exists. Aborting...' """ - s = "A skill with id '{}' already exists. Aborting...".format( - self.skill_author + "/" + self.skill_name - ) + s = f"A skill with id '{self.skill_id}' already exists. Aborting..." assert self.result.exception.message == s @mock.patch("aea.cli.add.get_package_path", return_value="dest/path") @@ -142,7 +141,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = PublicId.from_str("fetchai/echo:0.8.0") + cls.skill_id = PublicId.from_str("fetchai/echo:0.9.0") cls.skill_name = cls.skill_id.name cls.skill_author = cls.skill_id.author cls.skill_version = cls.skill_id.version @@ -201,9 +200,7 @@ def test_error_message_skill_already_existing(self): The expected message is: 'A skill with id '{skill_id}' already exists. Aborting...' """ - s = "A skill with id '{}' already exists. Aborting...".format( - self.skill_author + "/" + self.skill_name - ) + s = f"A skill with id '{self.skill_id}' already exists. Aborting..." assert self.result.exception.message == s @classmethod @@ -337,7 +334,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/echo:0.8.0" + cls.skill_id = "fetchai/echo:0.9.0" cls.skill_name = "echo" # copy the 'packages' directory in the parent of the agent folder. @@ -409,7 +406,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/echo:0.8.0" + cls.skill_id = "fetchai/echo:0.9.0" cls.skill_name = "echo" # copy the 'packages' directory in the parent of the agent folder. @@ -471,7 +468,7 @@ class TestAddSkillWithContractsDeps(AEATestCaseEmpty): def test_add_skill_with_contracts_positive(self): """Test add skill with contract dependencies positive result.""" - self.add_item("skill", "fetchai/erc1155_client:0.13.0") + self.add_item("skill", "fetchai/erc1155_client:0.14.0") contracts_path = os.path.join(self.agent_name, "vendor", "fetchai", "contracts") contracts_folders = os.listdir(contracts_path) @@ -482,14 +479,14 @@ def test_add_skill_with_contracts_positive(self): class TestAddSkillFromRemoteRegistry(AEATestCaseEmpty): """Test case for add skill from Registry command.""" + IS_LOCAL = False + IS_EMPTY = True + @pytest.mark.integration @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_add_skill_from_remote_registry_positive(self): """Test add skill from Registry positive result.""" - self.run_cli_command( - *["remove", "protocol", "fetchai/default:0.6.0"], cwd=self._get_cwd() - ) - self.add_item("skill", "fetchai/echo:0.3.0", local=False) + self.add_item("skill", "fetchai/echo:0.8.0", local=self.IS_LOCAL) items_path = os.path.join(self.agent_name, "vendor", "fetchai", "skills") items_folders = os.listdir(items_path) diff --git a/tests/test_cli/test_add_key.py b/tests/test_cli/test_add_key.py index f8c84aa07c..d98b4d75f6 100644 --- a/tests/test_cli/test_add_key.py +++ b/tests/test_cli/test_add_key.py @@ -220,7 +220,9 @@ def test_add_key_fails_bad_key(): tmpdir = tempfile.mkdtemp() os.chdir(tmpdir) try: - with mock.patch.object(aea.crypto.helpers.logger, "error") as mock_logger_error: + with mock.patch.object( + aea.crypto.helpers._default_logger, "error" + ) as mock_logger_error: result = runner.invoke( cli, [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR] diff --git a/tests/test_cli/test_config.py b/tests/test_cli/test_config.py index aae0e685c2..6df898b077 100644 --- a/tests/test_cli/test_config.py +++ b/tests/test_cli/test_config.py @@ -16,7 +16,6 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This test module contains the tests for the `aea config` sub-command.""" import os @@ -24,8 +23,14 @@ import tempfile from pathlib import Path +import pytest +from click.exceptions import ClickException + from aea.cli import cli from aea.cli.utils.constants import ALLOWED_PATH_ROOTS +from aea.configurations.base import AgentConfig, DEFAULT_AEA_CONFIG_FILE, PackageType +from aea.configurations.loader import ConfigLoader +from aea.helpers.yaml_utils import yaml_load from tests.conftest import CLI_LOG_OPTION, CUR_PATH, CliRunner @@ -48,6 +53,7 @@ def test_get_agent_name(self): cli, [*CLI_LOG_OPTION, "config", "get", "agent.agent_name"], standalone_mode=False, + catch_exceptions=False, ) assert result.exit_code == 0 assert result.output == "Agent0\n" @@ -167,18 +173,46 @@ def test_get_fails_when_getting_nested_object(self): standalone_mode=False, ) assert result.exit_code == 1 - s = "Cannot get attribute 'non_existing_attribute'" + s = "Cannot get attribute 'non_existing_attribute'." assert result.exception.message == s def test_get_fails_when_getting_non_dict_attribute(self): """Test that the get fails because the path point to a non-dict object.""" + attribute = "protocols" + result = self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "config", "get", f"skills.dummy.{attribute}.protocol"], + standalone_mode=False, + ) + assert result.exit_code == 1 + s = f"Attribute '{attribute}' is not a dictionary." + assert result.exception.message == s + + def test_get_fails_when_getting_non_dict_attribute_in_between(self): + """Test that the get fails because an object in between is not a dictionary.""" result = self.runner.invoke( cli, - [*CLI_LOG_OPTION, "config", "get", "skills.dummy.protocols.protocol"], + [*CLI_LOG_OPTION, "config", "get", "agent.skills.some_attribute"], standalone_mode=False, ) assert result.exit_code == 1 - s = "The target object is not a dictionary." + s = "Attribute 'skills' is not a dictionary." + assert result.exception.message == s + + def test_get_fails_when_getting_vendor_dependency_with_wrong_component_type(self): + """Test that getting a vendor component with wrong component type raises error.""" + result = self.runner.invoke( + cli, + [ + *CLI_LOG_OPTION, + "config", + "get", + "vendor.fetchai.component_type_not_correct.error.non_existing_attribute", + ], + standalone_mode=False, + ) + assert result.exit_code == 1 + s = "'component_type_not_correct' is not a valid component type. Please use one of ['protocols', 'connections', 'skills', 'contracts']." assert result.exception.message == s @classmethod @@ -203,21 +237,45 @@ def setup_class(cls): os.chdir(Path(cls.t, "dummy_aea")) cls.runner = CliRunner() - def test_set_agent_name(self): + def test_set_agent_logging_options(self): """Test setting the agent name.""" result = self.runner.invoke( cli, - [*CLI_LOG_OPTION, "config", "set", "agent.agent_name", "new_name"], + [ + *CLI_LOG_OPTION, + "config", + "set", + "agent.logging_config.disable_existing_loggers", + "True", + ], standalone_mode=False, + catch_exceptions=False, ) assert result.exit_code == 0 result = self.runner.invoke( cli, - [*CLI_LOG_OPTION, "config", "get", "agent.agent_name"], + [ + *CLI_LOG_OPTION, + "config", + "get", + "agent.logging_config.disable_existing_loggers", + ], standalone_mode=False, ) assert result.exit_code == 0 - assert result.output == "new_name\n" + assert result.output == "True\n" + + def test_set_agent_incorrect_value(self): + """Test setting the agent name.""" + with pytest.raises( + ClickException, match="Field `not_agent_name` is not allowed to change!" + ): + self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "config", "set", "agent.not_agent_name", "new_name"], + standalone_mode=False, + catch_exceptions=False, + ) def test_set_type_bool(self): """Test setting the agent name.""" @@ -251,48 +309,49 @@ def test_set_invalid_value(self): ) assert result.exit_code == 1 - def test_set_skill_name(self): + def test_set_skill_name_should_fail(self): """Test setting the 'dummy' skill name.""" result = self.runner.invoke( cli, [*CLI_LOG_OPTION, "config", "set", "skills.dummy.name", "new_dummy_name"], standalone_mode=False, ) - assert result.exit_code == 0 + assert result.exit_code == 1 + + def test_set_nested_attribute(self): + """Test setting a nested attribute.""" + path = "skills.dummy.behaviours.dummy.args.behaviour_arg_1" + new_value = "new_dummy_name" result = self.runner.invoke( cli, - [*CLI_LOG_OPTION, "config", "get", "skills.dummy.name"], + [*CLI_LOG_OPTION, "config", "set", path, new_value], standalone_mode=False, + catch_exceptions=False, ) assert result.exit_code == 0 - assert result.output == "new_dummy_name\n" - - def test_set_nested_attribute(self): - """Test setting a nested attribute.""" result = self.runner.invoke( cli, - [ - *CLI_LOG_OPTION, - "config", - "set", - "skills.dummy.behaviours.dummy.class_name", - "new_dummy_name", - ], + [*CLI_LOG_OPTION, "config", "get", path], standalone_mode=False, + catch_exceptions=False, ) assert result.exit_code == 0 + assert new_value in result.output + + def test_set_nested_attribute_not_allowed(self): + """Test setting a nested attribute.""" + path = "skills.dummy.behaviours.dummy.config.behaviour_arg_1" + new_value = "new_dummy_name" result = self.runner.invoke( cli, - [ - *CLI_LOG_OPTION, - "config", - "get", - "skills.dummy.behaviours.dummy.class_name", - ], + [*CLI_LOG_OPTION, "config", "set", path, new_value], standalone_mode=False, ) - assert result.exit_code == 0 - assert result.output == "new_dummy_name\n" + assert result.exit_code == 1 + assert ( + result.exception.message + == "Field `behaviours.dummy.config` is not allowed to change!" + ) def test_no_recognized_root(self): """Test that the 'get' fails because the root is not recognized.""" @@ -312,7 +371,9 @@ def test_no_recognized_root(self): def test_too_short_path_but_root_correct(self): """Test that the 'get' fails because the path is too short but the root is correct.""" result = self.runner.invoke( - cli, [*CLI_LOG_OPTION, "config", "set", "agent"], standalone_mode=False + cli, + [*CLI_LOG_OPTION, "config", "set", "agent", "data"], + standalone_mode=False, ) assert result.exit_code == 1 assert ( @@ -351,21 +412,22 @@ def test_resource_not_existing(self): ) def test_attribute_not_found(self): - """Test that the 'get' fails because the attribute is not found.""" - result = self.runner.invoke( - cli, - [ - *CLI_LOG_OPTION, - "config", - "set", - "skills.dummy.non_existing_attribute", - "value", - ], - standalone_mode=False, - ) - assert result.exit_code == 1 - s = "Attribute 'non_existing_attribute' not found." - assert result.exception.message == s + """Test that the 'set' fails because the attribute is not found.""" + with pytest.raises( + ClickException, match="Field `.*` is not allowed to change!" + ): + self.runner.invoke( + cli, + [ + *CLI_LOG_OPTION, + "config", + "set", + "skills.dummy.non_existing_attribute", + "value", + ], + standalone_mode=False, + catch_exceptions=False, + ) def test_set_fails_when_setting_non_primitive_type(self): """Test that setting the 'dummy' skill behaviours fails because not a primitive type.""" @@ -375,41 +437,38 @@ def test_set_fails_when_setting_non_primitive_type(self): standalone_mode=False, ) assert result.exit_code == 1 - s = "Attribute 'behaviours' is not of primitive type." + s = "Path 'behaviours' not valid for skill." assert result.exception.message == s def test_get_fails_when_setting_nested_object(self): """Test that setting a nested object in 'dummy' skill fails because path is not valid.""" - result = self.runner.invoke( - cli, - [ - *CLI_LOG_OPTION, - "config", - "set", - "skills.dummy.non_existing_attribute.dummy", - "new_value", - ], - standalone_mode=False, - ) - assert result.exit_code == 1 - s = "Cannot get attribute 'non_existing_attribute'" - assert result.exception.message == s + with pytest.raises( + ClickException, match=r"Field `.*` is not allowed to change!" + ): + self.runner.invoke( + cli, + [ + *CLI_LOG_OPTION, + "config", + "set", + "skills.dummy.non_existing_attribute.dummy", + "new_value", + ], + standalone_mode=False, + catch_exceptions=False, + ) def test_get_fails_when_setting_non_dict_attribute(self): """Test that the set fails because the path point to a non-dict object.""" + behaviour_arg_1 = "behaviour_arg_1" + path = f"skills.dummy.behaviours.dummy.args.{behaviour_arg_1}.over_the_string" result = self.runner.invoke( cli, - [ - *CLI_LOG_OPTION, - "config", - "set", - "skills.dummy.protocols.protocol", - "new_value", - ], + [*CLI_LOG_OPTION, "config", "set", path, "new_value"], standalone_mode=False, ) assert result.exit_code == 1 - s = "The target object is not a dictionary." + s = f"Attribute '{behaviour_arg_1}' is not a dictionary." assert result.exception.message == s @classmethod @@ -420,3 +479,112 @@ def teardown_class(cls): shutil.rmtree(cls.t) except (OSError, IOError): pass + + +class TestConfigNestedGetSet: + """Test that the command 'aea config set' works as expected.""" + + PATH = "skills.dummy.behaviours.dummy.args.behaviour_arg_1" + INCORRECT_PATH = "skills.dummy.behaviours.dummy.args.behaviour_arg_100500" + INITIAL_VALUE = 1 + NEW_VALUE = 100 + + def setup(self): + """Set the test up.""" + self.cwd = os.getcwd() + self.t = tempfile.mkdtemp() + shutil.copytree(Path(CUR_PATH, "data", "dummy_aea"), Path(self.t, "dummy_aea")) + os.chdir(Path(self.t, "dummy_aea")) + self.runner = CliRunner() + + def teardown(self): + """Tear dowm the test.""" + os.chdir(self.cwd) + try: + shutil.rmtree(self.t) + except (OSError, IOError): + pass + + def test_set_get_incorrect_path(self): + """Fail on incorrect attribute tryed to be updated.""" + with pytest.raises(ClickException, match="Attribute .* not found."): + self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "config", "get", self.INCORRECT_PATH], + standalone_mode=False, + catch_exceptions=False, + ) + + with pytest.raises(ClickException, match="Attribute '.*' not found."): + self.runner.invoke( + cli, + [ + *CLI_LOG_OPTION, + "config", + "set", + self.INCORRECT_PATH, + str(self.NEW_VALUE), + ], + standalone_mode=False, + catch_exceptions=False, + ) + + def load_agent_config(self) -> AgentConfig: + """Load agent config for current dir.""" + agent_loader = ConfigLoader.from_configuration_type(PackageType.AGENT) + with open(DEFAULT_AEA_CONFIG_FILE, "r") as fp: + agent_config = agent_loader.load(fp) + return agent_config + + def get_component_config_value(self) -> dict: + """Get component variable value.""" + package_type, package_name, *path = self.PATH.split(".") + file_path = Path(f"{package_type}") / package_name / f"{package_type[:-1]}.yaml" + + with open(file_path, "r") as fp: + data = yaml_load(fp) + + value = data + for i in path: + value = value[i] + return value + + def test_set_get_correct_path(self): + """Test component value updated in agent config not in component config.""" + agent_config = self.load_agent_config() + assert not agent_config.component_configurations + + config_value = self.get_component_config_value() + assert config_value == self.INITIAL_VALUE + + result = self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "config", "get", self.PATH], + standalone_mode=False, + catch_exceptions=False, + ) + assert result.exit_code == 0 + assert str(self.INITIAL_VALUE) in result.output + + result = self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "config", "set", self.PATH, str(self.NEW_VALUE)], + standalone_mode=False, + catch_exceptions=False, + ) + assert result.exit_code == 0 + + config_value = self.get_component_config_value() + assert config_value == self.INITIAL_VALUE + + result = self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "config", "get", self.PATH], + standalone_mode=False, + catch_exceptions=False, + ) + assert result.exit_code == 0 + assert str(self.NEW_VALUE) in result.output + + agent_config = self.load_agent_config() + assert agent_config.component_configurations diff --git a/tests/test_cli/test_eject.py b/tests/test_cli/test_eject.py index 7ef05aca81..28672fcc7e 100644 --- a/tests/test_cli/test_eject.py +++ b/tests/test_cli/test_eject.py @@ -33,29 +33,29 @@ def test_eject_commands_positive(self): self.set_agent_context(agent_name) cwd = os.path.join(self.t, agent_name) - self.add_item("connection", "fetchai/gym:0.8.0") - self.add_item("skill", "fetchai/gym:0.8.0") - self.add_item("contract", "fetchai/erc1155:0.10.0") + self.add_item("connection", "fetchai/gym:0.9.0") + self.add_item("skill", "fetchai/gym:0.9.0") + self.add_item("contract", "fetchai/erc1155:0.11.0") - self.run_cli_command("eject", "connection", "fetchai/gym:0.8.0", cwd=cwd) + self.run_cli_command("eject", "connection", "fetchai/gym:0.9.0", cwd=cwd) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "connections")) ) assert "gym" in os.listdir((os.path.join(cwd, "connections"))) - self.run_cli_command("eject", "protocol", "fetchai/gym:0.6.0", cwd=cwd) + self.run_cli_command("eject", "protocol", "fetchai/gym:0.7.0", cwd=cwd) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "protocols")) ) assert "gym" in os.listdir((os.path.join(cwd, "protocols"))) - self.run_cli_command("eject", "skill", "fetchai/gym:0.8.0", cwd=cwd) + self.run_cli_command("eject", "skill", "fetchai/gym:0.9.0", cwd=cwd) assert "gym" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "skills")) ) assert "gym" in os.listdir((os.path.join(cwd, "skills"))) - self.run_cli_command("eject", "contract", "fetchai/erc1155:0.10.0", cwd=cwd) + self.run_cli_command("eject", "contract", "fetchai/erc1155:0.11.0", cwd=cwd) assert "erc1155" not in os.listdir( (os.path.join(cwd, "vendor", "fetchai", "contracts")) ) diff --git a/tests/test_cli/test_fetch.py b/tests/test_cli/test_fetch.py index df6613975c..4ac17341b8 100644 --- a/tests/test_cli/test_fetch.py +++ b/tests/test_cli/test_fetch.py @@ -150,7 +150,7 @@ class TestFetchFromRemoteRegistry(AEATestCaseMany): @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) def test_fetch_agent_from_remote_registry_positive(self): """Test fetch agent from Registry for positive result.""" - self.run_cli_command("fetch", "fetchai/my_first_aea:0.7.0") + self.run_cli_command("fetch", "fetchai/my_first_aea:0.12.0") assert "my_first_aea" in os.listdir(self.t) diff --git a/tests/test_cli/test_generate_wealth.py b/tests/test_cli/test_generate_wealth.py index f061b96d13..59d37a322e 100644 --- a/tests/test_cli/test_generate_wealth.py +++ b/tests/test_cli/test_generate_wealth.py @@ -39,10 +39,10 @@ class GenerateWealthTestCase(TestCase): """Test case for _generate_wealth method.""" - @mock.patch("aea.cli.generate_wealth.Wallet") + @mock.patch("aea.cli.utils.package_utils.Wallet") @mock.patch("aea.cli.generate_wealth.click.echo") @mock.patch("aea.cli.generate_wealth.try_generate_testnet_wealth") - @mock.patch("aea.cli.generate_wealth.verify_or_create_private_keys_ctx") + @mock.patch("aea.cli.utils.package_utils.verify_or_create_private_keys_ctx") def test__generate_wealth_positive(self, *mocks): """Test for _generate_wealth method positive result.""" ctx = ContextMock() @@ -50,7 +50,7 @@ def test__generate_wealth_positive(self, *mocks): @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") -@mock.patch("aea.cli.generate_wealth.verify_or_create_private_keys_ctx") +@mock.patch("aea.cli.utils.package_utils.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.generate_wealth._try_generate_wealth") class GenerateWealthCommandTestCase(TestCase): """Test case for CLI generate_wealth command.""" @@ -107,7 +107,7 @@ def test_wealth_commands_negative(self): self.add_private_key() settings = {"unsupported_crypto": "path"} - self.force_set_config("agent.private_key_paths", settings) + self.nested_set_config("agent.private_key_paths", settings) with pytest.raises(AEATestingException) as excinfo: self.generate_wealth() diff --git a/tests/test_cli/test_get_address.py b/tests/test_cli/test_get_address.py index 82c81b59e3..3b2e43cd26 100644 --- a/tests/test_cli/test_get_address.py +++ b/tests/test_cli/test_get_address.py @@ -30,8 +30,8 @@ class GetAddressTestCase(TestCase): """Test case for _get_address method.""" - @mock.patch("aea.cli.get_address.Wallet") - @mock.patch("aea.cli.get_address.verify_or_create_private_keys_ctx") + @mock.patch("aea.cli.utils.package_utils.Wallet") + @mock.patch("aea.cli.utils.package_utils.verify_or_create_private_keys_ctx") def test__get_address_positive(self, *mocks): """Test for _get_address method positive result.""" ctx = ContextMock() @@ -39,7 +39,7 @@ def test__get_address_positive(self, *mocks): @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") -@mock.patch("aea.cli.get_address.verify_or_create_private_keys_ctx") +@mock.patch("aea.cli.utils.package_utils.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.get_address._try_get_address") @mock.patch("aea.cli.get_address.click.echo") class GetAddressCommandTestCase(TestCase): diff --git a/tests/test_cli/test_get_multiaddress.py b/tests/test_cli/test_get_multiaddress.py index 0f7643724c..832e78efec 100644 --- a/tests/test_cli/test_get_multiaddress.py +++ b/tests/test_cli/test_get_multiaddress.py @@ -69,17 +69,17 @@ def test_run(self, *mocks): self.generate_private_key(FETCHAI) self.add_private_key(FETCHAI, connection=True) - self.force_set_config( - "vendor.fetchai.connections.stub.config.host", "127.0.0.1" + self.nested_set_config( + "vendor.fetchai.connections.stub.config", + {"host": "127.0.0.1", "port": 10000}, ) - self.force_set_config("vendor.fetchai.connections.stub.config.port", 10000) result = self.run_cli_command( "get-multiaddress", FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--host-field", "host", "--port-field", @@ -103,8 +103,8 @@ def test_run(self, *mocks): self.generate_private_key(FETCHAI) self.add_private_key(FETCHAI, connection=True) - self.force_set_config( - "vendor.fetchai.connections.stub.config.public_uri", "127.0.0.1:10000" + self.nested_set_config( + "vendor.fetchai.connections.stub.config", {"public_uri": "127.0.0.1:10000"} ) result = self.run_cli_command( @@ -112,7 +112,7 @@ def test_run(self, *mocks): FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--uri-field", "public_uri", cwd=self.current_agent_context, @@ -188,14 +188,14 @@ def test_run(self, *mocks): # this will cause exception because no host configuration is in stub connection by default. with pytest.raises( Exception, - match="Host field 'some_host' not present in connection configuration fetchai/stub:0.10.0", + match="Host field 'some_host' not present in connection configuration fetchai/stub:0.11.0", ): self.run_cli_command( "get-multiaddress", FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--host-field", "some_host", "--port-field", @@ -212,21 +212,21 @@ def test_run(self, *mocks): self.generate_private_key(FETCHAI) self.add_private_key(FETCHAI, connection=True) - self.force_set_config( - "vendor.fetchai.connections.stub.config.host", "127.0.0.1" + self.nested_set_config( + "vendor.fetchai.connections.stub.config", {"host": "127.0.0.1"} ) # this will cause exception because no port configuration is in stub connection by default. with pytest.raises( Exception, - match="Port field 'some_port' not present in connection configuration fetchai/stub:0.10.0", + match="Port field 'some_port' not present in connection configuration fetchai/stub:0.11.0", ): self.run_cli_command( "get-multiaddress", FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--host-field", "host", "--port-field", @@ -271,10 +271,10 @@ def test_run(self, *mocks): self.generate_private_key(FETCHAI) self.add_private_key(FETCHAI, connection=True) - self.force_set_config( - "vendor.fetchai.connections.stub.config.host", "127.0.0.1" + self.nested_set_config( + "vendor.fetchai.connections.stub.config", + {"host": "127.0.0.1", "port": 10000}, ) - self.force_set_config("vendor.fetchai.connections.stub.config.port", 10000) # this will cause exception due to the mocking. with pytest.raises( @@ -286,7 +286,7 @@ def test_run(self, *mocks): FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--host-field", "host", "--port-field", @@ -313,7 +313,7 @@ def test_run(self, *mocks): FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--host-field", "some_host", cwd=self.current_agent_context, @@ -331,14 +331,14 @@ def test_run(self, *mocks): # this will cause exception because only the host, and not the port, are specified. with pytest.raises( Exception, - match="URI field 'some_uri' not present in connection configuration fetchai/stub:0.10.0", + match="URI field 'some_uri' not present in connection configuration fetchai/stub:0.11.0", ): self.run_cli_command( "get-multiaddress", FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--uri-field", "some_uri", cwd=self.current_agent_context, @@ -353,8 +353,9 @@ def test_run(self, *mocks): self.generate_private_key(FETCHAI) self.add_private_key(FETCHAI, connection=True) - self.force_set_config( - "vendor.fetchai.connections.stub.config.some_uri", "some-unparsable_URI" + self.nested_set_config( + "vendor.fetchai.connections.stub.config", + {"some_uri": "some-unparsable_URI"}, ) # this will cause exception because only the host, and not the port, are specified. @@ -367,7 +368,7 @@ def test_run(self, *mocks): FETCHAI, "--connection", "--connection-id", - "fetchai/stub:0.10.0", + "fetchai/stub:0.11.0", "--uri-field", "some_uri", cwd=self.current_agent_context, diff --git a/tests/test_cli/test_get_wealth.py b/tests/test_cli/test_get_wealth.py index 1d7f45d3a6..24d2933e92 100644 --- a/tests/test_cli/test_get_wealth.py +++ b/tests/test_cli/test_get_wealth.py @@ -30,8 +30,8 @@ class GetWealthTestCase(TestCase): """Test case for _get_wealth method.""" - @mock.patch("aea.cli.get_wealth.Wallet") - @mock.patch("aea.cli.get_wealth.verify_or_create_private_keys_ctx") + @mock.patch("aea.cli.utils.package_utils.Wallet") + @mock.patch("aea.cli.utils.package_utils.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.get_wealth.try_get_balance") def test__get_wealth_positive(self, *mocks): """Test for _get_wealth method positive result.""" @@ -40,7 +40,7 @@ def test__get_wealth_positive(self, *mocks): @mock.patch("aea.cli.utils.decorators.try_to_load_agent_config") -@mock.patch("aea.cli.get_wealth.verify_or_create_private_keys_ctx") +@mock.patch("aea.cli.utils.package_utils.verify_or_create_private_keys_ctx") @mock.patch("aea.cli.get_wealth._try_get_wealth") @mock.patch("aea.cli.get_wealth.click.echo") class GetWealthCommandTestCase(TestCase): diff --git a/tests/test_cli/test_install.py b/tests/test_cli/test_install.py index 060efd06fd..d7e81d440f 100644 --- a/tests/test_cli/test_install.py +++ b/tests/test_cli/test_install.py @@ -23,14 +23,11 @@ import shutil import tempfile from pathlib import Path -from unittest import TestCase, mock import yaml from aea.cli import cli -from aea.cli.install import _install_dependency from aea.configurations.base import DEFAULT_PROTOCOL_CONFIG_FILE -from aea.exceptions import AEAException from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner @@ -193,18 +190,3 @@ def teardown_class(cls): shutil.rmtree(cls.t) except (OSError, IOError): pass - - -@mock.patch("aea.cli.install.subprocess.Popen") -@mock.patch("aea.cli.install.subprocess.Popen.wait") -@mock.patch("aea.cli.install.sys.exit") -class InstallDependencyTestCase(TestCase): - """Test case for _install_dependency method.""" - - def test__install_dependency_with_git_url(self, *mocks): - """Test for _install_dependency method with git url.""" - dependency = { - "git": "url", - } - with self.assertRaises(AEAException): - _install_dependency("dependency_name", dependency) diff --git a/tests/test_cli/test_interact.py b/tests/test_cli/test_interact.py index abd4b995da..7f5496f502 100644 --- a/tests/test_cli/test_interact.py +++ b/tests/test_cli/test_interact.py @@ -117,6 +117,8 @@ def test__try_construct_envelope_positive(self, *mocks): """Test _try_construct_envelope for positive result.""" dialogues_mock = mock.Mock() msg_mock = mock.Mock() + msg_mock.to = "to" + msg_mock.sender = "sender" dialogues_mock.create.return_value = msg_mock, None envelope = _try_construct_envelope("agent_name", dialogues_mock) self.assertIsInstance(envelope, Envelope) @@ -201,7 +203,7 @@ class TestInteractEcho(AEATestCaseEmpty): @pytest.mark.integration def test_interact(self): """Test the 'aea interact' command with the echo skill.""" - self.add_item("skill", "fetchai/echo:0.8.0") + self.add_item("skill", "fetchai/echo:0.9.0") self.run_agent() process = self.run_interaction() @@ -209,7 +211,7 @@ def test_interact(self): process, [ "Starting AEA interaction channel...", - "Provide message of protocol fetchai/default:0.6.0 for performative bytes", + "Provide message of protocol fetchai/default:0.7.0 for performative bytes", ], timeout=10, is_terminating=False, @@ -225,9 +227,9 @@ def test_interact(self): "Sending envelope:", f"to: {self.agent_name}", f"sender: {self.agent_name}_interact", - "protocol_id: fetchai/default:0.6.0", + "protocol_id: fetchai/default:0.7.0", "message_id=1,target=0,performative=bytes,content=b'hello')", - "Provide message of protocol fetchai/default:0.6.0 for performative bytes:", + "Provide message of protocol fetchai/default:0.7.0 for performative bytes:", ], timeout=10, is_terminating=False, @@ -243,9 +245,9 @@ def test_interact(self): "Received envelope:", f"to: {self.agent_name}_interact", f"sender: {self.agent_name}", - "protocol_id: fetchai/default:0.6.0", + "protocol_id: fetchai/default:0.7.0", "message_id=2,target=1,performative=bytes,content=b'hello')", - "Provide message of protocol fetchai/default:0.6.0 for performative bytes:", + "Provide message of protocol fetchai/default:0.7.0 for performative bytes:", ], timeout=10, is_terminating=False, @@ -259,7 +261,7 @@ def test_interact(self): [ "Interrupting input, checking inbox ...", "Received no new envelope!", - "Provide message of protocol fetchai/default:0.6.0 for performative bytes:", + "Provide message of protocol fetchai/default:0.7.0 for performative bytes:", ], timeout=10, is_terminating=False, diff --git a/tests/test_cli/test_misc.py b/tests/test_cli/test_misc.py index fd2f4368eb..266c6f5764 100644 --- a/tests/test_cli/test_misc.py +++ b/tests/test_cli/test_misc.py @@ -50,12 +50,12 @@ def test_flag_help(): Command-line tool for setting up an Autonomous Economic Agent. Options: - --version Show the version and exit. - -v, --verbosity LVL One of NOTSET, DEBUG, INFO, WARNING, ERROR, - CRITICAL, OFF + --version Show the version and exit. + -v, --verbosity LVL One of NOTSET, DEBUG, INFO, WARNING, ERROR, + CRITICAL, OFF - --skip-consistency-check Skip consistency check. - --help Show this message and exit. + -s, --skip-consistency-check Skip consistency check. + --help Show this message and exit. Commands: add Add a resource to the agent. @@ -89,5 +89,7 @@ def test_flag_help(): run Run the agent. scaffold Scaffold a resource for the agent. search Search for components in the registry. + transfer Get the wealth associated with the private key. + upgrade Upgrade agent's component. """ ) diff --git a/tests/test_cli/test_push.py b/tests/test_cli/test_push.py index 5856162ea5..dbcc498683 100644 --- a/tests/test_cli/test_push.py +++ b/tests/test_cli/test_push.py @@ -17,13 +17,18 @@ # # ------------------------------------------------------------------------------ """Test module for Registry push methods.""" - +import filecmp from unittest import TestCase, mock +import pytest from click import ClickException from aea.cli import cli from aea.cli.push import _check_package_public_id, _save_item_locally +from aea.cli.utils.constants import ITEM_TYPES +from aea.configurations.base import PublicId +from aea.test_tools.constants import DEFAULT_AUTHOR +from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import AUTHOR, CLI_LOG_OPTION, CliRunner from tests.test_cli.tools_for_testing import ContextMock, PublicIdMock @@ -73,18 +78,14 @@ class CheckPackagePublicIdTestCase(TestCase): def test__check_package_public_id_positive(self, *mocks): """Test for _check_package_public_id positive result.""" _check_package_public_id( - "source-path", - "item-type", - PublicIdMock.from_str("{}/name:0.1.0".format(AUTHOR)), + "source-path", "item-type", PublicId.from_str(f"{AUTHOR}/name:0.1.0"), ) def test__check_package_public_id_negative(self, *mocks): """Test for _check_package_public_id negative result.""" with self.assertRaises(ClickException): _check_package_public_id( - "source-path", - "item-type", - PublicIdMock.from_str("{}/name:0.1.1".format(AUTHOR)), + "source-path", "item-type", PublicId.from_str(f"{AUTHOR}/name:0.1.1"), ) @@ -207,3 +208,32 @@ def test_push_contract_registry_positive(self, *mocks): standalone_mode=False, ) self.assertEqual(result.exit_code, 0) + + +class TestPushLocallyWithLatest(AEATestCaseEmpty): + """Test push locally with 'latest' as version.""" + + @pytest.mark.parametrize("component_type", ITEM_TYPES) + def test_command(self, component_type): + """Run the test.""" + item_name = f"my_{component_type}" + version = ":latest" + self.scaffold_item(component_type, item_name) + self.run_cli_command( + "push", + "--local", + component_type, + f"{DEFAULT_AUTHOR}/{item_name}{version}", + cwd=self._get_cwd(), + ) + + component_type_plural = component_type + "s" + path_to_pushed_package = ( + self.packages_dir_path / DEFAULT_AUTHOR / component_type_plural / item_name + ) + path_to_current_package = ( + self.t / self.agent_name / component_type_plural / item_name + ) + assert path_to_pushed_package.exists() + comparison = filecmp.dircmp(path_to_pushed_package, path_to_current_package) + assert comparison.diff_files == [] diff --git a/tests/test_cli/test_registry/test_add.py b/tests/test_cli/test_registry/test_add.py index 9a81089cfc..696df8054d 100644 --- a/tests/test_cli/test_registry/test_add.py +++ b/tests/test_cli/test_registry/test_add.py @@ -25,7 +25,7 @@ from aea.configurations.base import PublicId -@mock.patch("aea.cli.registry.add.request_api", return_value={"file": "url"}) +@mock.patch("aea.cli.registry.utils.request_api", return_value={"file": "url"}) @mock.patch("aea.cli.registry.add.download_file", return_value="filepath") @mock.patch("aea.cli.registry.add.extract") class FetchPackageTestCase(TestCase): diff --git a/tests/test_cli/test_remove/test_base.py b/tests/test_cli/test_remove/test_base.py index 036bdc6b0d..6ffba0cbc5 100644 --- a/tests/test_cli/test_remove/test_base.py +++ b/tests/test_cli/test_remove/test_base.py @@ -18,20 +18,33 @@ # ------------------------------------------------------------------------------ """Test module for aea.cli.remove.remove_item method.""" import os +from pathlib import Path from unittest import TestCase, mock +from unittest.mock import patch import pytest from click import ClickException +from aea.cli.core import cli from aea.cli.remove import remove_item -from aea.configurations.base import PublicId +from aea.configurations.base import ( + AgentConfig, + DEFAULT_AEA_CONFIG_FILE, + PackageId, + PackageType, + PublicId, +) from aea.configurations.constants import ( DEFAULT_CONNECTION, DEFAULT_PROTOCOL, DEFAULT_SKILL, ) +from aea.configurations.loader import ConfigLoader +from aea.helpers.base import cd from aea.test_tools.test_cases import AEATestCaseEmpty +from packages.fetchai.connections.soef.connection import PUBLIC_ID as SOEF_PUBLIC_ID + from tests.test_cli.tools_for_testing import ContextMock, PublicIdMock @@ -40,7 +53,9 @@ class RemoveItemTestCase(TestCase): """Test case for remove_item method.""" - def test_remove_item_item_folder_not_exists(self, *mocks): + def test_remove_item_item_folder_not_exists( + self, *mocks + ): # pylint: disable=unused-argument """Test for save_agent_locally item folder not exists.""" public_id = PublicIdMock.from_str("author/name:0.1.0") with self.assertRaises(ClickException): @@ -73,8 +88,67 @@ def test_remove_pacakge_latest_version(self, type_, public_id): assert item_name in items_folders # remove the package - self.run_cli_command(*["remove", type_, str(public_id)], cwd=self._get_cwd()) + with patch("aea.cli.remove.RemoveItem.is_required_by", False): + self.run_cli_command( + *["remove", type_, str(public_id)], cwd=self._get_cwd() + ) # check that the 'aea remove' took effect. items_folders = os.listdir(items_path) assert item_name not in items_folders + + +class TestRemoveConfig( + AEATestCaseEmpty +): # pylint: disable=attribute-defined-outside-init + """Test component configuration also removed from agent config.""" + + ITEM_TYPE = "connection" + ITEM_PUBLIC_ID = SOEF_PUBLIC_ID + + @staticmethod + def loader() -> ConfigLoader: + """Return Agent config loader.""" + return ConfigLoader.from_configuration_type(PackageType.AGENT) + + def load_config(self) -> AgentConfig: + """Load AgentConfig from current directory.""" + with cd(self._get_cwd()): + agent_loader = self.loader() + path = Path(DEFAULT_AEA_CONFIG_FILE) + with path.open(mode="r", encoding="utf-8") as fp: + agent_config = agent_loader.load(fp) + return agent_config + + def test_component_configuration_removed_from_agent_config(self): + """Test component configuration removed from agent config.""" + with cd(self._get_cwd()): + self.run_cli_command( + "add", "--local", self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID) + ) + self.runner.invoke( + cli, + [ + "config", + "set", + "vendor.fetchai.connections.soef.config.api_key", + "some_api_key", + ], + standalone_mode=False, + catch_exceptions=False, + ) + config = self.load_config() + + assert ( + PackageId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) + in config.component_configurations + ) + + self.run_cli_command("remove", self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID)) + + config = self.load_config() + + assert ( + PackageId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) + not in config.component_configurations + ) diff --git a/tests/test_cli/test_remove/test_connection.py b/tests/test_cli/test_remove/test_connection.py index 2ed0523fbd..535ce26621 100644 --- a/tests/test_cli/test_remove/test_connection.py +++ b/tests/test_cli/test_remove/test_connection.py @@ -47,7 +47,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" cls.connection_name = "http_client" os.chdir(cls.t) @@ -109,7 +109,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.connection_id = "fetchai/local:0.9.0" + cls.connection_id = "fetchai/local:0.10.0" os.chdir(cls.t) result = cls.runner.invoke( @@ -164,7 +164,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" cls.connection_name = "http_client" os.chdir(cls.t) diff --git a/tests/test_cli/test_remove/test_dependencies.py b/tests/test_cli/test_remove/test_dependencies.py new file mode 100644 index 0000000000..8654590327 --- /dev/null +++ b/tests/test_cli/test_remove/test_dependencies.py @@ -0,0 +1,225 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This test module contains the tests for the `aea add connection` sub-command.""" + +import os +import shutil +import tempfile +from pathlib import Path + +import pytest +from click.exceptions import ClickException + +from aea.cli import cli +from aea.cli.upgrade import ItemRemoveHelper +from aea.configurations.base import ( + AgentConfig, + DEFAULT_AEA_CONFIG_FILE, + PackageId, + PackageType, +) +from aea.configurations.loader import ConfigLoader + +from packages.fetchai.connections import oef +from packages.fetchai.connections.soef.connection import PUBLIC_ID as SOEF_PUBLIC_ID +from packages.fetchai.protocols.oef_search.message import OefSearchMessage + +from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner + + +class TestRemoveAndDependencies: # pylint: disable=attribute-defined-outside-init + """Test dependency remove helper and upgrade with dependency removed.""" + + ITEM_TYPE = "connection" + ITEM_PUBLIC_ID = SOEF_PUBLIC_ID + DEPENDENCY_TYPE = "protocol" + DEPENDENCY_PUBLIC_ID = OefSearchMessage.protocol_id + + @staticmethod + def loader() -> ConfigLoader: + """Return Agent config loader.""" + return ConfigLoader.from_configuration_type(PackageType.AGENT) + + def load_config(self) -> AgentConfig: + """Load AgentConfig from current directory.""" + agent_loader = self.loader() + path = Path(DEFAULT_AEA_CONFIG_FILE) + with path.open(mode="r", encoding="utf-8") as fp: + agent_config = agent_loader.load(fp) + return agent_config + + def setup(self): + """Set the test up.""" + self.runner = CliRunner() + self.agent_name = "myagent" + self.cwd = os.getcwd() + self.t = tempfile.mkdtemp() + # copy the 'packages' directory in the parent of the agent folder. + shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(self.t, "packages")) + + os.chdir(self.t) + result = self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR], + standalone_mode=False, + ) + assert result.exit_code == 0 + result = self.runner.invoke( + cli, + [*CLI_LOG_OPTION, "create", "--local", self.agent_name], + standalone_mode=False, + ) + assert result.exit_code == 0 + os.chdir(self.agent_name) + + # add connection first time + self.DEPENDENCY_PACKAGE_ID = PackageId( + self.DEPENDENCY_TYPE, self.DEPENDENCY_PUBLIC_ID + ) + result = self.runner.invoke( + cli, + ["-v", "DEBUG", "add", "--local", self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID)], + standalone_mode=False, + catch_exceptions=False, + ) + + def teardown(self): + """Tear the test down.""" + os.chdir(self.cwd) + try: + shutil.rmtree(self.t) + except (OSError, IOError): + pass + + def check_remove(self, item_type, public_id): + """Check remove can be performed with remove helper.""" + return ItemRemoveHelper(self.load_config()).check_remove(item_type, public_id) + + def test_package_can_be_removed_with_its_dependency(self): + """Test package (soef) can be removed with its dependency (oef_search).""" + required_by, can_be_removed, can_not_be_removed = self.check_remove( + self.ITEM_TYPE, self.ITEM_PUBLIC_ID + ) + + assert not required_by, required_by + assert self.DEPENDENCY_PACKAGE_ID in can_be_removed + assert self.DEPENDENCY_PACKAGE_ID not in can_not_be_removed + + def _install_oef(self): + self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "add", + "--local", + "connection", + str(oef.connection.PUBLIC_ID), + ], + standalone_mode=False, + catch_exceptions=False, + ) + + def test_package_can_be_removed_but_not_dependency(self): + """Test package (soef) can be removed but not its shared dependency (oef_search) with other package (oef).""" + self._install_oef() + required_by, can_be_removed, can_not_be_removed = self.check_remove( + self.ITEM_TYPE, self.ITEM_PUBLIC_ID + ) + + assert not required_by, required_by + assert self.DEPENDENCY_PACKAGE_ID not in can_be_removed + assert self.DEPENDENCY_PACKAGE_ID in can_not_be_removed + + def test_package_can_not_be_removed_cause_required_by_another_package(self): + """Test package (oef_search) can not be removed cause required by another package (soef).""" + required_by, can_be_removed, can_not_be_removed = self.check_remove( + self.DEPENDENCY_TYPE, self.DEPENDENCY_PUBLIC_ID + ) + + assert PackageId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) in required_by + assert not can_be_removed + assert not can_not_be_removed + + def test_removed_with_dependencies(self): + """ + Test dependency removed after upgrade. + + Done with mocking _add_item_deps to avoid dependencies installation. + """ + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + + self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "remove", + "--with-dependencies", + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}", + ], + standalone_mode=False, + catch_exceptions=False, + ) + assert self.DEPENDENCY_PUBLIC_ID not in self.load_config().protocols + + def test_removed_and_dependency_not_removed_caused_required_by_another_item(self): + """Test dependency is not removed after upgrade cause required by another item.""" + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + # do not add dependencies for the package + + self._install_oef() + self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "remove", + "--with-dependencies", + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}", + ], + standalone_mode=False, + catch_exceptions=False, + ) + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + + def test_not_removed_cause_required(self): + """Test dependency is not removed after upgrade cause required by another item.""" + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + # do not add dependencies for the package + with pytest.raises( + ClickException, + match="Package .* can not be removed because it is required by .*", + ): + self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "remove", + "--with-dependencies", + self.DEPENDENCY_TYPE, + f"{self.DEPENDENCY_PUBLIC_ID.author}/{self.DEPENDENCY_PUBLIC_ID.name}", + ], + standalone_mode=False, + catch_exceptions=False, + ) + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols diff --git a/tests/test_cli/test_remove/test_protocol.py b/tests/test_cli/test_remove/test_protocol.py index f4b82b2ea9..53cf525fb2 100644 --- a/tests/test_cli/test_remove/test_protocol.py +++ b/tests/test_cli/test_remove/test_protocol.py @@ -47,7 +47,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.protocol_id = "fetchai/gym:0.6.0" + cls.protocol_id = "fetchai/gym:0.7.0" cls.protocol_name = "gym" os.chdir(cls.t) @@ -109,7 +109,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.protocol_id = "fetchai/gym:0.6.0" + cls.protocol_id = "fetchai/gym:0.7.0" os.chdir(cls.t) result = cls.runner.invoke( @@ -164,7 +164,7 @@ def setup_class(cls): cls.t = tempfile.mkdtemp() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) - cls.protocol_id = "fetchai/gym:0.6.0" + cls.protocol_id = "fetchai/gym:0.7.0" os.chdir(cls.t) result = cls.runner.invoke( diff --git a/tests/test_cli/test_remove/test_skill.py b/tests/test_cli/test_remove/test_skill.py index 372836292d..cafbf8f246 100644 --- a/tests/test_cli/test_remove/test_skill.py +++ b/tests/test_cli/test_remove/test_skill.py @@ -45,7 +45,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/gym:0.8.0" + cls.skill_id = "fetchai/gym:0.9.0" cls.skill_name = "gym" os.chdir(cls.t) @@ -114,7 +114,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/gym:0.8.0" + cls.skill_id = "fetchai/gym:0.9.0" os.chdir(cls.t) result = cls.runner.invoke( @@ -168,7 +168,7 @@ def setup_class(cls): cls.agent_name = "myagent" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() - cls.skill_id = "fetchai/gym:0.8.0" + cls.skill_id = "fetchai/gym:0.9.0" cls.skill_name = "gym" os.chdir(cls.t) diff --git a/tests/test_cli/test_run.py b/tests/test_cli/test_run.py index d4f258cce7..7193a43f77 100644 --- a/tests/test_cli/test_run.py +++ b/tests/test_cli/test_run.py @@ -76,7 +76,7 @@ def test_run(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 @@ -87,7 +87,7 @@ def test_run(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -168,9 +168,9 @@ def test_run_with_default_connection(): @pytest.mark.parametrize( argnames=["connection_ids"], argvalues=[ - ["fetchai/http_client:0.9.0,{}".format(str(DEFAULT_CONNECTION))], - ["'fetchai/http_client:0.9.0, {}'".format(str(DEFAULT_CONNECTION))], - ["fetchai/http_client:0.9.0,,{},".format(str(DEFAULT_CONNECTION))], + ["fetchai/http_client:0.10.0,{}".format(str(DEFAULT_CONNECTION))], + ["'fetchai/http_client:0.10.0, {}'".format(str(DEFAULT_CONNECTION))], + ["fetchai/http_client:0.10.0,,{},".format(str(DEFAULT_CONNECTION))], ], ) def test_run_multiple_connections(connection_ids): @@ -195,7 +195,7 @@ def test_run_multiple_connections(connection_ids): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 @@ -251,7 +251,7 @@ def test_run_unknown_private_key(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 result = runner.invoke( @@ -261,7 +261,7 @@ def test_run_unknown_private_key(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -290,7 +290,7 @@ def test_run_unknown_private_key(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.10.0"], standalone_mode=False, ) @@ -326,7 +326,7 @@ def test_run_fet_private_key_config(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 @@ -350,7 +350,9 @@ def test_run_fet_private_key_config(): error_msg = "" try: - cli.main([*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.9.0"]) + cli.main( + [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.10.0"] + ) except SystemExit as e: error_msg = str(e) @@ -385,7 +387,7 @@ def test_run_ethereum_private_key_config(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 @@ -409,7 +411,9 @@ def test_run_ethereum_private_key_config(): error_msg = "" try: - cli.main([*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.9.0"]) + cli.main( + [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.10.0"] + ) except SystemExit as e: error_msg = str(e) @@ -447,7 +451,7 @@ def test_run_with_install_deps(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 result = runner.invoke( @@ -457,7 +461,7 @@ def test_run_with_install_deps(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -473,7 +477,7 @@ def test_run_with_install_deps(): "run", "--install-deps", "--connections", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], env=os.environ, maxread=10000, @@ -519,7 +523,7 @@ def test_run_with_install_deps_and_requirement_file(): result = runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.9.0"], + [*CLI_LOG_OPTION, "add", "--local", "connection", "fetchai/http_client:0.10.0"], ) assert result.exit_code == 0 result = runner.invoke( @@ -529,7 +533,7 @@ def test_run_with_install_deps_and_requirement_file(): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -549,7 +553,7 @@ def test_run_with_install_deps_and_requirement_file(): "run", "--install-deps", "--connections", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], env=os.environ, maxread=10000, @@ -607,7 +611,7 @@ def setup_class(cls): "add", "--local", "connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], standalone_mode=False, ) @@ -624,7 +628,7 @@ def setup_class(cls): try: cli.main( - [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.9.0"] + [*CLI_LOG_OPTION, "run", "--connections", "fetchai/http_client:0.10.0"] ) except SystemExit as e: cls.exit_code = e.code @@ -874,7 +878,7 @@ def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" - cls.connection_id = PublicId.from_str("fetchai/http_client:0.9.0") + cls.connection_id = PublicId.from_str("fetchai/http_client:0.10.0") cls.connection_name = cls.connection_id.name cls.connection_author = cls.connection_id.author cls.cwd = os.getcwd() @@ -908,7 +912,7 @@ def setup_class(cls): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -967,7 +971,7 @@ def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" - cls.connection_id = PublicId.from_str("fetchai/http_client:0.9.0") + cls.connection_id = PublicId.from_str("fetchai/http_client:0.10.0") cls.connection_author = cls.connection_id.author cls.connection_name = cls.connection_id.name cls.cwd = os.getcwd() @@ -1001,7 +1005,7 @@ def setup_class(cls): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -1059,7 +1063,7 @@ def setup_class(cls): """Set the test up.""" cls.runner = CliRunner() cls.agent_name = "myagent" - cls.connection_id = "fetchai/http_client:0.9.0" + cls.connection_id = "fetchai/http_client:0.10.0" cls.connection_name = "http_client" cls.cwd = os.getcwd() cls.t = tempfile.mkdtemp() @@ -1092,7 +1096,7 @@ def setup_class(cls): "config", "set", "agent.default_connection", - "fetchai/http_client:0.9.0", + "fetchai/http_client:0.10.0", ], ) assert result.exit_code == 0 @@ -1246,7 +1250,7 @@ def setup_class(cls): result = cls.runner.invoke( cli, - [*CLI_LOG_OPTION, "add", "--local", "protocol", "fetchai/fipa:0.7.0"], + [*CLI_LOG_OPTION, "add", "--local", "protocol", "fetchai/fipa:0.8.0"], standalone_mode=False, ) assert result.exit_code == 0 diff --git a/tests/test_cli/test_search.py b/tests/test_cli/test_search.py index 606f70e860..06be5a87ff 100644 --- a/tests/test_cli/test_search.py +++ b/tests/test_cli/test_search.py @@ -214,6 +214,7 @@ def setup_class(cls): "Some description.", ], standalone_mode=False, + catch_exceptions=False, ) assert result.exit_code == 0 result = cls.runner.invoke( @@ -363,8 +364,8 @@ def test_exit_code_equal_to_zero(self): def test_correct_output(self,): """Test that the command has printed the correct output..""" - public_id_echo = PublicId.from_str("fetchai/echo:0.8.0") - public_id_error = PublicId.from_str("fetchai/error:0.6.0") + public_id_echo = PublicId.from_str("fetchai/echo:0.9.0") + public_id_error = PublicId.from_str("fetchai/error:0.7.0") expected = ( 'Searching for ""...\n' "Skills found:\n\n" @@ -445,8 +446,8 @@ def test_exit_code_equal_to_zero(self): def test_correct_output(self,): """Test that the command has printed the correct output..""" - public_id_echo = PublicId.from_str("fetchai/echo:0.8.0") - public_id_error = PublicId.from_str("fetchai/error:0.6.0") + public_id_echo = PublicId.from_str("fetchai/echo:0.9.0") + public_id_error = PublicId.from_str("fetchai/error:0.7.0") expected = ( 'Searching for ""...\n' "Skills found:\n\n" diff --git a/tests/test_cli/test_transfer.py b/tests/test_cli/test_transfer.py new file mode 100644 index 0000000000..632f9d7896 --- /dev/null +++ b/tests/test_cli/test_transfer.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2020 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This test module contains the tests for commands in aea.cli.transfer module.""" +import random +import string +from pathlib import Path +from unittest.mock import patch + +import pytest +from click.exceptions import ClickException + +from aea.cli.core import cli +from aea.cli.transfer import wait_tx_settled +from aea.cli.utils.package_utils import get_wallet_from_agent_config, try_get_balance +from aea.crypto.cosmos import CosmosCrypto +from aea.crypto.fetchai import FetchAICrypto +from aea.crypto.helpers import verify_or_create_private_keys +from aea.helpers.base import cd +from aea.test_tools.test_cases import AEATestCaseEmpty + +from tests.common.utils import wait_for_condition + + +class TestCliTransferFetchAINetwork(AEATestCaseEmpty): + """Test cli transfer command.""" + + LEDGER_ID = FetchAICrypto.identifier + ANOTHER_LEDGER_ID = CosmosCrypto.identifier + + @classmethod + def setup_class(cls): + """Set up the test class.""" + super(TestCliTransferFetchAINetwork, cls).setup_class() + cls.agent_name2 = "agent-" + "".join( + random.choices(string.ascii_lowercase, k=5) + ) + cls.create_agents(cls.agent_name2) + + cls.gen_key(cls.agent_name) + cls.gen_key(cls.agent_name2) + + @classmethod + def gen_key(cls, agent_name: str) -> None: + """Generate crypto key.""" + cls.set_agent_context(agent_name) + key_file = f"{cls.LEDGER_ID}.key" + assert cls.run_cli_command( + "generate-key", cls.LEDGER_ID, key_file, cwd=cls._get_cwd() + ) + assert cls.run_cli_command( + "add-key", cls.LEDGER_ID, key_file, cwd=cls._get_cwd() + ) + + def get_address(self) -> str: + """Get current agent address.""" + result = self.invoke("get-address", self.LEDGER_ID) + return result.stdout_bytes.decode("utf-8").strip() + + def invoke(self, *args): + """Call the cli command.""" + with cd(self._get_cwd()): + result = self.runner.invoke( + cli, args, standalone_mode=False, catch_exceptions=False + ) + return result + + def get_balance(self) -> int: + """Get balance for current agent.""" + with cd(self._get_cwd()): + agent_config = verify_or_create_private_keys(Path("."), False) + wallet = get_wallet_from_agent_config(agent_config) + return int(try_get_balance(agent_config, wallet, self.LEDGER_ID)) + + def test_integration(self): + """Perform integration tests of cli transfer command with real transfer.""" + self.set_agent_context(self.agent_name2) + agent2_balance = self.get_balance() + agent2_address = self.get_address() + assert agent2_balance == 0 + + self.set_agent_context(self.agent_name) + self.generate_wealth() + + wait_for_condition(lambda: self.get_balance() > 0, timeout=15, period=1) + + agent1_balance = self.get_balance() + assert agent1_balance > 0 + + amount = round(agent1_balance / 10) + fee = round(agent1_balance / 20) + + self.invoke( + "transfer", self.LEDGER_ID, agent2_address, str(amount), str(fee), "-y" + ) + + wait_for_condition( + lambda: self.get_balance() == (agent1_balance - amount - fee), + timeout=15, + period=1, + ) + + self.set_agent_context(self.agent_name2) + wait_for_condition( + lambda: self.get_balance() == (agent2_balance + amount), + timeout=15, + period=1, + ) + + @patch("aea.cli.transfer.do_transfer", return_value="some_digest") + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_yes_option_enabled( + self, wait_tx_settled_mock, confirm_mock, do_transfer_mock + ): + """Test yes option is enabled.""" + self.invoke( + "transfer", self.LEDGER_ID, self.get_address(), "100000", "100", "-y" + ) + confirm_mock.assert_not_called() + + @patch("aea.cli.transfer.do_transfer", return_value="some_digest") + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_yes_option_disabled( + self, wait_tx_settled_mock, confirm_mock, do_transfer_mock + ): + """Test yes option is disabled.""" + self.invoke("transfer", self.LEDGER_ID, self.get_address(), "100000", "100") + confirm_mock.assert_called_once() + + @patch("aea.cli.transfer.do_transfer", return_value="some_digest") + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_sync_option_enabled( + self, wait_tx_settled_mock, confirm_mock, do_transfer_mock + ): + """Test sync option is enabled.""" + self.invoke( + "transfer", self.LEDGER_ID, self.get_address(), "100000", "100", "-y" + ) + wait_tx_settled_mock.assert_not_called() + + @patch("aea.cli.transfer.do_transfer", return_value="some_digest") + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_sync_option_disabled( + self, wait_tx_settled_mock, confirm_mock, do_transfer_mock + ): + """Test sync option is disabled.""" + self.invoke( + "transfer", + self.LEDGER_ID, + self.get_address(), + "100000", + "100", + "-y", + "--sync", + ) + wait_tx_settled_mock.assert_called_once() + + @patch("aea.cli.transfer.do_transfer", return_value=None) + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_failed_on_send(self, wait_tx_settled_mock, confirm_mock, do_transfer_mock): + """Test fail to send a transaction.""" + with pytest.raises(ClickException, match=r"Failed to send a transaction!"): + self.invoke("transfer", self.LEDGER_ID, self.get_address(), "100000", "100") + + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_no_wallet_registered(self, wait_tx_settled_mock, confirm_mock): + """Test no wallet for crypto id registered.""" + with pytest.raises( + ClickException, match=r"No private key registered for `.*` in wallet!" + ): + self.invoke( + "transfer", self.ANOTHER_LEDGER_ID, self.get_address(), "100000", "100" + ) + + @patch("aea.cli.transfer.try_get_balance", return_value=10) + @patch("aea.cli.transfer.click.confirm", return_value=None) + @patch("aea.cli.transfer.wait_tx_settled", return_value=None) + def test_balance_too_low( + self, wait_tx_settled_mock, confirm_mock, do_transfer_mock + ): + """Test balance too low exception.""" + with pytest.raises( + ClickException, + match=r"Balance is not enough! Available=[0-9]+, required=[0-9]+!", + ): + self.invoke("transfer", self.LEDGER_ID, self.get_address(), "100000", "100") + + @patch( + "aea.cli.transfer.LedgerApis.is_transaction_settled", side_effects=[False, True] + ) + def test_wait_tx_settled_ok(self, is_transaction_settled_mock): + """Test wait tx settle is ok.""" + wait_tx_settled("some", "some", timeout=4) + + @patch("aea.cli.transfer.LedgerApis.is_transaction_settled", return_value=False) + def test_wait_tx_settled_timeout(self, is_transaction_settled_mock): + """Test wait tx settle fails with timeout error.""" + with pytest.raises(TimeoutError): + wait_tx_settled("some", "some", timeout=0.5) diff --git a/tests/test_cli/test_upgrade.py b/tests/test_cli/test_upgrade.py new file mode 100644 index 0000000000..9e7927be51 --- /dev/null +++ b/tests/test_cli/test_upgrade.py @@ -0,0 +1,562 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This test module contains the tests for the `aea add connection` sub-command.""" + +import os +import shutil +import tempfile +from contextlib import contextmanager +from pathlib import Path +from typing import List +from unittest.mock import patch + +import pytest +from click.exceptions import ClickException + +from aea.cli import cli +from aea.cli.upgrade import ItemRemoveHelper +from aea.configurations.base import ( + AgentConfig, + ComponentId, + DEFAULT_AEA_CONFIG_FILE, + PackageId, + PackageType, + PublicId, +) +from aea.configurations.loader import ConfigLoader +from aea.helpers.base import cd +from aea.test_tools.test_cases import BaseAEATestCase + +from packages.fetchai.connections import oef +from packages.fetchai.connections.soef.connection import PUBLIC_ID as SOEF_PUBLIC_ID +from packages.fetchai.protocols.oef_search.message import OefSearchMessage + +from tests.conftest import AUTHOR, CLI_LOG_OPTION, CUR_PATH, CliRunner + + +class BaseTestCase: + """Base test case class with setup and teardown and some utils.""" + + ITEM_TYPE = "connection" + ITEM_PUBLIC_ID = SOEF_PUBLIC_ID + LOCAL: List[str] = ["--local"] + DEPENDENCY_TYPE = "protocol" + DEPENDENCY_PUBLIC_ID = OefSearchMessage.protocol_id + + @staticmethod + def loader() -> ConfigLoader: + """Return Agent config loader.""" + return ConfigLoader.from_configuration_type(PackageType.AGENT) + + def load_config(self) -> AgentConfig: + """Load AgentConfig from current directory.""" + agent_loader = self.loader() + path = Path(DEFAULT_AEA_CONFIG_FILE) + with path.open(mode="r", encoding="utf-8") as fp: + agent_config = agent_loader.load(fp) + return agent_config + + def dump_config(self, agent_config: AgentConfig) -> None: + """Dump AgentConfig to current directory.""" + agent_loader = self.loader() + path = Path(DEFAULT_AEA_CONFIG_FILE) + + with path.open(mode="w", encoding="utf-8") as fp: + agent_loader.dump(agent_config, fp) + + @classmethod + def setup(cls): + """Set the test up.""" + cls.runner = CliRunner() + cls.agent_name = "myagent" + cls.cwd = os.getcwd() + cls.t = tempfile.mkdtemp() + # copy the 'packages' directory in the parent of the agent folder. + shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(cls.t, "packages")) + + os.chdir(cls.t) + result = cls.runner.invoke( + cli, + [*CLI_LOG_OPTION, "init", "--local", "--author", AUTHOR], + standalone_mode=False, + ) + assert result.exit_code == 0 + result = cls.runner.invoke( + cli, + [*CLI_LOG_OPTION, "create", "--local", cls.agent_name], + standalone_mode=False, + ) + assert result.exit_code == 0 + os.chdir(cls.agent_name) + # add connection first time + + @contextmanager + def with_oef_installed(self): + """Add and remove oef connection.""" + result = self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "add", + "--local", + "connection", + str(oef.connection.PUBLIC_ID), + ], + standalone_mode=False, + ) + assert result.exit_code == 0 + try: + yield + finally: + result = self.runner.invoke( + cli, + ["-v", "DEBUG", "remove", "connection", str(oef.connection.PUBLIC_ID)], + standalone_mode=False, + ) + assert result.exit_code == 0 + + @contextmanager + def with_config_update(self): + """Context manager to update item version to 0.0.1.""" + original_config = self.load_config() + + config_data = original_config.json + if str(self.ITEM_PUBLIC_ID) in config_data[f"{self.ITEM_TYPE}s"]: + config_data[f"{self.ITEM_TYPE}s"].remove(str(self.ITEM_PUBLIC_ID)) + config_data[f"{self.ITEM_TYPE}s"].append( + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:0.0.1" + ) + self.dump_config(AgentConfig.from_json(config_data)) + try: + yield + finally: + self.dump_config(original_config) + + @classmethod + def teardown(cls): + """Tear the test down.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass + + +class TestRemoveAndDependencies(BaseTestCase): + """Test dependency remove helper and upgrade with dependency removed.""" + + ITEM_TYPE = "connection" + ITEM_PUBLIC_ID = SOEF_PUBLIC_ID + LOCAL: List[str] = ["--local"] + DEPENDENCY_TYPE = "protocol" + DEPENDENCY_PUBLIC_ID = OefSearchMessage.protocol_id + + @classmethod + def setup(cls): + """Set the test up.""" + super(TestRemoveAndDependencies, cls).setup() + cls.DEPENDENCY_PACKAGE_ID = PackageId( + cls.DEPENDENCY_TYPE, cls.DEPENDENCY_PUBLIC_ID + ) + result = cls.runner.invoke( + cli, + ["-v", "DEBUG", "add", "--local", cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID)], + standalone_mode=False, + ) + assert result.exit_code == 0 + + def test_upgrade_and_dependency_removed(self): + """ + Test dependency removed after upgrade. + + Done with mocking _add_item_deps to avoid dependencies installation. + + Also checks dependency configuration removed with component + """ + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + + # add empty component config to aea-config.py + agent_config = self.load_config() + component_id = ComponentId(self.DEPENDENCY_TYPE, self.DEPENDENCY_PUBLIC_ID) + agent_config.component_configurations[component_id] = {} # just empty + agent_config.component_configurations[ + ComponentId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) + ] = {} # just empty + self.dump_config(agent_config) + + agent_config = self.load_config() + assert component_id in agent_config.component_configurations + + with patch( + "aea.cli.upgrade.ItemUpgrader.check_upgrade_is_required", return_value=True + ), patch("aea.cli.add._add_item_deps"): + result = self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + catch_exceptions=True, + ) + try: + assert result.exit_code == 0 + + assert self.DEPENDENCY_PUBLIC_ID not in self.load_config().protocols + agent_config = self.load_config() + + # check configuration was removed too + assert component_id not in agent_config.component_configurations + assert ( + ComponentId(self.ITEM_TYPE, self.ITEM_PUBLIC_ID) + in agent_config.component_configurations + ) + finally: + # restore component removed + result = self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "add", + *self.LOCAL, + self.DEPENDENCY_TYPE, + f"{self.DEPENDENCY_PUBLIC_ID.author}/{self.DEPENDENCY_PUBLIC_ID.name}:latest", + ], + catch_exceptions=True, + ) + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + + def test_upgrade_and_dependency_not_removed_caused_required_by_another_item(self): + """Test dependency is not removed after upgrade cause required by another item.""" + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + # do not add dependencies for the package + + with self.with_oef_installed(), self.with_config_update(), patch( + "aea.cli.add._add_item_deps" + ): + result = self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + catch_exceptions=True, + ) + assert result.exit_code == 0 + assert self.DEPENDENCY_PUBLIC_ID in self.load_config().protocols + + +class TestUpgradeProject(BaseAEATestCase, BaseTestCase): + """Test that the command 'aea upgrade' works.""" + + capture_log = True + + @classmethod + def setup(cls): + """Set up test case.""" + super(TestUpgradeProject, cls).setup() + cls.agent_name = "generic_buyer_0.9.0" + cls.latest_agent_name = "generic_buyer_latest" + cls.run_cli_command( + "fetch", "fetchai/generic_buyer:0.9.0", "--alias", cls.agent_name + ) + cls.run_cli_command( + "fetch", "fetchai/generic_buyer:latest", "--alias", cls.latest_agent_name + ) + cls.agents.add(cls.agent_name) + cls.set_agent_context(cls.agent_name) + + def test_upgrade(self): + """Test upgrade project old version to latest one and compare with latest project fetched.""" + with cd(self.latest_agent_name): + latest_agent_items = set( + ItemRemoveHelper(self.load_config()) + .get_agent_dependencies_with_reverse_dependencies() + .keys() + ) + + with cd(self.agent_name): + self.runner.invoke( # pylint: disable=no-member + cli, ["upgrade"], standalone_mode=False, catch_exceptions=False + ) + agent_items = set( + ItemRemoveHelper(self.load_config()) + .get_agent_dependencies_with_reverse_dependencies() + .keys() + ) + assert latest_agent_items == agent_items + + # upgrade again to check it workd with upgraded version + with cd(self.agent_name): + self.runner.invoke( # pylint: disable=no-member + cli, ["upgrade"], standalone_mode=False, catch_exceptions=False + ) + agent_items = set( + ItemRemoveHelper(self.load_config()) + .get_agent_dependencies_with_reverse_dependencies() + .keys() + ) + assert latest_agent_items == agent_items + + +class TestUpgradeConnectionLocally(BaseTestCase): + """Test that the command 'aea upgrade connection' works.""" + + ITEM_TYPE = "connection" + ITEM_PUBLIC_ID = SOEF_PUBLIC_ID + LOCAL: List[str] = ["--local"] + + @classmethod + def setup(cls): + """Set the test up.""" + super(TestUpgradeConnectionLocally, cls).setup() + + result = cls.runner.invoke( + cli, + ["-v", "DEBUG", "add", "--local", cls.ITEM_TYPE, str(cls.ITEM_PUBLIC_ID)], + standalone_mode=False, + ) + assert result.exit_code == 0 + + def test_upgrade_to_same_version(self): + """Test do not upgrade to version already installed.""" + with pytest.raises( + ClickException, + match=r"The .* with id '.*' already has version .*. Nothing to upgrade.", + ): + self.runner.invoke( + cli, + ["upgrade", *self.LOCAL, self.ITEM_TYPE, str(self.ITEM_PUBLIC_ID)], + standalone_mode=False, + catch_exceptions=False, + ) + + def test_upgrade_to_latest_but_same_version(self): + """Test no update to latest if already latest component.""" + with pytest.raises( + ClickException, + match=r"The .* with id '.*' already has version .*. Nothing to upgrade.", + ): + self.runner.invoke( + cli, + [ + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + standalone_mode=False, + catch_exceptions=False, + ) + + def test_upgrade_to_non_registered(self): + """Test can not upgrade not registered component.""" + with pytest.raises( + ClickException, + match=r".* with id .* is not registered. Please use the `add` command. Aborting...", + ): + self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + "nonexits/dummy:0.0.0", + ], + standalone_mode=False, + catch_exceptions=False, + ) + + def test_upgrade_required_mock(self): + """Test upgrade with mocking upgrade required.""" + with patch( + "aea.cli.upgrade.ItemUpgrader.check_upgrade_is_required", + return_value="100.0.0", + ): + result = self.runner.invoke( + cli, + [ + "-v", + "DEBUG", + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + catch_exceptions=False, + ) + assert result.exit_code == 0 + + def test_do_upgrade(self): + """Test real full upgrade.""" + with self.with_config_update(): + result = self.runner.invoke( + cli, + [ + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + standalone_mode=False, + ) + assert result.exit_code == 0 + + def test_package_can_not_be_found_in_registry(self): + """Test no package in registry.""" + with self.with_config_update(): + with patch( + "aea.cli.registry.utils.get_package_meta", + side_effects=Exception("expected!"), + ), patch( + "aea.cli.registry.utils.find_item_locally", + side_effects=Exception("expected!"), + ), pytest.raises( + ClickException, + match=r"Package .* details can not be fetched from the registry!", + ): + self.runner.invoke( + cli, + [ + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + standalone_mode=False, + catch_exceptions=False, + ) + + def test_package_can_not_upgraded_cause_required(self): + """Test no package in registry.""" + with self.with_config_update(): + with patch( + "aea.cli.upgrade.ItemRemoveHelper.check_remove", + return_value=( + set([PackageId("connection", PublicId("test", "test", "0.0.1"))]), + set(), + dict(), + ), + ), pytest.raises( + ClickException, + match=r"Can not upgrade .* because it is required by '.*'", + ): + self.runner.invoke( + cli, + [ + "upgrade", + *self.LOCAL, + self.ITEM_TYPE, + f"{self.ITEM_PUBLIC_ID.author}/{self.ITEM_PUBLIC_ID.name}:latest", + ], + standalone_mode=False, + catch_exceptions=False, + ) + + @classmethod + def teardown(cls): + """Tear the test down.""" + super(TestUpgradeConnectionLocally, cls).teardown() + + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.t) + except (OSError, IOError): + pass + + +class TestUpgradeConnectionRemoteRegistry(TestUpgradeConnectionLocally): + """Test that the command 'aea upgrade connection' works.""" + + LOCAL: List[str] = [] + + def test_upgrade_to_latest_but_same_version(self): + """Skip.""" + pass + + +class TestUpgradeProtocolLocally(TestUpgradeConnectionLocally): + """Test that the command 'aea upgrade protocol --local' works.""" + + ITEM_TYPE = "protocol" + ITEM_PUBLIC_ID = PublicId.from_str("fetchai/http:0.7.0") + + +class TestUpgradeProtocolRemoteRegistry(TestUpgradeProtocolLocally): + """Test that the command 'aea upgrade protocol' works.""" + + LOCAL: List[str] = [] + + def test_upgrade_to_latest_but_same_version(self): + """Skip.""" + pass + + +class TestUpgradeSkillLocally(TestUpgradeConnectionLocally): + """Test that the command 'aea upgrade skill --local' works.""" + + ITEM_TYPE = "skill" + ITEM_PUBLIC_ID = PublicId.from_str("fetchai/echo:0.9.0") + + +class TestUpgradeSkillRemoteRegistry(TestUpgradeSkillLocally): + """Test that the command 'aea upgrade skill' works.""" + + LOCAL: List[str] = [] + + def test_upgrade_to_latest_but_same_version(self): + """Skip.""" + pass + + def test_upgrade_required_mock(self): + """Skip.""" + pass + + def test_do_upgrade(self): + """Skip.""" + pass + + +class TestUpgradeContractLocally(TestUpgradeConnectionLocally): + """Test that the command 'aea upgrade contract' works.""" + + ITEM_TYPE = "contract" + ITEM_PUBLIC_ID = PublicId.from_str("fetchai/erc1155:0.11.0") + + +class TestUpgradeContractRemoteRegistry(TestUpgradeContractLocally): + """Test that the command 'aea upgrade contract --local' works.""" + + LOCAL: List[str] = [] + + def test_upgrade_to_latest_but_same_version(self): + """Skip.""" + pass diff --git a/tests/test_cli/test_utils/test_utils.py b/tests/test_cli/test_utils/test_utils.py index 21dae77ca7..f94cb08efe 100644 --- a/tests/test_cli/test_utils/test_utils.py +++ b/tests/test_cli/test_utils/test_utils.py @@ -16,7 +16,6 @@ # limitations under the License. # # ------------------------------------------------------------------------------ - """This test module contains the tests for aea.cli.utils module.""" from builtins import FileNotFoundError @@ -28,7 +27,7 @@ from jsonschema import ValidationError from yaml import YAMLError -from aea.cli.utils.click_utils import AEAJsonPathType, PublicIdParameter +from aea.cli.utils.click_utils import PublicIdParameter from aea.cli.utils.config import ( _init_cli_config, get_or_create_cli_config, @@ -42,6 +41,7 @@ find_item_in_distribution, find_item_locally, get_package_path_unified, + get_wallet_from_context, is_fingerprint_correct, is_item_present_unified, is_local_item, @@ -59,6 +59,9 @@ SIGNING_PROTOCOL, STATE_UPDATE_PROTOCOL, ) +from aea.crypto.wallet import Wallet +from aea.helpers.base import cd +from aea.test_tools.test_cases import AEATestCaseEmpty from tests.conftest import FETCHAI from tests.test_cli.tools_for_testing import ( @@ -289,7 +292,7 @@ class FindItemLocallyTestCase(TestCase): ) def test_find_item_locally_bad_config(self, *mocks): """Test find_item_locally for bad config result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.8.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.9.0") with self.assertRaises(ClickException) as cm: find_item_locally(ContextMock(), "skill", public_id) @@ -303,7 +306,7 @@ def test_find_item_locally_bad_config(self, *mocks): ) def test_find_item_locally_cant_find(self, from_conftype_mock, *mocks): """Test find_item_locally for can't find result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.8.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.9.0") with self.assertRaises(ClickException) as cm: find_item_locally(ContextMock(), "skill", public_id) @@ -322,7 +325,7 @@ class FindItemInDistributionTestCase(TestCase): ) def testfind_item_in_distribution_bad_config(self, *mocks): """Test find_item_in_distribution for bad config result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.8.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.9.0") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) @@ -331,7 +334,7 @@ def testfind_item_in_distribution_bad_config(self, *mocks): @mock.patch("aea.cli.utils.package_utils.Path.exists", return_value=False) def testfind_item_in_distribution_not_found(self, *mocks): """Test find_item_in_distribution for not found result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.8.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.9.0") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) @@ -345,7 +348,7 @@ def testfind_item_in_distribution_not_found(self, *mocks): ) def testfind_item_in_distribution_cant_find(self, from_conftype_mock, *mocks): """Test find_item_locally for can't find result.""" - public_id = PublicIdMock.from_str("fetchai/echo:0.8.0") + public_id = PublicIdMock.from_str("fetchai/echo:0.9.0") with self.assertRaises(ClickException) as cm: find_item_in_distribution(ContextMock(), "skill", public_id) @@ -391,29 +394,6 @@ def test_is_fingerprint_correct_negative(self, *mocks): self.assertFalse(result) -@mock.patch("aea.cli.config.click.ParamType") -class AEAJsonPathTypeTestCase(TestCase): - """Test case for AEAJsonPathType class.""" - - @mock.patch("aea.cli.utils.click_utils.Path.exists", return_value=True) - def test_convert_root_vendor_positive(self, *mocks): - """Test for convert method with root "vendor" positive result.""" - value = "vendor.author.protocols.package_name.attribute_name" - ctx_mock = ContextMock() - ctx_mock.obj = mock.Mock() - ctx_mock.obj.set_config = mock.Mock() - obj = AEAJsonPathType() - obj.convert(value, "param", ctx_mock) - - @mock.patch("aea.cli.utils.click_utils.Path.exists", return_value=False) - def test_convert_root_vendor_path_not_exists(self, *mocks): - """Test for convert method with root "vendor" path not exists.""" - value = "vendor.author.protocols.package_name.attribute_name" - obj = AEAJsonPathType() - with self.assertRaises(BadParameter): - obj.convert(value, "param", "ctx") - - @mock.patch("aea.cli.utils.package_utils.LedgerApis", mock.MagicMock()) class TryGetBalanceTestCase(TestCase): """Test case for try_get_balance method.""" @@ -485,3 +465,13 @@ def test_is_item_present_unified(mock_, vendor): def test_is_local_item(public_id, expected_outcome): """Test the 'is_local_item' CLI utility function.""" assert is_local_item(public_id) is expected_outcome + + +class TestGetWalletFromtx(AEATestCaseEmpty): + """Test get_wallet_from_context.""" + + def test_get_wallet_from_ctx(self): + """Test get_wallet_from_context.""" + ctx = mock.Mock() + with cd(self._get_cwd()): + assert isinstance(get_wallet_from_context(ctx), Wallet) diff --git a/tests/test_cli/tools_for_testing.py b/tests/test_cli/tools_for_testing.py index 10b95f1543..561d117dad 100644 --- a/tests/test_cli/tools_for_testing.py +++ b/tests/test_cli/tools_for_testing.py @@ -54,6 +54,8 @@ def __init__(self, *args, **kwargs): return_value=connection_private_key_paths ) self.get = lambda x: getattr(self, x, None) + self.component_configurations = {} + self.package_dependencies = set() registry_path = "registry" name = "name" diff --git a/tests/test_cli_gui/test_run_agent.py b/tests/test_cli_gui/test_run_agent.py index 1912b998f1..7ac9943490 100644 --- a/tests/test_cli_gui/test_run_agent.py +++ b/tests/test_cli_gui/test_run_agent.py @@ -63,7 +63,7 @@ def test_create_and_run_agent(): response_add = app.post( "api/agent/" + agent_id + "/connection", content_type="application/json", - data=json.dumps("fetchai/local:0.9.0"), + data=json.dumps("fetchai/local:0.10.0"), ) assert response_add.status_code == 201 diff --git a/tests/test_configurations/test_aea_config.py b/tests/test_configurations/test_aea_config.py index 54ae447d41..de0b5d8e8c 100644 --- a/tests/test_configurations/test_aea_config.py +++ b/tests/test_configurations/test_aea_config.py @@ -16,6 +16,7 @@ # limitations under the License. # # ------------------------------------------------------------------------------ + """This module contains the tests for the aea configurations.""" import io from enum import Enum @@ -39,6 +40,7 @@ ) from aea.configurations.loader import ConfigLoader, ConfigLoaders from aea.helpers.exception_policy import ExceptionPolicyEnum +from aea.helpers.yaml_utils import yaml_load_all from tests.conftest import CUR_PATH, ROOT_DIR @@ -59,7 +61,7 @@ class NotSet(type): contracts: [] protocols: [] skills: [] -default_connection: fetchai/stub:0.10.0 +default_connection: fetchai/stub:0.11.0 default_ledger: cosmos private_key_paths: cosmos: tests/data/cosmos_private_key.txt @@ -386,3 +388,47 @@ def test_agent_configuration_loading_multipage_positive_case(component_type): assert isinstance(agent_config.component_configurations, dict) assert len(agent_config.component_configurations) assert set(agent_config.component_configurations.keys()) == {expected_component_id} + + +def test_agent_configuration_dump_multipage(): + """Test agent configuration dump with component configuration.""" + loader = ConfigLoaders.from_package_type(PackageType.AGENT) + agent_config = loader.load( + Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() + ) + + # test main agent configuration loaded correctly + assert agent_config.agent_name == "myagent" + assert agent_config.author == "fetchai" + + # test component configurations loaded correctly + assert len(agent_config.component_configurations) == 1 + fp = io.StringIO() + loader.dump(agent_config, fp) + fp.seek(0) + agent_config = yaml_load_all(fp) + assert agent_config[0]["agent_name"] == "myagent" + assert agent_config[1]["name"] == "dummy" + + +def test_agent_configuration_dump_multipage_fails_bad_component_configuration(): + """Test agent configuration dump with INCORRECT component configuration.""" + loader = ConfigLoaders.from_package_type(PackageType.AGENT) + agent_config = loader.load( + Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() + ) + + # test main agent configuration loaded correctly + assert agent_config.agent_name == "myagent" + assert agent_config.author == "fetchai" + + # test component configurations loaded correctly + assert len(agent_config.component_configurations) == 1 + list(agent_config.component_configurations.values())[0][ + "BAD FIELD" + ] = "not in specs!" + fp = io.StringIO() + with pytest.raises( + ValueError, match="Configuration of component .* is not valid..*'BAD FIELD'" + ): + loader.dump(agent_config, fp) diff --git a/tests/test_configurations/test_base.py b/tests/test_configurations/test_base.py index 78fd3b0fd8..b96a25bd8e 100644 --- a/tests/test_configurations/test_base.py +++ b/tests/test_configurations/test_base.py @@ -35,7 +35,10 @@ ConnectionConfig, ContractConfig, DEFAULT_AEA_CONFIG_FILE, + DEFAULT_GIT_REF, + DEFAULT_PYPI_INDEX_URL, DEFAULT_SKILL_CONFIG_FILE, + Dependency, PackageId, PackageType, PackageVersion, @@ -48,6 +51,8 @@ _check_aea_version, _compare_fingerprints, _get_default_configuration_file_name_from_type, + dependencies_from_json, + dependencies_to_json, ) from aea.configurations.constants import DEFAULT_LEDGER from aea.configurations.loader import ConfigLoaders, load_component_configuration @@ -242,14 +247,33 @@ def test_update_method_raises_error_if_skill_component_not_allowed(self): loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) new_configurations = { - "behaviours": {"new_behaviour": {"args": {}, "class_name": "SomeClass"}}, - "handlers": {"new_handler": {"args": {}, "class_name": "SomeClass"}}, - "models": {"new_model": {"args": {}, "class_name": "SomeClass"}}, + "behaviours": {"new_behaviour": {"args": {}}}, + "handlers": {"new_handler": {"args": {}}}, + "models": {"new_model": {"args": {}}}, } with pytest.raises( ValueError, - match="The custom configuration for skill fetchai/error:0.6.0 includes new behaviours: {'new_behaviour'}. This is not allowed.", + match="The custom configuration for skill fetchai/error:0.7.0 includes new behaviours: {'new_behaviour'}. This is not allowed.", + ): + skill_config.update(new_configurations) + + def test_update_method_raises_error_if_we_try_to_change_classname_of_skill_component( + self, + ): + """Test that we raise error if we try to change the 'class_name' field of a skill component configuration.""" + skill_config_path = Path( + ROOT_DIR, "aea", "skills", "error", DEFAULT_SKILL_CONFIG_FILE + ) + loader = ConfigLoaders.from_package_type(PackageType.SKILL) + skill_config = loader.load(skill_config_path.open()) + new_configurations = { + "handlers": {"error_handler": {"class_name": "SomeClass", "args": {}}}, + } + + with pytest.raises( + ValueError, + match="These fields of skill component configuration 'error_handler' of skill 'fetchai/error:0.7.0' are not allowed to change: {'class_name'}.", ): skill_config.update(new_configurations) @@ -273,58 +297,87 @@ def test_from_json_and_to_json(self, agent_path): actual_json = actual_config.json assert expected_json == actual_json - def test_update(self): - """Test the update method.""" - aea_config_path = Path(CUR_PATH, "data", "dummy_aea", DEFAULT_AEA_CONFIG_FILE) - loader = ConfigLoaders.from_package_type(PackageType.AGENT) - aea_config: AgentConfig = loader.load(aea_config_path.open()) - dummy_skill_component_id = ComponentId( +class TestAgentConfigUpdate: + """Test methods that change the agent configuration.""" + + def setup(self): + """Set up the tests.""" + self.aea_config_path = Path( + CUR_PATH, "data", "dummy_aea", DEFAULT_AEA_CONFIG_FILE + ) + self.loader = ConfigLoaders.from_package_type(PackageType.AGENT) + self.aea_config: AgentConfig = self.loader.load(self.aea_config_path.open()) + self.dummy_skill_component_id = ComponentId( ComponentType.SKILL, DUMMY_SKILL_PUBLIC_ID ) - new_dummy_skill_config = { + self.new_dummy_skill_config = { "behaviours": {"dummy": {"args": dict(behaviour_arg_1=42)}}, "handlers": {"dummy": {"args": dict(handler_arg_1=42)}}, "models": {"dummy": {"args": dict(model_arg_1=42)}}, } + def test_component_configurations_setter(self): + """Test component configuration setter.""" + assert self.aea_config.component_configurations == {} + new_component_configurations = { + self.dummy_skill_component_id: self.new_dummy_skill_config + } + self.aea_config.component_configurations = new_component_configurations + + def test_component_configurations_setter_negative(self): + """Test component configuration setter with wrong configurations.""" + assert self.aea_config.component_configurations == {} + new_component_configurations = { + self.dummy_skill_component_id: { + "handlers": {"dummy": {"class_name": "SomeClass"}} + } + } + with pytest.raises( + ValueError, match=r"Configuration of component .* is not valid.*" + ): + self.aea_config.component_configurations = new_component_configurations + + def test_update(self): + """Test the update method.""" new_private_key_paths = dict(ethereum="foo") expected_private_key_paths = dict( ethereum="foo", cosmos="cosmos_private_key.txt" ) - aea_config.update( + self.aea_config.update( dict( component_configurations={ - dummy_skill_component_id: new_dummy_skill_config + self.dummy_skill_component_id: self.new_dummy_skill_config }, private_key_paths=new_private_key_paths, connection_private_key_paths=new_private_key_paths, ) ) assert ( - aea_config.component_configurations[dummy_skill_component_id] - == new_dummy_skill_config + self.aea_config.component_configurations[self.dummy_skill_component_id] + == self.new_dummy_skill_config ) assert ( - dict(aea_config.private_key_paths.read_all()) == expected_private_key_paths + dict(self.aea_config.private_key_paths.read_all()) + == expected_private_key_paths ) assert ( - dict(aea_config.connection_private_key_paths.read_all()) + dict(self.aea_config.connection_private_key_paths.read_all()) == expected_private_key_paths ) # test idempotence - aea_config.update( + self.aea_config.update( dict( component_configurations={ - dummy_skill_component_id: new_dummy_skill_config + self.dummy_skill_component_id: self.new_dummy_skill_config } ) ) assert ( - aea_config.component_configurations[dummy_skill_component_id] - == new_dummy_skill_config + self.aea_config.component_configurations[self.dummy_skill_component_id] + == self.new_dummy_skill_config ) @@ -377,6 +430,11 @@ def test_public_id_lt_positive(self): obj2 = PublicId(AUTHOR, "name", "2.0.0") self.assertTrue(obj1 < obj2) + def test_is_valid_str(self): + """Test is_valid_str method.""" + assert PublicId.is_valid_str("author/name:0.1.0") + assert not PublicId.is_valid_str("author!name:0.1.0") + class AgentConfigTestCase(TestCase): """Test case for AgentConfig class.""" @@ -624,6 +682,12 @@ def test_package_id_str(): assert str(package_id) == "(protocol, author/name:0.1.0)" +def test_package_id_repr(): + """Test PackageId.__repr__""" + package_id = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) + assert repr(package_id) == "PackageId(protocol, author/name:0.1.0)" + + def test_package_id_lt(): """Test PackageId.__lt__""" package_id_1 = PackageId(PackageType.PROTOCOL, PublicId("author", "name", "0.1.0")) @@ -810,3 +874,98 @@ def test_configuration_class(): assert PackageType.CONTRACT.configuration_class() == ContractConfig assert PackageType.SKILL.configuration_class() == SkillConfig assert PackageType.AGENT.configuration_class() == AgentConfig + + +class TestDependencyGetPipInstallArgs: + """Test 'get_pip_install_args' of 'Dependency' class.""" + + @classmethod + def setup_class(cls): + """Set up the class.""" + cls.package_name = "package_name" + cls.version = "<0.2.0,>=0.1.0" + cls.custom_index = "https://test.pypi.org" + cls.git_url = "https://github.com/some-author/some-repository.git" + cls.ref = "develop" + + def test_only_name_and_version(self): + """Test only with name and version.""" + # no index and no git + dep = Dependency(self.package_name, self.version) + assert dep.get_pip_install_args() == [ + f"{self.package_name}{self.version}", + ] + + def test_name_version_index(self): + """Test the method with name, version and index.""" + dep = Dependency(self.package_name, self.version, self.custom_index) + assert dep.get_pip_install_args() == [ + "-i", + self.custom_index, + f"{self.package_name}{self.version}", + ] + + def test_name_version_index_git(self): + """Test the method when name, version, index and git fields are provided.""" + dep = Dependency( + self.package_name, self.version, self.custom_index, self.git_url + ) + git_url = f"git+{self.git_url}@{DEFAULT_GIT_REF}#egg={self.package_name}" + assert dep.get_pip_install_args() == ["-i", self.custom_index, git_url] + + def test_name_version_index_git_ref(self): + """Test the method when name, version, index, git and ref fields are provided.""" + dep = Dependency( + self.package_name, self.version, self.custom_index, self.git_url, self.ref + ) + git_url = f"git+{self.git_url}@{self.ref}#egg={self.package_name}" + assert dep.get_pip_install_args() == ["-i", self.custom_index, git_url] + + +def test_dependencies_from_to_json(): + """Test serialization and deserialization of Dependencies object.""" + version_str = "==0.1.0" + git_url = "https://some-git-repo.git" + branch = "some-branch" + dep1 = Dependency("package_1", version_str, DEFAULT_PYPI_INDEX_URL, git_url, branch) + dep2 = Dependency("package_2", version_str) + expected_obj = {"package_1": dep1, "package_2": dep2} + expected_obj_json = dependencies_to_json(expected_obj) + assert expected_obj_json == { + "package_1": { + "version": "==0.1.0", + "index": DEFAULT_PYPI_INDEX_URL, + "git": git_url, + "ref": branch, + }, + "package_2": {"version": version_str}, + } + + actual_obj = dependencies_from_json(expected_obj_json) + assert expected_obj == actual_obj + + +def test_dependency_from_json_fail_more_than_one_key(): + """Test failure of Dependency.from_json due to more than one key at the top level.""" + bad_obj = {"field_1": {}, "field_2": {}} + keys = set(bad_obj.keys()) + with pytest.raises(ValueError, match=f"Only one key allowed, found {keys}"): + Dependency.from_json(bad_obj) + + +def test_dependency_from_json_fail_not_allowed_keys(): + """Test failure of Dependency.from_json due to unallowed keys""" + bad_obj = {"field_1": {"not-allowed-key": "value"}} + with pytest.raises(ValueError, match="Not allowed keys: {'not-allowed-key'}"): + Dependency.from_json(bad_obj) + + +def test_dependency_to_string(): + """Test dependency.__str__ method.""" + dependency = Dependency( + "package_1", "==0.1.0", "https://index.com", "https://some-repo.git", "branch" + ) + assert ( + str(dependency) + == "Dependency(name='package_1', version='==0.1.0', index='https://index.com', git='https://some-repo.git', ref='branch')" + ) diff --git a/tests/test_configurations/test_loader.py b/tests/test_configurations/test_loader.py index 11fb64e920..d1d59fd06b 100644 --- a/tests/test_configurations/test_loader.py +++ b/tests/test_configurations/test_loader.py @@ -71,20 +71,6 @@ def test_config_loader_dump_agent_config(): config_loader.dump(configuration, open("foo")) -@mock.patch.object( - aea.configurations.loader, "yaml_load", return_value=dict(type="connection") -) -def test_load_wrong_type(_): - """Test the case when the loaded configuration file has the wrong 'type' field value.""" - loader = ConfigLoader.from_configuration_type(PackageType.PROTOCOL) - with mock.patch.object(loader.validator, "validate", side_effect=None): - with pytest.raises( - ValueError, - match="The field type is not correct: expected protocol, found connection.", - ): - loader.load(MagicMock()) - - @pytest.mark.parametrize("spec_file_path", protocol_specification_files) def test_load_protocol_specification(spec_file_path): """Test for the utility function 'load_protocol_specification'""" diff --git a/tests/test_configurations/test_project.py b/tests/test_configurations/test_project.py new file mode 100644 index 0000000000..ebe4190b50 --- /dev/null +++ b/tests/test_configurations/test_project.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests for the aea configurations project.""" +import os +import shutil +import tempfile +from unittest.mock import Mock + +from aea.cli import cli +from aea.configurations.base import PublicId +from aea.configurations.project import AgentAlias, Project +from aea.helpers.base import cd +from aea.test_tools.click_testing import CliRunner + + +class TestProjectAndAgentAlias: + """Check project and agent alias.""" + + def setup(self): + """Set the test up.""" + self.cwd = os.getcwd() + self.t = tempfile.mkdtemp() + os.chdir(self.t) + self.runner = CliRunner() + self.project_public_id = PublicId("fetchai", "my_first_aea", "0.11.0") + self.project_path = os.path.join( + self.t, self.project_public_id.author, self.project_public_id.name + ) + + def test_project(self): + """Test project laoded and removed.""" + project = Project.load(self.t, self.project_public_id) + assert os.path.exists(self.project_path) + + with cd(self.project_path): + result = self.runner.invoke( + cli, + ["config", "get", "agent.agent_name"], + catch_exceptions=False, + standalone_mode=False, + ) + assert self.project_public_id.name in result.output + project.remove() + assert not os.path.exists(self.project_path) + + def test_agents(self): + """Test agent added to project and rmeoved.""" + project = Project(self.project_public_id, self.project_path) + alias = AgentAlias(project, "test", [], Mock(), Mock()) + assert project.agents + alias.remove_from_project() + assert not project.agents + + def teardown(self): + """Tear dowm the test.""" + os.chdir(self.cwd) + try: + shutil.rmtree(self.t) + except (OSError, IOError): + pass diff --git a/tests/test_configurations/test_pypi.py b/tests/test_configurations/test_pypi.py index 62fa3b636d..d2ee522719 100644 --- a/tests/test_configurations/test_pypi.py +++ b/tests/test_configurations/test_pypi.py @@ -19,6 +19,7 @@ """This module contains tests for the aea.helpers.pypi module.""" from packaging.specifiers import SpecifierSet +from aea.configurations.base import Dependency from aea.configurations.pypi import ( is_satisfiable, is_simple_dep, @@ -48,6 +49,13 @@ def test_is_satisfiable_with_compatibility_constraints(): assert is_satisfiable(SpecifierSet("~=1.1,==1.2")) is True assert is_satisfiable(SpecifierSet("~=1.1,>1.2")) is True assert is_satisfiable(SpecifierSet("==1.1,==1.2")) is False + assert is_satisfiable(SpecifierSet("~= 1.4.5.0")) is True + assert is_satisfiable(SpecifierSet("~= 1.4.5.0,>1.4.6")) is False + assert is_satisfiable(SpecifierSet("~=2.2.post3,==2.*")) is True + assert is_satisfiable(SpecifierSet("~=2.2.post3,>2.3")) is True + assert is_satisfiable(SpecifierSet("~=2.2.post3,>3")) is False + assert is_satisfiable(SpecifierSet("~=1.4.5a4,>1.4.6")) is True + assert is_satisfiable(SpecifierSet("~=1.4.5a4,>1.5")) is False def test_is_satisfiable_with_legacy_version(): @@ -58,33 +66,35 @@ def test_is_satisfiable_with_legacy_version(): def test_merge_dependencies(): """Test the 'merge_dependencies' function.""" dependencies_a = { - "package_1": {"version": "==0.1.0"}, - "package_2": {"version": "==0.3.0"}, - "package_3": {"version": "0.2.0", "index": "pypi"}, + "package_1": Dependency("package_1", "==0.1.0"), + "package_2": Dependency("package_2", "==0.3.0"), + "package_3": Dependency("package_3", "==0.2.0", "https://pypi.org"), } dependencies_b = { - "package_1": {"version": "==0.1.0"}, - "package_2": {"version": "==0.2.0"}, - "package_4": {"version": "0.1.0", "index": "pypi"}, + "package_1": Dependency("package_1", "==0.1.0"), + "package_2": Dependency("package_2", "==0.2.0"), + "package_4": Dependency("package_4", "==0.1.0", "https://pypi.org"), } - merged_dependencies = { - "package_1": {"version": "==0.1.0"}, - "package_2": {"version": "==0.2.0,==0.3.0"}, + expected_merged_dependencies = { + "package_1": Dependency("package_1", "==0.1.0"), + "package_2": Dependency("package_2", "==0.2.0,==0.3.0"), } - assert merged_dependencies == merge_dependencies(dependencies_a, dependencies_b) + assert expected_merged_dependencies == merge_dependencies( + dependencies_a, dependencies_b + ) def test_is_simple_dep(): """Test the `is_simple_dep` function.""" - dependency_a = {"version": "==0.1.0"} + dependency_a = Dependency("name", "==0.1.0") assert is_simple_dep(dependency_a), "Should be a simple dependency." - dependency_b = {} + dependency_b = Dependency("name") assert is_simple_dep(dependency_b), "Should be a simple dependency." - dependency_c = {"version": "==0.1.0", "index": "pypi"} + dependency_c = Dependency("name", "==0.1.0", "pypi") assert not is_simple_dep(dependency_c), "Should not be a simple dependency." def test_to_set_specifier(): """Test the 'to_set_specifier' function.""" - dependency_a = {"version": "==0.1.0"} - assert to_set_specifier(dependency_a) == "==0.1.0" + dependency_a = Dependency("name", "==0.1.0") + assert to_set_specifier(dependency_a) == SpecifierSet("==0.1.0") diff --git a/tests/test_connections/test_stub.py b/tests/test_connections/test_stub.py index d6c9346796..45d1ed1e2b 100644 --- a/tests/test_connections/test_stub.py +++ b/tests/test_connections/test_stub.py @@ -140,11 +140,11 @@ def test_reception_b(self): def test_reception_c(self): """Test that the connection receives what has been enqueued in the input file.""" - encoded_envelope = b"0x5E22777dD831A459535AA4306AceC9cb22eC4cB5,default_oef,fetchai/oef_search:0.7.0,\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd," + encoded_envelope = b"0x5E22777dD831A459535AA4306AceC9cb22eC4cB5,default_oef,fetchai/oef_search:0.8.0,\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd," expected_envelope = Envelope( to="0x5E22777dD831A459535AA4306AceC9cb22eC4cB5", sender="default_oef", - protocol_id=PublicId.from_str("fetchai/oef_search:0.7.0"), + protocol_id=PublicId.from_str("fetchai/oef_search:0.8.0"), message=b"\x08\x02\x12\x011\x1a\x011 \x01:,\n*0x32468dB8Ab79549B49C88DC991990E7910891dbd", ) with open(self.input_file_path, "ab+") as f: @@ -155,7 +155,9 @@ def test_reception_c(self): def test_reception_fails(self): """Test the case when an error occurs during the processing of a line.""" - patch = mock.patch.object(aea.connections.stub.connection.logger, "error") + patch = mock.patch.object( + aea.connections.stub.connection._default_logger, "error" + ) mocked_logger_error = patch.start() with mock.patch( "aea.connections.stub.connection._decode", diff --git a/tests/test_crypto/test_cosmos.py b/tests/test_crypto/test_cosmos.py index 521cd0267e..034b448328 100644 --- a/tests/test_crypto/test_cosmos.py +++ b/tests/test_crypto/test_cosmos.py @@ -91,3 +91,10 @@ def test_generate_nonce(): assert len(nonce) > 0 and int( nonce, 16 ), "The len(nonce) must not be 0 and must be hex" + + +def test_validate_address(): + """Test the is_valid_address functionality.""" + account = CosmosCrypto() + assert CosmosApi.is_valid_address(account.address) + assert not CosmosApi.is_valid_address(account.address + "wrong") diff --git a/tests/test_crypto/test_ethereum.py b/tests/test_crypto/test_ethereum.py index e0aafd7d78..9c7abb4015 100644 --- a/tests/test_crypto/test_ethereum.py +++ b/tests/test_crypto/test_ethereum.py @@ -118,6 +118,13 @@ def test_api_none(): assert eth_api.api is not None, "The api property is None." +def test_validate_address(): + """Test the is_valid_address functionality.""" + account = EthereumCrypto() + assert EthereumApi.is_valid_address(account.address) + assert not EthereumApi.is_valid_address(account.address + "wrong") + + @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger @@ -190,7 +197,7 @@ def test_construct_sign_and_submit_transfer_transaction(): @pytest.mark.ledger def test_get_wealth_positive(caplog): """Test the balance is zero for a new account.""" - with caplog.at_level(logging.DEBUG, logger="aea.crypto.ethereum"): + with caplog.at_level(logging.DEBUG, logger="aea.crypto.ethereum._default_logger"): ethereum_faucet_api = EthereumFaucetApi() ec = EthereumCrypto() ethereum_faucet_api.get_wealth(ec.address) diff --git a/tests/test_crypto/test_fetchai.py b/tests/test_crypto/test_fetchai.py index aec465043f..b3cd917436 100644 --- a/tests/test_crypto/test_fetchai.py +++ b/tests/test_crypto/test_fetchai.py @@ -127,6 +127,13 @@ def test_get_address_from_public_key(): assert address == fet_crypto.address, "The address must be the same." +def test_validate_address(): + """Test the is_valid_address functionality.""" + account = FetchAICrypto() + assert FetchAIApi.is_valid_address(account.address) + assert not FetchAIApi.is_valid_address(account.address + "wrong") + + # @pytest.mark.flaky(reruns=MAX_FLAKY_RERUNS) @pytest.mark.integration @pytest.mark.ledger @@ -214,7 +221,7 @@ def get_wealth(address: str): @pytest.mark.ledger def test_get_wealth_positive(caplog): """Test the balance is zero for a new account.""" - with caplog.at_level(logging.DEBUG, logger="aea.crypto.fetchai"): + with caplog.at_level(logging.DEBUG, logger="aea.crypto.fetchai._default_logger"): fetchai_faucet_api = FetchAIFaucetApi() fc = FetchAICrypto() fetchai_faucet_api.get_wealth(fc.address) diff --git a/tests/test_crypto/test_helpers.py b/tests/test_crypto/test_helpers.py index a4821001fb..46a03a5e47 100644 --- a/tests/test_crypto/test_helpers.py +++ b/tests/test_crypto/test_helpers.py @@ -82,7 +82,7 @@ def tests_private_keys(self): ) try_validate_private_key_path(EthereumCrypto.identifier, private_key_path) - @patch("aea.crypto.ethereum.logger") + @patch("aea.crypto.ethereum._default_logger") def tests_generate_wealth_ethereum(self, mock_logging): """Test generate wealth for ethereum.""" address = "my_address" diff --git a/tests/test_decision_maker/test_default.py b/tests/test_decision_maker/test_default.py index c475bd2d29..e30bed0317 100644 --- a/tests/test_decision_maker/test_default.py +++ b/tests/test_decision_maker/test_default.py @@ -127,7 +127,7 @@ class TestDecisionMaker: @classmethod def _patch_logger(cls): cls.patch_logger_warning = mock.patch.object( - aea.decision_maker.default.logger, "warning" + aea.decision_maker.default._default_logger, "warning" ) cls.mocked_logger_warning = cls.patch_logger_warning.__enter__() @@ -246,7 +246,7 @@ class TestDecisionMaker2: @classmethod def _patch_logger(cls): cls.patch_logger_warning = mock.patch.object( - aea.decision_maker.default.logger, "warning" + aea.decision_maker.default._default_logger, "warning" ) cls.mocked_logger_warning = cls.patch_logger_warning.__enter__() diff --git a/tests/test_decision_maker/test_scaffold.py b/tests/test_decision_maker/test_scaffold.py index 658fbccf02..fc9ab58052 100644 --- a/tests/test_decision_maker/test_scaffold.py +++ b/tests/test_decision_maker/test_scaffold.py @@ -22,10 +22,13 @@ import pytest from aea.decision_maker.scaffold import DecisionMakerHandler +from aea.identity.base import Identity def test_init_and_not_implemented(): """Initialise the decision maker handler.""" - decision_maker_handler = DecisionMakerHandler(identity="identity", wallet="wallet") + decision_maker_handler = DecisionMakerHandler( + identity=Identity("name", "address"), wallet="wallet" + ) with pytest.raises(NotImplementedError): decision_maker_handler.handle("message") diff --git a/tests/test_docs/test_agent_vs_aea/agent_code_block.py b/tests/test_docs/test_agent_vs_aea/agent_code_block.py index 0a7f29ac47..022aa506e6 100644 --- a/tests/test_docs/test_agent_vs_aea/agent_code_block.py +++ b/tests/test_docs/test_agent_vs_aea/agent_code_block.py @@ -113,7 +113,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the agent message_text = ( - b"my_agent,other_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + b"my_agent,other_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) diff --git a/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py b/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py index 65ff225dc2..789e2debc2 100644 --- a/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py +++ b/tests/test_docs/test_agent_vs_aea/test_agent_vs_aea.py @@ -61,7 +61,7 @@ def test_run_agent(self): assert os.path.exists(Path(self.t, "input_file")) message_text = ( - b"other_agent,my_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + b"other_agent,my_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) path = os.path.join(self.t, "output_file") with open(path, "rb") as file: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md b/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md index 17edd0104d..23fdebc183 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-aries-cloud-agent-demo.md @@ -15,17 +15,17 @@ aca-py start --admin 127.0.0.1 8021 --admin-insecure-mode --inbound-transport ht aca-py start --admin 127.0.0.1 8031 --admin-insecure-mode --inbound-transport http 0.0.0.0 8030 --outbound-transp http --webhook-url http://127.0.0.1:8032/webhooks ``` ``` bash -aea fetch fetchai/aries_alice:0.11.0 +aea fetch fetchai/aries_alice:0.12.0 cd aries_alice ``` ``` bash aea create aries_alice cd aries_alice -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/http_client:0.9.0 -aea add connection fetchai/webhook:0.7.0 -aea add skill fetchai/aries_alice:0.8.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/http_client:0.10.0 +aea add connection fetchai/webhook:0.8.0 +aea add skill fetchai/aries_alice:0.9.0 ``` ``` bash aea config set vendor.fetchai.skills.aries_alice.models.strategy.args.admin_host 127.0.0.1 @@ -54,17 +54,17 @@ aea install aea run ``` ``` bash -aea fetch fetchai/aries_faber:0.11.0 +aea fetch fetchai/aries_faber:0.12.0 cd aries_faber ``` ``` bash aea create aries_faber cd aries_faber -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/http_client:0.9.0 -aea add connection fetchai/webhook:0.7.0 -aea add skill fetchai/aries_faber:0.7.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/http_client:0.10.0 +aea add connection fetchai/webhook:0.8.0 +aea add skill fetchai/aries_faber:0.8.0 ``` ``` bash aea config set vendor.fetchai.skills.aries_faber.models.strategy.args.admin_host 127.0.0.1 diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md index 526aada233..4969ba2d21 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-car-park-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/car_detector:0.13.0 +aea fetch fetchai/car_detector:0.14.0 cd car_detector aea install ``` ``` bash aea create car_detector cd car_detector -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/carpark_detection:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/carpark_detection:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea fetch fetchai/car_data_buyer:0.13.0 +aea fetch fetchai/car_data_buyer:0.14.0 cd car_data_buyer aea install ``` ``` bash aea create car_data_buyer cd car_data_buyer -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/carpark_client:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/carpark_client:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea generate-key fetchai @@ -54,13 +54,13 @@ aea delete car_data_buyer ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md b/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md index 2d271b9c25..627f8a1092 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-cli-vs-programmatic-aeas.md @@ -2,7 +2,7 @@ svn export https://github.com/fetchai/agents-aea.git/trunk/packages ``` ``` bash -aea fetch fetchai/weather_station:0.13.0 +aea fetch fetchai/weather_station:0.14.0 cd weather_station ``` ``` bash diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-config.md b/tests/test_docs/test_bash_yaml/md_files/bash-config.md index e7db01434d..54ecd45aaf 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-config.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-config.md @@ -14,13 +14,13 @@ aea_version: '>=0.6.0, <0.7.0' # AEA framework version(s) compa fingerprint: {} # Fingerprint of AEA project components. fingerprint_ignore_patterns: [] # Ignore pattern for the fingerprinting tool. connections: # The list of connection public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX) -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 contracts: [] # The list of contract public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). protocols: # The list of protocol public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: # The list of skill public ids the AEA project depends on (each public id must satisfy PUBLIC_ID_REGEX). -- fetchai/error:0.6.0 -default_connection: fetchai/p2p_libp2p:0.10.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). +- fetchai/error:0.7.0 +default_connection: fetchai/p2p_libp2p:0.11.0 # The default connection used for envelopes sent by the AEA (must satisfy PUBLIC_ID_REGEX). default_ledger: fetchai # The default ledger identifier the AEA project uses (must satisfy LEDGER_ID_REGEX) logging_config: # The logging configurations the AEA project uses disable_existing_loggers: false diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md index 5dba54b5a9..90d23446a2 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-erc1155-skills.md @@ -1,17 +1,17 @@ ``` bash -aea fetch fetchai/erc1155_deployer:0.14.0 +aea fetch fetchai/erc1155_deployer:0.15.0 cd erc1155_deployer aea install ``` ``` bash aea create erc1155_deployer cd erc1155_deployer -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/erc1155_deploy:0.14.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/erc1155_deploy:0.15.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea config set agent.default_ledger ethereum @@ -25,19 +25,19 @@ aea generate-key cosmos aea add-key cosmos cosmos_private_key.txt --connection ``` ``` bash -aea fetch fetchai/erc1155_client:0.14.0 +aea fetch fetchai/erc1155_client:0.15.0 cd erc1155_client aea install ``` ``` bash aea create erc1155_client cd erc1155_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/erc1155_client:0.13.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/erc1155_client:0.14.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea config set agent.default_ledger ethereum @@ -75,15 +75,15 @@ aea delete erc1155_client ``` ``` yaml default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/contract_api:0.5.0: fetchai/ledger:0.6.0 - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/contract_api:0.6.0: fetchai/ledger:0.7.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md index d0349421f3..a11f4e2ced 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills-step-by-step.md @@ -5,15 +5,15 @@ sudo nano 99-hidraw-permissions.rules KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev" ``` ``` bash -aea fetch fetchai/generic_seller:0.10.0 +aea fetch fetchai/generic_seller:0.11.0 cd generic_seller -aea eject skill fetchai/generic_seller:0.13.0 +aea eject skill fetchai/generic_seller:0.14.0 cd .. ``` ``` bash -aea fetch fetchai/generic_buyer:0.10.0 +aea fetch fetchai/generic_buyer:0.11.0 cd generic_buyer -aea eject skill fetchai/generic_buyer:0.12.0 +aea eject skill fetchai/generic_buyer:0.13.0 cd .. ``` ``` bash @@ -48,22 +48,22 @@ aea add-key fetchai fetchai_private_key.txt --connection aea generate-wealth fetchai --sync ``` ``` bash -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add protocol fetchai/fipa:0.7.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add protocol fetchai/fipa:0.8.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea run ``` ``` bash -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add protocol fetchai/fipa:0.7.0 -aea add protocol fetchai/signing:0.4.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add protocol fetchai/fipa:0.8.0 +aea add protocol fetchai/signing:0.5.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea run @@ -91,10 +91,10 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: service_registration: @@ -160,11 +160,11 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/default:0.6.0 -- fetchai/fipa:0.7.0 -- fetchai/ledger_api:0.4.0 -- fetchai/oef_search:0.7.0 -- fetchai/signing:0.4.0 +- fetchai/default:0.7.0 +- fetchai/fipa:0.8.0 +- fetchai/ledger_api:0.5.0 +- fetchai/oef_search:0.8.0 +- fetchai/signing:0.5.0 skills: [] behaviours: search: @@ -225,8 +225,8 @@ addr: ${OEF_ADDR: 127.0.0.1} ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md index f4454ad427..fd868a8fdc 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-generic-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/generic_seller:0.10.0 --alias my_seller_aea +aea fetch fetchai/generic_seller:0.11.0 --alias my_seller_aea cd my_seller_aea aea install ``` ``` bash aea create my_seller_aea cd my_seller_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/generic_seller:0.13.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/generic_seller:0.14.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea fetch fetchai/generic_buyer:0.10.0 --alias my_buyer_aea +aea fetch fetchai/generic_buyer:0.11.0 --alias my_buyer_aea cd my_buyer_aea aea install ``` ``` bash aea create my_buyer_aea cd my_buyer_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/generic_buyer:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/generic_buyer:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea generate-key fetchai @@ -62,13 +62,13 @@ aea delete my_buyer_aea ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml models: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md b/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md index c0cfb5601d..e672b9f89f 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-gym-skill.md @@ -1,5 +1,5 @@ ``` bash -aea fetch fetchai/gym_aea:0.11.0 --alias my_gym_aea +aea fetch fetchai/gym_aea:0.12.0 --alias my_gym_aea cd my_gym_aea aea install ``` @@ -8,10 +8,10 @@ aea create my_gym_aea cd my_gym_aea ``` ``` bash -aea add skill fetchai/gym:0.8.0 +aea add skill fetchai/gym:0.9.0 ``` ``` bash -aea add connection fetchai/gym:0.8.0 +aea add connection fetchai/gym:0.9.0 aea config set agent.default_connection fetchai/gym:0.7.0 ``` ``` bash diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md b/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md index 2c64e18ca6..3b6d5eefab 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-http-connection-and-skill.md @@ -3,10 +3,10 @@ aea create my_aea cd my_aea ``` ``` bash -aea add connection fetchai/http_server:0.9.0 +aea add connection fetchai/http_server:0.10.0 ``` ``` bash -aea config set agent.default_connection fetchai/http_server:0.9.0 +aea config set agent.default_connection fetchai/http_server:0.10.0 ``` ``` bash aea config set vendor.fetchai.connections.http_server.config.api_spec_path "../examples/http_ex/petstore.yaml" @@ -18,7 +18,7 @@ aea install aea scaffold skill http_echo ``` ``` bash -aea fingerprint skill fetchai/http_echo:0.7.0 +aea fingerprint skill fetchai/http_echo:0.8.0 ``` ``` bash aea run diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-logging.md b/tests/test_docs/test_bash_yaml/md_files/bash-logging.md index 2001ac16c8..31fc631f22 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-logging.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-logging.md @@ -12,13 +12,13 @@ aea_version: 0.6.0 fingerprint: {} fingerprint_ignore_patterns: [] connections: -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 contracts: [] protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 skills: -- fetchai/error:0.6.0 -default_connection: fetchai/stub:0.10.0 +- fetchai/error:0.7.0 +default_connection: fetchai/stub:0.11.0 default_ledger: fetchai logging_config: disable_existing_loggers: false diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md index 8d369d30e1..7c9454eaa0 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-ml-skills.md @@ -1,31 +1,31 @@ ``` bash -aea fetch fetchai/ml_data_provider:0.13.0 +aea fetch fetchai/ml_data_provider:0.14.0 cd ml_data_provider aea install ``` ``` bash aea create ml_data_provider cd ml_data_provider -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/ml_data_provider:0.12.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/ml_data_provider:0.13.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea install ``` ``` bash -aea fetch fetchai/ml_model_trainer:0.13.0 +aea fetch fetchai/ml_model_trainer:0.14.0 cd ml_model_trainer aea install ``` ``` bash aea create ml_model_trainer cd ml_model_trainer -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/ml_train:0.12.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/ml_train:0.13.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea install ``` ``` bash @@ -54,13 +54,13 @@ aea delete ml_model_trainer ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md b/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md index d62b6c9dcb..3ca37983ca 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-orm-integration.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/thermometer_aea:0.11.0 --alias my_thermometer_aea +aea fetch fetchai/thermometer_aea:0.12.0 --alias my_thermometer_aea cd my_thermometer_aea aea install ``` ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea fetch fetchai/thermometer_client:0.11.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.12.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer_client:0.11.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer_client:0.12.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea generate-key fetchai @@ -45,7 +45,7 @@ aea generate-wealth fetchai aea install ``` ``` bash -aea eject skill fetchai/thermometer:0.12.0 +aea eject skill fetchai/thermometer:0.13.0 ``` ``` bash aea fingerprint skill {YOUR_AUTHOR_HANDLE}/thermometer:0.1.0 @@ -63,13 +63,13 @@ aea delete my_thermometer_client ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml models: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md b/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md index 422262bf4b..49b26c0cc0 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-p2p-connection.md @@ -1,18 +1,18 @@ ``` bash aea create my_genesis_aea cd my_genesis_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 -aea run --connections fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 +aea run --connections fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea create my_other_aea cd my_other_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea run --connections fetchai/p2p_libp2p:0.10.0 +aea run --connections fetchai/p2p_libp2p:0.11.0 ``` ``` bash svn export https://github.com/fetchai/agents-aea.git/trunk/packages/fetchai/connections/p2p_libp2p @@ -54,8 +54,8 @@ config: ``` ``` yaml default_routing: - ? "fetchai/oef_search:0.7.0" - : "fetchai/oef:0.10.0" + ? "fetchai/oef_search:0.8.0" + : "fetchai/oef:0.11.0" ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md b/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md index 35404987c2..582d36b73d 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-package-imports.md @@ -29,5 +29,5 @@ aea_name/ ``` ``` yaml connections: -- fetchai/stub:0.10.0 +- fetchai/stub:0.11.0 ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md b/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md index 8a9bfd9c2b..e74875d6e2 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-quickstart.md @@ -36,12 +36,12 @@ Confirm password: / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.6.2 +v0.6.3 AEA configurations successfully initialized: {'author': 'fetchai'} ``` ``` bash -aea fetch fetchai/my_first_aea:0.12.0 +aea fetch fetchai/my_first_aea:0.13.0 cd my_first_aea ``` ``` bash @@ -49,19 +49,19 @@ aea create my_first_aea cd my_first_aea ``` ``` bash -aea add skill fetchai/echo:0.8.0 +aea add skill fetchai/echo:0.9.0 ``` ``` bash TO,SENDER,PROTOCOL_ID,ENCODED_MESSAGE, ``` ``` bash -recipient_aea,sender_aea,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello, +recipient_aea,sender_aea,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello, ``` ``` bash aea run ``` ``` bash -aea run --connections fetchai/stub:0.10.0 +aea run --connections fetchai/stub:0.11.0 ``` ``` bash _ _____ _ @@ -70,7 +70,7 @@ aea run --connections fetchai/stub:0.10.0 / ___ \ | |___ / ___ \ /_/ \_\|_____|/_/ \_\ -v0.6.2 +v0.6.3 Starting AEA 'my_first_aea' in 'async' mode ... info: Echo Handler: setup method called. @@ -92,7 +92,7 @@ info: Echo Behaviour: act method called. info: Echo Behaviour: act method called. ``` ``` bash -echo 'my_first_aea,sender_aea,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello,' >> input_file +echo 'my_first_aea,sender_aea,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello,' >> input_file ``` ``` bash info: Echo Behaviour: act method called. diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md b/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md index 2cf8e232b9..2ea5e3999d 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-skill-guide.md @@ -6,16 +6,16 @@ aea scaffold skill my_search aea fingerprint skill fetchai/my_search:0.1.0 ``` ``` bash -aea add protocol fetchai/oef_search:0.7.0 +aea add protocol fetchai/oef_search:0.8.0 ``` ``` bash -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/p2p_libp2p:0.10.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea fetch fetchai/simple_service_registration:0.13.0 && cd simple_service_registration +aea fetch fetchai/simple_service_registration:0.14.0 && cd simple_service_registration && aea install ``` ``` bash aea generate-key fetchai @@ -45,7 +45,7 @@ fingerprint: {} fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: my_search_behaviour: @@ -72,7 +72,7 @@ dependencies: {} ``` ``` yaml default_routing: - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml name: simple_service_registration @@ -91,7 +91,7 @@ fingerprint: fingerprint_ignore_patterns: [] contracts: [] protocols: -- fetchai/oef_search:0.7.0 +- fetchai/oef_search:0.8.0 skills: [] behaviours: service: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-skill.md b/tests/test_docs/test_bash_yaml/md_files/bash-skill.md index ef0ab5fad6..44110b4f02 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-skill.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-skill.md @@ -16,5 +16,5 @@ handlers: models: {} dependencies: {} protocols: -- fetchai/default:0.6.0 +- fetchai/default:0.7.0 ``` diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md index e7d2030463..7a263e90a4 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills-contract.md @@ -1,17 +1,17 @@ ``` bash -aea fetch fetchai/tac_controller_contract:0.11.0 +aea fetch fetchai/tac_controller_contract:0.12.0 cd tac_controller_contract aea install ``` ``` bash aea create tac_controller_contract cd tac_controller_contract -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_control_contract:0.9.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_control_contract:0.10.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger ethereum ``` ``` bash @@ -25,12 +25,12 @@ aea generate-wealth ethereum aea get-wealth ethereum ``` ``` bash -aea fetch fetchai/tac_participant:0.11.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.12.0 --alias tac_participant_one cd tac_participant_one aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool cd .. -aea fetch fetchai/tac_participant:0.11.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.12.0 --alias tac_participant_two cd tac_participant_two aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool @@ -42,26 +42,26 @@ aea create tac_participant_two ``` ``` bash cd tac_participant_one -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_participation:0.9.0 -aea add skill fetchai/tac_negotiation:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_participation:0.10.0 +aea add skill fetchai/tac_negotiation:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool ``` ``` bash cd tac_participant_two -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_participation:0.9.0 -aea add skill fetchai/tac_negotiation:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_participation:0.10.0 +aea add skill fetchai/tac_negotiation:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger ethereum aea config set vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract 'True' --type bool aea config set vendor.fetchai.skills.tac_negotiation.models.strategy.args.is_contract_tx 'True' --type bool @@ -125,5 +125,5 @@ models: class_name: Transactions args: pending_transaction_timeout: 30 -protocols: ['fetchai/oef_search:0.7.0', 'fetchai/fipa:0.7.0'] +protocols: ['fetchai/oef_search:0.8.0', 'fetchai/fipa:0.8.0'] ``` \ No newline at end of file diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md index f2ef4b05a6..5f8c7789bc 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-tac-skills.md @@ -1,22 +1,22 @@ ``` bash -aea fetch fetchai/tac_controller:0.10.0 +aea fetch fetchai/tac_controller:0.11.0 cd tac_controller aea install ``` ``` bash aea create tac_controller cd tac_controller -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_control:0.8.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_control:0.9.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger fetchai ``` ``` bash -aea fetch fetchai/tac_participant:0.11.0 --alias tac_participant_one -aea fetch fetchai/tac_participant:0.11.0 --alias tac_participant_two +aea fetch fetchai/tac_participant:0.12.0 --alias tac_participant_one +aea fetch fetchai/tac_participant:0.12.0 --alias tac_participant_two cd tac_participant_two aea install ``` @@ -26,24 +26,24 @@ aea create tac_participant_two ``` ``` bash cd tac_participant_one -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_participation:0.9.0 -aea add skill fetchai/tac_negotiation:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_participation:0.10.0 +aea add skill fetchai/tac_negotiation:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger fetchai ``` ``` bash cd tac_participant_two -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/tac_participation:0.9.0 -aea add skill fetchai/tac_negotiation:0.10.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/tac_participation:0.10.0 +aea add skill fetchai/tac_negotiation:0.11.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 aea config set agent.default_ledger fetchai ``` ``` bash @@ -68,19 +68,24 @@ aea delete tac_participant_two ``` ``` yaml default_routing: - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml +--- +name: p2p_libp2p +author: fetchai +version: 0.11.0 +type: connection config: delegate_uri: 127.0.0.1:11001 entry_peers: ['SOME_ADDRESS'] @@ -89,6 +94,11 @@ config: public_uri: 127.0.0.1:9001 ``` ``` yaml +--- +name: p2p_libp2p +author: fetchai +version: 0.11.0 +type: connection config: delegate_uri: 127.0.0.1:11002 entry_peers: ['SOME_ADDRESS'] diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md index f592063fc2..9bee99c7d3 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-thermometer-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/thermometer_aea:0.11.0 --alias my_thermometer_aea -cd thermometer_aea +aea fetch fetchai/thermometer_aea:0.12.0 --alias my_thermometer_aea +cd my_thermometer_aea aea install ``` ``` bash aea create my_thermometer_aea cd my_thermometer_aea -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea fetch fetchai/thermometer_client:0.11.0 --alias my_thermometer_client +aea fetch fetchai/thermometer_client:0.12.0 --alias my_thermometer_client cd my_thermometer_client aea install ``` ``` bash aea create my_thermometer_client cd my_thermometer_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/thermometer_client:0.11.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/thermometer_client:0.12.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea generate-key fetchai @@ -54,13 +54,13 @@ aea delete my_thermometer_client ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md b/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md index f1057e4bd7..4e085cc251 100644 --- a/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md +++ b/tests/test_docs/test_bash_yaml/md_files/bash-weather-skills.md @@ -1,32 +1,32 @@ ``` bash -aea fetch fetchai/weather_station:0.13.0 --alias my_weather_station +aea fetch fetchai/weather_station:0.14.0 --alias my_weather_station cd my_weather_station aea install ``` ``` bash aea create my_weather_station cd my_weather_station -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/weather_station:0.12.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/weather_station:0.13.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash -aea fetch fetchai/weather_client:0.13.0 --alias my_weather_client +aea fetch fetchai/weather_client:0.14.0 --alias my_weather_client cd my_weather_client aea install ``` ``` bash aea create my_weather_client cd my_weather_client -aea add connection fetchai/p2p_libp2p:0.10.0 -aea add connection fetchai/soef:0.9.0 -aea add connection fetchai/ledger:0.6.0 -aea add skill fetchai/weather_client:0.11.0 +aea add connection fetchai/p2p_libp2p:0.11.0 +aea add connection fetchai/soef:0.10.0 +aea add connection fetchai/ledger:0.7.0 +aea add skill fetchai/weather_client:0.12.0 aea install -aea config set agent.default_connection fetchai/p2p_libp2p:0.10.0 +aea config set agent.default_connection fetchai/p2p_libp2p:0.11.0 ``` ``` bash aea generate-key fetchai @@ -54,13 +54,13 @@ aea delete my_weather_client ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml default_routing: - fetchai/ledger_api:0.4.0: fetchai/ledger:0.6.0 - fetchai/oef_search:0.7.0: fetchai/soef:0.9.0 + fetchai/ledger_api:0.5.0: fetchai/ledger:0.7.0 + fetchai/oef_search:0.8.0: fetchai/soef:0.10.0 ``` ``` yaml config: diff --git a/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py b/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py index 656673d69d..d41b56d5ed 100644 --- a/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py +++ b/tests/test_docs/test_build_aea_programmatically/programmatic_aea.py @@ -100,7 +100,7 @@ def handle(self, message: Message) -> None: time.sleep(4) # Create a message inside an envelope and get the stub connection to pass it on to the echo skill - message_text = b"my_aea,other_agent,fetchai/default:0.6.0,\x08\x01\x12\x011*\x07\n\x05hello," + message_text = b"my_aea,other_agent,fetchai/default:0.7.0,\x08\x01\x12\x011*\x07\n\x05hello," with open(INPUT_FILE, "wb") as f: write_with_lock(f, message_text) print(b"input message: " + message_text) diff --git a/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py b/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py index bdb7052a06..7e07c87a66 100644 --- a/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py +++ b/tests/test_docs/test_build_aea_programmatically/test_programmatic_aea.py @@ -59,7 +59,7 @@ def test_run_agent(self): assert os.path.exists(Path(self.t, "output_file")) assert os.path.exists(Path(self.t, DEFAULT_PRIVATE_KEY_FILE)) - message_text_1 = b"other_agent,my_aea,fetchai/default:0.6.0," + message_text_1 = b"other_agent,my_aea,fetchai/default:0.7.0," message_text_2 = b"\x01*\x07\n\x05hello," path = os.path.join(self.t, "output_file") with open(path, "rb") as file: diff --git a/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py b/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py index 4785ffb54d..99c0d31683 100644 --- a/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py +++ b/tests/test_docs/test_cli_vs_programmatic_aeas/programmatic_aea.py @@ -78,8 +78,8 @@ def run(): # specify the default routing for some protocols default_routing = { - PublicId.from_str("fetchai/ledger_api:0.4.0"): LedgerConnection.connection_id, - PublicId.from_str("fetchai/oef_search:0.7.0"): SOEFConnection.connection_id, + PublicId.from_str("fetchai/ledger_api:0.5.0"): LedgerConnection.connection_id, + PublicId.from_str("fetchai/oef_search:0.8.0"): SOEFConnection.connection_id, } default_connection = P2PLibp2pConnection.connection_id @@ -146,7 +146,7 @@ def run(): api_key=API_KEY, soef_addr=SOEF_ADDR, soef_port=SOEF_PORT, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.7.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.8.0")}, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection(configuration=configuration, identity=identity) diff --git a/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py b/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py index daf2c05033..2572f829f2 100644 --- a/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py +++ b/tests/test_docs/test_cli_vs_programmatic_aeas/test_cli_vs_programmatic_aea.py @@ -28,6 +28,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( CUR_PATH, FETCHAI, @@ -63,7 +65,7 @@ def test_cli_programmatic_communication(self): """Test the communication of the two agents.""" weather_station = "weather_station" - self.fetch_agent("fetchai/weather_station:0.13.0", weather_station) + self.fetch_agent("fetchai/weather_station:0.14.0", weather_station) self.set_agent_context(weather_station) self.set_config( "vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx", @@ -90,7 +92,7 @@ def test_cli_programmatic_communication(self): setting_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) weather_station_process = self.run_agent() @@ -100,7 +102,7 @@ def test_cli_programmatic_communication(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=240, is_terminating=False @@ -121,7 +123,7 @@ def test_cli_programmatic_communication(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_client_process, check_strings, timeout=240, is_terminating=False, @@ -180,6 +182,6 @@ def _inject_location(self, location, dst_file_path): ) lines.insert( line_insertion_position + 1, - f" strategy._agent_location = Location(longitude={location['longitude']}, latitude={location['latitude']})", + f" strategy._agent_location = Location(latitude={location['latitude']}, longitude={location['longitude']})", ) file.write_text("\n".join(lines)) diff --git a/tests/test_docs/test_docs_protocol.py b/tests/test_docs/test_docs_protocol.py index 00a6ce763f..2a6ed81a98 100644 --- a/tests/test_docs/test_docs_protocol.py +++ b/tests/test_docs/test_docs_protocol.py @@ -69,7 +69,7 @@ def test_custom_protocol(self): ) def test_oef_search_protocol(self): - """Test the fetchai/oef_search:0.7.0 protocol documentation.""" + """Test the fetchai/oef_search:0.8.0 protocol documentation.""" # this is the offset of code blocks for the section under testing offset = 4 @@ -106,7 +106,7 @@ def test_oef_search_protocol(self): compare_enum_classes(ExpectedOefErrorOperation, ActualOefErrorOperation) def test_fipa_protocol(self): - """Test the fetchai/fipa:0.7.0 documentation.""" + """Test the fetchai/fipa:0.8.0 documentation.""" offset = 15 locals_dict = {"Enum": Enum} compile_and_exec(self.code_blocks[offset]["text"], locals_dict=locals_dict) diff --git a/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py b/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py index 69d4cfaebc..3beb236003 100644 --- a/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py +++ b/tests/test_docs/test_multiplexer_standalone/multiplexer_standalone.py @@ -65,7 +65,7 @@ def run(): # Create a message inside an envelope and get the stub connection to pass it into the multiplexer message_text = ( - "multiplexer,some_agent,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + "multiplexer,some_agent,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) with open(INPUT_FILE, "w") as f: write_with_lock(f, message_text) diff --git a/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py b/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py index 62dfe2ab8a..25f84c4c38 100644 --- a/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py +++ b/tests/test_docs/test_multiplexer_standalone/test_multiplexer_standalone.py @@ -58,7 +58,7 @@ def test_run_agent(self): assert os.path.exists(Path(self.t, "output.txt")) message_text = ( - "some_agent,multiplexer,fetchai/default:0.6.0,\x08\x01*\x07\n\x05hello," + "some_agent,multiplexer,fetchai/default:0.7.0,\x08\x01*\x07\n\x05hello," ) path = os.path.join(str(self.t), "output.txt") with open(path, "r", encoding="utf-8") as file: diff --git a/tests/test_docs/test_orm_integration/test_orm_integration.py b/tests/test_docs/test_orm_integration/test_orm_integration.py index c82b6eb22a..4edf1854c1 100644 --- a/tests/test_docs/test_orm_integration/test_orm_integration.py +++ b/tests/test_docs/test_orm_integration/test_orm_integration.py @@ -27,6 +27,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -126,8 +128,8 @@ def test_orm_integration_docs_example(self): self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -138,20 +140,21 @@ def test_orm_integration_docs_example(self): # Setup seller self.set_agent_context(seller_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/thermometer:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/thermometer:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) # ejecting changes author and version! - self.eject_item("skill", "fetchai/thermometer:0.12.0") + self.eject_item("skill", "fetchai/thermometer:0.13.0") seller_skill_config_replacement = yaml.safe_load(seller_strategy_replacement) - self.force_set_config( - "skills.thermometer.models", seller_skill_config_replacement["models"], + self.nested_set_config( + "skills.thermometer.models.strategy.args", + seller_skill_config_replacement["models"]["strategy"]["args"], ) - self.force_set_config( + self.nested_set_config( "skills.thermometer.dependencies", seller_skill_config_replacement["dependencies"], ) @@ -176,25 +179,25 @@ def test_orm_integration_docs_example(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = "skills.thermometer.models.strategy.args.location" - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # Setup Buyer self.set_agent_context(buyer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/thermometer_client:0.11.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/thermometer_client:0.12.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) buyer_skill_config_replacement = yaml.safe_load(buyer_strategy_replacement) - self.force_set_config( - "vendor.fetchai.skills.thermometer_client.models", - buyer_skill_config_replacement["models"], + self.nested_set_config( + "vendor.fetchai.skills.thermometer_client.models.strategy.args", + buyer_skill_config_replacement["models"]["strategy"]["args"], ) self.run_install() @@ -211,15 +214,13 @@ def test_orm_integration_docs_example(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # Fire the sub-processes and the threads. self.set_agent_context(seller_aea_name) @@ -231,7 +232,7 @@ def test_orm_integration_docs_example(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=240, is_terminating=False @@ -249,7 +250,7 @@ def test_orm_integration_docs_example(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=240, is_terminating=False, diff --git a/tests/test_docs/test_skill_guide/test_skill_guide.py b/tests/test_docs/test_skill_guide/test_skill_guide.py index c48519f435..2f53ac319b 100644 --- a/tests/test_docs/test_skill_guide/test_skill_guide.py +++ b/tests/test_docs/test_skill_guide/test_skill_guide.py @@ -30,6 +30,8 @@ from aea.configurations.base import DEFAULT_VERSION from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( AUTHOR, COSMOS, @@ -76,7 +78,7 @@ def test_update_skill_and_run(self): simple_service_registration_aea = "simple_service_registration" self.fetch_agent( - "fetchai/simple_service_registration:0.13.0", + "fetchai/simple_service_registration:0.14.0", simple_service_registration_aea, ) self.set_agent_context(simple_service_registration_aea) @@ -91,15 +93,15 @@ def test_update_skill_and_run(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) default_routing = { - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # replace location setting_path = "vendor.fetchai.skills.simple_service_registration.models.strategy.args.location" - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) search_aea = "search_aea" self.create_agents(search_aea) @@ -107,11 +109,11 @@ def test_update_skill_and_run(self): skill_name = "my_search" skill_id = AUTHOR + "/" + skill_name + ":" + DEFAULT_VERSION self.scaffold_item("skill", skill_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) # manually change the files: path = Path(self.t, search_aea, "skills", skill_name, "behaviours.py") @@ -156,15 +158,13 @@ def test_update_skill_and_run(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = "skills.{}.behaviours.my_search_behaviour.args.location".format( skill_name ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run agents self.set_agent_context(simple_service_registration_aea) @@ -176,7 +176,7 @@ def test_update_skill_and_run(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( simple_service_registration_aea_process, @@ -199,7 +199,7 @@ def test_update_skill_and_run(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( search_aea_process, check_strings, timeout=240, is_terminating=False diff --git a/tests/test_helpers/test_async_utils.py b/tests/test_helpers/test_async_utils.py index 6264b21e13..a5b401e876 100644 --- a/tests/test_helpers/test_async_utils.py +++ b/tests/test_helpers/test_async_utils.py @@ -240,11 +240,15 @@ def handler2(item): handler, item = await asyncio.wait_for(getter.get(), timeout=1) -def test_libp2pconnection_awaitable_proc_cancelled(): +@pytest.mark.asyncio +async def test_libp2pconnection_awaitable_proc_cancelled(): """Test awaitable proc.""" proc = AwaitableProc(["sleep", "100"], shell=False) proc_task = asyncio.ensure_future(proc.start()) + await asyncio.sleep(0.1) proc_task.cancel() + with suppress(asyncio.CancelledError): + await proc_task class RunAndExit(Runnable): @@ -396,10 +400,35 @@ async def run(self): run = TestRun(threaded=True) run.start() await asyncio.sleep(0.4) - assert run._task with pytest.raises(Exception, match="awaited"): await run.wait_completed(timeout=1) run.stop() await run.wait_completed() + + @pytest.mark.asyncio + async def test_wait_async_threaded_no_exception(self): + """Test runnable threaded wait completed.""" + # for pydocstyle + class TestRun(Runnable): + async def run(self): + await asyncio.sleep(0.1) + + run = TestRun(threaded=True) + run.start() + await run.wait_completed() + + @pytest.mark.asyncio + async def test_double_stop(self): + """Test runnable double stop.""" + # for pydocstyle + class TestRun(Runnable): + async def run(self): + await asyncio.sleep(0.1) + + run = TestRun() + run.start() + run.stop() + run.stop() + await run.wait_completed() diff --git a/tests/test_helpers/test_base.py b/tests/test_helpers/test_base.py index 916b4218f1..3b9852229f 100644 --- a/tests/test_helpers/test_base.py +++ b/tests/test_helpers/test_base.py @@ -18,15 +18,14 @@ # ------------------------------------------------------------------------------ """This module contains the tests for the helper module.""" -import io import os import platform import re import signal import time -from collections import OrderedDict from pathlib import Path from subprocess import Popen # nosec +from typing import Dict, Set from unittest.mock import patch import pytest @@ -35,6 +34,7 @@ MaxRetriesError, RegexConstrainedString, exception_log_and_reraise, + find_topological_order, load_env_file, load_module, locate, @@ -43,10 +43,6 @@ send_control_c, try_decorator, win_popen_kwargs, - yaml_dump, - yaml_dump_all, - yaml_load, - yaml_load_all, ) from packages.fetchai.connections.oef.connection import OEFConnection @@ -110,16 +106,6 @@ def test_regex_constrained_string_initialization(): RegexConstrainedString(RegexConstrainedString("abcde")) -def test_yaml_dump_load(): - """Test yaml dump/load works.""" - data = OrderedDict({"a": 12, "b": None}) - stream = io.StringIO() - yaml_dump(data, stream) - stream.seek(0) - loaded_data = yaml_load(stream) - assert loaded_data == data - - def test_load_module(): """Test load module from filepath and dotted notation.""" load_module( @@ -234,16 +220,6 @@ def test_send_control_c_windows(): mock_kill.assert_called_with(pid, mock_signal.CTRL_C_EVENT) -def test_yaml_dump_all_load_all(): - """Test yaml_dump_all and yaml_load_all.""" - f = io.StringIO() - data = [{"a": "12"}, {"b": "13"}] - yaml_dump_all(data, f) - - f.seek(0) - assert yaml_load_all(f) == data - - def test_recursive_update_no_recursion(): """Test the 'recursive update' utility, in the case there's no recursion.""" to_update = dict(not_updated=0, an_integer=1, a_list=[1, 2, 3], a_tuple=(1, 2, 3)) @@ -290,3 +266,53 @@ def test_recursive_update_negative_unknown_field(): match="Key 'new_field' is not contained in the dictionary to update.", ): recursive_update(to_update, new_values) + + +class TestTopologicalOrder: + """Test the computation of topological order.""" + + def test_empty_graph(self): + """Test the function with empty input.""" + order = find_topological_order({}) + assert order == [] + + def test_one_node(self): + """Test the function with only one node.""" + order = find_topological_order({0: set()}) + assert order == [0] + + def test_one_node_with_cycle(self): + """Test the function with only one node and a loop.""" + with pytest.raises(ValueError, match="Graph has at least one cycle."): + find_topological_order({0: {0}}) + + def test_two_nodes_no_edges(self): + """Test the function with two nodes, but no edges.""" + order = find_topological_order({0: set(), 1: set()}) + assert order == [0, 1] + + def test_two_nodes_no_cycle(self): + """Test the function with two nodes, but no cycles.""" + order = find_topological_order({0: {1}}) + assert order == [0, 1] + + def test_two_nodes_with_cycle(self): + """Test the function with two nodes and a cycle between them.""" + with pytest.raises(ValueError, match="Graph has at least one cycle."): + find_topological_order({0: {1}, 1: {0}}) + + def test_two_nodes_clique(self): + """Test the function with a clique of two nodes.""" + with pytest.raises(ValueError, match="Graph has at least one cycle."): + find_topological_order({0: {1, 0}, 1: {0, 1}}) + + @pytest.mark.parametrize("chain_length", [3, 5, 10, 100]) + def test_chain(self, chain_length): + """Test the function with a chain.""" + adj_list: Dict[int, Set[int]] = {} + for i in range(chain_length - 1): + adj_list[i] = {i + 1} + adj_list[chain_length - 1] = set() + + order = find_topological_order(adj_list) + assert order == list(range(chain_length)) diff --git a/tests/test_helpers/test_install_dependency.py b/tests/test_helpers/test_install_dependency.py new file mode 100644 index 0000000000..781a779289 --- /dev/null +++ b/tests/test_helpers/test_install_dependency.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Test helper to install python dependecies.""" +from unittest import mock +from unittest.case import TestCase + +from aea.configurations.base import Dependency +from aea.exceptions import AEAException +from aea.helpers.install_dependency import install_dependency + + +class InstallDependencyTestCase(TestCase): + """Test case for _install_dependency method.""" + + def test__install_dependency_fails(self, *mocks): + """Test for install_dependency method fails.""" + result = mock.Mock() + result.returncode = 1 + with mock.patch( + "aea.helpers.install_dependency.subprocess.Popen", return_value=result + ): + with self.assertRaises(AEAException): + install_dependency("test", Dependency("test", "==10.0.0")) + + def test__install_dependency_ok(self, *mocks): + """Test for install_dependency method ok.""" + result = mock.Mock() + result.returncode = 0 + with mock.patch( + "aea.helpers.install_dependency.subprocess.Popen", return_value=result + ): + install_dependency("test", Dependency("test", "==10.0.0")) diff --git a/tests/test_helpers/test_logging.py b/tests/test_helpers/test_logging.py index 457c39a47e..4bbe0b6365 100644 --- a/tests/test_helpers/test_logging.py +++ b/tests/test_helpers/test_logging.py @@ -20,7 +20,16 @@ import logging from unittest.mock import patch -from aea.helpers.logging import AgentLoggerAdapter, WithLogger +from aea.helpers.logging import AgentLoggerAdapter, WithLogger, get_logger + + +def test_get_logger(): + """Test the get_logger function.""" + module_path = "some.dotted.module.path" + agent_name = "agent_name" + expected_name = "some.agent_name.dotted.module.path" + logger = get_logger(module_path, agent_name) + assert logger.name == expected_name def test_agent_logger_adapter(): diff --git a/tests/test_helpers/test_multiaddr.py b/tests/test_helpers/test_multiaddr.py index 022c65e22b..1824af50cf 100644 --- a/tests/test_helpers/test_multiaddr.py +++ b/tests/test_helpers/test_multiaddr.py @@ -18,9 +18,12 @@ # ------------------------------------------------------------------------------ """This module contains the tests for MultiAddr helper class.""" + import tempfile from shutil import rmtree +import pytest + from aea.configurations.constants import DEFAULT_LEDGER from aea.crypto.registries import make_crypto from aea.helpers.multiaddr.base import MultiAddr @@ -66,3 +69,16 @@ def test_multiaddr_correctness(): rmtree(tmpdir) assert maddr._peerid == PEER_ID + + +def test_multiaddr_from_string(): + """Test multiaddress from string""" + maddr_str = "/dns4/" + HOST + "/tcp/" + str(PORT) + "/p2p/" + maddr = MultiAddr.from_string(maddr_str + PEER_ID) + assert maddr.host == HOST and maddr.port == PORT and maddr.peer_id == PEER_ID + + with pytest.raises(ValueError): + MultiAddr.from_string("") + + with pytest.raises(ValueError): + MultiAddr.from_string(maddr_str + "wrong-peer-id") diff --git a/tests/test_helpers/test_search/test_models.py b/tests/test_helpers/test_search/test_models.py index fbf6e152ac..7db8ee70a3 100644 --- a/tests/test_helpers/test_search/test_models.py +++ b/tests/test_helpers/test_search/test_models.py @@ -50,6 +50,7 @@ def test_location(): assert Location(1.1, 2.2) is not None assert isinstance(loc.tuple, Tuple) assert loc.tuple == (loc.latitude, loc.longitude) + assert str(loc) == "Location(latitude=1.1,longitude=2.2)" def test_attribute(): @@ -59,6 +60,10 @@ def test_attribute(): assert Attribute(**params) == Attribute(**params) assert Attribute(**params) is not None assert Attribute(**params) != Attribute(name="another", type_=int, is_required=True) + assert ( + str(Attribute(**params)) + == "Attribute(name=test,type=,is_required=True)" + ) def test_data_model(): @@ -68,6 +73,10 @@ def test_data_model(): data_model = DataModel("test", [Attribute(**params)]) data_model._check_validity() + assert ( + str(data_model) + == "DataModel(name=test,attributes={'test': \"Attribute(name=test,type=,is_required=True)\"},description=)" + ) with pytest.raises(ValueError): data_model = DataModel("test", [Attribute(**params), Attribute(**params)]) data_model._check_validity() @@ -93,7 +102,11 @@ def test_description(): """Test model description.""" values = {"test": "test"} Description(values=values, data_model=generate_data_model("test", values)) - Description(values=values) + desc = Description(values=values) + assert ( + str(desc) + == "Description(values={'test': 'test'},data_model=DataModel(name=,attributes={'test': \"Attribute(name=test,type=,is_required=True)\"},description=))" + ) assert Description(values=values) == Description(values=values) assert list(Description(values=values)) == list(values.values()) @@ -147,6 +160,7 @@ def test_constraint_type(): constrant_type = ConstraintType(ConstraintTypes.WITHIN, [1, 2]) constrant_type.is_valid(Attribute("test", int, True)) constrant_type.check(13) + assert str(constrant_type) == "ConstraintType(value=[1, 2],type=within)" constrant_type = ConstraintType(ConstraintTypes.IN, [1, 2]) constrant_type.is_valid(Attribute("test", int, True)) @@ -239,6 +253,10 @@ def test_query(): """Test Query.""" c1 = Constraint("author", ConstraintType("==", "Stephen King")) query = Query([c1]) + assert ( + str(query) + == "Query(constraints=['Constraint(attribute_name=author,constraint_type=ConstraintType(value=Stephen King,type===))'],model=None)" + ) query.check_validity() assert query.check( Description({"author": "Stephen King", "year": 1991, "genre": "horror"}) diff --git a/tests/test_helpers/test_yaml_utils.py b/tests/test_helpers/test_yaml_utils.py new file mode 100644 index 0000000000..d72b9a3c8d --- /dev/null +++ b/tests/test_helpers/test_yaml_utils.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2020 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests for the yaml utils module.""" +import io +import os +import random +import string +from collections import OrderedDict +from textwrap import dedent + +import pytest + +from aea.helpers.yaml_utils import ( + _AEAYamlLoader, + yaml_dump, + yaml_dump_all, + yaml_load, + yaml_load_all, +) + + +def test_yaml_dump_load(): + """Test yaml dump/load works.""" + data = OrderedDict({"a": 12, "b": None}) + stream = io.StringIO() + yaml_dump(data, stream) + stream.seek(0) + loaded_data = yaml_load(stream) + assert loaded_data == data + + +def test_yaml_dump_all_load_all(): + """Test yaml_dump_all and yaml_load_all.""" + f = io.StringIO() + data = [{"a": "12"}, {"b": "13"}] + yaml_dump_all(data, f) + + f.seek(0) + assert yaml_load_all(f) == data + + +def test_instantiate_loader_twice(): + """Test that instantiating the AEA YAML loader twice doesn't add twice implicit resolvers.""" + loader = _AEAYamlLoader(io.StringIO()) + old_length = len(loader.yaml_implicit_resolvers) + loader = _AEAYamlLoader(io.StringIO()) + assert len(loader.yaml_implicit_resolvers) == old_length + + +def _generate_random_string(n: int = 100): + return "".join( + random.choice(string.ascii_uppercase + string.digits) for _ in range(n) # nosec + ) + + +def test_resolve_env_variable_with_default(): + """Test that the AEAYamlLoader resolves env default variable correctly.""" + random_variable_name = _generate_random_string() + variable_value = "my_variable_default" + variable_key = "my_variable" + yaml_file = dedent( + f""" + {variable_key}: ${{{random_variable_name}:{variable_value}}} + """ + ) + stream = io.StringIO(yaml_file) + yaml_obj = yaml_load(stream) + assert yaml_obj[variable_key] == variable_value + + +def test_resolve_env_variable_without_default_positive(): + """Test that the AEAYamlLoader resolves env variable without default correctly.""" + random_variable_name = _generate_random_string() + variable_value = "my_value" + variable_key = "my_variable" + os.environ[random_variable_name] = variable_value + try: + yaml_file = dedent( + f""" + {variable_key}: ${{{random_variable_name}}} + """ + ) + stream = io.StringIO(yaml_file) + yaml_obj = yaml_load(stream) + finally: + os.environ.pop(random_variable_name) + assert yaml_obj[variable_key] == variable_value + + +def test_resolve_env_variable_without_default_negative(): + """Test that the AEAYamlLoader resolves unspecified env variable without default correctly.""" + random_variable_name = _generate_random_string() + variable_key = "my_variable" + yaml_file = dedent( + f""" + {variable_key}: ${{{random_variable_name}}} + """ + ) + stream = io.StringIO(yaml_file) + yaml_obj = yaml_load(stream) + assert yaml_obj[variable_key] == "" + + +def test_resolve_env_variable_fails(): + """Test the case when resolving the environment variable fails.""" + with pytest.raises( + ValueError, match="Cannot resolve environment variable 'some:wrong:variable'." + ): + wrong_var = "some:wrong:variable" + yaml_file = dedent( + f""" + some_variable_name: ${{{wrong_var}}} + """ + ) + stream = io.StringIO(yaml_file) + yaml_load(stream) diff --git a/tests/test_mail/test_base.py b/tests/test_mail/test_base.py index d4fdb3e624..3f4c99cd9d 100644 --- a/tests/test_mail/test_base.py +++ b/tests/test_mail/test_base.py @@ -95,6 +95,8 @@ def test_envelope_initialisation(): ), "Cannot set protocol_id on Envelope " assert envelope.message == b"HelloWorld", "Cannot set message on Envelope" assert envelope.context.uri_raw is not None + assert not envelope.is_sender_public_id + assert not envelope.is_to_public_id def test_inbox_empty(): @@ -320,7 +322,7 @@ def test_envelope_connection_id(): def test_envelope_skill_id_raises_value_error(): """Test the property Envelope.skill_id raises ValueError if the URI is not a package id..""" with unittest.mock.patch.object( - aea.mail.base.logger, "debug" + aea.mail.base._default_logger, "debug" ) as mock_logger_method: bad_uri = "skill/author/skill_name/bad_version" envelope_context = EnvelopeContext(uri=URI(bad_uri)) @@ -341,7 +343,7 @@ def test_envelope_skill_id_raises_value_error(): def test_envelope_skill_id_raises_value_error_wrong_package_type(): """Test the property Envelope.skill_id raises ValueError if the URI is not a valid package type.""" with unittest.mock.patch.object( - aea.mail.base.logger, "debug" + aea.mail.base._default_logger, "debug" ) as mock_logger_method: invalid_uri = "protocol/author/skill_name/0.1.0" envelope_context = EnvelopeContext(uri=URI(invalid_uri)) diff --git a/tests/test_manager.py b/tests/test_manager.py index b78e9866bb..f64653ab68 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -288,6 +288,34 @@ def test_remove_running_agent(self): self.manager.remove_agent(self.agent_name) assert self.agent_name not in self.manager.list_agents() + def test_install_pypi_dependencies(self): + """Test add agent alias.""" + self.manager.start_manager() + + self.manager.add_project(self.project_public_id) + + # check empty project, nothing should be installed + with patch( + "aea.aea_builder.AEABuilder.install_pypi_dependencies", return_value=None + ) as install_mock: + self.manager.install_pypi_dependencies() + install_mock.assert_not_called() + + self.manager.add_agent( + self.project_public_id, self.agent_name, + ) + + self.manager.add_agent( + self.project_public_id, self.agent_name + "2222", + ) + + # check project with two agents, install once! + with patch( + "aea.aea_builder.AEABuilder.install_pypi_dependencies", return_value=None + ) as install_mock: + self.manager.install_pypi_dependencies() + install_mock.assert_called_once() + class TestMultiAgentManagerThreadedMode(TestMultiAgentManagerAsyncMode): """Tests for MultiAgentManager in threaded mode.""" diff --git a/tests/test_multiplexer.py b/tests/test_multiplexer.py index e16806ca3b..e8e0fd6096 100644 --- a/tests/test_multiplexer.py +++ b/tests/test_multiplexer.py @@ -72,7 +72,7 @@ async def test_receiving_loop_terminated(): multiplexer = Multiplexer([_make_dummy_connection()]) multiplexer.connect() - with unittest.mock.patch.object(aea.mail.base.logger, "debug") as mock_logger_debug: + with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: multiplexer.connection_status.set(ConnectionStates.disconnected) await multiplexer._receiving_loop() mock_logger_debug.assert_called_with("Receiving loop terminated.") @@ -114,7 +114,7 @@ def test_connect_twice_with_loop(): multiplexer = Multiplexer([_make_dummy_connection()], loop=running_loop) with unittest.mock.patch.object( - aea.mail.base.logger, "debug" + multiplexer.logger, "debug" ) as mock_logger_debug: assert not multiplexer.connection_status.is_connected multiplexer.connect() @@ -138,7 +138,7 @@ async def test_connect_twice_a_single_connection(): assert not multiplexer.connection_status.is_connected await multiplexer._connect_one(connection.connection_id) - with unittest.mock.patch.object(aea.mail.base.logger, "debug") as mock_logger_debug: + with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: await multiplexer._connect_one(connection.connection_id) mock_logger_debug.assert_called_with( "Connection fetchai/dummy:0.1.0 already established." @@ -213,7 +213,7 @@ async def test_disconnect_twice_a_single_connection(): multiplexer = Multiplexer([_make_dummy_connection()]) assert not multiplexer.connection_status.is_connected - with unittest.mock.patch.object(aea.mail.base.logger, "debug") as mock_logger_debug: + with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: await multiplexer._disconnect_one(connection.connection_id) mock_logger_debug.assert_called_with( "Connection fetchai/dummy:0.1.0 already disconnected." @@ -293,7 +293,7 @@ async def test_sending_loop_does_not_start_if_multiplexer_not_connected(): """Test that the sending loop is stopped does not start if the multiplexer is not connected.""" multiplexer = Multiplexer([_make_dummy_connection()]) - with unittest.mock.patch.object(aea.mail.base.logger, "debug") as mock_logger_debug: + with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: await multiplexer._send_loop() mock_logger_debug.assert_called_with( "Sending loop not started. The multiplexer is not connected." @@ -307,7 +307,7 @@ async def test_sending_loop_cancelled(): multiplexer.connect() await asyncio.sleep(0.1) - with unittest.mock.patch.object(aea.mail.base.logger, "debug") as mock_logger_debug: + with unittest.mock.patch.object(multiplexer.logger, "debug") as mock_logger_debug: multiplexer.disconnect() mock_logger_debug.assert_any_call("Sending loop cancelled.") @@ -320,7 +320,7 @@ async def test_receiving_loop_raises_exception(): with unittest.mock.patch("asyncio.wait", side_effect=Exception("a weird error.")): with unittest.mock.patch.object( - aea.mail.base.logger, "error" + multiplexer.logger, "error" ) as mock_logger_error: multiplexer.connect() time.sleep(0.1) @@ -367,7 +367,7 @@ def test_send_envelope_error_is_logged_by_send_loop(): context=EnvelopeContext(connection_id=fake_connection_id), ) - with unittest.mock.patch.object(aea.mail.base.logger, "error") as mock_logger_error: + with unittest.mock.patch.object(multiplexer.logger, "error") as mock_logger_error: multiplexer.put(envelope) time.sleep(0.1) mock_logger_error.assert_called_with( @@ -401,7 +401,7 @@ def test_send_message_no_supported_protocol(): multiplexer.connect() - with mock.patch.object(aea.mail.base.logger, "warning") as mock_logger_warning: + with mock.patch.object(multiplexer.logger, "warning") as mock_logger_warning: protocol_id = UNKNOWN_PROTOCOL_PUBLIC_ID envelope = Envelope( to=identity_1.address, @@ -502,6 +502,48 @@ async def test_inbox_outbox(): await multiplexer.disconnect() +@pytest.mark.asyncio +async def test_threaded_mode(): + """Test InBox OutBox objects in threaded mode.""" + connection_1 = _make_dummy_connection() + connections = [connection_1] + multiplexer = AsyncMultiplexer(connections, threaded=True) + msg = DefaultMessage(performative=DefaultMessage.Performative.BYTES, content=b"",) + msg.to = "to" + msg.sender = "sender" + context = EnvelopeContext(connection_id=connection_1.connection_id) + envelope = Envelope( + to="to", + sender="sender", + protocol_id=msg.protocol_id, + message=msg, + context=context, + ) + try: + multiplexer.start() + await asyncio.sleep(0.5) + inbox = InBox(multiplexer) + outbox = OutBox(multiplexer) + + assert inbox.empty() + assert outbox.empty() + + outbox.put(envelope) + received = await inbox.async_get() + assert received == envelope + + assert inbox.empty() + assert outbox.empty() + + outbox.put_message(msg, context=context) + await inbox.async_wait() + received = inbox.get_nowait() + assert received == envelope + + finally: + multiplexer.stop() + + @pytest.mark.asyncio async def test_outbox_negative(): """Test InBox OutBox objects.""" diff --git a/tests/test_package_loading.py b/tests/test_package_loading.py index c463a07e3b..9427552af9 100644 --- a/tests/test_package_loading.py +++ b/tests/test_package_loading.py @@ -29,7 +29,7 @@ def test_loading(): """Test that we correctly load AEA package modules.""" - agent_context_mock = Mock() + agent_context_mock = Mock(agent_name="name") skill_directory = os.path.join(CUR_PATH, "data", "dummy_skill") prefixes = [ diff --git a/tests/test_packages/test_connections/test_http_server/test_http_server.py b/tests/test_packages/test_connections/test_http_server/test_http_server.py index 7f8ce4bc3d..41999715ff 100644 --- a/tests/test_packages/test_connections/test_http_server/test_http_server.py +++ b/tests/test_packages/test_connections/test_http_server/test_http_server.py @@ -119,7 +119,7 @@ def setup(self): ROOT_DIR, "tests", "data", "petstore_sim.yaml" ) self.connection_id = HTTPServerConnection.connection_id - self.protocol_id = PublicId.from_str("fetchai/http:0.6.0") + self.protocol_id = PublicId.from_str("fetchai/http:0.7.0") self.configuration = ConnectionConfig( host=self.host, diff --git a/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py b/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py index 6dc25ac3c2..7808591f52 100644 --- a/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py +++ b/tests/test_packages/test_connections/test_http_server/test_http_server_and_client.py @@ -53,7 +53,7 @@ def setup_server(self): self.host = get_host() self.port = get_unused_tcp_port() self.connection_id = HTTPServerConnection.connection_id - self.protocol_id = PublicId.from_str("fetchai/http:0.6.0") + self.protocol_id = PublicId.from_str("fetchai/http:0.7.0") self.configuration = ConnectionConfig( host=self.host, diff --git a/tests/test_packages/test_connections/test_ledger/test_contract_api.py b/tests/test_packages/test_connections/test_ledger/test_contract_api.py index 88bb315259..74ca73c856 100644 --- a/tests/test_packages/test_connections/test_ledger/test_contract_api.py +++ b/tests/test_packages/test_connections/test_ledger/test_contract_api.py @@ -85,7 +85,7 @@ async def test_erc1155_get_deploy_transaction(erc1155_contract, ledger_apis_conn counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({"deployer_address": address}), ) @@ -126,7 +126,7 @@ async def test_erc1155_get_raw_transaction(erc1155_contract, ledger_apis_connect counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=contract_address, callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -171,7 +171,7 @@ async def test_erc1155_get_raw_message(erc1155_contract, ledger_apis_connection) counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_RAW_MESSAGE, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=contract_address, callable="get_hash_single", kwargs=ContractApiMessage.Kwargs( @@ -223,7 +223,7 @@ async def test_erc1155_get_state(erc1155_contract, ledger_apis_connection): counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( @@ -268,7 +268,7 @@ def _raise(): counterparty="str(ledger_apis_connection.connection_id)", performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address="test addr", callable="get_create_batch_transaction", kwargs=ContractApiMessage.Kwargs( @@ -313,7 +313,7 @@ async def test_callable_wrong_number_of_arguments_api_and_contract_address( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( @@ -365,7 +365,7 @@ async def test_callable_wrong_number_of_arguments_apis( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({}), ) @@ -418,7 +418,7 @@ async def test_callable_wrong_number_of_arguments_apis_method_call( counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_DEPLOY_TRANSACTION, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", callable="get_deploy_transaction", kwargs=ContractApiMessage.Kwargs({}), ) @@ -454,7 +454,7 @@ async def test_callable_generic_error(erc1155_contract, ledger_apis_connection): counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=contract_address, callable="get_balance", kwargs=ContractApiMessage.Kwargs( @@ -499,7 +499,7 @@ async def test_callable_cannot_find(erc1155_contract, ledger_apis_connection, ca counterparty=str(ledger_apis_connection.connection_id), performative=ContractApiMessage.Performative.GET_STATE, ledger_id=ETHEREUM, - contract_id="fetchai/erc1155:0.10.0", + contract_id="fetchai/erc1155:0.11.0", contract_address=contract_address, callable="unknown_callable", kwargs=ContractApiMessage.Kwargs( diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py index 956e4956c4..2ed44f9577 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_aea_cli.py @@ -45,8 +45,8 @@ def setup_class(cls): @libp2p_log_on_failure def test_agent(self): """Test with aea.""" - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") # for logging config_path = "vendor.fetchai.connections.p2p_libp2p.config" @@ -59,7 +59,7 @@ def test_agent(self): is_running = self.is_running(process, timeout=LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" - check_strings = "My libp2p addresses: [" + check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] @@ -90,7 +90,7 @@ def setup_class(cls): @libp2p_log_on_failure def test_agent(self): """Test with aea.""" - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") # setup a full node: with public uri, relay service, and delegate service config_path = "vendor.fetchai.connections.p2p_libp2p.config" @@ -116,7 +116,7 @@ def test_agent(self): is_running = self.is_running(process, timeout=LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" - check_strings = "My libp2p addresses: ['/dns4/" + check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py index c16e33f7a5..46c66d25fa 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_errors.py @@ -37,6 +37,7 @@ P2PLibp2pConnection, _golang_module_build_async, _golang_module_run, + _ip_all_private_or_all_public, ) from tests.conftest import COSMOS, _make_libp2p_connection @@ -216,3 +217,19 @@ def test_libp2pconnection_awaitable_proc_cancelled(): proc = AwaitableProc(["sleep", "100"], shell=False) proc_task = asyncio.ensure_future(proc.start()) proc_task.cancel() + + +def test_libp2pconnection_mixed_ip_address(): + """Test correct public uri ip and entry peers ips configuration.""" + assert _ip_all_private_or_all_public([]) is True + assert _ip_all_private_or_all_public(["127.0.0.1", "127.0.0.1"]) is True + assert _ip_all_private_or_all_public(["localhost", "127.0.0.1"]) is True + assert _ip_all_private_or_all_public(["10.0.0.1", "127.0.0.1"]) is False + assert _ip_all_private_or_all_public(["fetch.ai", "127.0.0.1"]) is False + assert _ip_all_private_or_all_public(["104.26.2.97", "127.0.0.1"]) is False + assert ( + _ip_all_private_or_all_public( + ["fetch.ai", "agents-p2p-dht.sandbox.fetch-ai.com"] + ) + is True + ) diff --git a/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py b/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py index c05e9c6c3c..8f1bd9c350 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p/test_public_dht.py @@ -356,21 +356,23 @@ class TestLibp2pConnectionPublicDHTRelayAEACli(AEATestCaseEmpty): @libp2p_log_on_failure def test_connectivity(self): """Test connectivity.""" - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - - config_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.set_config( - "{}.local_uri".format(config_path), "127.0.0.1:{}".format(DEFAULT_PORT) - ) - self.force_set_config( - "{}.entry_peers".format(config_path), PUBLIC_DHT_MADDRS, - ) + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") # for logging log_file = "libp2p_node_{}.log".format(self.agent_name) log_file = os.path.join(os.path.abspath(os.getcwd()), log_file) - self.set_config("{}.log_file".format(config_path), log_file) + + config_path = "vendor.fetchai.connections.p2p_libp2p.config" + self.nested_set_config( + config_path, + { + "local_uri": "127.0.0.1:{}".format(DEFAULT_PORT), + "entry_peers": PUBLIC_DHT_MADDRS, + "log_file": log_file, + }, + ) + self.log_files = [log_file] process = self.run_agent() @@ -378,7 +380,7 @@ def test_connectivity(self): is_running = self.is_running(process, timeout=AEA_LIBP2P_LAUNCH_TIMEOUT) assert is_running, "AEA not running within timeout!" - check_strings = "My libp2p addresses: [" + check_strings = "Peer running in " missing_strings = self.missing_from_output(process, check_strings) assert ( missing_strings == [] @@ -402,11 +404,11 @@ class TestLibp2pConnectionPublicDHTDelegateAEACli(AEATestCaseEmpty): def test_connectivity(self): """Test connectivity.""" - self.add_item("connection", "fetchai/p2p_libp2p_client:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p_client:0.8.0") config_path = "vendor.fetchai.connections.p2p_libp2p_client.config" - self.force_set_config( - "{}.nodes".format(config_path), - [{"uri": "{}".format(uri)} for uri in PUBLIC_DHT_DELEGATE_URIS], + self.nested_set_config( + config_path, + {"nodes": [{"uri": "{}".format(uri)} for uri in PUBLIC_DHT_DELEGATE_URIS]}, ) process = self.run_agent() diff --git a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py index 63351cecfa..3286c79371 100644 --- a/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py +++ b/tests/test_packages/test_connections/test_p2p_libp2p_client/test_aea_cli.py @@ -62,11 +62,11 @@ def test_node(self): def test_connection(self): """Test the connection can be used in an aea.""" - self.add_item("connection", "fetchai/p2p_libp2p_client:0.7.0") + self.add_item("connection", "fetchai/p2p_libp2p_client:0.8.0") config_path = "vendor.fetchai.connections.p2p_libp2p_client.config" - self.force_set_config( - "{}.nodes".format(config_path), - [{"uri": "{}:{}".format(DEFAULT_HOST, DEFAULT_DELEGATE_PORT)}], + self.nested_set_config( + config_path, + {"nodes": [{"uri": "{}:{}".format(DEFAULT_HOST, DEFAULT_DELEGATE_PORT)}]}, ) process = self.run_agent() diff --git a/tests/test_packages/test_connections/test_soef/test_soef.py b/tests/test_packages/test_connections/test_soef/test_soef.py index 4666c67b9b..ef8d651028 100644 --- a/tests/test_packages/test_connections/test_soef/test_soef.py +++ b/tests/test_packages/test_connections/test_soef/test_soef.py @@ -120,7 +120,7 @@ def setup(self): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.7.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.8.0")}, connection_id=SOEFConnection.connection_id, ) self.connection = SOEFConnection( @@ -339,7 +339,7 @@ async def test_register_personailty_pieces(self): await asyncio.wait_for(self.connection.receive(), timeout=1) @pytest.mark.asyncio - async def test_send_excluded_protocol(self, caplog): + async def test_send_excluded_protocol(self): """Test fail on unsupported protocol.""" envelope = Envelope( to="soef", @@ -354,7 +354,7 @@ async def test_send_excluded_protocol(self, caplog): await self.connection.send(envelope) @pytest.mark.asyncio - async def test_bad_message(self, caplog): + async def test_bad_message(self): """Test fail on bad message.""" envelope = Envelope( to="soef", @@ -366,7 +366,7 @@ async def test_bad_message(self, caplog): await self.connection.send(envelope) @pytest.mark.asyncio - async def test_bad_performative(self, caplog): + async def test_bad_performative(self): """Test fail on bad perfromative.""" agent_location = Location(52.2057092, 2.1183431) service_instance = {"location": agent_location} @@ -390,7 +390,7 @@ async def test_bad_performative(self, caplog): await self.connection.send(envelope) @pytest.mark.asyncio - async def test_bad_search_query(self, caplog): + async def test_bad_search_query(self): """Test fail on invalid query for search.""" await self.test_register_service() closeness_query = Query([], model=models.AGENT_LOCATION_MODEL) @@ -661,7 +661,7 @@ def test_chain_identifier_fail(self): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.7.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.8.0")}, connection_id=SOEFConnection.connection_id, chain_identifier=chain_identifier, ) @@ -679,7 +679,7 @@ def test_chain_identifier_ok(self): api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.7.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.8.0")}, connection_id=SOEFConnection.connection_id, chain_identifier=chain_identifier, ) diff --git a/tests/test_packages/test_connections/test_soef/test_soef_integration.py b/tests/test_packages/test_connections/test_soef/test_soef_integration.py index 67500b8dd0..f9ba9bee7a 100644 --- a/tests/test_packages/test_connections/test_soef/test_soef_integration.py +++ b/tests/test_packages/test_connections/test_soef/test_soef_integration.py @@ -67,7 +67,7 @@ def make_multiplexer_and_dialogues() -> Tuple[Multiplexer, OefSearchDialogues, C api_key="TwiCIriSl0mLahw17pyqoA", soef_addr="soef.fetch.ai", soef_port=9002, - restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.7.0")}, + restricted_to_protocols={PublicId.from_str("fetchai/oef_search:0.8.0")}, connection_id=SOEFConnection.connection_id, ) soef_connection = SOEFConnection(configuration=configuration, identity=identity,) diff --git a/tests/test_packages/test_connections/test_tcp/test_base.py b/tests/test_packages/test_connections/test_tcp/test_base.py index 4381d84e28..81902cd88f 100644 --- a/tests/test_packages/test_connections/test_tcp/test_base.py +++ b/tests/test_packages/test_connections/test_tcp/test_base.py @@ -65,7 +65,7 @@ async def test_connect_raises_exception(): @pytest.mark.asyncio -async def test_disconnect_when_already_disconnected(caplog): +async def test_disconnect_when_already_disconnected(): """Test that disconnecting a connection already disconnected works correctly.""" port = get_unused_tcp_port() tcp_connection = _make_tcp_server_connection("address", "127.0.0.1", port) diff --git a/tests/test_packages/test_connections/test_webhook/test_webhook.py b/tests/test_packages/test_connections/test_webhook/test_webhook.py index 1c40eabdc6..4d333614f0 100644 --- a/tests/test_packages/test_connections/test_webhook/test_webhook.py +++ b/tests/test_packages/test_connections/test_webhook/test_webhook.py @@ -164,7 +164,7 @@ async def test_send(self): envelope = Envelope( to="addr", sender="my_id", - protocol_id=PublicId.from_str("fetchai/http:0.6.0"), + protocol_id=PublicId.from_str("fetchai/http:0.7.0"), message=http_message, ) with patch.object(self.webhook_connection.logger, "warning") as mock_logger: diff --git a/tests/test_packages/test_protocols/test_contract_api.py b/tests/test_packages/test_protocols/test_contract_api.py index 30c727b5a9..d0be5d4700 100644 --- a/tests/test_packages/test_protocols/test_contract_api.py +++ b/tests/test_packages/test_protocols/test_contract_api.py @@ -40,7 +40,7 @@ ) from packages.fetchai.protocols.contract_api.message import ContractApiMessage from packages.fetchai.protocols.contract_api.message import ( - logger as contract_api_message_logger, + _default_logger as contract_api_message_logger, ) from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_fipa.py b/tests/test_packages/test_protocols/test_fipa.py index 8cf6e672cc..e71c502ce8 100644 --- a/tests/test_packages/test_protocols/test_fipa.py +++ b/tests/test_packages/test_protocols/test_fipa.py @@ -37,7 +37,9 @@ import packages from packages.fetchai.protocols.fipa.dialogues import FipaDialogue, FipaDialogues from packages.fetchai.protocols.fipa.message import FipaMessage -from packages.fetchai.protocols.fipa.message import logger as fipa_message_logger +from packages.fetchai.protocols.fipa.message import ( + _default_logger as fipa_message_logger, +) from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_gym.py b/tests/test_packages/test_protocols/test_gym.py index 01536401ad..4515633683 100644 --- a/tests/test_packages/test_protocols/test_gym.py +++ b/tests/test_packages/test_protocols/test_gym.py @@ -35,7 +35,7 @@ import packages from packages.fetchai.protocols.gym.dialogues import GymDialogue, GymDialogues from packages.fetchai.protocols.gym.message import GymMessage -from packages.fetchai.protocols.gym.message import logger as gym_message_logger +from packages.fetchai.protocols.gym.message import _default_logger as gym_message_logger from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_http.py b/tests/test_packages/test_protocols/test_http.py index ea6980397e..0971584a25 100644 --- a/tests/test_packages/test_protocols/test_http.py +++ b/tests/test_packages/test_protocols/test_http.py @@ -35,7 +35,9 @@ import packages from packages.fetchai.protocols.http.dialogues import HttpDialogue, HttpDialogues from packages.fetchai.protocols.http.message import HttpMessage -from packages.fetchai.protocols.http.message import logger as http_message_logger +from packages.fetchai.protocols.http.message import ( + _default_logger as http_message_logger, +) from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_ledger_api.py b/tests/test_packages/test_protocols/test_ledger_api.py index eee0e6955f..c56228d373 100644 --- a/tests/test_packages/test_protocols/test_ledger_api.py +++ b/tests/test_packages/test_protocols/test_ledger_api.py @@ -39,7 +39,7 @@ ) from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage from packages.fetchai.protocols.ledger_api.message import ( - logger as ledger_api_message_logger, + _default_logger as ledger_api_message_logger, ) from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_ml_trade.py b/tests/test_packages/test_protocols/test_ml_trade.py index ebb435e104..ea24fd8027 100644 --- a/tests/test_packages/test_protocols/test_ml_trade.py +++ b/tests/test_packages/test_protocols/test_ml_trade.py @@ -41,7 +41,7 @@ ) from packages.fetchai.protocols.ml_trade.message import MlTradeMessage from packages.fetchai.protocols.ml_trade.message import ( - logger as ml_trade_message_logger, + _default_logger as ml_trade_message_logger, ) from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_oef_search.py b/tests/test_packages/test_protocols/test_oef_search.py index 9bb2c4506c..53233a8919 100644 --- a/tests/test_packages/test_protocols/test_oef_search.py +++ b/tests/test_packages/test_protocols/test_oef_search.py @@ -40,7 +40,7 @@ ) from packages.fetchai.protocols.oef_search.message import OefSearchMessage from packages.fetchai.protocols.oef_search.message import ( - logger as oef_search_message_logger, + _default_logger as oef_search_message_logger, ) from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_protocols/test_tac.py b/tests/test_packages/test_protocols/test_tac.py index fc1f4c2336..6ec88128f4 100644 --- a/tests/test_packages/test_protocols/test_tac.py +++ b/tests/test_packages/test_protocols/test_tac.py @@ -35,7 +35,7 @@ import packages from packages.fetchai.protocols.tac.dialogues import TacDialogue, TacDialogues from packages.fetchai.protocols.tac.message import TacMessage -from packages.fetchai.protocols.tac.message import logger as tac_message_logger +from packages.fetchai.protocols.tac.message import _default_logger as tac_message_logger from tests.conftest import ROOT_DIR diff --git a/tests/test_packages/test_skills/test_generic_buyer/__init__.py b/tests/test_packages/test_skills/test_generic_buyer/__init__.py new file mode 100644 index 0000000000..008a7a0b87 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_buyer/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""The tests module contains the tests of the packages/skills/generic_buyer dir.""" diff --git a/tests/test_packages/test_skills/test_generic_buyer/test_behaviours.py b/tests/test_packages/test_skills/test_generic_buyer/test_behaviours.py new file mode 100644 index 0000000000..7257f8d492 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_buyer/test_behaviours.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the behaviour classes of the generic buyer skill.""" + +from pathlib import Path +from typing import cast + +from aea.test_tools.test_skill import BaseSkillTestCase + +from packages.fetchai.connections.ledger.base import CONNECTION_ID as LEDGER_PUBLIC_ID +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.generic_buyer.behaviours import GenericSearchBehaviour +from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy + +from tests.conftest import ROOT_DIR + + +FETCHAI = "fetchai" + + +class TestSkillBehaviour(BaseSkillTestCase): + """Test behaviours of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.search_behaviour = cast( + GenericSearchBehaviour, cls._skill.skill_context.behaviours.search + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + + def test_setup_is_ledger_tx(self): + """Test the setup method of the search behaviour where is_ledger_tx is True.""" + # operation + self.search_behaviour.setup() + + # after + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=LedgerApiMessage, + performative=LedgerApiMessage.Performative.GET_BALANCE, + to=str(LEDGER_PUBLIC_ID), + sender=self.skill.skill_context.agent_address, + ledger_id=FETCHAI, + address=self.skill.skill_context.agent_address, + ) + assert has_attributes, error_str + + def test_setup_not_is_ledger_tx(self): + """Test the setup method of the search behaviour where is_ledger_tx is False.""" + # setup + self.strategy._is_ledger_tx = False + + # before + assert not self.strategy.is_searching + + # operation + self.search_behaviour.setup() + + # after + assert self.strategy.is_searching + + def test_act_is_searching(self): + """Test the act method of the search behaviour where is_searching is True.""" + # setup + self.strategy._is_searching = True + + # operation + self.search_behaviour.act() + + # after + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=OefSearchMessage, + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + to=self.skill.skill_context.search_service_address, + sender=self.skill.skill_context.agent_address, + query=self.skill.skill_context.strategy.get_location_and_service_query(), + ) + assert has_attributes, error_str + + def test_act_not_is_searching(self): + """Test the act method of the search behaviour where is_searching is False.""" + # setup + self.strategy._is_searching = False + + # operation + self.search_behaviour.act() + + # after + self.assert_quantity_in_outbox(0) + + def test_teardown(self): + """Test the teardown method of the search behaviour.""" + assert self.search_behaviour.teardown() is None + self.assert_quantity_in_outbox(0) diff --git a/tests/test_packages/test_skills/test_generic_buyer/test_dialogues.py b/tests/test_packages/test_skills/test_generic_buyer/test_dialogues.py new file mode 100644 index 0000000000..61f4608721 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_buyer/test_dialogues.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the dialogue classes of the generic buyer skill.""" + +from pathlib import Path +from typing import cast + +import pytest + +from aea.exceptions import AEAEnforceError +from aea.helpers.transaction.base import Terms +from aea.protocols.default.message import DefaultMessage +from aea.protocols.dialogue.base import DialogueLabel +from aea.protocols.signing.message import SigningMessage +from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_NAME + +from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.generic_buyer.dialogues import ( + DefaultDialogue, + DefaultDialogues, + FipaDialogue, + FipaDialogues, + LedgerApiDialogue, + LedgerApiDialogues, + OefSearchDialogue, + OefSearchDialogues, + SigningDialogue, + SigningDialogues, +) + +from tests.conftest import ROOT_DIR + + +class TestDialogues(BaseSkillTestCase): + """Test dialogue classes of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.default_dialogues = cast( + DefaultDialogues, cls._skill.skill_context.default_dialogues + ) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + cls.ledger_api_dialogues = cast( + LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues + ) + cls.oef_search_dialogues = cast( + OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues + ) + cls.signing_dialogues = cast( + SigningDialogues, cls._skill.skill_context.signing_dialogues + ) + + def test_default_dialogues(self): + """Test the DefaultDialogues class.""" + _, dialogue = self.default_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=DefaultMessage.Performative.BYTES, + content=b"some_content", + ) + assert dialogue.role == DefaultDialogue.Role.AGENT + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_fipa_dialogue(self): + """Test the FipaDialogue class.""" + fipa_dialogue = FipaDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=DefaultDialogue.Role.AGENT, + ) + + # terms + with pytest.raises(AEAEnforceError, match="Terms not set!"): + assert fipa_dialogue.terms + terms = Terms( + "some_ledger_id", + self.skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + fipa_dialogue.terms = terms + with pytest.raises(AEAEnforceError, match="Terms already set!"): + fipa_dialogue.terms = terms + assert fipa_dialogue.terms == terms + + # associated_ledger_api_dialogue + with pytest.raises(AEAEnforceError, match="LedgerApiDialogue not set!"): + assert fipa_dialogue.associated_ledger_api_dialogue + ledger_api_dialogue = LedgerApiDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=LedgerApiDialogue.Role.AGENT, + ) + fipa_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue + with pytest.raises(AEAEnforceError, match="LedgerApiDialogue already set!"): + fipa_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue + assert fipa_dialogue.associated_ledger_api_dialogue == ledger_api_dialogue + + def test_fipa_dialogues(self): + """Test the FipaDialogues class.""" + _, dialogue = self.fipa_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=FipaMessage.Performative.CFP, + query="some_query", + ) + assert dialogue.role == FipaDialogue.Role.BUYER + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_ledger_api_dialogue(self): + """Test the LedgerApiDialogue class.""" + ledger_api_dialogue = LedgerApiDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=LedgerApiDialogue.Role.AGENT, + ) + + # associated_fipa_dialogue + with pytest.raises(AEAEnforceError, match="FipaDialogue not set!"): + assert ledger_api_dialogue.associated_fipa_dialogue + fipa_dialogue = FipaDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=FipaDialogue.Role.BUYER, + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + with pytest.raises(AEAEnforceError, match="FipaDialogue already set!"): + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + assert ledger_api_dialogue.associated_fipa_dialogue == fipa_dialogue + + def test_ledger_api_dialogues(self): + """Test the LedgerApiDialogues class.""" + _, dialogue = self.ledger_api_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=LedgerApiMessage.Performative.GET_BALANCE, + ledger_id="some_ledger_id", + address="some_address", + ) + assert dialogue.role == LedgerApiDialogue.Role.AGENT + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_oef_search_dialogues(self): + """Test the OefSearchDialogues class.""" + _, dialogue = self.oef_search_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + ledger_id="some_ledger_id", + query="some_query", + ) + assert dialogue.role == OefSearchDialogue.Role.AGENT + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_signing_dialogue(self): + """Test the SigningDialogue class.""" + signing_dialogue = SigningDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=SigningDialogue.Role.SKILL, + ) + + # associated_fipa_dialogue + with pytest.raises(AEAEnforceError, match="FipaDialogue not set!"): + assert signing_dialogue.associated_fipa_dialogue + fipa_dialogue = FipaDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=FipaDialogue.Role.BUYER, + ) + signing_dialogue.associated_fipa_dialogue = fipa_dialogue + with pytest.raises(AEAEnforceError, match="FipaDialogue already set!"): + signing_dialogue.associated_fipa_dialogue = fipa_dialogue + assert signing_dialogue.associated_fipa_dialogue == fipa_dialogue + + def test_signing_dialogues(self): + """Test the SigningDialogues class.""" + _, dialogue = self.signing_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=SigningMessage.Performative.SIGN_TRANSACTION, + terms="some_terms", + raw_transaction="some_raw_transaction", + ) + assert dialogue.role == SigningDialogue.Role.SKILL + assert dialogue.self_address == str(self.skill.skill_context.skill_id) diff --git a/tests/test_packages/test_skills/test_generic_buyer/test_handlers.py b/tests/test_packages/test_skills/test_generic_buyer/test_handlers.py new file mode 100644 index 0000000000..f876ef5c8e --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_buyer/test_handlers.py @@ -0,0 +1,1337 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the handler classes of the generic buyer skill.""" + +import logging +from pathlib import Path +from typing import cast +from unittest.mock import patch + +import pytest + +from aea.helpers.search.models import Description +from aea.helpers.transaction.base import ( + RawTransaction, + SignedTransaction, + Terms, + TransactionDigest, +) +from aea.protocols.default.message import DefaultMessage +from aea.protocols.dialogue.base import DialogueMessage +from aea.protocols.signing.message import SigningMessage +from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_NAME + +from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.generic_buyer.dialogues import ( + FipaDialogue, + FipaDialogues, + LedgerApiDialogue, + LedgerApiDialogues, + OefSearchDialogues, + SigningDialogue, + SigningDialogues, +) +from packages.fetchai.skills.generic_buyer.handlers import ( + GenericFipaHandler, + GenericLedgerApiHandler, + GenericOefSearchHandler, + GenericSigningHandler, + LEDGER_API_ADDRESS, +) +from packages.fetchai.skills.generic_buyer.strategy import GenericStrategy + +from tests.conftest import ROOT_DIR + + +class TestGenericFipaHandler(BaseSkillTestCase): + """Test fipa handler of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.fipa_handler = cast( + GenericFipaHandler, cls._skill.skill_context.handlers.fipa + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + cls.list_of_messages = ( + DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage(FipaMessage.Performative.ACCEPT), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + {"info": {"address": "some_term_sender_address"}}, + ), + DialogueMessage( + FipaMessage.Performative.INFORM, + {"info": {"transaction_digest": "some_transaction_digest_body"}}, + ), + ) + + def test_setup(self): + """Test the setup method of the fipa handler.""" + assert self.fipa_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the fipa handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=FipaMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=FipaMessage.Performative.ACCEPT, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid fipa message={incoming_message}, unidentified dialogue.", + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=DefaultMessage, + performative=DefaultMessage.Performative.ERROR, + to=incoming_message.sender, + sender=self.skill.skill_context.agent_address, + error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, + error_msg="Invalid dialogue.", + error_data={"fipa_message": incoming_message.encode()}, + ) + assert has_attributes, error_str + + def test_handle_propose_is_affordable_and_is_acceptable(self): + """Test the _handle_propose method of the fipa handler.""" + # setup + proposal = Description( + { + "ledger_id": self.strategy.ledger_id, + "price": 100, + "currency_id": "FET", + "service_id": "some_service_id", + "quantity": 1, + "tx_nonce": "some_tx_nonce", + } + ) + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:1], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.PROPOSE, + proposal=proposal, + ) + + # operation + with patch.object( + self.strategy, "is_acceptable_proposal", return_value=True, + ): + with patch.object( + self.strategy, "is_affordable_proposal", return_value=True, + ): + with patch.object( + self.fipa_handler.context.logger, "log" + ) as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + incoming_message = cast(FipaMessage, incoming_message) + mock_logger.assert_any_call( + logging.INFO, + f"received proposal={incoming_message.proposal.values} from sender={COUNTERPARTY_NAME[-5:]}", + ) + mock_logger.assert_any_call( + logging.INFO, f"accepting the proposal from sender={COUNTERPARTY_NAME[-5:]}" + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.ACCEPT, + to=incoming_message.sender, + sender=self.skill.skill_context.agent_address, + target=incoming_message.message_id, + ) + assert has_attributes, error_str + + def test_handle_propose_not_is_affordable_or_not_is_acceptable(self): + """Test the _handle_propose method of the fipa handler.""" + # setup + proposal = Description( + { + "ledger_id": self.strategy.ledger_id, + "price": 100, + "currency_id": "FET", + "service_id": "some_service_id", + "quantity": 1, + "tx_nonce": "some_tx_nonce", + } + ) + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:1], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.PROPOSE, + proposal=proposal, + ) + + # operation + with patch.object( + self.strategy, "is_acceptable_proposal", return_value=False, + ): + with patch.object( + self.strategy, "is_affordable_proposal", return_value=False, + ): + with patch.object( + self.fipa_handler.context.logger, "log" + ) as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + incoming_message = cast(FipaMessage, incoming_message) + mock_logger.assert_any_call( + logging.INFO, + f"received proposal={incoming_message.proposal.values} from sender={COUNTERPARTY_NAME[-5:]}", + ) + mock_logger.assert_any_call( + logging.INFO, f"declining the proposal from sender={COUNTERPARTY_NAME[-5:]}" + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.DECLINE, + to=incoming_message.sender, + sender=self.skill.skill_context.agent_address, + target=incoming_message.message_id, + ) + assert has_attributes, error_str + + def test_handle_decline_decline_cfp(self): + """Test the _handle_decline method of the fipa handler where the end state is decline_cfp.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:1], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, + ) + + # before + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): + assert end_state_numbers == 0 + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received DECLINE from sender={COUNTERPARTY_NAME[-5:]}" + ) + + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state, + end_state_numbers, + ) in self.fipa_dialogues.dialogue_stats.self_initiated.items(): + if end_state == FipaDialogue.EndState.DECLINED_CFP: + assert end_state_numbers == 1 + else: + assert end_state_numbers == 0 + + def test_handle_decline_decline_accept(self): + """Test the _handle_decline method of the fipa handler where the end state is decline_accept.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:3], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, + ) + + # before + for end_state_numbers in list( + self.fipa_dialogues.dialogue_stats.self_initiated.values() + ) + list(self.fipa_dialogues.dialogue_stats.other_initiated.values()): + assert end_state_numbers == 0 + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received DECLINE from sender={COUNTERPARTY_NAME[-5:]}" + ) + + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state, + end_state_numbers, + ) in self.fipa_dialogues.dialogue_stats.self_initiated.items(): + if end_state == FipaDialogue.EndState.DECLINED_ACCEPT: + assert end_state_numbers == 1 + else: + assert end_state_numbers == 0 + + def test_handle_match_accept_is_ledger_tx(self): + """Test the _handle_match_accept method of the fipa handler where is_ledger_tx is True.""" + # setup + self.strategy._is_ledger_tx = True + + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:3], + ) + fipa_dialogue.terms = Terms( + "some_ledger_id", + self.skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + info={"info": {"address": "some_term_sender_address"}}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received MATCH_ACCEPT_W_INFORM from sender={COUNTERPARTY_NAME[-5:]} with info={incoming_message.info}", + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=LedgerApiMessage, + performative=LedgerApiMessage.Performative.GET_RAW_TRANSACTION, + to=LEDGER_API_ADDRESS, + sender=self.skill.skill_context.agent_address, + terms=fipa_dialogue.terms, + ) + assert has_attributes, error_str + + mock_logger.assert_any_call( + logging.INFO, "requesting transfer transaction from ledger api..." + ) + + def test_handle_match_accept_not_is_ledger_tx(self): + """Test the _handle_match_accept method of the fipa handler where is_ledger_tx is False.""" + # setup + self.strategy._is_ledger_tx = False + + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:3], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + info={"info": {"address": "some_term_sender_address"}}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received MATCH_ACCEPT_W_INFORM from sender={COUNTERPARTY_NAME[-5:]} with info={incoming_message.info}", + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.INFORM, + to=incoming_message.sender, + sender=self.skill.skill_context.agent_address, + target=incoming_message.message_id, + info={"Done": "Sending payment via bank transfer"}, + ) + assert has_attributes, error_str + + mock_logger.assert_any_call( + logging.INFO, f"informing counterparty={COUNTERPARTY_NAME[-5:]} of payment." + ) + + def test_handle_inform_with_data(self): + """Test the _handle_inform method of the fipa handler where info has data.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.INFORM, + info={"data_name": "data"}, + ) + + # before + for end_state_numbers in list( + self.fipa_dialogues.dialogue_stats.self_initiated.values() + ) + list(self.fipa_dialogues.dialogue_stats.other_initiated.values()): + assert end_state_numbers == 0 + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received INFORM from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.INFO, "received the following data={'data_name': 'data'}" + ) + + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state, + end_state_numbers, + ) in self.fipa_dialogues.dialogue_stats.self_initiated.items(): + if end_state == FipaDialogue.EndState.SUCCESSFUL: + assert end_state_numbers == 1 + else: + assert end_state_numbers == 0 + + def test_handle_inform_without_data(self): + """Test the _handle_inform method of the fipa handler where info has NO data.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.INFORM, + info={}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received INFORM from sender={COUNTERPARTY_NAME[-5:]}" + ) + + mock_logger.assert_any_call( + logging.INFO, f"received no data from sender={COUNTERPARTY_NAME[-5:]}" + ) + + def test_handle_invalid(self): + """Test the _handle_invalid method of the fipa handler.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle fipa message of performative={incoming_message.performative} in dialogue={fipa_dialogue}.", + ) + + def test_teardown(self): + """Test the teardown method of the fipa handler.""" + assert self.fipa_handler.teardown() is None + self.assert_quantity_in_outbox(0) + + +class TestGenericOefSearchHandler(BaseSkillTestCase): + """Test oef search handler of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.oef_search_handler = cast( + GenericOefSearchHandler, cls._skill.skill_context.handlers.oef_search + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.oef_dialogues = cast( + OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues + ) + cls.list_of_messages = ( + DialogueMessage( + OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} + ), + ) + + def test_setup(self): + """Test the setup method of the oef_search handler.""" + assert self.oef_search_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the oef_search handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=OefSearchMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid oef_search message={incoming_message}, unidentified dialogue.", + ) + + def test_handle_error(self): + """Test the _handle_error method of the oef_search handler.""" + # setup + oef_dialogue = self.prepare_skill_dialogue( + dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=oef_dialogue, + performative=OefSearchMessage.Performative.OEF_ERROR, + oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", + ) + + def test_handle_search_zero_agents(self): + """Test the _handle_search method of the oef_search handler.""" + # setup + oef_dialogue = self.prepare_skill_dialogue( + dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=oef_dialogue, + performative=OefSearchMessage.Performative.SEARCH_RESULT, + agents=tuple(), + agents_info=OefSearchMessage.AgentsInfo({}), + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"found no agents in dialogue={oef_dialogue}, continue searching.", + ) + + def test_handle_search(self): + """Test the _handle_search method of the oef_search handler.""" + # setup + self.strategy._max_negotiations = 3 + oef_dialogue = self.prepare_skill_dialogue( + dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], + ) + agents = ("agnt1", "agnt2") + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=oef_dialogue, + performative=OefSearchMessage.Performative.SEARCH_RESULT, + agents=agents, + agents_info=OefSearchMessage.AgentsInfo( + {"agent_1": {"key_1": "value_1"}, "agent_2": {"key_2": "value_2"}} + ), + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"found agents={list(agents)}, stopping search." + ) + + assert not self.strategy.is_searching + + self.assert_quantity_in_outbox(len(agents)) + for agent in agents: + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.CFP, + to=agent, + sender=self.skill.skill_context.agent_address, + target=0, + query=self.strategy.get_service_query(), + ) + assert has_attributes, error_str + mock_logger.assert_any_call(logging.INFO, f"sending CFP to agent={agent}") + + def test_handle_search_more_than_max_negotiation(self): + """Test the _handle_search method of the oef_search handler where number of agents is more than max_negotiation.""" + # setup + self.strategy._max_negotiations = 1 + oef_dialogue = self.prepare_skill_dialogue( + dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], + ) + agents = ("agnt1", "agnt2") + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=oef_dialogue, + performative=OefSearchMessage.Performative.SEARCH_RESULT, + agents=agents, + agents_info=OefSearchMessage.AgentsInfo( + {"agent_1": {"key_1": "value_1"}, "agent_2": {"key_2": "value_2"}} + ), + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"found agents={list(agents)}, stopping search." + ) + + assert not self.strategy.is_searching + + self.assert_quantity_in_outbox(self.strategy._max_negotiations) + for idx in range(0, self.strategy._max_negotiations): + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.CFP, + to=agents[idx], + sender=self.skill.skill_context.agent_address, + target=0, + query=self.strategy.get_service_query(), + ) + assert has_attributes, error_str + mock_logger.assert_any_call( + logging.INFO, f"sending CFP to agent={agents[idx]}" + ) + + def test_handle_invalid(self): + """Test the _handle_invalid method of the oef_search handler.""" + # setup + invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE + incoming_message = self.build_incoming_message( + message_type=OefSearchMessage, + dialogue_reference=("1", ""), + performative=invalid_performative, + service_description="some_service_description", + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", + ) + + def test_teardown(self): + """Test the teardown method of the oef_search handler.""" + assert self.oef_search_handler.teardown() is None + self.assert_quantity_in_outbox(0) + + +class TestGenericSigningHandler(BaseSkillTestCase): + """Test signing handler of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.signing_handler = cast( + GenericSigningHandler, cls._skill.skill_context.handlers.signing + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + cls.ledger_api_dialogues = cast( + LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues + ) + cls.signing_dialogues = cast( + SigningDialogues, cls._skill.skill_context.signing_dialogues + ) + cls.terms = Terms( + "some_ledger_id", + cls._skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + cls.list_of_fipa_messages = ( + DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage(FipaMessage.Performative.ACCEPT), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + {"info": {"address": "some_term_sender_address"}}, + ), + DialogueMessage( + FipaMessage.Performative.INFORM, + {"info": {"transaction_digest": "some_transaction_digest_body"}}, + ), + ) + cls.list_of_signing_messages = ( + DialogueMessage( + SigningMessage.Performative.SIGN_TRANSACTION, + { + "terms": cls.terms, + "raw_transaction": SigningMessage.RawTransaction( + "some_ledger_id", "some_body" + ), + }, + ), + ) + cls.list_of_ledger_api_messages = ( + DialogueMessage(LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {}), + DialogueMessage(LedgerApiMessage.Performative.RAW_TRANSACTION, {}), + DialogueMessage(LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, {}), + DialogueMessage(LedgerApiMessage.Performative.TRANSACTION_DIGEST, {}), + ) + + def test_setup(self): + """Test the setup method of the signing handler.""" + assert self.signing_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the signing handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=SigningMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=SigningMessage.Performative.ERROR, + error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_MESSAGE_SIGNING, + to=str(self.skill.skill_context.skill_id), + ) + + # operation + with patch.object(self.signing_handler.context.logger, "log") as mock_logger: + self.signing_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid signing message={incoming_message}, unidentified dialogue.", + ) + + def test_handle_signed_transaction_last_ledger_api_message_is_none(self,): + """Test the _handle_signed_transaction method of the signing handler.""" + # setup + signing_dialogue = cast( + SigningDialogue, + self.prepare_skill_dialogue( + dialogues=self.signing_dialogues, + messages=self.list_of_signing_messages[:1], + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], + ), + ) + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:2], + ), + ) + signing_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue + signing_dialogue.associated_fipa_dialogue.associated_ledger_api_dialogue._incoming_messages = ( + [] + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=signing_dialogue, + performative=SigningMessage.Performative.SIGNED_TRANSACTION, + signed_transaction=SigningMessage.SignedTransaction( + "some_ledger_id", "some_body" + ), + ) + + # operation + with pytest.raises( + ValueError, match="Could not retrieve last message in ledger api dialogue" + ): + with patch.object( + self.signing_handler.context.logger, "log" + ) as mock_logger: + self.signing_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") + + def test_handle_signed_transaction_last_ledger_api_message_is_not_none(self,): + """Test the _handle_signed_transaction method of the signing handler where the last ledger_api message is not None.""" + # setup + signing_counterparty = self.skill.skill_context.decision_maker_address + signing_dialogue = cast( + SigningDialogue, + self.prepare_skill_dialogue( + dialogues=self.signing_dialogues, + messages=self.list_of_signing_messages[:1], + counterparty=signing_counterparty, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], + ), + ) + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:2], + counterparty=LEDGER_API_ADDRESS, + ), + ) + signing_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.associated_ledger_api_dialogue = ledger_api_dialogue + incoming_message = cast( + SigningMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=signing_dialogue, + performative=SigningMessage.Performative.SIGNED_TRANSACTION, + signed_transaction=SigningMessage.SignedTransaction( + "some_ledger_id", "some_body" + ), + ), + ) + # operation + with patch.object(self.signing_handler.context.logger, "log") as mock_logger: + self.signing_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call(logging.INFO, "transaction signing was successful.") + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=LedgerApiMessage, + performative=LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, + to=LEDGER_API_ADDRESS, + sender=self.skill.skill_context.agent_address, + signed_transaction=incoming_message.signed_transaction, + ) + assert has_attributes, error_str + + mock_logger.assert_any_call(logging.INFO, "sending transaction to ledger.") + + def test_handle_error(self): + """Test the _handle_error method of the signing handler.""" + # setup + signing_counterparty = self.skill.skill_context.decision_maker_address + signing_dialogue = self.prepare_skill_dialogue( + dialogues=self.signing_dialogues, + messages=self.list_of_signing_messages[:1], + counterparty=signing_counterparty, + ) + incoming_message = cast( + SigningMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=signing_dialogue, + performative=SigningMessage.Performative.ERROR, + error_code=SigningMessage.ErrorCode.UNSUCCESSFUL_TRANSACTION_SIGNING, + ), + ) + + # operation + with patch.object(self.signing_handler.context.logger, "log") as mock_logger: + self.signing_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"transaction signing was not successful. Error_code={incoming_message.error_code} in dialogue={signing_dialogue}", + ) + + def test_handle_invalid(self): + """Test the _handle_invalid method of the signing handler.""" + # setup + invalid_performative = SigningMessage.Performative.SIGN_TRANSACTION + incoming_message = self.build_incoming_message( + message_type=SigningMessage, + dialogue_reference=("1", ""), + performative=invalid_performative, + terms=self.terms, + raw_transaction=SigningMessage.RawTransaction( + "some_ledger_id", "some_body" + ), + to=str(self.skill.skill_context.skill_id), + ) + + # operation + with patch.object(self.signing_handler.context.logger, "log") as mock_logger: + self.signing_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle signing message of performative={invalid_performative} in dialogue={self.signing_dialogues.get_dialogue(incoming_message)}.", + ) + + def test_teardown(self): + """Test the teardown method of the signing handler.""" + assert self.signing_handler.teardown() is None + self.assert_quantity_in_outbox(0) + + +class TestGenericLedgerApiHandler(BaseSkillTestCase): + """Test ledger_api handler of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.ledger_api_handler = cast( + GenericLedgerApiHandler, cls._skill.skill_context.handlers.ledger_api + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + cls.ledger_api_dialogues = cast( + LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues + ) + cls.terms = Terms( + "some_ledger_id", + cls._skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + cls.list_of_fipa_messages = ( + DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage(FipaMessage.Performative.ACCEPT), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + {"info": {"address": "some_term_sender_address"}}, + ), + DialogueMessage( + FipaMessage.Performative.INFORM, + {"info": {"transaction_digest": "some_transaction_digest_body"}}, + ), + ) + cls.raw_transaction = RawTransaction("some_ledger_id", "some_body") + cls.signed_transaction = SignedTransaction("some_ledger_id", "some_body") + cls.transaction_digest = TransactionDigest("some_ledger_id", "some_body") + cls.list_of_ledger_api_messages = ( + DialogueMessage( + LedgerApiMessage.Performative.GET_RAW_TRANSACTION, {"terms": cls.terms} + ), + DialogueMessage( + LedgerApiMessage.Performative.RAW_TRANSACTION, + {"raw_transaction": cls.raw_transaction}, + ), + DialogueMessage( + LedgerApiMessage.Performative.SEND_SIGNED_TRANSACTION, + {"signed_transaction": cls.signed_transaction}, + ), + DialogueMessage( + LedgerApiMessage.Performative.TRANSACTION_DIGEST, + {"transaction_digest": cls.transaction_digest}, + ), + ) + + def test_setup(self): + """Test the setup method of the ledger_api handler.""" + assert self.ledger_api_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_ledger_api_handler_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=LedgerApiMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=LedgerApiMessage.Performative.GET_BALANCE, + ledger_id="some_ledger_id", + address="some_address", + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", + ) + + def test_ledger_api_handler_handle_balance_positive_balance(self): + """Test the _handle_balance method of the ledger_api handler where balance is positive.""" + # setup + balance = 10 + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=( + DialogueMessage( + LedgerApiMessage.Performative.GET_BALANCE, + {"ledger_id": "some_ledger_id", "address": "some_address"}, + ), + ), + counterparty=LEDGER_API_ADDRESS, + ), + ) + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.BALANCE, + ledger_id="some-Ledger_id", + balance=balance, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"starting balance on {self.strategy.ledger_id} ledger={incoming_message.balance}.", + ) + assert self.strategy.balance == balance + assert self.strategy.is_searching + + def test_ledger_api_handler_handle_balance_zero_balance(self): + """Test the _handle_balance method of the ledger_api handler where balance is zero.""" + # setup + balance = 0 + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=( + DialogueMessage( + LedgerApiMessage.Performative.GET_BALANCE, + {"ledger_id": "some_ledger_id", "address": "some_address"}, + ), + ), + counterparty=LEDGER_API_ADDRESS, + ), + ) + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.BALANCE, + ledger_id="some-Ledger_id", + balance=balance, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"you have no starting balance on {self.strategy.ledger_id} ledger!", + ) + assert not self.skill.skill_context.is_active + + def test_ledger_api_handler_handle_raw_transaction(self): + """Test the _handle_raw_transaction method of the ledger_api handler.""" + # setup + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:1], + counterparty=LEDGER_API_ADDRESS, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], + ), + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.terms = self.terms + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.RAW_TRANSACTION, + raw_transaction=self.raw_transaction, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received raw transaction={incoming_message}" + ) + + message_quantity = self.get_quantity_in_decision_maker_inbox() + assert ( + message_quantity == 1 + ), f"Invalid number of messages in decision maker queue. Expected {1}. Found {message_quantity}." + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_decision_maker_inbox(), + message_type=SigningMessage, + performative=SigningMessage.Performative.SIGN_TRANSACTION, + to=self.skill.skill_context.decision_maker_address, + sender=str(self.skill.skill_context.skill_id), + terms=self.terms, + ) + assert has_attributes, error_str + + mock_logger.assert_any_call( + logging.INFO, + "proposing the transaction to the decision maker. Waiting for confirmation ...", + ) + + def test_ledger_api_handler_handle_transaction_digest_last_fipa_message_is_none( + self, + ): + """Test the _handle_transaction_digest method of the ledger_api handler where the last incoming fipa message os None.""" + # setup + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:3], + counterparty=LEDGER_API_ADDRESS, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], + ), + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + ledger_api_dialogue.associated_fipa_dialogue._incoming_messages = [] + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, + transaction_digest=self.transaction_digest, + ), + ) + + # operation + with pytest.raises(ValueError, match="Could not retrieve fipa message"): + with patch.object( + self.ledger_api_handler.context.logger, "log" + ) as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", + ) + + def test_ledger_api_handler_handle_transaction_digest(self): + """Test the _handle_transaction_digest method of the ledger_api handler.""" + # setup + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:3], + counterparty=LEDGER_API_ADDRESS, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:4], + ), + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.terms = self.terms + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.TRANSACTION_DIGEST, + transaction_digest=self.transaction_digest, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"transaction was successfully submitted. Transaction digest={incoming_message.transaction_digest}", + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.INFORM, + to=COUNTERPARTY_NAME, + sender=self.skill.skill_context.agent_address, + info={"transaction_digest": incoming_message.transaction_digest.body}, + ) + assert has_attributes, error_str + + mock_logger.assert_any_call( + logging.INFO, + f"informing counterparty={COUNTERPARTY_NAME[-5:]} of transaction digest.", + ) + + def test_ledger_api_handler_handle_error(self): + """Test the _handle_error method of the ledger_api handler.""" + # setup + ledger_api_dialogue = self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:1], + ) + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.ERROR, + code=1, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", + ) + + def test_ledger_api_handler_handle_invalid(self): + """Test the _handle_invalid method of the ledger_api handler.""" + # setup + invalid_performative = LedgerApiMessage.Performative.GET_BALANCE + incoming_message = self.build_incoming_message( + message_type=LedgerApiMessage, + dialogue_reference=("1", ""), + performative=invalid_performative, + ledger_id="some_ledger_id", + address="some_address", + to=self.skill.skill_context.agent_address, + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", + ) + + def test_teardown(self): + """Test the teardown method of the ledger_api handler.""" + assert self.ledger_api_handler.teardown() is None + self.assert_quantity_in_outbox(0) diff --git a/tests/test_packages/test_skills/test_generic_buyer/test_models.py b/tests/test_packages/test_skills/test_generic_buyer/test_models.py new file mode 100644 index 0000000000..462e4af174 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_buyer/test_models.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the strategy class of the generic buyer skill.""" + +from pathlib import Path + +from aea.configurations.constants import DEFAULT_LEDGER +from aea.helpers.search.models import Constraint, ConstraintType, Description, Query +from aea.helpers.transaction.base import Terms +from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_NAME + +from packages.fetchai.skills.generic_buyer.strategy import ( + GenericStrategy, + SIMPLE_SERVICE_MODEL, +) + +from tests.conftest import ROOT_DIR + + +class TestGenericStrategy(BaseSkillTestCase): + """Test GenericStrategy of generic buyer.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_buyer") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.ledger_id = DEFAULT_LEDGER + cls.is_ledger_tx = True + cls.currency_id = "some_currency_id" + cls.max_unit_price = 20 + cls.max_tx_fee = 1 + cls.service_id = "some_service_id" + cls.search_query = { + "constraint_type": "==", + "search_key": "seller_service", + "search_value": "some_search_value", + } + cls.location = { + "longitude": 51.5194, + "latitude": 0.127, + } + cls.search_radius = 5.0 + cls.max_negotiations = 2 + cls.strategy = GenericStrategy( + ledger_id=cls.ledger_id, + is_ledger_tx=cls.is_ledger_tx, + currency_id=cls.currency_id, + max_unit_price=cls.max_unit_price, + max_tx_fee=cls.max_tx_fee, + service_id=cls.service_id, + search_query=cls.search_query, + location=cls.location, + search_radius=cls.search_radius, + max_negotiations=cls.max_negotiations, + name="strategy", + skill_context=cls._skill.skill_context, + ) + + def test_properties(self): + """Test the properties of GenericStrategy class.""" + assert self.strategy.ledger_id == self.ledger_id + assert self.strategy.is_ledger_tx == self.is_ledger_tx + assert self.strategy.is_searching is False + + self.strategy.is_searching = True + assert self.strategy.is_searching is True + + assert self.strategy.balance == 0 + + self.strategy.balance = 100 + assert self.strategy.balance == 100 + + assert self.strategy.max_negotiations is self.max_negotiations + + def test_get_location_and_service_query(self): + """Test the get_location_and_service_query method of the GenericStrategy class.""" + query = self.strategy.get_location_and_service_query() + + assert type(query) == Query + assert len(query.constraints) == 2 + assert query.model is None + + location_constraint = Constraint( + "location", + ConstraintType( + "distance", (self.strategy._agent_location, self.search_radius) + ), + ) + assert query.constraints[0] == location_constraint + + service_key_constraint = Constraint( + self.search_query["search_key"], + ConstraintType( + self.search_query["constraint_type"], self.search_query["search_value"], + ), + ) + assert query.constraints[1] == service_key_constraint + + def test_get_service_query(self): + """Test the get_service_query method of the GenericStrategy class.""" + query = self.strategy.get_service_query() + + assert type(query) == Query + assert len(query.constraints) == 1 + + assert query.model == SIMPLE_SERVICE_MODEL + + service_key_constraint = Constraint( + self.search_query["search_key"], + ConstraintType( + self.search_query["constraint_type"], self.search_query["search_value"], + ), + ) + assert query.constraints[0] == service_key_constraint + + def test_is_acceptable_proposal(self): + """Test the is_acceptable_proposal method of the GenericStrategy class.""" + acceptable_description = Description( + { + "ledger_id": self.ledger_id, + "price": 150, + "currency_id": self.currency_id, + "service_id": self.service_id, + "quantity": 10, + "tx_nonce": "some_tx_nonce", + } + ) + is_acceptable = self.strategy.is_acceptable_proposal(acceptable_description) + assert is_acceptable + + unacceptable_description = Description( + { + "ledger_id": self.ledger_id, + "price": 250, + "currency_id": self.currency_id, + "service_id": self.service_id, + "quantity": 10, + "tx_nonce": "some_tx_nonce", + } + ) + is_acceptable = self.strategy.is_acceptable_proposal(unacceptable_description) + assert not is_acceptable + + def test_is_affordable_proposal(self): + """Test the is_affordable_proposal method of the GenericStrategy class.""" + description = Description( + { + "ledger_id": self.ledger_id, + "price": 150, + "currency_id": self.currency_id, + "service_id": self.service_id, + "quantity": 10, + "tx_nonce": "some_tx_nonce", + } + ) + self.strategy.balance = 151 + is_affordable = self.strategy.is_affordable_proposal(description) + assert is_affordable + + self.strategy.balance = 150 + is_affordable = self.strategy.is_affordable_proposal(description) + assert not is_affordable + + self.strategy._is_ledger_tx = False + is_affordable = self.strategy.is_affordable_proposal(description) + assert is_affordable + + def test_terms_from_proposal(self): + """Test the terms_from_proposal method of the GenericStrategy class.""" + description = Description( + { + "ledger_id": self.ledger_id, + "price": 150, + "currency_id": self.currency_id, + "service_id": self.service_id, + "quantity": 10, + "tx_nonce": "some_tx_nonce", + } + ) + terms = Terms( + ledger_id=self.ledger_id, + sender_address=self.skill.skill_context.agent_address, + counterparty_address=COUNTERPARTY_NAME, + amount_by_currency_id={self.currency_id: -150}, + quantities_by_good_id={self.service_id: 10}, + is_sender_payable_tx_fee=True, + nonce="some_tx_nonce", + fee_by_currency_id={self.currency_id: self.max_tx_fee}, + ) + assert ( + self.strategy.terms_from_proposal(description, COUNTERPARTY_NAME) == terms + ) diff --git a/tests/test_packages/test_skills/test_generic_seller/__init__.py b/tests/test_packages/test_skills/test_generic_seller/__init__.py new file mode 100644 index 0000000000..9518e3593f --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_seller/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""The tests module contains the tests of the packages/skills/generic_seller dir.""" diff --git a/tests/test_packages/test_skills/test_generic_seller/test_behaviours.py b/tests/test_packages/test_skills/test_generic_seller/test_behaviours.py new file mode 100644 index 0000000000..a2b763f438 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_seller/test_behaviours.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the behaviour classes of the generic seller skill.""" + +import logging +from pathlib import Path +from typing import cast +from unittest.mock import patch + +from aea.test_tools.test_skill import BaseSkillTestCase + +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.generic_seller.behaviours import ( + GenericServiceRegistrationBehaviour, + LEDGER_API_ADDRESS, +) +from packages.fetchai.skills.generic_seller.strategy import GenericStrategy + +from tests.conftest import ROOT_DIR + + +class TestSkillBehaviour(BaseSkillTestCase): + """Test behaviours of generic seller.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.service_registration = cast( + GenericServiceRegistrationBehaviour, + cls._skill.skill_context.behaviours.service_registration, + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + + def _assert_oef_message_and_logging_output( + self, service_description, mocked_logger, logger_message + ): + """ + Check there is a specific OEFMessage in the outbox and the mocked logger is called with a specific message. + + This method is reused a few times in the following tests and its purpose is to avoid code duplication. + + :param service_description: the service description + :param mocked_logger: the mocked logger + :param logger_message: the logger message + + :return: None + """ + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=OefSearchMessage, + performative=OefSearchMessage.Performative.REGISTER_SERVICE, + to=self.skill.skill_context.search_service_address, + sender=self.skill.skill_context.agent_address, + service_description=service_description, + ) + assert has_attributes, error_str + mocked_logger.assert_any_call(logging.INFO, logger_message) + + def test_setup_is_ledger_tx(self): + """Test the setup method of the service_registration behaviour where is_ledger_tx is True.""" + # setup + self.strategy._is_ledger_tx = True + mocked_description_1 = "some_description_1" + mocked_description_2 = "some_description_2" + + # operation + with patch.object( + self.strategy, + "get_location_description", + return_value=mocked_description_1, + ): + with patch.object( + self.strategy, + "get_register_service_description", + return_value=mocked_description_2, + ): + with patch.object( + self.service_registration.context.logger, "log" + ) as mock_logger: + self.service_registration.setup() + + # after + self.assert_quantity_in_outbox(3) + + # message 1 + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=LedgerApiMessage, + performative=LedgerApiMessage.Performative.GET_BALANCE, + to=LEDGER_API_ADDRESS, + sender=self.skill.skill_context.agent_address, + ledger_id=self.strategy.ledger_id, + address=self.skill.skill_context.agent_address, + ) + assert has_attributes, error_str + + # message 2 + self._assert_oef_message_and_logging_output( + mocked_description_1, mock_logger, "registering agent on SOEF.", + ) + + # message 3 + self._assert_oef_message_and_logging_output( + mocked_description_2, mock_logger, "registering service on SOEF.", + ) + + def test_setup_not_is_ledger_tx(self): + """Test the setup method of the service_registration behaviour: where is_ledger_tx is False.""" + # setup + self.strategy._is_ledger_tx = False + mocked_description_1 = "some_description_1" + mocked_description_2 = "some_description_2" + + # operation + with patch.object( + self.strategy, + "get_location_description", + return_value=mocked_description_1, + ): + with patch.object( + self.strategy, + "get_register_service_description", + return_value=mocked_description_2, + ): + with patch.object( + self.service_registration.context.logger, "log" + ) as mock_logger: + self.service_registration.setup() + + # after + self.assert_quantity_in_outbox(2) + + # message 1 + self._assert_oef_message_and_logging_output( + mocked_description_1, mock_logger, "registering agent on SOEF.", + ) + + # message 2 + self._assert_oef_message_and_logging_output( + mocked_description_2, mock_logger, "registering service on SOEF.", + ) + + def test_act(self): + """Test the act method of the service_registration behaviour.""" + assert self.service_registration.act() is None + self.assert_quantity_in_outbox(0) + + def test_teardown(self): + """Test the teardown method of the service_registration behaviour.""" + # setup + mocked_description_1 = "some_description_1" + mocked_description_2 = "some_description_2" + + # operation + with patch.object( + self.strategy, + "get_unregister_service_description", + return_value=mocked_description_1, + ): + with patch.object( + self.strategy, + "get_location_description", + return_value=mocked_description_2, + ): + with patch.object( + self.service_registration.context.logger, "log" + ) as mock_logger: + self.service_registration.teardown() + + # after + self.assert_quantity_in_outbox(2) + + # message 1 + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=OefSearchMessage, + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + to=self.skill.skill_context.search_service_address, + sender=self.skill.skill_context.agent_address, + service_description=mocked_description_1, + ) + assert has_attributes, error_str + mock_logger.assert_any_call(logging.INFO, "unregistering service from SOEF.") + + # message 2 + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=OefSearchMessage, + performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, + to=self.skill.skill_context.search_service_address, + sender=self.skill.skill_context.agent_address, + service_description=mocked_description_2, + ) + assert has_attributes, error_str + mock_logger.assert_any_call(logging.INFO, "unregistering agent from SOEF.") diff --git a/tests/test_packages/test_skills/test_generic_seller/test_dialogues.py b/tests/test_packages/test_skills/test_generic_seller/test_dialogues.py new file mode 100644 index 0000000000..a607501e19 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_seller/test_dialogues.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the dialogue classes of the generic seller skill.""" + +from pathlib import Path +from typing import cast + +import pytest + +from aea.exceptions import AEAEnforceError +from aea.helpers.transaction.base import Terms +from aea.protocols.default.message import DefaultMessage +from aea.protocols.dialogue.base import DialogueLabel +from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_NAME + +from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.generic_seller.dialogues import ( + DefaultDialogue, + DefaultDialogues, + FipaDialogue, + FipaDialogues, + LedgerApiDialogue, + LedgerApiDialogues, + OefSearchDialogue, + OefSearchDialogues, +) + +from tests.conftest import ROOT_DIR + + +class TestDialogues(BaseSkillTestCase): + """Test dialogue classes of generic seller.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.default_dialogues = cast( + DefaultDialogues, cls._skill.skill_context.default_dialogues + ) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + cls.ledger_api_dialogues = cast( + LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues + ) + cls.oef_search_dialogues = cast( + OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues + ) + + def test_default_dialogues(self): + """Test the DefaultDialogues class.""" + _, dialogue = self.default_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=DefaultMessage.Performative.BYTES, + content=b"some_content", + ) + assert dialogue.role == DefaultDialogue.Role.AGENT + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_fipa_dialogue(self): + """Test the FipaDialogue class.""" + fipa_dialogue = FipaDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=DefaultDialogue.Role.AGENT, + ) + + # terms + with pytest.raises(AEAEnforceError, match="Terms not set!"): + assert fipa_dialogue.terms + terms = Terms( + "some_ledger_id", + self.skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + fipa_dialogue.terms = terms + with pytest.raises(AEAEnforceError, match="Terms already set!"): + fipa_dialogue.terms = terms + assert fipa_dialogue.terms == terms + + def test_fipa_dialogues(self): + """Test the FipaDialogues class.""" + _, dialogue = self.fipa_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=FipaMessage.Performative.CFP, + query="some_query", + ) + assert dialogue.role == FipaDialogue.Role.SELLER + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_ledger_api_dialogue(self): + """Test the LedgerApiDialogue class.""" + ledger_api_dialogue = LedgerApiDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=LedgerApiDialogue.Role.AGENT, + ) + + # associated_fipa_dialogue + with pytest.raises(AEAEnforceError, match="FipaDialogue not set!"): + assert ledger_api_dialogue.associated_fipa_dialogue + fipa_dialogue = FipaDialogue( + DialogueLabel( + ("", ""), COUNTERPARTY_NAME, self.skill.skill_context.agent_address, + ), + self.skill.skill_context.agent_address, + role=FipaDialogue.Role.BUYER, + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + with pytest.raises(AEAEnforceError, match="FipaDialogue already set!"): + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + assert ledger_api_dialogue.associated_fipa_dialogue == fipa_dialogue + + def test_ledger_api_dialogues(self): + """Test the LedgerApiDialogues class.""" + _, dialogue = self.ledger_api_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=LedgerApiMessage.Performative.GET_BALANCE, + ledger_id="some_ledger_id", + address="some_address", + ) + assert dialogue.role == LedgerApiDialogue.Role.AGENT + assert dialogue.self_address == self.skill.skill_context.agent_address + + def test_oef_search_dialogues(self): + """Test the OefSearchDialogues class.""" + _, dialogue = self.oef_search_dialogues.create( + counterparty=COUNTERPARTY_NAME, + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + ledger_id="some_ledger_id", + query="some_query", + ) + assert dialogue.role == OefSearchDialogue.Role.AGENT + assert dialogue.self_address == self.skill.skill_context.agent_address diff --git a/tests/test_packages/test_skills/test_generic_seller/test_handlers.py b/tests/test_packages/test_skills/test_generic_seller/test_handlers.py new file mode 100644 index 0000000000..a306f5469b --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_seller/test_handlers.py @@ -0,0 +1,979 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the handler classes of the generic seller skill.""" + +import logging +from pathlib import Path +from typing import cast +from unittest.mock import patch + +import pytest + +import aea +from aea.helpers.search.models import Description +from aea.helpers.transaction.base import Terms, TransactionDigest, TransactionReceipt +from aea.protocols.default.message import DefaultMessage +from aea.protocols.dialogue.base import DialogueMessage, Dialogues +from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_NAME + +from packages.fetchai.protocols.fipa.message import FipaMessage +from packages.fetchai.protocols.ledger_api.message import LedgerApiMessage +from packages.fetchai.protocols.oef_search.message import OefSearchMessage +from packages.fetchai.skills.generic_seller.dialogues import ( + FipaDialogue, + FipaDialogues, + LedgerApiDialogue, + LedgerApiDialogues, + OefSearchDialogues, +) +from packages.fetchai.skills.generic_seller.handlers import ( + GenericFipaHandler, + GenericLedgerApiHandler, + GenericOefSearchHandler, + LEDGER_API_ADDRESS, +) +from packages.fetchai.skills.generic_seller.strategy import GenericStrategy + +from tests.conftest import ROOT_DIR + + +class TestGenericFipaHandler(BaseSkillTestCase): + """Test fipa handler of generic seller.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.fipa_handler = cast( + GenericFipaHandler, cls._skill.skill_context.handlers.fipa + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + + cls.list_of_messages = ( + DialogueMessage( + FipaMessage.Performative.CFP, {"query": "some_query"}, True + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage(FipaMessage.Performative.ACCEPT), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + {"info": {"address": "some_term_sender_address"}}, + ), + DialogueMessage( + FipaMessage.Performative.INFORM, + {"info": {"transaction_digest": "some_transaction_digest_body"}}, + ), + ) + + def test_setup(self): + """Test the setup method of the fipa handler.""" + assert self.fipa_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the fipa handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=FipaMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=FipaMessage.Performative.ACCEPT, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid fipa message={incoming_message}, unidentified dialogue.", + ) + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=DefaultMessage, + performative=DefaultMessage.Performative.ERROR, + to=incoming_message.sender, + sender=self.skill.skill_context.agent_address, + error_code=DefaultMessage.ErrorCode.INVALID_DIALOGUE, + error_msg="Invalid dialogue.", + error_data={"fipa_message": incoming_message.encode()}, + ) + assert has_attributes, error_str + + def test_handle_cfp_is_matching_supply(self): + """Test the _handle_cfp method of the fipa handler where is_matching_supply is True.""" + # setup + proposal = Description( + { + "ledger_id": "some_ledger_id", + "price": 100, + "currency_id": "FET", + "service_id": "some_service_id", + "quantity": 1, + "tx_nonce": "some_tx_nonce", + } + ) + terms = "some_terms" + data = {"data_type": "data"} + + incoming_message = self.build_incoming_message( + message_type=FipaMessage, + performative=FipaMessage.Performative.CFP, + dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), + query="some_query", + ) + + # operation + with patch.object( + self.strategy, "is_matching_supply", return_value=True, + ): + with patch.object( + self.strategy, + "generate_proposal_terms_and_data", + return_value=(proposal, terms, data), + ): + with patch.object( + self.fipa_handler.context.logger, "log" + ) as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received CFP from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.INFO, + f"sending a PROPOSE with proposal={proposal.values} to sender={COUNTERPARTY_NAME[-5:]}", + ) + + self.assert_quantity_in_outbox(1) + + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.PROPOSE, + to=COUNTERPARTY_NAME, + sender=self.skill.skill_context.agent_address, + target=incoming_message.message_id, + proposal=proposal, + ) + assert has_attributes, error_str + + def test_handle_cfp_not_is_matching_supply(self): + """Test the _handle_cfp method of the fipa handler where is_matching_supply is False.""" + # setup + incoming_message = self.build_incoming_message( + message_type=FipaMessage, + performative=FipaMessage.Performative.CFP, + dialogue_reference=Dialogues.new_self_initiated_dialogue_reference(), + query="some_query", + ) + + # operation + with patch.object(self.strategy, "is_matching_supply", return_value=False): + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received CFP from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.INFO, f"declined the CFP from sender={COUNTERPARTY_NAME[-5:]}" + ) + + self.assert_quantity_in_outbox(1) + + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.DECLINE, + to=COUNTERPARTY_NAME, + sender=self.skill.skill_context.agent_address, + target=incoming_message.message_id, + ) + assert has_attributes, error_str + + def test_handle_decline(self): + """Test the _handle_decline method of the fipa handler.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, performative=FipaMessage.Performative.DECLINE, + ) + + # before + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): + assert end_state_numbers == 0 + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received DECLINE from sender={COUNTERPARTY_NAME[-5:]}" + ) + + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state, + end_state_numbers, + ) in self.fipa_dialogues.dialogue_stats.other_initiated.items(): + if end_state == FipaDialogue.EndState.DECLINED_PROPOSE: + assert end_state_numbers == 1 + else: + assert end_state_numbers == 0 + + def test_handle_accept(self): + """Test the _handle_accept method of the fipa handler.""" + # setup + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], + ), + ) + fipa_dialogue.terms = Terms( + "some_ledger_id", + self.skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, performative=FipaMessage.Performative.ACCEPT, + ) + info = {"address": fipa_dialogue.terms.sender_address} + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received ACCEPT from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.INFO, + f"sending MATCH_ACCEPT_W_INFORM to sender={COUNTERPARTY_NAME[-5:]} with info={info}", + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + to=COUNTERPARTY_NAME, + sender=self.skill.skill_context.agent_address, + info=info, + ) + assert has_attributes, error_str + + def test_handle_inform_is_ledger_tx_and_with_tx_digest(self): + """Test the _handle_inform method of the fipa handler where is_ledger_tx is True and info contains transaction_digest.""" + # setup + self.strategy._is_ledger_tx = True + tx_digest = "some_transaction_digest_body" + ledger_id = "some_ledger_id" + + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], + ) + fipa_dialogue.terms = Terms( + ledger_id, + self.skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.INFORM, + info={"transaction_digest": tx_digest}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + incoming_message = cast(FipaMessage, incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received INFORM from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.INFO, + f"checking whether transaction={incoming_message.info['transaction_digest']} has been received ...", + ) + + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=LedgerApiMessage, + performative=LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, + to=LEDGER_API_ADDRESS, + sender=self.skill.skill_context.agent_address, + target=0, + transaction_digest=TransactionDigest(ledger_id, tx_digest), + ) + assert has_attributes, error_str + + def test_handle_inform_is_ledger_tx_and_no_tx_digest(self): + """Test the _handle_inform method of the fipa handler where is_ledger_tx is True and info does not have a transaction_digest.""" + # setup + self.strategy._is_ledger_tx = True + + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], + ) + fipa_dialogue.terms = Terms( + "some_ledger_id", + self.skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.INFORM, + info={}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received INFORM from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.WARNING, + f"did not receive transaction digest from sender={COUNTERPARTY_NAME[-5:]}.", + ) + + def test_handle_inform_not_is_ledger_tx_and_with_done(self): + """Test the _handle_inform method of the fipa handler where is_ledger_tx is False and info contains done.""" + # setup + self.strategy._is_ledger_tx = False + data = { + "data_type_1": "data_1", + "data_type_2": "data_2", + } + + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], + ), + ) + fipa_dialogue.data_for_sale = data + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.INFORM, + info={"Done": "Sending payment via bank transfer"}, + ) + + # before + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.other_initiated.values(): + assert end_state_numbers == 0 + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received INFORM from sender={COUNTERPARTY_NAME[-5:]}" + ) + + # check outgoing message + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.INFORM, + to=COUNTERPARTY_NAME, + sender=self.skill.skill_context.agent_address, + target=incoming_message.message_id, + info=fipa_dialogue.data_for_sale, + ) + assert has_attributes, error_str + + # check updated end_state + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state, + end_state_numbers, + ) in self.fipa_dialogues.dialogue_stats.other_initiated.items(): + if end_state == FipaDialogue.EndState.SUCCESSFUL: + assert end_state_numbers == 1 + else: + assert end_state_numbers == 0 + + # check logger output + mock_logger.assert_any_call( + logging.INFO, + f"transaction confirmed, sending data={data} to buyer={COUNTERPARTY_NAME[-5:]}.", + ) + + def test_handle_inform_not_is_ledger_tx_and_nothin_in_info(self): + """Test the _handle_inform method of the fipa handler where is_ledger_tx is False and info does not contain done or transaction_digest.""" + # setup + self.strategy._is_ledger_tx = False + + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:4], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.INFORM, + info={}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, f"received INFORM from sender={COUNTERPARTY_NAME[-5:]}" + ) + mock_logger.assert_any_call( + logging.WARNING, + f"did not receive transaction confirmation from sender={COUNTERPARTY_NAME[-5:]}.", + ) + + def test_handle_invalid(self): + """Test the _handle_invalid method of the fipa handler.""" + # setup + fipa_dialogue = self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_messages[:2], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=fipa_dialogue, + performative=FipaMessage.Performative.ACCEPT_W_INFORM, + info={}, + ) + + # operation + with patch.object(self.fipa_handler.context.logger, "log") as mock_logger: + self.fipa_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle fipa message of performative={incoming_message.performative} in dialogue={fipa_dialogue}.", + ) + + def test_teardown(self): + """Test the teardown method of the fipa handler.""" + assert self.fipa_handler.teardown() is None + self.assert_quantity_in_outbox(0) + + +class TestGenericLedgerApiHandler(BaseSkillTestCase): + """Test ledger_api handler of generic seller.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.ledger_api_handler = cast( + GenericLedgerApiHandler, cls._skill.skill_context.handlers.ledger_api + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.fipa_dialogues = cast( + FipaDialogues, cls._skill.skill_context.fipa_dialogues + ) + cls.ledger_api_dialogues = cast( + LedgerApiDialogues, cls._skill.skill_context.ledger_api_dialogues + ) + cls.terms = Terms( + "some_ledger_id", + cls._skill.skill_context.agent_address, + "counterprty", + {"currency_id": 50}, + {"good_id": -10}, + "some_nonce", + ) + cls.list_of_fipa_messages = ( + DialogueMessage( + FipaMessage.Performative.CFP, {"query": "some_query"}, True + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage(FipaMessage.Performative.ACCEPT), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, + {"info": {"address": "some_term_sender_address"}}, + ), + DialogueMessage( + FipaMessage.Performative.INFORM, + {"info": {"transaction_digest": "some_transaction_digest_body"}}, + ), + ) + cls.transaction_digest = TransactionDigest("some_ledger_id", "some_body") + cls.transaction_receipt = TransactionReceipt( + "some_ledger_id", "some_receipt", "some_transaction" + ) + cls.list_of_ledger_api_messages = ( + DialogueMessage( + LedgerApiMessage.Performative.GET_TRANSACTION_RECEIPT, + {"transaction_digest": cls.transaction_digest}, + ), + DialogueMessage( + LedgerApiMessage.Performative.TRANSACTION_RECEIPT, + {"transaction_receipt": cls.transaction_receipt}, + ), + ) + + def test_setup(self): + """Test the setup method of the ledger_api handler.""" + assert self.ledger_api_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the ledger_api handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=LedgerApiMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=LedgerApiMessage.Performative.GET_BALANCE, + ledger_id="some_ledger_id", + address="some_address", + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid ledger_api message={incoming_message}, unidentified dialogue.", + ) + + def test_handle_balance(self): + """Test the _handle_balance method of the ledger_api handler.""" + # setup + ledger_id = "some_Ledger_id" + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=( + DialogueMessage( + LedgerApiMessage.Performative.GET_BALANCE, + {"ledger_id": "some_ledger_id", "address": "some_address"}, + ), + ), + counterparty=LEDGER_API_ADDRESS, + ), + ) + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.BALANCE, + ledger_id=ledger_id, + balance=10, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"starting balance on {ledger_id} ledger={incoming_message.balance}.", + ) + + def test_handle_transaction_receipt_is_settled_and_is_valid_last_incoming_fipa_message_is_none( + self, + ): + """Test the _handle_transaction_receipt method of the ledger_api handler where is_settled and is_valid are True and the last incoming FipaMessage is None.""" + # setup + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:1], + counterparty=LEDGER_API_ADDRESS, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:5], + ), + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.terms = self.terms + fipa_dialogue.data_for_sale = {"data_type_1": "data_1"} + fipa_dialogue._incoming_messages = [] + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, + transaction_receipt=self.transaction_receipt, + ), + ) + + # operation + with patch.object( + aea.crypto.ledger_apis.LedgerApis, + "is_transaction_settled", + return_value=True, + ): + with patch.object( + aea.crypto.ledger_apis.LedgerApis, + "is_transaction_valid", + return_value=True, + ): + with pytest.raises( + ValueError, match="Cannot retrieve last fipa message." + ): + self.ledger_api_handler.handle(incoming_message) + + def test_handle_transaction_receipt_is_settled_and_is_valid(self,): + """Test the _handle_transaction_receipt method of the ledger_api handler where is_settled and is_valid are True.""" + # setup + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:1], + counterparty=LEDGER_API_ADDRESS, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:5], + ), + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.terms = self.terms + fipa_dialogue.data_for_sale = {"data_type_1": "data_1"} + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, + transaction_receipt=self.transaction_receipt, + ), + ) + + # before + for end_state_numbers in list( + self.fipa_dialogues.dialogue_stats.self_initiated.values() + ) + list(self.fipa_dialogues.dialogue_stats.other_initiated.values()): + assert end_state_numbers == 0 + + # operation + with patch.object( + aea.crypto.ledger_apis.LedgerApis, + "is_transaction_settled", + return_value=True, + ): + with patch.object( + aea.crypto.ledger_apis.LedgerApis, + "is_transaction_valid", + return_value=True, + ): + with patch.object( + self.ledger_api_handler.context.logger, "log" + ) as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + + # check outgoing message + self.assert_quantity_in_outbox(1) + has_attributes, error_str = self.message_has_attributes( + actual_message=self.get_message_from_outbox(), + message_type=FipaMessage, + performative=FipaMessage.Performative.INFORM, + to=COUNTERPARTY_NAME, + sender=self.skill.skill_context.agent_address, + target=fipa_dialogue.last_incoming_message.message_id, + info=fipa_dialogue.data_for_sale, + ) + assert has_attributes, error_str + + # check updated end_state + for ( + end_state_numbers + ) in self.fipa_dialogues.dialogue_stats.self_initiated.values(): + assert end_state_numbers == 0 + for ( + end_state, + end_state_numbers, + ) in self.fipa_dialogues.dialogue_stats.other_initiated.items(): + if end_state == FipaDialogue.EndState.SUCCESSFUL: + assert end_state_numbers == 1 + else: + assert end_state_numbers == 0 + + # check logger output + mock_logger.assert_any_call( + logging.INFO, + f"transaction confirmed, sending data={fipa_dialogue.data_for_sale} to buyer={COUNTERPARTY_NAME[-5:]}.", + ) + + def test_handle_transaction_receipt_not_is_settled_or_not_is_valid(self,): + """Test the _handle_transaction_receipt method of the ledger_api handler where is_settled or is_valid is False.""" + # setup + ledger_api_dialogue = cast( + LedgerApiDialogue, + self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:1], + counterparty=LEDGER_API_ADDRESS, + ), + ) + fipa_dialogue = cast( + FipaDialogue, + self.prepare_skill_dialogue( + dialogues=self.fipa_dialogues, messages=self.list_of_fipa_messages[:5], + ), + ) + ledger_api_dialogue.associated_fipa_dialogue = fipa_dialogue + fipa_dialogue.terms = self.terms + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.TRANSACTION_RECEIPT, + transaction_receipt=self.transaction_receipt, + ), + ) + + # operation + with patch.object( + aea.crypto.ledger_apis.LedgerApis, + "is_transaction_settled", + return_value=True, + ): + with patch.object( + aea.crypto.ledger_apis.LedgerApis, + "is_transaction_valid", + return_value=False, + ): + with patch.object( + self.ledger_api_handler.context.logger, "log" + ) as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"transaction_receipt={self.transaction_receipt} not settled or not valid, aborting", + ) + + def test_handle_error(self): + """Test the _handle_error method of the ledger_api handler.""" + # setup + ledger_api_dialogue = self.prepare_skill_dialogue( + dialogues=self.ledger_api_dialogues, + messages=self.list_of_ledger_api_messages[:1], + ) + incoming_message = cast( + LedgerApiMessage, + self.build_incoming_message_for_skill_dialogue( + dialogue=ledger_api_dialogue, + performative=LedgerApiMessage.Performative.ERROR, + code=1, + ), + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received ledger_api error message={incoming_message} in dialogue={ledger_api_dialogue}.", + ) + + def test_handle_invalid(self): + """Test the _handle_invalid method of the ledger_api handler.""" + # setup + invalid_performative = LedgerApiMessage.Performative.GET_BALANCE + incoming_message = self.build_incoming_message( + message_type=LedgerApiMessage, + dialogue_reference=("1", ""), + performative=invalid_performative, + ledger_id="some_ledger_id", + address="some_address", + to=self.skill.skill_context.agent_address, + ) + + # operation + with patch.object(self.ledger_api_handler.context.logger, "log") as mock_logger: + self.ledger_api_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle ledger_api message of performative={invalid_performative} in dialogue={self.ledger_api_dialogues.get_dialogue(incoming_message)}.", + ) + + def test_teardown(self): + """Test the teardown method of the ledger_api handler.""" + assert self.ledger_api_handler.teardown() is None + self.assert_quantity_in_outbox(0) + + +class TestGenericOefSearchHandler(BaseSkillTestCase): + """Test oef search handler of generic seller.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.oef_search_handler = cast( + GenericOefSearchHandler, cls._skill.skill_context.handlers.oef_search + ) + cls.strategy = cast(GenericStrategy, cls._skill.skill_context.strategy) + cls.oef_dialogues = cast( + OefSearchDialogues, cls._skill.skill_context.oef_search_dialogues + ) + cls.list_of_messages = ( + DialogueMessage( + OefSearchMessage.Performative.SEARCH_SERVICES, {"query": "some_query"} + ), + ) + + def test_setup(self): + """Test the setup method of the oef_search handler.""" + assert self.oef_search_handler.setup() is None + self.assert_quantity_in_outbox(0) + + def test_handle_unidentified_dialogue(self): + """Test the _handle_unidentified_dialogue method of the oef_search handler.""" + # setup + incorrect_dialogue_reference = ("", "") + incoming_message = self.build_incoming_message( + message_type=OefSearchMessage, + dialogue_reference=incorrect_dialogue_reference, + performative=OefSearchMessage.Performative.SEARCH_SERVICES, + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received invalid oef_search message={incoming_message}, unidentified dialogue.", + ) + + def test_handle_error(self): + """Test the _handle_error method of the oef_search handler.""" + # setup + oef_dialogue = self.prepare_skill_dialogue( + dialogues=self.oef_dialogues, messages=self.list_of_messages[:1], + ) + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=oef_dialogue, + performative=OefSearchMessage.Performative.OEF_ERROR, + oef_error_operation=OefSearchMessage.OefErrorOperation.SEARCH_SERVICES, + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.INFO, + f"received oef_search error message={incoming_message} in dialogue={oef_dialogue}.", + ) + + def test_handle_invalid(self): + """Test the _handle_invalid method of the oef_search handler.""" + # setup + invalid_performative = OefSearchMessage.Performative.UNREGISTER_SERVICE + incoming_message = self.build_incoming_message( + message_type=OefSearchMessage, + dialogue_reference=("1", ""), + performative=invalid_performative, + service_description="some_service_description", + ) + + # operation + with patch.object(self.oef_search_handler.context.logger, "log") as mock_logger: + self.oef_search_handler.handle(incoming_message) + + # after + mock_logger.assert_any_call( + logging.WARNING, + f"cannot handle oef_search message of performative={invalid_performative} in dialogue={self.oef_dialogues.get_dialogue(incoming_message)}.", + ) + + def test_teardown(self): + """Test the teardown method of the oef_search handler.""" + assert self.oef_search_handler.teardown() is None + self.assert_quantity_in_outbox(0) diff --git a/tests/test_packages/test_skills/test_generic_seller/test_models.py b/tests/test_packages/test_skills/test_generic_seller/test_models.py new file mode 100644 index 0000000000..7a444c5864 --- /dev/null +++ b/tests/test_packages/test_skills/test_generic_seller/test_models.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains the tests of the strategy class of the generic seller skill.""" + +from pathlib import Path + +import pytest + +from aea.configurations.constants import DEFAULT_LEDGER +from aea.crypto.ledger_apis import LedgerApis +from aea.helpers.search.models import ( + Constraint, + ConstraintType, + Description, + Location, + Query, +) +from aea.helpers.transaction.base import Terms +from aea.test_tools.test_skill import BaseSkillTestCase, COUNTERPARTY_NAME + +from packages.fetchai.skills.generic_seller.strategy import ( + AGENT_LOCATION_MODEL, + AGENT_REMOVE_SERVICE_MODEL, + AGENT_SET_SERVICE_MODEL, + GenericStrategy, + SIMPLE_SERVICE_MODEL, +) + +from tests.conftest import ROOT_DIR + + +class TestGenericStrategy(BaseSkillTestCase): + """Test GenericStrategy of generic seller.""" + + path_to_skill = Path(ROOT_DIR, "packages", "fetchai", "skills", "generic_seller") + + @classmethod + def setup(cls): + """Setup the test class.""" + super().setup() + cls.ledger_id = DEFAULT_LEDGER + cls.is_ledger_tx = True + cls.currency_id = "some_currency_id" + cls.unit_price = 20 + cls.service_id = "some_service_id" + cls.location = { + "longitude": 51.5194, + "latitude": 0.127, + } + cls.service_data = {"key": "seller_service", "value": "some_service"} + cls.has_data_source = False + cls.data_for_sale = {"some_data_type": "some_data"} + cls.strategy = GenericStrategy( + ledger_id=cls.ledger_id, + is_ledger_tx=cls.is_ledger_tx, + currency_id=cls.currency_id, + unit_price=cls.unit_price, + service_id=cls.service_id, + location=cls.location, + service_data=cls.service_data, + has_data_source=cls.has_data_source, + data_for_sale=cls.data_for_sale, + name="strategy", + skill_context=cls._skill.skill_context, + ) + + def test_properties(self): + """Test the properties of GenericStrategy class.""" + assert self.strategy.ledger_id == self.ledger_id + assert self.strategy.is_ledger_tx == self.is_ledger_tx + + def test_get_location_description(self): + """Test the get_location_description method of the GenericStrategy class.""" + description = self.strategy.get_location_description() + + assert type(description) == Description + assert description.data_model is AGENT_LOCATION_MODEL + assert description.values.get("location", "") == Location( + latitude=self.location["latitude"], longitude=self.location["longitude"] + ) + + def test_get_register_service_description(self): + """Test the get_register_service_description method of the GenericStrategy class.""" + description = self.strategy.get_register_service_description() + + assert type(description) == Description + assert description.data_model is AGENT_SET_SERVICE_MODEL + assert description.values.get("key", "") == "seller_service" + assert description.values.get("value", "") == "some_service" + + def test_get_service_description(self): + """Test the get_service_description method of the GenericStrategy class.""" + description = self.strategy.get_service_description() + + assert type(description) == Description + assert description.data_model is SIMPLE_SERVICE_MODEL + assert description.values.get("seller_service", "") == "some_service" + + def test_get_unregister_service_description(self): + """Test the get_unregister_service_description method of the GenericStrategy class.""" + description = self.strategy.get_unregister_service_description() + + assert type(description) == Description + assert description.data_model is AGENT_REMOVE_SERVICE_MODEL + assert description.values.get("key", "") == "seller_service" + + def test_is_matching_supply(self): + """Test the is_matching_supply method of the GenericStrategy class.""" + acceptable_constraint = Constraint( + "seller_service", ConstraintType("==", "some_service") + ) + matching_query = Query([acceptable_constraint]) + is_matching_supply = self.strategy.is_matching_supply(matching_query) + assert is_matching_supply + + unacceptable_constraint = Constraint( + "seller_service", ConstraintType("==", "some_other_service") + ) + unmatching_query = Query([unacceptable_constraint]) + is_matching_supply = self.strategy.is_matching_supply(unmatching_query) + assert not is_matching_supply + + def test_generate_proposal_terms_and_data(self): + """Test the generate_proposal_terms_and_data method of the GenericStrategy class.""" + # setup + seller = self.skill.skill_context.agent_address + total_price = len(self.data_for_sale) * self.unit_price + sale_quantity = len(self.data_for_sale) + tx_nonce = LedgerApis.generate_tx_nonce( + identifier=self.ledger_id, seller=seller, client=COUNTERPARTY_NAME, + ) + query = Query( + [Constraint("seller_service", ConstraintType("==", "some_service"))] + ) + + # expected returned values + expected_proposal = Description( + { + "ledger_id": self.ledger_id, + "price": total_price, + "currency_id": self.currency_id, + "service_id": self.service_id, + "quantity": sale_quantity, + "tx_nonce": tx_nonce, + } + ) + expected_terms = Terms( + ledger_id=self.ledger_id, + sender_address=seller, + counterparty_address=COUNTERPARTY_NAME, + amount_by_currency_id={self.currency_id: total_price}, + quantities_by_good_id={self.service_id: -sale_quantity}, + is_sender_payable_tx_fee=False, + nonce=tx_nonce, + fee_by_currency_id={self.currency_id: 0}, + ) + + # operation + proposal, terms, data = self.strategy.generate_proposal_terms_and_data( + query, COUNTERPARTY_NAME + ) + + # after + assert proposal == expected_proposal + assert terms == expected_terms + assert data == self.data_for_sale + + def test_collect_from_data_source(self): + """Test the collect_from_data_source method of the GenericStrategy class.""" + with pytest.raises(NotImplementedError): + self.strategy.collect_from_data_source() diff --git a/tests/test_packages/test_skills_integration/__init__.py b/tests/test_packages/test_skills_integration/__init__.py new file mode 100644 index 0000000000..84bd9d1813 --- /dev/null +++ b/tests/test_packages/test_skills_integration/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""The tests module contains the integration tests of the packages/skills dir.""" diff --git a/tests/test_packages/test_skills/test_carpark.py b/tests/test_packages/test_skills_integration/test_carpark.py similarity index 83% rename from tests/test_packages/test_skills/test_carpark.py rename to tests/test_packages/test_skills_integration/test_carpark.py index 28d3996b69..aa4fae4699 100644 --- a/tests/test_packages/test_skills/test_carpark.py +++ b/tests/test_packages/test_skills_integration/test_carpark.py @@ -25,6 +25,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -51,8 +53,8 @@ def test_carpark(self): self.create_agents(carpark_aea_name, carpark_client_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -63,17 +65,17 @@ def test_carpark(self): # Setup agent one self.set_agent_context(carpark_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/carpark_detection:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/carpark_detection:0.13.0") setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -88,27 +90,27 @@ def test_carpark(self): ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # Setup agent two self.set_agent_context(carpark_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/carpark_client:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/carpark_client:0.13.0") setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -121,15 +123,13 @@ def test_carpark(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # Fire the sub-processes and the threads. self.set_agent_context(carpark_aea_name) @@ -141,7 +141,7 @@ def test_carpark(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_aea_process, check_strings, timeout=240, is_terminating=False @@ -159,7 +159,7 @@ def test_carpark(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_client_aea_process, check_strings, timeout=240, is_terminating=False @@ -226,8 +226,8 @@ def test_carpark(self): self.create_agents(carpark_aea_name, carpark_client_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -238,17 +238,17 @@ def test_carpark(self): # Setup agent one self.set_agent_context(carpark_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/carpark_detection:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/carpark_detection:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/car_detector:0.13.0", carpark_aea_name + "fetchai/car_detector:0.14.0", carpark_aea_name ) assert ( diff == [] @@ -266,27 +266,27 @@ def test_carpark(self): ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = ( "vendor.fetchai.skills.carpark_detection.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # Setup agent two self.set_agent_context(carpark_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/carpark_client:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/carpark_client:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/car_data_buyer:0.13.0", carpark_client_aea_name + "fetchai/car_data_buyer:0.14.0", carpark_client_aea_name ) assert ( diff == [] @@ -305,15 +305,13 @@ def test_carpark(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.carpark_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # Fire the sub-processes and the threads. self.set_agent_context(carpark_aea_name) @@ -325,7 +323,7 @@ def test_carpark(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_aea_process, check_strings, timeout=240, is_terminating=False @@ -343,7 +341,7 @@ def test_carpark(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( carpark_client_aea_process, check_strings, timeout=240, is_terminating=False diff --git a/tests/test_packages/test_skills/test_echo.py b/tests/test_packages/test_skills_integration/test_echo.py similarity index 98% rename from tests/test_packages/test_skills/test_echo.py rename to tests/test_packages/test_skills_integration/test_echo.py index 9a59110501..cffd87c948 100644 --- a/tests/test_packages/test_skills/test_echo.py +++ b/tests/test_packages/test_skills_integration/test_echo.py @@ -65,7 +65,7 @@ class TestEchoSkill(AEATestCaseEmpty): def test_echo(self): """Run the echo skill sequence.""" - self.add_item("skill", "fetchai/echo:0.8.0") + self.add_item("skill", "fetchai/echo:0.9.0") process = self.run_agent() is_running = self.is_running(process) diff --git a/tests/test_packages/test_skills/test_erc1155.py b/tests/test_packages/test_skills_integration/test_erc1155.py similarity index 86% rename from tests/test_packages/test_skills/test_erc1155.py rename to tests/test_packages/test_skills_integration/test_erc1155.py index 30b13cded6..6352f1e7f8 100644 --- a/tests/test_packages/test_skills/test_erc1155.py +++ b/tests/test_packages/test_skills_integration/test_erc1155.py @@ -24,6 +24,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -56,9 +58,9 @@ def test_generic(self): # add ethereum ledger in both configuration files default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/contract_api:0.5.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/contract_api:0.6.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -69,17 +71,17 @@ def test_generic(self): # add packages for agent one self.set_agent_context(deploy_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") self.set_config("agent.default_ledger", ETHEREUM) setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) - self.add_item("skill", "fetchai/erc1155_deploy:0.14.0") + self.nested_set_config(setting_path, default_routing) + self.add_item("skill", "fetchai/erc1155_deploy:0.15.0") diff = self.difference_to_fetched_agent( - "fetchai/erc1155_deployer:0.14.0", deploy_aea_name + "fetchai/erc1155_deployer:0.15.0", deploy_aea_name ) assert ( diff == [] @@ -102,28 +104,28 @@ def test_generic(self): setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "ethereum") setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) self.run_install() # replace location setting_path = ( "vendor.fetchai.skills.erc1155_deploy.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # add packages for agent two self.set_agent_context(client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") self.set_config("agent.default_ledger", ETHEREUM) setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) - self.add_item("skill", "fetchai/erc1155_client:0.13.0") + self.nested_set_config(setting_path, default_routing) + self.add_item("skill", "fetchai/erc1155_client:0.14.0") diff = self.difference_to_fetched_agent( - "fetchai/erc1155_client:0.14.0", client_aea_name + "fetchai/erc1155_client:0.15.0", client_aea_name ) assert ( diff == [] @@ -143,16 +145,14 @@ def test_generic(self): setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" self.set_config(setting_path, "ethereum") setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) self.run_install() # replace location setting_path = ( "vendor.fetchai.skills.erc1155_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run agents self.set_agent_context(deploy_aea_name) @@ -164,7 +164,7 @@ def test_generic(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( deploy_aea_process, check_strings, timeout=240, is_terminating=False @@ -203,7 +203,7 @@ def test_generic(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( client_aea_process, check_strings, timeout=240, is_terminating=False diff --git a/tests/test_packages/test_skills/test_generic.py b/tests/test_packages/test_skills_integration/test_generic.py similarity index 84% rename from tests/test_packages/test_skills/test_generic.py rename to tests/test_packages/test_skills_integration/test_generic.py index 77c13b6cb9..98e4f48e95 100644 --- a/tests/test_packages/test_skills/test_generic.py +++ b/tests/test_packages/test_skills_integration/test_generic.py @@ -24,6 +24,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -50,8 +52,8 @@ def test_generic(self, pytestconfig): self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -62,17 +64,17 @@ def test_generic(self, pytestconfig): # prepare seller agent self.set_agent_context(seller_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/generic_seller:0.13.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/generic_seller:0.14.0") setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -87,7 +89,7 @@ def test_generic(self, pytestconfig): ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" @@ -97,21 +99,21 @@ def test_generic(self, pytestconfig): setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # prepare buyer agent self.set_agent_context(buyer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/generic_buyer:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/generic_buyer:0.13.0") setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -124,9 +126,7 @@ def test_generic(self, pytestconfig): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # make runable: setting_path = "vendor.fetchai.skills.generic_buyer.is_abstract" @@ -136,7 +136,7 @@ def test_generic(self, pytestconfig): setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run AEAs self.set_agent_context(seller_aea_name) @@ -148,7 +148,7 @@ def test_generic(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=240, is_terminating=False @@ -166,7 +166,7 @@ def test_generic(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=240, is_terminating=False @@ -231,8 +231,8 @@ def test_generic(self, pytestconfig): self.create_agents(seller_aea_name, buyer_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -243,17 +243,17 @@ def test_generic(self, pytestconfig): # prepare seller agent self.set_agent_context(seller_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/generic_seller:0.13.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/generic_seller:0.14.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/generic_seller:0.10.0", seller_aea_name + "fetchai/generic_seller:0.11.0", seller_aea_name ) assert ( diff == [] @@ -271,7 +271,7 @@ def test_generic(self, pytestconfig): ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # make runable: setting_path = "vendor.fetchai.skills.generic_seller.is_abstract" @@ -281,21 +281,21 @@ def test_generic(self, pytestconfig): setting_path = ( "vendor.fetchai.skills.generic_seller.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # prepare buyer agent self.set_agent_context(buyer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/generic_buyer:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/generic_buyer:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/generic_buyer:0.10.0", buyer_aea_name + "fetchai/generic_buyer:0.11.0", buyer_aea_name ) assert ( diff == [] @@ -317,15 +317,13 @@ def test_generic(self, pytestconfig): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.generic_buyer.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run AEAs self.set_agent_context(seller_aea_name) @@ -337,7 +335,7 @@ def test_generic(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( seller_aea_process, check_strings, timeout=240, is_terminating=False @@ -355,7 +353,7 @@ def test_generic(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( buyer_aea_process, check_strings, timeout=240, is_terminating=False diff --git a/tests/test_packages/test_skills/test_gym.py b/tests/test_packages/test_skills_integration/test_gym.py similarity index 91% rename from tests/test_packages/test_skills/test_gym.py rename to tests/test_packages/test_skills_integration/test_gym.py index 91619b6314..9df55f43b6 100644 --- a/tests/test_packages/test_skills/test_gym.py +++ b/tests/test_packages/test_skills_integration/test_gym.py @@ -32,16 +32,16 @@ class TestGymSkill(AEATestCaseEmpty): def test_gym(self): """Run the gym skill sequence.""" - self.add_item("skill", "fetchai/gym:0.8.0") - self.add_item("connection", "fetchai/gym:0.8.0") + self.add_item("skill", "fetchai/gym:0.9.0") + self.add_item("connection", "fetchai/gym:0.9.0") self.run_install() # change default connection setting_path = "agent.default_connection" - self.set_config(setting_path, "fetchai/gym:0.8.0") + self.set_config(setting_path, "fetchai/gym:0.9.0") diff = self.difference_to_fetched_agent( - "fetchai/gym_aea:0.11.0", self.agent_name + "fetchai/gym_aea:0.12.0", self.agent_name ) assert ( diff == [] diff --git a/tests/test_packages/test_skills/test_http_echo.py b/tests/test_packages/test_skills_integration/test_http_echo.py similarity index 95% rename from tests/test_packages/test_skills/test_http_echo.py rename to tests/test_packages/test_skills_integration/test_http_echo.py index 1385db73cb..39331bb97b 100644 --- a/tests/test_packages/test_skills/test_http_echo.py +++ b/tests/test_packages/test_skills_integration/test_http_echo.py @@ -36,9 +36,9 @@ class TestHttpEchoSkill(AEATestCaseEmpty): def test_echo(self): """Run the echo skill sequence.""" - self.add_item("connection", "fetchai/http_server:0.9.0") - self.add_item("skill", "fetchai/http_echo:0.7.0") - self.set_config("agent.default_connection", "fetchai/http_server:0.9.0") + self.add_item("connection", "fetchai/http_server:0.10.0") + self.add_item("skill", "fetchai/http_echo:0.8.0") + self.set_config("agent.default_connection", "fetchai/http_server:0.10.0") self.set_config( "vendor.fetchai.connections.http_server.config.api_spec_path", API_SPEC_PATH ) diff --git a/tests/test_packages/test_skills/test_ml_skills.py b/tests/test_packages/test_skills_integration/test_ml_skills.py similarity index 83% rename from tests/test_packages/test_skills/test_ml_skills.py rename to tests/test_packages/test_skills_integration/test_ml_skills.py index 9d7aaa4e37..985039db8c 100644 --- a/tests/test_packages/test_skills/test_ml_skills.py +++ b/tests/test_packages/test_skills_integration/test_ml_skills.py @@ -26,6 +26,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -64,8 +66,8 @@ def test_ml_skills(self, pytestconfig): self.create_agents(data_provider_aea_name, model_trainer_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -76,17 +78,17 @@ def test_ml_skills(self, pytestconfig): # prepare data provider agent self.set_agent_context(data_provider_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/ml_data_provider:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/ml_data_provider:0.13.0") setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -101,27 +103,27 @@ def test_ml_skills(self, pytestconfig): ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # prepare model trainer agent self.set_agent_context(model_trainer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/ml_train:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/ml_train:0.13.0") setting_path = ( "vendor.fetchai.skills.ml_train.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -134,13 +136,11 @@ def test_ml_skills(self, pytestconfig): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = "vendor.fetchai.skills.ml_train.models.strategy.args.location" - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) self.set_agent_context(data_provider_aea_name) data_provider_aea_process = self.run_agent() @@ -151,7 +151,7 @@ def test_ml_skills(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( data_provider_aea_process, check_strings, timeout=240, is_terminating=False @@ -171,7 +171,7 @@ def test_ml_skills(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( model_trainer_aea_process, check_strings, timeout=240, is_terminating=False @@ -241,8 +241,8 @@ def test_ml_skills(self, pytestconfig): self.create_agents(data_provider_aea_name, model_trainer_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -253,17 +253,17 @@ def test_ml_skills(self, pytestconfig): # prepare data provider agent self.set_agent_context(data_provider_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/ml_data_provider:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/ml_data_provider:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/ml_data_provider:0.13.0", data_provider_aea_name + "fetchai/ml_data_provider:0.14.0", data_provider_aea_name ) assert ( diff == [] @@ -281,27 +281,27 @@ def test_ml_skills(self, pytestconfig): ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = ( "vendor.fetchai.skills.ml_data_provider.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # prepare model trainer agent self.set_agent_context(model_trainer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/ml_train:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/ml_train:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/ml_model_trainer:0.13.0", model_trainer_aea_name + "fetchai/ml_model_trainer:0.14.0", model_trainer_aea_name ) assert ( diff == [] @@ -320,13 +320,11 @@ def test_ml_skills(self, pytestconfig): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = "vendor.fetchai.skills.ml_train.models.strategy.args.location" - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) self.set_agent_context(data_provider_aea_name) data_provider_aea_process = self.run_agent() @@ -337,7 +335,7 @@ def test_ml_skills(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( data_provider_aea_process, check_strings, timeout=240, is_terminating=False @@ -357,7 +355,7 @@ def test_ml_skills(self, pytestconfig): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( model_trainer_aea_process, check_strings, timeout=240, is_terminating=False diff --git a/tests/test_packages/test_skills/test_tac.py b/tests/test_packages/test_skills_integration/test_tac.py similarity index 77% rename from tests/test_packages/test_skills/test_tac.py rename to tests/test_packages/test_skills_integration/test_tac.py index 251bf42dca..a12df35e95 100644 --- a/tests/test_packages/test_skills/test_tac.py +++ b/tests/test_packages/test_skills_integration/test_tac.py @@ -24,6 +24,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -42,6 +44,9 @@ ) +MAX_FLAKY_RERUNS_ETH -= 1 + + class TestTacSkills(AEATestCaseMany): """Test that tac skills work.""" @@ -61,22 +66,22 @@ def test_tac(self): ) default_routing = { - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # prepare tac controller for test self.set_agent_context(tac_controller_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.add_item("skill", "fetchai/tac_control:0.8.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.add_item("skill", "fetchai/tac_control:0.9.0") self.set_config("agent.default_ledger", FETCHAI) setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_controller:0.10.0", tac_controller_name + "fetchai/tac_controller:0.11.0", tac_controller_name ) assert ( diff == [] @@ -93,11 +98,11 @@ def test_tac(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # prepare agents for test @@ -106,18 +111,18 @@ def test_tac(self): (tac_aea_two, NON_GENESIS_CONFIG_TWO), ): self.set_agent_context(agent_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/tac_participation:0.9.0") - self.add_item("skill", "fetchai/tac_negotiation:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/tac_participation:0.10.0") + self.add_item("skill", "fetchai/tac_negotiation:0.11.0") self.set_config("agent.default_ledger", FETCHAI) setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_participant:0.11.0", agent_name + "fetchai/tac_participant:0.12.0", agent_name ) assert ( diff == [] @@ -135,9 +140,7 @@ def test_tac(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, config) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, config) # run tac controller self.set_agent_context(tac_controller_name) @@ -155,7 +158,7 @@ def test_tac(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( tac_controller_process, check_strings, timeout=240, is_terminating=False @@ -177,7 +180,7 @@ def test_tac(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=240, is_terminating=False @@ -192,7 +195,7 @@ def test_tac(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=240, is_terminating=False @@ -227,17 +230,21 @@ def test_tac(self): "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", + "received cfp from", + "received decline from", + "received propose from", + "received accept from", + "received match_accept_w_inform from", "sending CFP to agent=", - "accepting propose", - "sending signing_msg=", - "message signed by decision maker.", - "sending transaction to controller.", - "sending match accept to", - # "Received transaction confirmation from the controller: transaction_id=", # noqa: E800 + "sending propose to", + "sending accept to", + "requesting signature, sending sign_message to decision_maker, message=", + "received signed_message from decision_maker, message=", + "sending transaction to controller, tx=", + "received transaction confirmation from the controller:", "Applying state update!", "found potential buyers agents=", "sending CFP to agent=", - "declining propose", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=240, is_terminating=False @@ -274,25 +281,25 @@ def test_tac(self): ) default_routing = { - "fetchai/contract_api:0.5.0": "fetchai/ledger:0.6.0", - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/contract_api:0.6.0": "fetchai/ledger:0.7.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # prepare tac controller for test self.set_agent_context(tac_controller_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/tac_control_contract:0.9.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/tac_control_contract:0.10.0") self.set_config("agent.default_ledger", ETHEREUM) setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_controller_contract:0.11.0", tac_controller_name + "fetchai/tac_controller_contract:0.12.0", tac_controller_name ) assert ( diff == [] @@ -312,15 +319,15 @@ def test_tac(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" - self.force_set_config(setting_path, ETHEREUM) + self.set_config(setting_path, ETHEREUM) setting_path = "vendor.fetchai.skills.tac_control.is_abstract" - self.force_set_config(setting_path, True) + self.set_config(setting_path, True, "bool") default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # prepare agents for test @@ -329,15 +336,15 @@ def test_tac(self): (tac_aea_two, NON_GENESIS_CONFIG_TWO, FUNDED_ETH_PRIVATE_KEY_3), ): self.set_agent_context(agent_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/tac_participation:0.9.0") - self.add_item("skill", "fetchai/tac_negotiation:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/tac_participation:0.10.0") + self.add_item("skill", "fetchai/tac_negotiation:0.11.0") self.set_config("agent.default_ledger", ETHEREUM) setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.set_config( "vendor.fetchai.skills.tac_participation.models.game.args.is_using_contract", True, @@ -350,7 +357,7 @@ def test_tac(self): ) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/tac_participant_contract:0.1.0", agent_name + "fetchai/tac_participant_contract:0.2.0", agent_name ) assert ( diff == [] @@ -369,11 +376,11 @@ def test_tac(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, config) + self.nested_set_config(setting_path, config) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) setting_path = "vendor.fetchai.connections.soef.config.chain_identifier" - self.force_set_config(setting_path, ETHEREUM) + self.set_config(setting_path, ETHEREUM) # run tac controller self.set_agent_context(tac_controller_name) @@ -393,7 +400,7 @@ def test_tac(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, "registering agent on SOEF.", "requesting contract deployment transaction...", "Start processing messages...", @@ -427,7 +434,7 @@ def test_tac(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, "Start processing messages...", "searching for TAC, search_id=", "found the TAC controller. Registering...", @@ -445,7 +452,7 @@ def test_tac(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, "Start processing messages...", "searching for TAC, search_id=", "found the TAC controller. Registering...", @@ -490,7 +497,24 @@ def test_tac(self): "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", + "found potential buyers agents=", "sending CFP to agent=", + "received cfp from", + "received propose from", + "received decline from", + "received accept from", + "received match_accept_w_inform from", + "sending propose to", + "sending accept to", + "requesting batch transaction hash, sending get_raw_message to fetchai/erc1155:0.11.0, message=", + "requesting batch atomic swap transaction, sending get_raw_transaction to fetchai/erc1155:0.11.0, message=", + "received raw transaction=", + "received raw message=", + "proposing the transaction to the decision maker. Waiting for confirmation ...", + "proposing the message to the decision maker. Waiting for confirmation ...", + "received signed_message from decision_maker, message=", + "received signed_transaction from decision_maker, message=", + "sending send_signed_transaction to ledger ethereum, message=", ) missing_strings = self.missing_from_output( tac_aea_one_process, check_strings, timeout=300, is_terminating=False @@ -506,7 +530,24 @@ def test_tac(self): "searching for sellers, search_id=", "searching for buyers, search_id=", "found potential sellers agents=", + "found potential buyers agents=", "sending CFP to agent=", + "received cfp from", + "received propose from", + "received decline from", + "received accept from", + "received match_accept_w_inform from", + "sending propose to", + "sending accept to", + "requesting batch transaction hash, sending get_raw_message to fetchai/erc1155:0.11.0, message=", + "requesting batch atomic swap transaction, sending get_raw_transaction to fetchai/erc1155:0.11.0, message=", + "received raw transaction=", + "received raw message=", + "proposing the transaction to the decision maker. Waiting for confirmation ...", + "proposing the message to the decision maker. Waiting for confirmation ...", + "received signed_message from decision_maker, message=", + "received signed_transaction from decision_maker, message=", + "sending send_signed_transaction to ledger ethereum, message=", ) missing_strings = self.missing_from_output( tac_aea_two_process, check_strings, timeout=360, is_terminating=False diff --git a/tests/test_packages/test_skills/test_thermometer.py b/tests/test_packages/test_skills_integration/test_thermometer.py similarity index 84% rename from tests/test_packages/test_skills/test_thermometer.py rename to tests/test_packages/test_skills_integration/test_thermometer.py index d78f337397..31c0ee65c2 100644 --- a/tests/test_packages/test_skills/test_thermometer.py +++ b/tests/test_packages/test_skills_integration/test_thermometer.py @@ -24,6 +24,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -51,8 +53,8 @@ def test_thermometer(self): self.create_agents(thermometer_aea_name, thermometer_client_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -63,17 +65,17 @@ def test_thermometer(self): # add packages for agent one and run it self.set_agent_context(thermometer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/thermometer:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/thermometer:0.13.0") setting_path = ( "vendor.fetchai.skills.thermometer.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -87,25 +89,25 @@ def test_thermometer(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = "vendor.fetchai.skills.thermometer.models.strategy.args.location" - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # add packages for agent two and run it self.set_agent_context(thermometer_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/thermometer_client:0.11.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/thermometer_client:0.12.0") setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.is_ledger_tx" ) self.set_config(setting_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -118,15 +120,13 @@ def test_thermometer(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run AEAs self.set_agent_context(thermometer_aea_name) @@ -138,7 +138,7 @@ def test_thermometer(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_aea_process, check_strings, timeout=240, is_terminating=False @@ -156,7 +156,7 @@ def test_thermometer(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_client_aea_process, @@ -227,8 +227,8 @@ def test_thermometer(self): self.create_agents(thermometer_aea_name, thermometer_client_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -239,17 +239,17 @@ def test_thermometer(self): # add packages for agent one and run it self.set_agent_context(thermometer_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/thermometer:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/thermometer:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/thermometer_aea:0.11.0", thermometer_aea_name + "fetchai/thermometer_aea:0.12.0", thermometer_aea_name ) assert ( diff == [] @@ -266,25 +266,25 @@ def test_thermometer(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = "vendor.fetchai.skills.thermometer.models.strategy.args.location" - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # add packages for agent two and run it self.set_agent_context(thermometer_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/thermometer_client:0.11.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/thermometer_client:0.12.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/thermometer_client:0.11.0", thermometer_client_aea_name + "fetchai/thermometer_client:0.12.0", thermometer_client_aea_name ) assert ( diff == [] @@ -303,15 +303,13 @@ def test_thermometer(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.thermometer_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run AEAs self.set_agent_context(thermometer_aea_name) @@ -323,7 +321,7 @@ def test_thermometer(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_aea_process, check_strings, timeout=240, is_terminating=False @@ -341,7 +339,7 @@ def test_thermometer(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( thermometer_client_aea_process, diff --git a/tests/test_packages/test_skills/test_weather.py b/tests/test_packages/test_skills_integration/test_weather.py similarity index 83% rename from tests/test_packages/test_skills/test_weather.py rename to tests/test_packages/test_skills_integration/test_weather.py index 7b6b15d03a..b376c6a965 100644 --- a/tests/test_packages/test_skills/test_weather.py +++ b/tests/test_packages/test_skills_integration/test_weather.py @@ -24,6 +24,8 @@ from aea.test_tools.test_cases import AEATestCaseMany +from packages.fetchai.connections.p2p_libp2p.connection import LIBP2P_SUCCESS_MESSAGE + from tests.conftest import ( COSMOS, COSMOS_PRIVATE_KEY_FILE_CONNECTION, @@ -50,8 +52,8 @@ def test_weather(self): self.create_agents(weather_station_aea_name, weather_client_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -62,18 +64,18 @@ def test_weather(self): # prepare agent one (weather station) self.set_agent_context(weather_station_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/weather_station:0.12.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/weather_station:0.13.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") dotted_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.is_ledger_tx" ) self.set_config(dotted_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -87,28 +89,28 @@ def test_weather(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # prepare agent two (weather client) self.set_agent_context(weather_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/weather_client:0.11.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/weather_client:0.12.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") dotted_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.is_ledger_tx" ) self.set_config(dotted_path, False, "bool") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() # add keys @@ -121,15 +123,13 @@ def test_weather(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # run agents self.set_agent_context(weather_station_aea_name) @@ -141,7 +141,7 @@ def test_weather(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=240, is_terminating=False @@ -159,7 +159,7 @@ def test_weather(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_client_process, check_strings, timeout=240, is_terminating=False, @@ -222,8 +222,8 @@ def test_weather(self): self.create_agents(weather_station_aea_name, weather_client_aea_name) default_routing = { - "fetchai/ledger_api:0.4.0": "fetchai/ledger:0.6.0", - "fetchai/oef_search:0.7.0": "fetchai/soef:0.9.0", + "fetchai/ledger_api:0.5.0": "fetchai/ledger:0.7.0", + "fetchai/oef_search:0.8.0": "fetchai/soef:0.10.0", } # generate random location @@ -234,17 +234,17 @@ def test_weather(self): # add packages for agent one self.set_agent_context(weather_station_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/weather_station:0.12.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/weather_station:0.13.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/weather_station:0.13.0", weather_station_aea_name + "fetchai/weather_station:0.14.0", weather_station_aea_name ) assert ( diff == [] @@ -261,27 +261,27 @@ def test_weather(self): NON_FUNDED_COSMOS_PRIVATE_KEY_1, COSMOS_PRIVATE_KEY_FILE_CONNECTION ) setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.set_config(setting_path, COSMOS) # replace location setting_path = ( "vendor.fetchai.skills.weather_station.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) # add packages for agent two self.set_agent_context(weather_client_aea_name) - self.add_item("connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/soef:0.9.0") - self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.10.0") - self.add_item("connection", "fetchai/ledger:0.6.0") - self.add_item("skill", "fetchai/weather_client:0.11.0") + self.add_item("connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/soef:0.10.0") + self.set_config("agent.default_connection", "fetchai/p2p_libp2p:0.11.0") + self.add_item("connection", "fetchai/ledger:0.7.0") + self.add_item("skill", "fetchai/weather_client:0.12.0") setting_path = "agent.default_routing" - self.force_set_config(setting_path, default_routing) + self.nested_set_config(setting_path, default_routing) self.run_install() diff = self.difference_to_fetched_agent( - "fetchai/weather_client:0.13.0", weather_client_aea_name + "fetchai/weather_client:0.14.0", weather_client_aea_name ) assert ( diff == [] @@ -300,15 +300,13 @@ def test_weather(self): # set p2p configs setting_path = "vendor.fetchai.connections.p2p_libp2p.config" - self.force_set_config(setting_path, NON_GENESIS_CONFIG) - setting_path = "vendor.fetchai.connections.p2p_libp2p.config.ledger_id" - self.force_set_config(setting_path, COSMOS) + self.nested_set_config(setting_path, NON_GENESIS_CONFIG) # replace location setting_path = ( "vendor.fetchai.skills.weather_client.models.strategy.args.location" ) - self.force_set_config(setting_path, location) + self.nested_set_config(setting_path, location) self.set_agent_context(weather_station_aea_name) weather_station_process = self.run_agent() @@ -319,7 +317,7 @@ def test_weather(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_station_process, check_strings, timeout=240, is_terminating=False @@ -337,7 +335,7 @@ def test_weather(self): "Starting libp2p node...", "Connecting to libp2p node...", "Successfully connected to libp2p node!", - "My libp2p addresses:", + LIBP2P_SUCCESS_MESSAGE, ) missing_strings = self.missing_from_output( weather_client_process, check_strings, timeout=240, is_terminating=False, diff --git a/tests/test_protocols/test_default.py b/tests/test_protocols/test_default.py index b02f0eb9b9..56266b9677 100644 --- a/tests/test_protocols/test_default.py +++ b/tests/test_protocols/test_default.py @@ -100,7 +100,9 @@ def test_default_valid_performatives(): def test_light_protocol_rule_3_target_0(): """Test that if message_id is not 1, target must be > 0""" - with patch.object(aea.protocols.default.message.logger, "error") as mock_logger: + with patch.object( + aea.protocols.default.message._default_logger, "error" + ) as mock_logger: message_id = 2 target = 0 DefaultMessage( @@ -116,7 +118,9 @@ def test_light_protocol_rule_3_target_0(): def test_light_protocol_rule_3_target_less_than_message_id(): """Test that if message_id is not 1, target must be > message_id""" - with patch.object(aea.protocols.default.message.logger, "error") as mock_logger: + with patch.object( + aea.protocols.default.message._default_logger, "error" + ) as mock_logger: message_id = 2 target = 2 DefaultMessage( diff --git a/tests/test_protocols/test_dialogue/test_base.py b/tests/test_protocols/test_dialogue/test_base.py index 272676ca91..659f4bff33 100644 --- a/tests/test_protocols/test_dialogue/test_base.py +++ b/tests/test_protocols/test_dialogue/test_base.py @@ -19,16 +19,19 @@ """This module contains the tests for the dialogue/base.py module.""" +import sys from typing import FrozenSet, Tuple, Type, cast +from unittest import mock import pytest +import aea from aea.common import Address from aea.exceptions import AEAEnforceError from aea.protocols.base import Message from aea.protocols.default.message import DefaultMessage from aea.protocols.dialogue.base import Dialogue as BaseDialogue -from aea.protocols.dialogue.base import DialogueLabel, DialogueStats +from aea.protocols.dialogue.base import DialogueLabel, DialogueMessage, DialogueStats from aea.protocols.dialogue.base import Dialogues as BaseDialogues from aea.protocols.dialogue.base import InvalidDialogueMessage from aea.protocols.state_update.message import StateUpdateMessage @@ -123,6 +126,39 @@ def role_from_first_message( # pylint: disable=unused-argument ) +@pytest.mark.skipif( + sys.version_info < (3, 7), + reason="This part of code is only defined for python version >= 3.7", +) +def test_dialogue_message_python_3_7(): + """Test DiallogueMessage if python is 3.7""" + dialogue_message = DialogueMessage(DefaultMessage.Performative.BYTES) + assert isinstance(dialogue_message.performative, Message.Performative) + + assert dialogue_message.performative == DefaultMessage.Performative.BYTES + assert dialogue_message.contents == {} + assert dialogue_message.is_incoming is None + assert dialogue_message.target is None + + +@pytest.mark.skipif( + sys.version_info >= (3, 7), + reason="This part of code is only defined for python version < 3.7", +) +def test_dialogue_message_python_3_6(): + """Test DiallogueMessage if python is 3.6""" + with mock.patch.object( + aea.protocols.dialogue.base.sys, "version_info", return_value=(3, 6) + ): + dialogue_message = DialogueMessage(DefaultMessage.Performative.BYTES) + assert isinstance(dialogue_message.performative, Message.Performative) + + assert dialogue_message.performative == DefaultMessage.Performative.BYTES + assert dialogue_message.contents == {} + assert dialogue_message.is_incoming is None + assert dialogue_message.target is None + + class TestDialogueLabel: """Test for DialogueLabel.""" @@ -268,6 +304,7 @@ def test_dialogue_properties(self): assert self.dialogue.rules.get_valid_replies( DefaultMessage.Performative.ERROR ) == frozenset({}) + assert self.dialogue.message_class == DefaultMessage assert self.dialogue.is_self_initiated @@ -462,6 +499,21 @@ def test_reply_negative_empty_dialogue(self): assert str(cm.value) == "Cannot reply in an empty dialogue!" assert self.dialogue.is_empty + def test_reply_negative_target_message_target_mismatch(self): + """Negative test for the 'reply' method: target message and target provided but do not match.""" + self.dialogue._update(self.valid_message_1_by_self) + assert self.dialogue.last_message.message_id == 1 + + with pytest.raises(AEAEnforceError) as cm: + self.dialogue.reply( + target_message=self.valid_message_1_by_self, + target=2, + performative=DefaultMessage.Performative.BYTES, + content=b"Hello Back", + ) + assert str(cm.value) == "The provided target and target_message do not match." + assert self.dialogue.last_message.message_id == 1 + def test_reply_negative_invalid_target(self): """Negative test for the 'reply' method: target message is not in the dialogue.""" self.dialogue._update(self.valid_message_1_by_self) @@ -1049,6 +1101,8 @@ def test_dialogues_properties(self): Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.FAILED: 0, } + assert self.own_dialogues.message_class == DefaultMessage + assert self.own_dialogues.dialogue_class == Dialogue def test_counterparty_from_message(self): """Test the 'counterparty_from_message' method.""" @@ -1258,7 +1312,10 @@ def test_update_negative_no_matching_to(self): with pytest.raises(AEAEnforceError) as cm: self.own_dialogues.update(invalid_message_1_by_other) - assert str(cm.value) == "Message to and dialogue self address do not match." + assert ( + str(cm.value) + == "Message to and dialogue self address do not match. Got 'to=agent 1wrong_stuff' expected 'to=agent 1'." + ) assert len(self.own_dialogues.dialogues) == 0 diff --git a/tests/test_protocols/test_generator/test_common.py b/tests/test_protocols/test_generator/test_common.py index bc6fae7f7e..cf1f2183cf 100644 --- a/tests/test_protocols/test_generator/test_common.py +++ b/tests/test_protocols/test_generator/test_common.py @@ -54,6 +54,11 @@ logging.basicConfig(level=logging.INFO) +def isort_is_not_installed_side_effect(*args, **kwargs): + """Isort not installed.""" + return not args[0] == "isort" + + def black_is_not_installed_side_effect(*args, **kwargs): """Black not installed.""" return not args[0] == "black" @@ -361,6 +366,17 @@ def test_check_prerequisites_negative_black_is_not_installed( with self.assertRaises(FileNotFoundError): check_prerequisites() + @mock.patch( + "aea.protocols.generator.common.is_installed", + side_effect=isort_is_not_installed_side_effect, + ) + def test_check_prerequisites_negative_isort_is_not_installed( + self, mocked_is_installed + ): + """Negative test for the 'check_prerequisites' method: isort isn't installed""" + with self.assertRaises(FileNotFoundError): + check_prerequisites() + @mock.patch( "aea.protocols.generator.common.is_installed", side_effect=protoc_is_not_installed_side_effect, diff --git a/tests/test_protocols/test_generator/test_end_to_end.py b/tests/test_protocols/test_generator/test_end_to_end.py index 1fc3d6d87c..6559f3e419 100644 --- a/tests/test_protocols/test_generator/test_end_to_end.py +++ b/tests/test_protocols/test_generator/test_end_to_end.py @@ -75,7 +75,7 @@ def test_generated_protocol_end_to_end(self): builder_1.set_name(agent_name_1) builder_1.add_private_key(DEFAULT_LEDGER, self.private_key_path_1) builder_1.set_default_ledger(DEFAULT_LEDGER) - builder_1.set_default_connection(PublicId.from_str("fetchai/oef:0.10.0")) + builder_1.set_default_connection(PublicId.from_str("fetchai/oef:0.11.0")) builder_1.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "fipa") ) @@ -101,7 +101,7 @@ def test_generated_protocol_end_to_end(self): builder_2.add_protocol( Path(ROOT_DIR, "packages", "fetchai", "protocols", "oef_search") ) - builder_2.set_default_connection(PublicId.from_str("fetchai/oef:0.10.0")) + builder_2.set_default_connection(PublicId.from_str("fetchai/oef:0.11.0")) builder_2.add_component( ComponentType.PROTOCOL, Path(PATH_TO_T_PROTOCOL), @@ -113,10 +113,10 @@ def test_generated_protocol_end_to_end(self): # create AEAs aea_1 = builder_1.build( - connection_ids=[PublicId.from_str("fetchai/oef:0.10.0")] + connection_ids=[PublicId.from_str("fetchai/oef:0.11.0")] ) aea_2 = builder_2.build( - connection_ids=[PublicId.from_str("fetchai/oef:0.10.0")] + connection_ids=[PublicId.from_str("fetchai/oef:0.11.0")] ) # dialogues diff --git a/tests/test_protocols/test_state_update.py b/tests/test_protocols/test_state_update.py index 2cc1888a04..6ccf073657 100644 --- a/tests/test_protocols/test_state_update.py +++ b/tests/test_protocols/test_state_update.py @@ -139,7 +139,7 @@ def test_performative_str(): def test_light_protocol_rule_3_target_less_than_message_id(): """Test that if message_id is not 1, target must be > message_id""" with patch.object( - aea.protocols.state_update.message.logger, "error" + aea.protocols.state_update.message._default_logger, "error" ) as mock_logger: currency_endowment = {"FET": 100} good_endowment = {"a_good": 2} diff --git a/tests/test_registries/test_base.py b/tests/test_registries/test_base.py index dac083792e..2a66e06985 100644 --- a/tests/test_registries/test_base.py +++ b/tests/test_registries/test_base.py @@ -64,8 +64,6 @@ class TestContractRegistry: @classmethod def setup_class(cls): """Set the tests up.""" - cls.patch = unittest.mock.patch.object(aea.registries.base.logger, "exception") - cls.mocked_logger = cls.patch.start() cls.oldcwd = os.getcwd() cls.agent_name = "agent_dir_test" @@ -79,9 +77,11 @@ def setup_class(cls): ) cls.registry = AgentComponentRegistry() + cls.patch = unittest.mock.patch.object(cls.registry.logger, "exception") + cls.mocked_logger = cls.patch.start() cls.registry.register(contract.component_id, cast(Contract, contract)) cls.expected_contract_ids = { - PublicId.from_str("fetchai/erc1155:0.10.0"), + PublicId.from_str("fetchai/erc1155:0.11.0"), } def test_fetch_all(self): @@ -92,14 +92,14 @@ def test_fetch_all(self): def test_fetch(self): """Test that the `fetch` method works as expected.""" - contract_id = PublicId.from_str("fetchai/erc1155:0.10.0") + contract_id = PublicId.from_str("fetchai/erc1155:0.11.0") contract = self.registry.fetch(ComponentId(ComponentType.CONTRACT, contract_id)) assert isinstance(contract, Contract) assert contract.id == contract_id def test_unregister(self): """Test that the 'unregister' method works as expected.""" - contract_id_removed = PublicId.from_str("fetchai/erc1155:0.10.0") + contract_id_removed = PublicId.from_str("fetchai/erc1155:0.11.0") component_id = ComponentId(ComponentType.CONTRACT, contract_id_removed) contract_removed = self.registry.fetch(component_id) self.registry.unregister(contract_removed.component_id) @@ -133,8 +133,6 @@ class TestProtocolRegistry: @classmethod def setup_class(cls): """Set the tests up.""" - cls.patch = unittest.mock.patch.object(aea.registries.base.logger, "exception") - cls.mocked_logger = cls.patch.start() cls.oldcwd = os.getcwd() cls.agent_name = "agent_dir_test" @@ -144,6 +142,8 @@ def setup_class(cls): os.chdir(cls.agent_folder) cls.registry = AgentComponentRegistry() + cls.patch = unittest.mock.patch.object(cls.registry.logger, "exception") + cls.mocked_logger = cls.patch.start() protocol_1 = Protocol.from_dir(Path(aea.AEA_DIR, "protocols", "default")) protocol_2 = Protocol.from_dir( @@ -154,7 +154,7 @@ def setup_class(cls): cls.expected_protocol_ids = { DEFAULT_PROTOCOL, - PublicId.from_str("fetchai/fipa:0.7.0"), + PublicId.from_str("fetchai/fipa:0.8.0"), } def test_fetch_all(self): @@ -199,11 +199,11 @@ class TestResources: @classmethod def _patch_logger(cls): cls.patch_logger_exception = unittest.mock.patch.object( - aea.registries.base.logger, "exception" + aea.registries.base._default_logger, "exception" ) cls.mocked_logger_exception = cls.patch_logger_exception.__enter__() cls.patch_logger_warning = unittest.mock.patch.object( - aea.registries.base.logger, "warning" + aea.registries.base._default_logger, "warning" ) cls.mocked_logger_warning = cls.patch_logger_warning.__enter__() @@ -215,7 +215,7 @@ def _unpatch_logger(cls): @classmethod def setup_class(cls): """Set the tests up.""" - cls._patch_logger() + # cls._patch_logger() # noqa: E800 # create temp agent folder cls.oldcwd = os.getcwd() @@ -232,19 +232,21 @@ def setup_class(cls): ) cls.resources.add_component( Skill.from_dir( - Path(CUR_PATH, "data", "dummy_skill"), agent_context=MagicMock(), + Path(CUR_PATH, "data", "dummy_skill"), + agent_context=MagicMock(agent_name="name"), ) ) cls.resources.add_component( Skill.from_dir( - Path(aea.AEA_DIR, "skills", "error"), agent_context=MagicMock(), + Path(aea.AEA_DIR, "skills", "error"), + agent_context=MagicMock(agent_name="name"), ) ) cls.error_skill_public_id = DEFAULT_SKILL cls.dummy_skill_public_id = PublicId.from_str("dummy_author/dummy:0.1.0") - cls.contract_public_id = PublicId.from_str("fetchai/erc1155:0.10.0") + cls.contract_public_id = PublicId.from_str("fetchai/erc1155:0.11.0") def test_unregister_handler(self): """Test that the unregister of handlers work correctly.""" @@ -336,7 +338,7 @@ def test_add_remove_connection(self): """Test that the 'add connection' and 'remove connection' methods work correctly.""" a_connection = Connection.from_dir( Path(ROOT_DIR, "packages", "fetchai", "connections", "oef"), - identity=MagicMock(), + identity=Identity("name", "address"), crypto_store=MagicMock(), ) self.resources.add_component(a_connection) @@ -348,7 +350,7 @@ def test_get_all_connections(self): """Test get all connections.""" a_connection = Connection.from_dir( Path(ROOT_DIR, "packages", "fetchai", "connections", "oef"), - identity=MagicMock(), + identity=Identity("name", "address"), crypto_store=MagicMock(), ) self.resources.add_component(a_connection) @@ -474,7 +476,7 @@ def test_model_configuration_loading(self): @classmethod def teardown_class(cls): """Tear the tests down.""" - cls._unpatch_logger() + # cls._unpatch_logger() # noqa: E800 os.chdir(cls.oldcwd) try: shutil.rmtree(cls.t) @@ -504,7 +506,8 @@ def setup_class(cls): resources.add_component( Skill.from_dir( - Path(CUR_PATH, "data", "dummy_skill"), agent_context=MagicMock(), + Path(CUR_PATH, "data", "dummy_skill"), + agent_context=MagicMock(agent_name="name"), ) ) @@ -621,7 +624,8 @@ def test_ids(self): def test_ids_non_empty(self): """Test ids, non-empty case.""" dummy_skill = Skill.from_dir( - Path(CUR_PATH, "data", "dummy_skill"), agent_context=MagicMock(), + Path(CUR_PATH, "data", "dummy_skill"), + agent_context=MagicMock(agent_name="name"), ) behaviour = next(iter(dummy_skill.behaviours.values())) skill_component_id = (dummy_skill.public_id, behaviour.name) @@ -654,7 +658,7 @@ def test_setup_with_inactive_skill(self): self.registry, "fetch_all", return_value=[mock_item] ): with unittest.mock.patch.object( - aea.registries.base.logger, "debug" + self.registry.logger, "debug" ) as mock_debug: self.registry.setup() mock_debug.assert_called_with( diff --git a/tests/test_registries/test_filter.py b/tests/test_registries/test_filter.py index 2ecbc969d4..0e3c01a425 100644 --- a/tests/test_registries/test_filter.py +++ b/tests/test_registries/test_filter.py @@ -22,7 +22,6 @@ import pytest -import aea from aea.configurations.base import PublicId, SkillConfig from aea.helpers.async_friendly_queue import AsyncFriendlyQueue from aea.protocols.signing import SigningMessage @@ -75,7 +74,7 @@ def test_get_active_behaviours(self): def test_handle_internal_message_when_none(self): """Test handle internal message when the received message is None.""" with unittest.mock.patch.object( - aea.registries.filter.logger, "warning" + self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(None) mock_logger_warning.assert_called_with( @@ -87,7 +86,7 @@ def test_handle_internal_message_when_no_handler(self): msg = MagicMock() msg.to = "author/name:0.1.0" with unittest.mock.patch.object( - aea.registries.filter.logger, "warning" + self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(msg) mock_logger_warning.assert_called_with( @@ -100,7 +99,7 @@ def test_handle_internal_message_when_invalid_to(self): msg = MagicMock() msg.to = "author!name" with unittest.mock.patch.object( - aea.registries.filter.logger, "warning" + self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(msg) mock_logger_warning.assert_called_with( @@ -141,7 +140,7 @@ def test_handle_internal_message_new_behaviours_with_error(self): self.resources.behaviour_registry, "register", side_effect=ValueError ): with unittest.mock.patch.object( - aea.registries.filter.logger, "warning" + self.filter.logger, "warning" ) as mock_logger_warning: skill.skill_context.new_behaviours.put(new_behaviour) self.filter.handle_new_handlers_and_behaviours() @@ -187,7 +186,7 @@ def test_handle_internal_message_new_handlers_with_error(self): self.resources.handler_registry, "register", side_effect=ValueError ): with unittest.mock.patch.object( - aea.registries.filter.logger, "warning" + self.filter.logger, "warning" ) as mock_logger_warning: skill.skill_context.new_handlers.put(new_handler) self.filter.handle_new_handlers_and_behaviours() @@ -209,7 +208,7 @@ def test_handle_signing_message(self): ) message.to = public_id with unittest.mock.patch.object( - aea.registries.filter.logger, "warning" + self.filter.logger, "warning" ) as mock_logger_warning: self.filter.handle_internal_message(message) mock_logger_warning.assert_called_with( diff --git a/tests/test_runner.py b/tests/test_runner.py index 170ef93eac..54b867e667 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -26,7 +26,7 @@ from aea.configurations.base import SkillConfig from aea.configurations.constants import DEFAULT_LEDGER from aea.helpers.multiple_executor import ExecutorExceptionPolicies -from aea.helpers.multiple_executor import logger as executor_logger +from aea.helpers.multiple_executor import _default_logger as executor_logger from aea.runner import AEARunner from aea.skills.base import Skill, SkillContext diff --git a/tests/test_runtime.py b/tests/test_runtime.py index 9f754bddf9..144bdb4006 100644 --- a/tests/test_runtime.py +++ b/tests/test_runtime.py @@ -17,7 +17,9 @@ # # ------------------------------------------------------------------------------ """This module contains tests for aea runtime.""" +import asyncio import os +import time from pathlib import Path from typing import Type from unittest.mock import patch @@ -26,10 +28,16 @@ from aea.aea_builder import AEABuilder from aea.configurations.constants import DEFAULT_LEDGER, DEFAULT_PRIVATE_KEY_FILE -from aea.runtime import AsyncRuntime, BaseRuntime, RuntimeStates, ThreadedRuntime +from aea.runtime import ( + AsyncRuntime, + BaseRuntime, + RuntimeStates, + ThreadedRuntime, + _StopRuntime, +) from tests.common.utils import wait_for_condition -from tests.conftest import CUR_PATH +from tests.conftest import CUR_PATH, DUMMY_SKILL_PUBLIC_ID class TestAsyncRuntime: @@ -60,12 +68,32 @@ def test_start_stop(self): self.runtime.stop() self.runtime.wait_completed(sync=True) + def test_stop_with_stopped_exception(self): + """Test runtime stopped by stopruntime exception.""" + behaviour = self.agent.resources.get_behaviour(DUMMY_SKILL_PUBLIC_ID, "dummy") + with patch.object( + behaviour, "act", side_effect=_StopRuntime(reraise=ValueError("expected")) + ): + self.runtime.start() + wait_for_condition(lambda: self.runtime.is_running, timeout=20) + time.sleep(1) + assert self.runtime.is_stopped + + with pytest.raises(ValueError, match="expected"): + self.runtime.wait_completed(timeout=10, sync=True) + def test_double_start(self): """Test runtime double start do nothing.""" assert self.runtime.start() assert not self.runtime.start() wait_for_condition(lambda: self.runtime.is_running, timeout=20) + def test_set_loop(self): + """Test set loop method.""" + loop = asyncio.new_event_loop() + self.runtime.set_loop(loop) + assert self.runtime.loop is loop + def test_double_stop(self): """Test runtime double stop do nothing.""" self.runtime.start() diff --git a/tests/test_skills/test_base.py b/tests/test_skills/test_base.py index 4becbd346d..a4f41b8cfa 100644 --- a/tests/test_skills/test_base.py +++ b/tests/test_skills/test_base.py @@ -298,7 +298,7 @@ def test_kwargs_not_empty(self): def test_load_skill(): """Test the loading of a skill.""" - agent_context = MagicMock() + agent_context = MagicMock(agent_name="name") skill = Skill.from_dir( Path(ROOT_DIR, "tests", "data", "dummy_skill"), agent_context=agent_context ) diff --git a/tests/test_test_tools/test_testcases.py b/tests/test_test_tools/test_test_cases.py similarity index 69% rename from tests/test_test_tools/test_testcases.py rename to tests/test_test_tools/test_test_cases.py index eee7a70b28..24a71c4969 100644 --- a/tests/test_test_tools/test_testcases.py +++ b/tests/test_test_tools/test_test_cases.py @@ -43,20 +43,76 @@ class TestConfigCases(AEATestCaseEmpty): """Test config set/get.""" - def test_agent_force_set(self): - """Test agent test force set from path.""" + def test_agent_nested_set_agent_crudcollection(self): + """Test agent test nested set from path.""" key_name = "agent.private_key_paths.cosmos" - self.force_set_config(key_name, "testdata2000") + self.nested_set_config(key_name, "testdata2000") result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) assert b"testdata2000" in result.stdout_bytes - def test_agent_set(self): - """Test agent test set from path.""" - self.set_config("agent.author", "testauthor21") + def test_agent_nested_set_agent_crudcollection_all(self): + """Test agent test nested set from path.""" + key_name = "agent.private_key_paths" + self.nested_set_config(key_name, {"cosmos": "testdata2000"}) + result = self.run_cli_command( + "config", "get", f"{key_name}.cosmos", cwd=self._get_cwd() + ) + assert b"testdata2000" in result.stdout_bytes + + def test_agent_nested_set_agent_simple(self): + """Test agent test nested set from path.""" + key_name = "agent.registry_path" + self.nested_set_config(key_name, "some_path") + result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) + assert b"some_path" in result.stdout_bytes + + def test_agent_nested_set_skill_simple(self): + """Test agent test nested set from path.""" + key_name = "vendor.fetchai.skills.error.handlers.error_handler.args.some_key" + self.nested_set_config(key_name, "some_value") + result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) + assert b"some_value" in result.stdout_bytes + + def test_agent_nested_set_skill_simple_nested(self): + """Test agent test nested set from path.""" + key_name = "vendor.fetchai.skills.error.handlers.error_handler.args.some_key" + self.nested_set_config(f"{key_name}.some_nested_key", "some_value") + + def test_agent_nested_set_skill_all(self): + """Test agent test nested set from path.""" + key_name = "vendor.fetchai.skills.error.handlers.error_handler.args" + self.nested_set_config(key_name, {"some_key": "some_value"}) result = self.run_cli_command( - "config", "get", "agent.author", cwd=self._get_cwd() + "config", "get", f"{key_name}.some_key", cwd=self._get_cwd() + ) + assert b"some_value" in result.stdout_bytes + + def test_agent_nested_set_skill_all_nested(self): + """Test agent test nested set from path.""" + key_name = "vendor.fetchai.skills.error.handlers.error_handler.args" + self.nested_set_config( + key_name, {"some_key": {"some_nested_key": "some_value"}} ) - assert b"testauthor21" in result.stdout_bytes + + def test_agent_nested_set_connection_simple(self): + """Test agent test nested set from path.""" + key_name = "vendor.fetchai.connections.stub.config.input_file" + self.nested_set_config(key_name, "some_value") + result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) + assert b"some_value" in result.stdout_bytes + + def test_agent_nested_set_connection_dependency(self): + """Test agent test nested set from path.""" + key_name = "vendor.fetchai.connections.stub.dependencies" + self.nested_set_config(key_name, {"dep": {"version": "==1.0.0"}}) + + def test_agent_set(self): + """Test agent test set from path.""" + value = "testvalue" + key_name = "agent.logging_config.disable_existing_loggers" + self.set_config(key_name, value) + result = self.run_cli_command("config", "get", key_name, cwd=self._get_cwd()) + assert value in str(result.stdout_bytes) def test_agent_get_exception(self): """Test agent test get non exists key.""" @@ -102,7 +158,7 @@ def fn(): def test_fetch_and_delete(self): """Fetch and delete agent from repo.""" agent_name = "some_agent_for_tests" - self.fetch_agent("fetchai/my_first_aea:0.12.0", agent_name) + self.fetch_agent("fetchai/my_first_aea:0.13.0", agent_name) assert os.path.exists(agent_name) self.delete_agents(agent_name) assert not os.path.exists(agent_name) @@ -110,7 +166,7 @@ def test_fetch_and_delete(self): def test_diff(self): """Test difference_to_fetched_agent.""" agent_name = "some_agent_for_tests2" - self.fetch_agent("fetchai/my_first_aea:0.12.0", agent_name) + self.fetch_agent("fetchai/my_first_aea:0.13.0", agent_name) self.run_cli_command( "config", "set", "agent.default_ledger", "test_ledger", cwd=agent_name ) @@ -119,7 +175,7 @@ def test_diff(self): ) assert b"test_ledger" in result.stdout_bytes diff = self.difference_to_fetched_agent( - "fetchai/my_first_aea:0.12.0", agent_name + "fetchai/my_first_aea:0.13.0", agent_name ) assert diff assert "test_ledger" in diff[1] @@ -127,9 +183,9 @@ def test_diff(self): def test_no_diff(self): """Test no difference for two aea configs.""" agent_name = "some_agent_for_tests3" - self.fetch_agent("fetchai/my_first_aea:0.12.0", agent_name) + self.fetch_agent("fetchai/my_first_aea:0.13.0", agent_name) diff = self.difference_to_fetched_agent( - "fetchai/my_first_aea:0.12.0", agent_name + "fetchai/my_first_aea:0.13.0", agent_name ) assert not diff @@ -168,10 +224,10 @@ class TestAddAndRejectComponent(AEATestCaseEmpty): def test_add_and_eject(self): """Test add/reject components.""" - result = self.add_item("skill", "fetchai/echo:0.8.0", local=True) + result = self.add_item("skill", "fetchai/echo:0.9.0", local=True) assert result.exit_code == 0 - result = self.eject_item("skill", "fetchai/echo:0.8.0") + result = self.eject_item("skill", "fetchai/echo:0.9.0") assert result.exit_code == 0 @@ -218,7 +274,7 @@ class TestSendReceiveEnvelopesSkill(AEATestCaseEmpty): def test_send_receive_envelope(self): """Run the echo skill sequence.""" - self.add_item("skill", "fetchai/echo:0.8.0") + self.add_item("skill", "fetchai/echo:0.9.0") process = self.run_agent() is_running = self.is_running(process) diff --git a/tests/test_test_tools/test_test_skill.py b/tests/test_test_tools/test_test_skill.py new file mode 100644 index 0000000000..605125e56a --- /dev/null +++ b/tests/test_test_tools/test_test_skill.py @@ -0,0 +1,596 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ +"""This module contains a test for aea.test_tools.test_cases.""" + +from pathlib import Path +from typing import cast + +import pytest + +from aea.exceptions import AEAEnforceError +from aea.mail.base import Address +from aea.protocols.base import Message +from aea.protocols.default.message import DefaultMessage +from aea.protocols.dialogue.base import Dialogue, DialogueLabel, DialogueMessage +from aea.skills.base import Skill +from aea.test_tools.test_skill import BaseSkillTestCase + +from packages.fetchai.protocols.fipa.dialogues import FipaDialogue +from packages.fetchai.protocols.fipa.dialogues import FipaDialogues as BaseFipaDialogues +from packages.fetchai.protocols.fipa.message import FipaMessage + +from tests.conftest import ROOT_DIR + + +class TestSkillTestCase(BaseSkillTestCase): + """Test case for BaseSkillTestCase.""" + + path_to_skill = Path(ROOT_DIR, "tests", "data", "dummy_skill") + + def test_setup(self): + """Test the setup() class method.""" + assert self.skill.skill_context.agent_address == "test_agent_address" + assert self.skill.skill_context.agent_name == "test_agent_name" + assert ( + self.skill.skill_context.search_service_address + == "dummy_search_service_address" + ) + assert ( + self.skill.skill_context.decision_maker_address + == "dummy_decision_maker_address" + ) + assert "dummy" in self.skill.behaviours.keys() + assert "dummy" in self.skill.handlers.keys() + assert "dummy_internal" in self.skill.handlers.keys() + assert "dummy" in self.skill.models.keys() + + def test_properties(self): + """Test the properties.""" + assert isinstance(self.skill, Skill) + assert self.skill.behaviours.get("dummy") is not None + + def test_get_quantity_in_outbox(self): + """Test the get_quantity_in_outbox method.""" + assert self.get_quantity_in_outbox() == 0 + + dummy_message = Message(dummy="dummy") + dummy_message.to = "some_to" + dummy_message.sender = "some_sender" + self.skill.skill_context.outbox.put_message(dummy_message) + + assert self.get_quantity_in_outbox() == 1 + + def test_get_message_from_outbox(self): + """Test the get_message_from_outbox method.""" + assert self.get_message_from_outbox() is None + + dummy_message_1 = Message(dummy_1="dummy_1") + dummy_message_1.to = "some_to_1" + dummy_message_1.sender = "some_sender_1" + self.skill.skill_context.outbox.put_message(dummy_message_1) + + dummy_message_2 = Message(dummy_2="dummy_2") + dummy_message_2.to = "some_to_2" + dummy_message_2.sender = "some_sender_2" + self.skill.skill_context.outbox.put_message(dummy_message_2) + + assert self.get_message_from_outbox() == dummy_message_1 + assert self.get_message_from_outbox() == dummy_message_2 + + def test_get_quantity_in_decision_maker_inbox(self): + """Test the get_quantity_in_decision_maker_inbox method.""" + assert self.get_quantity_in_decision_maker_inbox() == 0 + + dummy_message = Message(dummy="dummy") + dummy_message.to = "some_to" + dummy_message.sender = "some_sender" + self.skill.skill_context.decision_maker_message_queue.put(dummy_message) + + assert self.get_quantity_in_decision_maker_inbox() == 1 + + def test_get_message_from_decision_maker_inbox(self): + """Test the get_message_from_decision_maker_inbox method.""" + assert self.get_message_from_decision_maker_inbox() is None + + dummy_message_1 = Message(dummy_1="dummy_1") + dummy_message_1.to = "some_to_1" + dummy_message_1.sender = "some_sender_1" + self.skill.skill_context.decision_maker_message_queue.put(dummy_message_1) + + dummy_message_2 = Message(dummy_2="dummy_2") + dummy_message_2.to = "some_to_2" + dummy_message_2.sender = "some_sender_2" + self.skill.skill_context.decision_maker_message_queue.put(dummy_message_2) + + assert self.get_message_from_decision_maker_inbox() == dummy_message_1 + assert self.get_message_from_decision_maker_inbox() == dummy_message_2 + + def test_assert_quantity_in_outbox(self): + """Test the assert_quantity_in_outbox method.""" + with pytest.raises( + AssertionError, + match=f"Invalid number of messages in outbox. Expected {1}. Found {0}.", + ): + self.assert_quantity_in_outbox(1) + + dummy_message = Message(dummy="dummy") + dummy_message.to = "some_to" + dummy_message.sender = "some_sender" + self.skill.skill_context.outbox.put_message(dummy_message) + + self.assert_quantity_in_outbox(1) + + def test_assert_quantity_in_decision_making_queue(self): + """Test the assert_quantity_in_decision_making_queue method.""" + with pytest.raises( + AssertionError, + match=f"Invalid number of messages in decision maker queue. Expected {1}. Found {0}.", + ): + self.assert_quantity_in_decision_making_queue(1) + + dummy_message = Message(dummy_1="dummy_1") + dummy_message.to = "some_to_1" + dummy_message.sender = "some_sender_1" + self.skill.skill_context.decision_maker_message_queue.put(dummy_message) + + self.assert_quantity_in_decision_making_queue(1) + + def test_positive_message_has_attributes_valid_type(self): + """Test the message_has_attributes method where the message is of the specified type.""" + dummy_message = FipaMessage( + dialogue_reference=("0", "0"), + message_id=1, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + ) + dummy_message.to = "some_to" + dummy_message.sender = "some_sender" + + valid_has_attribute, valid_has_attribute_msg = self.message_has_attributes( + actual_message=dummy_message, + message_type=FipaMessage, + message_id=1, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + to="some_to", + sender="some_sender", + ) + assert valid_has_attribute + assert ( + valid_has_attribute_msg + == "The message has the provided expected attributes." + ) + + def test_negative_message_has_attributes_invalid_message_id(self): + """Negative test for message_has_attributes method where the message id does NOT match.""" + dummy_message = FipaMessage( + dialogue_reference=("0", "0"), + message_id=1, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + ) + dummy_message.to = "some_to" + dummy_message.sender = "some_sender" + + invalid_has_attribute, invalid_has_attribute_msg = self.message_has_attributes( + actual_message=dummy_message, + message_type=FipaMessage, + message_id=2, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + to="some_to", + sender="some_sender", + ) + assert not invalid_has_attribute + assert ( + invalid_has_attribute_msg + == "The 'message_id' fields do not match. Actual 'message_id': 1. Expected 'message_id': 2" + ) + + def test_negative_message_has_attributes_invalid_type(self): + """Test the message_has_attributes method where the message is NOT of the specified type.""" + dummy_message = FipaMessage( + dialogue_reference=("0", "0"), + message_id=1, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + ) + dummy_message.to = "some_to" + dummy_message.sender = "some_sender" + + valid_has_attribute, valid_has_attribute_msg = self.message_has_attributes( + actual_message=dummy_message, + message_type=DefaultMessage, + message_id=1, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + to="some_to", + sender="some_sender", + ) + assert not valid_has_attribute + assert ( + valid_has_attribute_msg + == f"The message types do not match. Actual type: {FipaMessage}. Expected type: {DefaultMessage}" + ) + + invalid_has_attribute, invalid_has_attribute_msg = self.message_has_attributes( + actual_message=dummy_message, + message_type=FipaMessage, + message_id=2, + performative=FipaMessage.Performative.CFP, + target=0, + query="some_query", + to="some_to", + sender="some_sender", + ) + assert not invalid_has_attribute + assert ( + invalid_has_attribute_msg + == "The 'message_id' fields do not match. Actual 'message_id': 1. Expected 'message_id': 2" + ) + + def test_build_incoming_message(self): + """Test the build_incoming_message method.""" + message_type = FipaMessage + performative = FipaMessage.Performative.CFP + dialogue_reference = ("1", "1") + to = "some_to" + query = "some_query" + incoming_message = self.build_incoming_message( + message_type=message_type, + performative=performative, + dialogue_reference=dialogue_reference, + to=to, + query=query, + ) + + assert type(incoming_message) == message_type + incoming_message = cast(FipaMessage, incoming_message) + assert incoming_message.dialogue_reference == dialogue_reference + assert incoming_message.message_id == 1 + assert incoming_message.target == 0 + assert incoming_message.performative == performative + assert incoming_message.query == query + assert incoming_message.sender == "counterparty" + assert incoming_message.to == to + + def test_positive_build_incoming_message_for_skill_dialogue(self): + """Positive test for build_incoming_message_for_skill_dialogue method.""" + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + _, dialogue = fipa_dialogues.create( + counterparty="some_counterparty", + performative=FipaMessage.Performative.CFP, + query="some_query", + ) + + performative = FipaMessage.Performative.PROPOSE + proposal = "some_proposal" + incoming_message = self.build_incoming_message_for_skill_dialogue( + dialogue=dialogue, performative=performative, proposal=proposal, + ) + + assert type(incoming_message) == FipaMessage + incoming_message = cast(FipaMessage, incoming_message) + assert ( + incoming_message.dialogue_reference + == dialogue.dialogue_label.dialogue_reference + ) + assert incoming_message.message_id == 2 + assert incoming_message.target == 1 + assert incoming_message.performative == performative + assert incoming_message.proposal == proposal + assert incoming_message.sender == dialogue.dialogue_label.dialogue_opponent_addr + assert incoming_message.to == dialogue.self_address + + def test_negative_build_incoming_message_for_skill_dialogue_dialogue_is_none(self): + """Negative test for build_incoming_message_for_skill_dialogue method where the provided dialogue is None.""" + performative = FipaMessage.Performative.PROPOSE + proposal = "some_proposal" + + with pytest.raises(AEAEnforceError, match="dialogue cannot be None."): + self.build_incoming_message_for_skill_dialogue( + dialogue=None, performative=performative, proposal=proposal, + ) + + def test_negative_build_incoming_message_for_skill_dialogue_dialogue_is_empty(self): + """Negative test for build_incoming_message_for_skill_dialogue method where the provided dialogue is empty.""" + performative = FipaMessage.Performative.PROPOSE + proposal = "some_proposal" + + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + dialogue = fipa_dialogues._create_self_initiated( + dialogue_opponent_addr="some_counterparty", + dialogue_reference=("0", ""), + role=FipaDialogue.Role.BUYER, + ) + + with pytest.raises(AEAEnforceError, match="dialogue cannot be empty."): + self.build_incoming_message_for_skill_dialogue( + dialogue=dialogue, performative=performative, proposal=proposal, + ) + + def test_provide_unspecified_fields(self): + """Test the _provide_unspecified_fields method.""" + dialogue_message_unspecified = DialogueMessage( + FipaMessage.Performative.ACCEPT, {} + ) + + is_incoming, target = self._provide_unspecified_fields( + dialogue_message_unspecified, last_is_incoming=False, message_id=2 + ) + assert is_incoming is True + assert target == 1 + + dialogue_message_specified = DialogueMessage( + FipaMessage.Performative.ACCEPT, {}, False, 4 + ) + + is_incoming, target = self._provide_unspecified_fields( + dialogue_message_specified, last_is_incoming=True, message_id=7 + ) + assert is_incoming is False + assert target == 4 + + def test_non_initial_incoming_message_dialogue_reference(self): + """Test the _non_initial_incoming_message_dialogue_reference method.""" + dialogue_incomplete_ref = FipaDialogue( + DialogueLabel(("2", ""), "opponent", "self_address"), + "self_address", + FipaDialogue.Role.BUYER, + ) + reference_incomplete = self._non_initial_incoming_message_dialogue_reference( + dialogue_incomplete_ref + ) + assert reference_incomplete[1] != "" + + dialogue_complete_ref = FipaDialogue( + DialogueLabel(("2", "7"), "opponent", "self_address"), + "self_address", + FipaDialogue.Role.BUYER, + ) + reference_complete = self._non_initial_incoming_message_dialogue_reference( + dialogue_complete_ref + ) + assert reference_complete[1] == "7" + + def test_extract_message_fields(self): + """Test the _extract_message_fields method.""" + expected_performative = FipaMessage.Performative.ACCEPT + expected_contents = {} + expected_is_incoming = False + expected_target = 4 + dialogue_message = DialogueMessage( + expected_performative, + expected_contents, + expected_is_incoming, + expected_target, + ) + + ( + actual_performative, + actual_contents, + actual_message_id, + actual_is_incoming, + actual_target, + ) = self._extract_message_fields( + message=dialogue_message, index=3, last_is_incoming=True + ) + + assert actual_message_id == 4 + assert actual_target == expected_target + assert actual_performative == expected_performative + assert actual_contents == expected_contents + assert actual_is_incoming == expected_is_incoming + + def test_prepare_skill_dialogue_valid_self_initiated(self): + """Positive test for prepare_skill_dialogue method with a valid dialogue initiated by self.""" + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + dialogue_messages = ( + DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_1"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_2"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_3"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_4"}, + ), + DialogueMessage(FipaMessage.Performative.ACCEPT, {}), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": "some_info"} + ), + ) + dialogue = self.prepare_skill_dialogue( + fipa_dialogues, dialogue_messages, "counterparty", + ) + + assert type(dialogue) == FipaDialogue + assert dialogue.is_self_initiated + assert len(dialogue._outgoing_messages) == 4 + assert len(dialogue._incoming_messages) == 4 + assert dialogue._get_message(4).proposal == "some_counter_proposal_2" + assert dialogue._get_message(8).info == "some_info" + + def test_prepare_skill_dialogue_valid_opponent_initiated(self): + """Positive test for prepare_skill_dialogue method with a valid dialogue initiated by the opponent.""" + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + dialogue_messages = ( + DialogueMessage( + FipaMessage.Performative.CFP, {"query": "some_query"}, True + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"} + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_1"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_2"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_3"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_4"}, + ), + DialogueMessage(FipaMessage.Performative.ACCEPT, {}), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": "some_info"} + ), + ) + dialogue = self.prepare_skill_dialogue( + fipa_dialogues, dialogue_messages, "counterparty", + ) + + assert type(dialogue) == FipaDialogue + assert not dialogue.is_self_initiated + assert len(dialogue._outgoing_messages) == 4 + assert len(dialogue._incoming_messages) == 4 + assert dialogue._get_message(4).proposal == "some_counter_proposal_2" + assert dialogue._get_message(8).info == "some_info" + + def test_negative_prepare_skill_dialogue_invalid_opponent_initiated(self): + """Negative test for prepare_skill_dialogue method with an invalid dialogue initiated by the opponent.""" + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + dialogue_messages = ( + DialogueMessage( + FipaMessage.Performative.PROPOSE, {"proposal": "some_proposal"}, True + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_1"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_2"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_3"}, + ), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_counter_proposal_4"}, + ), + DialogueMessage(FipaMessage.Performative.ACCEPT, {}), + DialogueMessage( + FipaMessage.Performative.MATCH_ACCEPT_W_INFORM, {"info": "some_info"} + ), + ) + with pytest.raises( + AEAEnforceError, match="Cannot update the dialogue with message number 1" + ): + self.prepare_skill_dialogue( + fipa_dialogues, dialogue_messages, "counterparty", + ) + + def test_negative_prepare_skill_dialogue_empty_messages(self): + """Negative test for prepare_skill_dialogue method where the list of DialogueMessages is emoty.""" + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + dialogue_messages = tuple() + + with pytest.raises( + AEAEnforceError, match="the list of messages must be positive." + ): + self.prepare_skill_dialogue( + fipa_dialogues, dialogue_messages, "counterparty", + ) + + def test_negative_prepare_skill_dialogue_invalid(self): + """Negative test for prepare_skill_dialogue method with an invalid dialogue (a message has invalid target).""" + fipa_dialogues = FipaDialogues( + self_address=self.skill.skill_context.agent_address + ) + dialogue_messages = ( + DialogueMessage(FipaMessage.Performative.CFP, {"query": "some_query"}), + DialogueMessage( + FipaMessage.Performative.PROPOSE, + {"proposal": "some_proposal"}, + target=2, + ), + ) + + with pytest.raises( + AEAEnforceError, match="Cannot update the dialogue with message number 2" + ): + self.prepare_skill_dialogue( + fipa_dialogues, dialogue_messages, "counterparty", + ) + + +class FipaDialogues(BaseFipaDialogues): + """The dialogues class keeps track of all dialogues.""" + + def __init__(self, self_address: Address, **kwargs) -> None: + """ + Initialize dialogues. + + :return: None + """ + + def role_from_first_message( # pylint: disable=unused-argument + message: Message, receiver_address: Address + ) -> Dialogue.Role: + """Infer the role of the agent from an incoming/outgoing first message + + :param message: an incoming/outgoing first message + :param receiver_address: the address of the receiving agent + :return: The role of the agent + """ + return FipaDialogue.Role.BUYER + + BaseFipaDialogues.__init__( + self, + self_address=self_address, + role_from_first_message=role_from_first_message, + dialogue_class=FipaDialogue, + ) diff --git a/tox.ini b/tox.ini index 651d091b37..3c1f250da1 100644 --- a/tox.ini +++ b/tox.ini @@ -36,7 +36,7 @@ deps = pexpect==4.8.0 pytest-rerunfailures==9.0 -commands = pytest -rfE --doctest-modules aea packages/fetchai/connections packages/fetchai/protocols tests/ --cov-report=html --cov-report=xml --cov-report=term --cov-report=term-missing --cov=aea --cov=packages/fetchai/connections --cov=packages/fetchai/protocols --cov-config=.coveragerc {posargs} +commands = pytest -rfE --doctest-modules aea packages/fetchai/connections packages/fetchai/protocols packages/fetchai/skills/generic_buyer tests/ --cov-report=html --cov-report=xml --cov-report=term --cov-report=term-missing --cov=aea --cov=packages/fetchai/connections --cov=packages/fetchai/protocols --cov=packages/fetchai/skills/generic_buyer --cov=packages/fetchai/skills/generic_seller --cov-config=.coveragerc {posargs} [testenv:py3.6] basepython = python3.6