def remove_unused_component_configurations(ctx: Context): """ 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: saved_configuration_by_component_prefix = { key.component_prefix: value for key, value in saved_configuration.items() } # need to reload agent configuration with the updated references try_to_load_agent_config(ctx) for component_id in ctx.agent_config.package_dependencies: if component_id.component_prefix in saved_configuration_by_component_prefix: ctx.agent_config.component_configurations[ component_id] = saved_configuration_by_component_prefix[ component_id.component_prefix] with open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as f: ctx.agent_loader.dump(ctx.agent_config, f)
def search(click_context, local): """Search for components in the registry. If called from an agent directory, it will check E.g. aea search connections aea search --local skills """ ctx = cast(Context, click_context.obj) if local: ctx.set_config("is_local", True) # if we are in an agent directory, try to load the configuration file. # otherwise, use the default path (i.e. 'packages/' in the current directory.) try: try_to_load_agent_config(ctx, is_exit_on_except=False) # path = Path(DEFAULT_AEA_CONFIG_FILE) # fp = open(str(path), mode="r", encoding="utf-8") # agent_config = ctx.agent_loader.load(fp) registry_directory = ctx.agent_config.registry_path except Exception: registry_directory = os.path.join(ctx.cwd, DEFAULT_REGISTRY_PATH) ctx.set_config("registry_directory", registry_directory) logger.debug("Using registry {}".format(registry_directory))
def publish_agent(ctx: Context): """Publish an agent.""" try_to_load_agent_config(ctx) check_is_author_logged_in(ctx.agent_config.author) name = ctx.agent_config.agent_name config_file_source_path = os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE) output_tar = os.path.join(ctx.cwd, "{}.tar.gz".format(name)) with tempfile.TemporaryDirectory() as temp_dir: package_dir = os.path.join(temp_dir, name) os.makedirs(package_dir) config_file_target_path = os.path.join(package_dir, DEFAULT_AEA_CONFIG_FILE) shutil.copy(config_file_source_path, config_file_target_path) _compress(output_tar, package_dir) data = { "name": name, "description": ctx.agent_config.description, "version": ctx.agent_config.version, "connections": ctx.agent_config.connections, "contracts": ctx.agent_config.contracts, "protocols": ctx.agent_config.protocols, "skills": ctx.agent_config.skills, } path = "/agents/create" logger.debug("Publishing agent {} to Registry ...".format(name)) resp = request_api("POST", path, data=data, is_auth=True, filepath=output_tar) click.echo( "Successfully published agent {} to the Registry. Public ID: {}".format( name, resp["public_id"] ) )
def fetch_agent_locally( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Fetch Agent from local packages. :param ctx: a Context object. :param public_id: public ID of agent to be fetched. :param alias: an optional alias. :param target_dir: the target directory to which the agent is fetched. :return: None """ packages_path = ( DEFAULT_REGISTRY_NAME if ctx.registry_path is None else ctx.registry_path ) source_path = try_get_item_source_path( packages_path, public_id.author, AGENTS, public_id.name ) enforce( ctx.config.get("is_local") is True or ctx.config.get("is_mixed") is True, "Please use `ctx.set_config('is_local', True)` or `ctx.set_config('is_mixed', True)` to fetch agent and all components locally.", ) try_to_load_agent_config(ctx, agent_src_path=source_path) if not _is_version_correct(ctx, public_id): raise click.ClickException( "Wrong agent version in public ID: specified {}, found {}.".format( public_id.version, ctx.agent_config.version ) ) folder_name = target_dir or (public_id.name if alias is None else alias) target_path = os.path.join(ctx.cwd, folder_name) if os.path.exists(target_path): path = Path(target_path) raise click.ClickException( f'Item "{path.name}" already exists in target folder "{path.parent}".' ) if target_dir is not None: os.makedirs(target_path) # pragma: nocover ctx.clean_paths.append(target_path) copy_tree(source_path, target_path) ctx.cwd = target_path try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias ctx.agent_loader.dump( ctx.agent_config, open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w"), ) _fetch_agent_deps(ctx) click.echo("Agent {} successfully fetched.".format(public_id.name))
def _fetch_agent_locally( click_context, public_id: PublicId, alias: Optional[str] = None ) -> None: """ Fetch Agent from local packages. :param click_context: click context object. :param public_id: public ID of agent to be fetched. :param click_context: the click context. :param alias: an optional alias. :return: None """ packages_path = os.path.basename(DEFAULT_REGISTRY_PATH) source_path = try_get_item_source_path( packages_path, public_id.author, "agents", public_id.name ) ctx = cast(Context, click_context.obj) try_to_load_agent_config(ctx, agent_src_path=source_path) if not _is_version_correct(ctx, public_id): raise click.ClickException( "Wrong agent version in public ID: specified {}, found {}.".format( public_id.version, ctx.agent_config.version ) ) folder_name = public_id.name if alias is None else alias target_path = os.path.join(ctx.cwd, folder_name) if os.path.exists(target_path): raise click.ClickException( 'Item "{}" already exists in target folder.'.format(public_id.name) ) ctx.clean_paths.append(target_path) copy_tree(source_path, target_path) ctx.cwd = target_path try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") ) # add dependencies for item_type in ("skill", "connection", "contract", "protocol"): item_type_plural = "{}s".format(item_type) required_items = getattr(ctx.agent_config, item_type_plural) for item_id in required_items: try: _add_item(click_context, item_type, item_id) except click.ClickException as e: raise click.ClickException( "Failed to add {} dependency {}: {}".format( item_type, item_id, str(e) ) ) click.echo("Agent {} successfully fetched.".format(public_id.name))
def fetch_agent( click_context, public_id: PublicId, alias: Optional[str] = None ) -> None: """ Fetch Agent from Registry. :param ctx: Context :param public_id: str public ID of desirable Agent. :param click_context: the click context. :param alias: an optional alias. :return: None """ author, name, version = public_id.author, public_id.name, public_id.version api_path = "/agents/{}/{}/{}".format(author, name, version) resp = request_api("GET", api_path) file_url = resp["file"] ctx = cast(Context, click_context.obj) filepath = download_file(file_url, ctx.cwd) folder_name = name if alias is None else alias aea_folder = os.path.join(ctx.cwd, folder_name) ctx.clean_paths.append(aea_folder) extract(filepath, ctx.cwd) if alias is not None: os.rename(name, alias) ctx.cwd = aea_folder try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") ) click.echo("Fetching dependencies...") for item_type in ("connection", "contract", "skill", "protocol"): item_type_plural = item_type + "s" # initialize fetched agent with empty folders for custom packages custom_items_folder = os.path.join(ctx.cwd, item_type_plural) os.makedirs(custom_items_folder) config = getattr(ctx.agent_config, item_type_plural) for item_public_id in config: try: _add_item(click_context, item_type, item_public_id) except Exception as e: raise click.ClickException( 'Unable to fetch dependency for agent "{}", aborting. {}'.format( name, e ) ) click.echo("Dependencies successfully fetched.") click.echo("Agent {} successfully fetched to {}.".format(name, aea_folder))
def _check_aea_project(args): try: click_context = args[0] ctx = cast(Context, click_context.obj) try_to_load_agent_config(ctx) skip_consistency_check = ctx.config["skip_consistency_check"] if not skip_consistency_check: _validate_config_consistency(ctx) except Exception as e: raise click.ClickException(str(e))
def _check_aea_project(args: Tuple[Any, ...], check_aea_version: bool = True) -> None: try: click_context = args[0] ctx = cast(Context, click_context.obj) try_to_load_agent_config(ctx) skip_consistency_check = ctx.config["skip_consistency_check"] if not skip_consistency_check: _validate_config_consistency(ctx, check_aea_version=check_aea_version) except Exception as e: # pylint: disable=broad-except raise click.ClickException(str(e))
def fetch_agent_locally( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, is_mixed: bool = True, ) -> None: """ Fetch Agent from local packages. :param ctx: a Context object. :param public_id: public ID of agent to be fetched. :param alias: an optional alias. :param target_dir: the target directory to which the agent is fetched. :param is_mixed: flag to enable mixed mode (try first local, then remote). :return: None """ packages_path = (DEFAULT_REGISTRY_PATH if ctx.registry_path is None else ctx.registry_path) source_path = try_get_item_source_path(packages_path, public_id.author, "agents", public_id.name) try_to_load_agent_config(ctx, agent_src_path=source_path) if not _is_version_correct(ctx, public_id): raise click.ClickException( "Wrong agent version in public ID: specified {}, found {}.".format( public_id.version, ctx.agent_config.version)) folder_name = target_dir or (public_id.name if alias is None else alias) target_path = os.path.join(ctx.cwd, folder_name) if os.path.exists(target_path): raise click.ClickException( 'Item "{}" already exists in target folder.'.format( public_id.name)) if target_dir is not None: os.makedirs(target_path) # pragma: nocover ctx.clean_paths.append(target_path) copy_tree(source_path, target_path) ctx.cwd = target_path try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w")) # add dependencies _fetch_agent_deps(ctx, is_mixed) click.echo("Agent {} successfully fetched.".format(public_id.name))
def _eject_item(ctx: Context, item_type: str, public_id: PublicId): """ Eject item from installed (vendor) to custom folder. :param ctx: context object. :param item_type: item type. :param public_id: item public ID. :return: None :raises: ClickException if item is absent at source path or present at destenation path. """ item_type_plural = item_type + "s" if not is_item_present(ctx, item_type, public_id): # pragma: no cover raise click.ClickException( "{} {} not found in agent's vendor items.".format( item_type.title(), public_id)) src = get_package_path(ctx, item_type, public_id) dst = get_package_path(ctx, item_type, public_id, is_vendor=False) if is_item_present(ctx, item_type, public_id, is_vendor=False): # pragma: no cover raise click.ClickException( "{} {} is already in a non-vendor item.".format( item_type.title(), public_id)) ctx.clean_paths.append(dst) copy_package_directory(Path(src), dst) try_to_load_agent_config(ctx) new_public_id = PublicId(author=ctx.agent_config.author, name=public_id.name, version=DEFAULT_VERSION) update_item_config(item_type, Path(dst), author=new_public_id.author, version=new_public_id.version) update_item_public_id_in_init(item_type, Path(dst), new_public_id) supported_items = getattr(ctx.agent_config, item_type_plural) for p_id in supported_items: if p_id.author == public_id.author and p_id.name == public_id.name: present_public_id = p_id break supported_items.add(new_public_id) supported_items.remove(present_public_id) update_item_config("agent", Path(ctx.cwd), **{item_type_plural: supported_items}) shutil.rmtree(src) fingerprint_item(ctx, item_type, new_public_id) click.echo("Successfully ejected {} {} to {} as {}.".format( item_type, public_id, dst, new_public_id))
def publish_agent(ctx: Context) -> None: """Publish an agent.""" try_to_load_agent_config(ctx) check_is_author_logged_in(ctx.agent_config.author) name = ctx.agent_config.agent_name config_file_source_path = os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE) readme_source_path = os.path.join(ctx.cwd, DEFAULT_README_FILE) output_tar = os.path.join(ctx.cwd, "{}.tar.gz".format(name)) with tempfile.TemporaryDirectory() as temp_dir: package_dir = os.path.join(temp_dir, name) os.makedirs(package_dir) config_file_target_path = os.path.join(package_dir, DEFAULT_AEA_CONFIG_FILE) shutil.copy(config_file_source_path, config_file_target_path) if is_readme_present(readme_source_path): readme_file_target_path = os.path.join(package_dir, DEFAULT_README_FILE) shutil.copy(readme_source_path, readme_file_target_path) _compress(output_tar, package_dir) data = { "name": name, "description": ctx.agent_config.description, "version": ctx.agent_config.version, CONNECTIONS: ctx.agent_config.connections, CONTRACTS: ctx.agent_config.contracts, PROTOCOLS: ctx.agent_config.protocols, SKILLS: ctx.agent_config.skills, } files = {} try: files["file"] = open(output_tar, "rb") if is_readme_present(readme_source_path): files["readme"] = open(readme_source_path, "rb") path = "/agents/create" logger.debug("Publishing agent {} to Registry ...".format(name)) resp = cast( JSONLike, request_api("POST", path, data=data, is_auth=True, files=files) ) finally: for fd in files.values(): fd.close() click.echo( "Successfully published agent {} to the Registry. Public ID: {}".format( name, resp["public_id"] ) )
def get_local_items(agent_id: str, item_type: str): """Return a list of protocols, skills or connections supported by a local agent.""" if agent_id == "NONE": return [], 200 # 200 (Success) # need to place ourselves one directory down so the searcher can find the packages ctx = Context(cwd=os.path.join(app_context.agents_dir, agent_id)) try: try_to_load_agent_config(ctx) result = cli_list_agent_items(ctx, item_type) except ClickException: return { "detail": "Failed to list agent items." }, 400 # 400 Bad request else: sorted_items = sort_items(result) return sorted_items, 200 # 200 (Success)
def convert(self, value, param, ctx): """Convert the value. This is not invoked for values that are `None` (the missing value).""" cwd = os.getcwd() path = Path(value) try: # check that the target folder is an AEA project. os.chdir(path) fp = open(DEFAULT_AEA_CONFIG_FILE, mode="r", encoding="utf-8") ctx.obj.agent_config = ctx.obj.agent_loader.load(fp) try_to_load_agent_config(ctx.obj) # everything ok - return the parameter to the command return value except Exception: raise click.ClickException( "The name provided is not a path to an AEA project.") finally: os.chdir(cwd)
def scaffold_item(agent_id: str, item_type: str, item_id: str): """Scaffold a moslty empty item on an agent (either protocol, skill or connection).""" agent_dir = os.path.join(app_context.agents_dir, agent_id) ctx = Context(cwd=agent_dir) try: try_to_load_agent_config(ctx) cli_scaffold_item(ctx, item_type, item_id) except ClickException: return ( { "detail": "Failed to scaffold a new {} in to agent {}".format( item_type, agent_id ) }, 400, ) # 400 Bad request else: return agent_id, 201 # 200 (OK)
def remove_local_item(agent_id: str, item_type: str, item_id: str): """Remove a protocol, skill or connection from a local agent.""" agent_dir = os.path.join(app_context.agents_dir, agent_id) ctx = Context(cwd=agent_dir) try: try_to_load_agent_config(ctx) cli_remove_item(ctx, item_type, PublicId.from_str(item_id)) except ClickException: return ( { "detail": "Failed to remove {} {} from agent {}".format( item_type, item_id, agent_id ) }, 400, ) # 400 Bad request else: return agent_id, 201 # 200 (OK)
def add_item(agent_id: str, item_type: str, item_id: str): """Add a protocol, skill or connection to the register to a local agent.""" ctx = Context(cwd=os.path.join(app_context.agents_dir, agent_id)) ctx.set_config("is_local", app_context.local) try: try_to_load_agent_config(ctx) cli_add_item(ctx, item_type, PublicId.from_str(item_id)) except ClickException as e: return ( { "detail": "Failed to add {} {} to agent {}. {}".format( item_type, item_id, agent_id, str(e) ) }, 400, ) # 400 Bad request else: return agent_id, 201 # 200 (OK)
def fetch_agent_locally(ctx: Context, public_id: PublicId, alias: Optional[str] = None) -> None: """ Fetch Agent from local packages. :param ctx: a Context object. :param public_id: public ID of agent to be fetched. :param alias: an optional alias. :return: None """ packages_path = os.path.basename(DEFAULT_REGISTRY_PATH) source_path = try_get_item_source_path(packages_path, public_id.author, "agents", public_id.name) try_to_load_agent_config(ctx, agent_src_path=source_path) if not _is_version_correct(ctx, public_id): raise click.ClickException( "Wrong agent version in public ID: specified {}, found {}.".format( public_id.version, ctx.agent_config.version)) folder_name = public_id.name if alias is None else alias target_path = os.path.join(ctx.cwd, folder_name) if os.path.exists(target_path): raise click.ClickException( 'Item "{}" already exists in target folder.'.format( public_id.name)) ctx.clean_paths.append(target_path) copy_tree(source_path, target_path) ctx.cwd = target_path try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w")) # add dependencies _fetch_agent_deps(ctx) click.echo("Agent {} successfully fetched.".format(public_id.name))
def setup_search_ctx(ctx: Context, local: bool) -> None: """ Set up search command. :param click_context: click context object. :param local: bool flag for local search. :return: None. """ if local: ctx.set_config("is_local", True) # if we are in an agent directory, try to load the configuration file. # otherwise, use the default path (i.e. 'packages/' in the current directory.) try: try_to_load_agent_config(ctx, is_exit_on_except=False) registry_directory = ctx.agent_config.registry_path except Exception: # pylint: disable=broad-except registry_directory = os.path.join(ctx.cwd, DEFAULT_REGISTRY_PATH) ctx.set_config("registry_directory", registry_directory) logger.debug("Using registry {}".format(registry_directory))
def _eject_item( ctx: Context, item_type: str, public_id: PublicId, quiet: bool = True, with_symlinks: bool = False, ) -> None: """ Eject item from installed (vendor) to custom folder. :param ctx: context object. :param item_type: item type. :param public_id: item public ID. :param quiet: if false, the function will ask the user in case of recursive eject. :return: None :raises: ClickException if item is absent at source path or present at destenation path. """ # we know cli_author is set because of the above checks. cli_author: str = cast(str, ctx.config.get("cli_author")) item_type_plural = item_type + "s" if not is_item_present( ctx.cwd, ctx.agent_config, item_type, public_id, is_vendor=True, with_version=True, ): # pragma: no cover raise click.ClickException( f"{item_type.title()} {public_id} not found in agent's vendor items." ) src = get_package_path(ctx.cwd, item_type, public_id) dst = get_package_path(ctx.cwd, item_type, public_id, is_vendor=False) if is_item_present(ctx.cwd, ctx.agent_config, item_type, public_id, is_vendor=False): # pragma: no cover raise click.ClickException( f"{item_type.title()} {public_id} is already a non-vendor package." ) configuration = load_item_config(item_type, Path(src)) if public_id.package_version.is_latest: # get 'concrete' public id, in case it is 'latest' component_prefix = ComponentType( item_type), public_id.author, public_id.name component_id = get_latest_component_id_from_prefix( ctx.agent_config, component_prefix) # component id is necessarily found, due to the checks above. public_id = cast(ComponentId, component_id).public_id package_id = PackageId(PackageType(item_type), public_id) click.echo( f"Ejecting item {package_id.package_type.value} {str(package_id.public_id)}" ) # first, eject all the vendor packages that depend on this item_remover = ItemRemoveHelper(ctx, ignore_non_vendor=True) reverse_dependencies = ( item_remover.get_agent_dependencies_with_reverse_dependencies()) reverse_reachable_dependencies = reachable_nodes(reverse_dependencies, {package_id}) # the reversed topological order of a graph # is the topological order of the reverse graph. eject_order = list( reversed(find_topological_order(reverse_reachable_dependencies))) eject_order.remove(package_id) if len(eject_order) > 0 and not quiet: click.echo( f"The following vendor packages will be ejected: {eject_order}") answer = click.confirm("Do you want to proceed?") if not answer: click.echo("Aborted.") return for dependency_package_id in eject_order: # 'dependency_package_id' depends on 'package_id', # so we need to eject it first _eject_item( ctx, dependency_package_id.package_type.value, dependency_package_id.public_id, quiet=True, ) # copy the vendor package into the non-vendor packages ctx.clean_paths.append(dst) copy_package_directory(Path(src), dst) new_public_id = PublicId(cli_author, public_id.name, DEFAULT_VERSION) current_version = Version(aea.__version__) new_aea_range = ( configuration.aea_version if configuration.aea_version_specifiers.contains(current_version) else compute_specifier_from_version(current_version)) update_item_config( item_type, Path(dst), author=new_public_id.author, version=new_public_id.version, aea_version=new_aea_range, ) update_item_public_id_in_init(item_type, Path(dst), new_public_id) shutil.rmtree(src) # update references in all the other packages component_type = ComponentType(item_type_plural[:-1]) old_component_id = ComponentId(component_type, public_id) new_component_id = ComponentId(component_type, new_public_id) update_references(ctx, {old_component_id: new_component_id}) # need to reload agent configuration with the updated references try_to_load_agent_config(ctx) # replace import statements in all the non-vendor packages replace_all_import_statements(Path(ctx.cwd), ComponentType(item_type), public_id, new_public_id) # fingerprint all (non-vendor) packages fingerprint_all(ctx) if with_symlinks: click.echo( "Adding symlinks from vendor to non-vendor and packages to vendor folders." ) create_symlink_vendor_to_local(ctx, item_type, new_public_id) create_symlink_packages_to_vendor(ctx) click.echo( f"Successfully ejected {item_type} {public_id} to {dst} as {new_public_id}." )
def fetch_agent( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Fetch Agent from Registry. :param ctx: Context :param public_id: str public ID of desirable agent. :param alias: an optional alias. :param target_dir: the target directory to which the agent is fetched. :return: None """ author, name, version = public_id.author, public_id.name, public_id.version folder_name = target_dir or (name if alias is None else alias) aea_folder = os.path.join(ctx.cwd, folder_name) if os.path.exists(aea_folder): raise ClickException( f'Item "{folder_name}" already exists in target folder.') ctx.clean_paths.append(aea_folder) api_path = f"/agents/{author}/{name}/{version}" resp = request_api("GET", api_path) file_url = resp["file"] filepath = download_file(file_url, ctx.cwd) extract(filepath, ctx.cwd) if alias or target_dir: shutil.move( os.path.join(ctx.cwd, name), aea_folder, ) ctx.cwd = aea_folder try_to_load_agent_config(ctx) if alias is not None: ctx.agent_config.agent_name = alias with open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as fp: ctx.agent_loader.dump(ctx.agent_config, fp) click.echo("Fetching dependencies...") for item_type in (CONNECTION, CONTRACT, SKILL, PROTOCOL): item_type_plural = item_type + "s" # initialize fetched agent with empty folders for custom packages custom_items_folder = os.path.join(ctx.cwd, item_type_plural) os.makedirs(custom_items_folder) config = getattr(ctx.agent_config, item_type_plural) for item_public_id in config: try: add_item(ctx, item_type, item_public_id) except Exception as e: raise click.ClickException( f'Unable to fetch dependency for agent "{name}", aborting. {e}' ) click.echo("Dependencies successfully fetched.") click.echo(f"Agent {name} successfully fetched to {aea_folder}.")