def _load_state(self, local: bool) -> None: """ Load saved state from file. :param local: bool is local project and agents re-creation. :return: None :raises: ValueError if failed to load state. """ if not os.path.exists(self._save_path): return save_json = {} with open(self._save_path) as f: save_json = json.load(f) if not save_json: return # pragma: nocover try: for public_id in save_json["projects"]: self.add_project( PublicId.from_str(public_id), local=local, restore=True ) for agent_settings in save_json["agents"]: self.add_agent_with_config( public_id=PublicId.from_str(agent_settings["public_id"]), agent_name=agent_settings["agent_name"], config=agent_settings["config"], ) except ValueError as e: # pragma: nocover raise ValueError(f"Failed to load state. {e}")
def add_project( self, public_id: PublicId, local: bool = True, restore: bool = False ) -> "MultiAgentManager": """ Fetch agent project and all dependencies to working_dir. :param public_id: the public if of the agent project. :param local: whether or not to fetch from local registry. :param restore: bool flag for restoring already fetched agent. """ if public_id.to_any() in self._versionless_projects_set: raise ValueError( f"The project ({public_id.author}/{public_id.name}) was already added!" ) self._versionless_projects_set.add(public_id.to_any()) project = Project.load( self.working_dir, public_id, local, registry_path=self.registry_path, is_restore=restore, ) if not restore: project.install_pypi_dependencies() project.build() self._projects[public_id] = project return self
def test_local_registry_update(): """Test local-registry-sync cli command.""" PACKAGES = [ PackageId(PackageType.CONNECTION, PublicId("fetchai", "local", "0.11.0")), PackageId(PackageType.AGENT, PublicId("fetchai", "my_first_aea", "0.10.0")), ] with TemporaryDirectory() as tmp_dir: for package_id in PACKAGES: package_dir = os.path.join( tmp_dir, package_id.public_id.author, str(package_id.package_type.to_plural()), package_id.public_id.name, ) os.makedirs(package_dir) fetch_package( str(package_id.package_type), public_id=package_id.public_id, cwd=tmp_dir, dest=package_dir, ) assert set(PACKAGES) == set([i[0] for i in enlist_packages(tmp_dir)]) runner = CliRunner() with cd(tmp_dir): # check intention to upgrade with patch( "aea.cli.local_registry_sync.replace_package" ) as replace_package_mock: result = runner.invoke( cli, ["-s", "local-registry-sync"], catch_exceptions=False ) assert result.exit_code == 0, result.stdout assert replace_package_mock.call_count == len(PACKAGES) # do actual upgrade result = runner.invoke( cli, ["-s", "local-registry-sync"], catch_exceptions=False ) assert result.exit_code == 0, result.stdout # check next update will do nothing with patch( "aea.cli.local_registry_sync.replace_package" ) as replace_package_mock: result = runner.invoke( cli, ["-s", "local-registry-sync"], catch_exceptions=False ) assert result.exit_code == 0, result.stdout assert replace_package_mock.call_count == 0 def sort_(packages): return sorted(packages, key=lambda x: str(x)) new_packages = [i[0] for i in enlist_packages(tmp_dir)] for new_package, old_package in zip(sort_(new_packages), sort_(PACKAGES)): assert new_package.public_id > old_package.public_id
def test_find_component_directory_from_component_id(): """Test find_component_directory_from_component_id.""" with pytest.raises(ValueError, match=r"Package .* not found."): find_component_directory_from_component_id( Path("."), ComponentId( component_type=CONNECTION, public_id=PublicId("test", "test", "1.0.1") ), )
def _load_state(self, local: bool, remote: bool) -> None: """ Load saved state from file. Fetch agent project and all dependencies to working_dir. If local = False and remote = False, then the packages are fetched in mixed mode (i.e. first try from local registry, and then from remote registry in case of failure). :param local: whether or not to fetch from local registry. :param remote: whether or not to fetch from remote registry. :return: None :raises: ValueError if failed to load state. """ if not os.path.exists(self._save_path): return save_json = {} with open_file(self._save_path) as f: save_json = json.load(f) if not save_json: return # pragma: nocover try: for public_id in save_json["projects"]: self.add_project( PublicId.from_str(public_id), local=local, remote=remote, restore=True, ) for agent_settings in save_json["agents"]: self.add_agent_with_config( public_id=PublicId.from_str(agent_settings["public_id"]), agent_name=agent_settings["agent_name"], config=agent_settings["config"], ) except ValueError as e: # pragma: nocover raise ValueError(f"Failed to load state. {e}")
def get_package_latest_public_id(package_id: PackageId) -> PublicId: """ Get package latest package id from the remote repo. :param package_id: id of the package to check :return: package id of the latest package in remote repo """ package_meta = get_package_meta(str(package_id.package_type), package_id.public_id.to_latest()) return PublicId.from_str(package_meta["public_id"])
def _bump_protocol_specification_id(package_path: Path, configuration: ProtocolConfig) -> None: """Bump spec id version.""" spec_id: PublicId = configuration.protocol_specification_id # type: ignore old_version = semver.VersionInfo.parse(spec_id.version) new_version = str(old_version.bump_minor()) new_spec_id = PublicId(spec_id.author, spec_id.name, new_version) configuration.protocol_specification_id = new_spec_id with (package_path / DEFAULT_PROTOCOL_CONFIG_FILE).open("w") as file_output: ConfigLoaders.from_package_type(configuration.package_type).dump( configuration, file_output)
def remove_project( self, public_id: PublicId, keep_files: bool = False ) -> "MultiAgentManager": """Remove agent project.""" if public_id not in self._projects: raise ValueError(f"Project {public_id} is not present!") if self._projects[public_id].agents: raise ValueError( f"Can not remove projects with aliases exists: {self._projects[public_id].agents}" ) project = self._projects.pop(public_id) self._versionless_projects_set.remove(public_id.to_any()) if not keep_files: project.remove() return self
class SampleConnection(BaseSyncConnection): """Sample connection for testing.""" MAX_WORKER_THREADS = 3 connection_id = PublicId("test", "test", "0.1.0") PAUSE = 0.5 def __init__(self, *args, **kwargs): """Init connection.""" super().__init__(*args, **kwargs) self.main_called = False self.send_counter = 0 self.on_connect_called = False self.on_disconnect_called = False def main(self): """Run main.""" self.main_called = True envelope = Mock() envelope.message = "main" self.put_envelope(envelope) def on_send(self, envelope: Envelope) -> None: """Run on send.""" time.sleep(self.PAUSE) resp_envelope = Mock() resp_envelope.message = f"resp for {str(envelope.message)}" self.put_envelope(resp_envelope) self.send_counter += 1 def on_connect(self): """Run on connect.""" self.on_connect_called = True def on_disconnect(self): """Run on disconnect.""" self.on_disconnect_called = True
def split_component_id_and_config( component_index: int, component_configuration_json: Dict) -> ComponentId: """ Split component id and configuration. :param component_index: the position of the component configuration in the agent config file.. :param component_configuration_json: the JSON object to process. :return: the component id and the configuration object. :raises ValueError: if the component id cannot be extracted. """ # author, name, version, type are mandatory fields missing_fields = {"public_id", "type" }.difference(component_configuration_json.keys()) if len(missing_fields) > 0: raise ValueError( f"There are missing fields in component id {component_index + 1}: {missing_fields}." ) public_id_str = component_configuration_json.pop("public_id") component_type = ComponentType( component_configuration_json.pop("type")) component_public_id = PublicId.from_str(public_id_str) component_id = ComponentId(component_type, component_public_id) return component_id
def handle_dotted_path( value: str, author: str, aea_project_path: Union[str, Path] = ".", ) -> Tuple[List[str], Path, ConfigLoader, Optional[ComponentId]]: """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|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. :param aea_project_path: project path :return: Tuple[list of settings dict keys, filepath, config loader, component id]. """ parts = value.split(".") aea_project_path = Path(aea_project_path) root = parts[0] if root not in ALLOWED_PATH_ROOTS: raise AEAException( "The root of the dotted path must be one of: {}".format( ALLOWED_PATH_ROOTS)) if (len(parts) < 2 or parts[0] == AGENT and len(parts) < 2 or parts[0] == VENDOR and len(parts) < 5 or parts[0] != AGENT and len(parts) < 3): raise AEAException( "The path is too short. Please specify a path up to an attribute name." ) # if the root is 'agent', stop. if root == AGENT: 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 = (aea_project_path / VENDOR / resource_author / resource_type_plural / resource_name) path_to_resource_configuration = ( path_to_resource_directory / RESOURCE_TYPE_TO_CONFIG_FILE[resource_type_plural]) json_path = parts[4:] if not path_to_resource_directory.exists(): raise AEAException( # pragma: nocover "Resource vendor/{}/{}/{} does not exist.".format( resource_author, resource_type_plural, resource_name)) else: # 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 / RESOURCE_TYPE_TO_CONFIG_FILE[resource_type_plural]) json_path = parts[2:] if not path_to_resource_directory.exists(): raise AEAException("Resource {}/{} does not exist.".format( resource_type_plural, resource_name)) config_loader = ConfigLoader.from_configuration_type( resource_type_plural[:-1]) return json_path, path_to_resource_configuration, config_loader, component_id