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) assert return_code == 0, "Return code != 0." except Exception as e: raise AEAException( "An error occurred while installing {}, {}: {}".format( dependency_name, dependency, str(e)))
def get_latest_public_id_mixed( ctx: Context, item_type: str, item_public_id: PublicId, aea_version: Optional[str] = None, ) -> PublicId: """ Get latest public id of the message, mixed mode. That is, give priority to local registry, and fall back to remote registry in case of failure. :param ctx: the CLI context. :param item_type: the item type. :param item_public_id: the item public id. :param aea_version: the AEA version constraint, or None :return: the path to the found package. """ try: _, item_config = find_item_locally(ctx, item_type, item_public_id) latest_item_public_id = item_config.public_id except click.ClickException: logger.debug( "Get latest public id from local registry failed, trying remote registry..." ) # the following might raise exception, but we don't catch it this time package_meta = get_package_meta( item_type, item_public_id, aea_version=aea_version ) latest_item_public_id = PublicId.from_str(cast(str, package_meta["public_id"])) return latest_item_public_id
def fetch_item_mixed( ctx: Context, item_type: str, item_public_id: PublicId, dest_path: str, ) -> Path: """ Find item, mixed mode. That is, give priority to local registry, and fall back to remote registry in case of failure. :param ctx: the CLI context. :param item_type: the item type. :param item_public_id: the item public id. :param dest_path: the path to the destination. :return: the path to the found package. """ try: package_path = find_item_locally_or_distributed( ctx, item_type, item_public_id, dest_path) except click.ClickException as e: logger.debug( f"Fetch from local registry failed (reason={str(e)}), trying remote registry..." ) # the following might raise exception, but we don't catch it this time package_path = fetch_package(item_type, public_id=item_public_id, cwd=ctx.cwd, dest=dest_path) return package_path
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. """ 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)) if (item_id not in existing_items_name_to_ids.keys() and item_id not in existing_item_ids): raise click.ClickException("The {} '{}' is not supported.".format( item_type, item_id)) 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(): raise click.ClickException("{} {} not found. Aborting.".format( item_type.title(), item_name)) elif (item_folder.exists() and not ctx.agent_config.author == item_id.author): # pragma: no cover raise click.ClickException( "{} {} author is different from {} agent author. " "Please fix the author field.".format(item_name, item_type, agent_name)) else: logger.debug("Removing local {} {}.".format( item_type, item_name)) # pragma: no cover try: shutil.rmtree(item_folder) except BaseException: raise click.ClickException("An error occurred.") # removing the protocol to 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 copy_package_directory( ctx: Context, package_path: Path, item_type: str, item_name: str, author_name: str, dest: str, ) -> Path: """ Copy a package directory to the agent vendor resources. :param ctx: the CLI context . :param package_path: the path to the package to be added. :param item_type: the type of the package. :param item_name: the name of the package. :param author_name: the author of the package. :return: copied folder target path. :raises SystemExit: if the copy raises an exception. """ # copy the item package into the agent's supported packages. item_type_plural = item_type + "s" src = str(package_path.absolute()) logger.debug("Copying {} modules. src={} dst={}".format(item_type, src, dest)) try: shutil.copytree(src, dest) except Exception as e: raise click.ClickException(str(e)) Path(ctx.cwd, "vendor", author_name, item_type_plural, "__init__.py").touch() return Path(dest)
def _process_connection( project_directory: str, agent_config_manager: AgentConfigManager, cert_requests: List[CertRequest], connection_id: PublicId, ): if len(cert_requests) == 0: logger.debug("No certificates to process.") return logger.debug(f"Processing connection '{connection_id}'...") for cert_request in cert_requests: click.echo( f"Issuing certificate '{cert_request.identifier}' for connection {connection_id}..." ) _process_certificate( project_directory, agent_config_manager.agent_config, cert_request, connection_id, ) click.echo( f"Dumped certificate '{cert_request.identifier}' in '{cert_request.save_path}' for connection {connection_id}." )
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 _generate_full_mode( ctx: Context, protocol_generator: ProtocolGenerator, protocol_spec: ProtocolSpecification, existing_id_list: Set[PublicId], language: str, ) -> None: """Generate a protocol in 'full' mode, and add it to the configuration file and agent.""" try: warning_message = protocol_generator.generate(protobuf_only=False, language=language) if warning_message is not None: click.echo(warning_message) # Add the item to the configurations logger.debug("Registering the {} into {}".format( PROTOCOL, DEFAULT_AEA_CONFIG_FILE)) existing_id_list.add( PublicId(protocol_spec.author, protocol_spec.name, protocol_spec.version)) ctx.agent_loader.dump( ctx.agent_config, open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w"), ) except FileExistsError: raise click.ClickException( # pragma: no cover "A {} with this name already exists. Please choose a different name and try again." .format(PROTOCOL)) except Exception as e: raise click.ClickException( "Protocol is NOT generated. The following error happened while generating the protocol:\n" + str(e)) fingerprint_item(ctx, PROTOCOL, protocol_spec.public_id)
def _add_protocols(click_context, protocols: Collection[PublicId]): ctx = cast(Context, click_context.obj) # check for dependencies not yet added, and add them. for protocol_public_id in protocols: if protocol_public_id not in ctx.agent_config.protocols: logger.debug( "Adding protocol '{}' to the agent...".format(protocol_public_id) ) _add_item(click_context, "protocol", protocol_public_id)
def _remove_from_config(self) -> None: """Remove item from agent config.""" current_item = self.get_current_item() logger.debug("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.ctx.dump_agent_config()
def launch(click_context, agents: List[str], multithreaded: bool): """Launch many agents at the same time.""" agents_directories = list(map(Path, list(OrderedDict.fromkeys(agents)))) if multithreaded: failed = _launch_threads(click_context, agents_directories) else: failed = _launch_subprocesses(click_context, agents_directories) logger.debug(f"Exit cli. code: {failed}") sys.exit(failed)
def set_config(self, key, value) -> None: """ Set a config. :param key: the key for the configuration. :param value: the value associated with the key. :return: None """ self.config[key] = value logger.debug(" config[{}] = {}".format(key, value))
def fetch_package(obj_type: str, public_id: PublicId, cwd: str, dest: str) -> Path: """ Fetch a package (connection/contract/protocol/skill) from Registry. :param obj_type: str type of object you want to fetch: 'connection', 'protocol', 'skill' :param public_id: str public ID of object. :param cwd: str path to current working directory. :return: package path """ logger.debug("Fetching {obj_type} {public_id} from Registry...".format( public_id=public_id, obj_type=obj_type)) 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 dest = os.path.split(dest)[0] logger.debug("Extracting {obj_type} {public_id}...".format( public_id=public_id, obj_type=obj_type)) extract(filepath, dest) logger.debug("Successfully fetched {obj_type} '{public_id}'.".format( public_id=public_id, obj_type=obj_type)) package_path = os.path.join(dest, public_id.name) return Path(package_path)
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 install(click_context, requirement: Optional[str]): """Install the dependencies.""" ctx = cast(Context, click_context.obj) try: if requirement: logger.debug("Installing the dependencies in '{}'...".format(requirement)) _install_from_requirement(requirement) else: logger.debug("Installing all the dependencies...") dependencies = ctx.get_dependencies() for name, d in dependencies.items(): _install_dependency(name, d) except AEAException as e: raise click.ClickException(str(e))
def _scaffold_non_package_item(ctx: Context, item_type: str, type_name: str, class_name: str, aea_dir: str) -> None: """ Scaffold a non-package item (e.g. decision maker handler, or error handler). :param ctx: the CLI context. :param item_type: the item type (e.g. 'decision_maker_handler') :param type_name: the type name (e.g. "decision maker") :param class_name: the class name (e.g. "DecisionMakerHandler") :param aea_dir: the AEA directory that contains the scaffold module :return: None """ existing_item = getattr(ctx.agent_config, item_type) if existing_item != {}: raise click.ClickException( f"A {type_name} specification already exists. Aborting...") dest = Path(f"{item_type}.py") agent_name = ctx.agent_config.agent_name click.echo(f"Adding {type_name} scaffold to the agent '{agent_name}'...") # create the file name dotted_path = f".{item_type}{DOTTED_PATH_MODULE_ELEMENT_SEPARATOR}{class_name}" try: # copy the item package into the agent project. src = Path(os.path.join(AEA_DIR, aea_dir, "scaffold.py")) logger.debug(f"Copying {type_name}. src={src} dst={dest}") shutil.copyfile(src, dest) # add the item to the configurations. logger.debug( f"Registering the {type_name} into {DEFAULT_AEA_CONFIG_FILE}") setattr( ctx.agent_config, item_type, { "dotted_path": str(dotted_path), "file_path": str(os.path.join(".", dest)), }, ) ctx.agent_loader.dump( ctx.agent_config, open_file(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w"), ) except Exception as e: os.remove(dest) raise click.ClickException(str(e))
def _launch_agents(click_context: click.core.Context, agents: List[str], multithreaded: bool) -> None: """ Run multiple agents. :param click_context: click context object. :param agents: agents names. :param multithreaded: bool flag to run as multithreads. :return: None. """ agents_directories = list(map(Path, list(OrderedDict.fromkeys(agents)))) mode = "threaded" if multithreaded else "multiprocess" ctx = cast(Context, click_context.obj) launcher = AEALauncher( agent_dirs=agents_directories, mode=mode, fail_policy=ExecutorExceptionPolicies.log_only, log_level=ctx.verbosity, ) try: """ run in threaded mode and wait for thread finished cause issue with python 3.6/3.7 on windows probably keyboard interrupt exception gets lost in executor pool or in asyncio module """ launcher.start(threaded=True) launcher.try_join_thread() except KeyboardInterrupt: logger.info("Keyboard interrupt detected.") finally: timeout: Optional[float] = None if os.name == "nt": # Windows bug: https://bugs.python.org/issue21822 timeout = 0 # pragma: nocover launcher.stop(timeout) for agent in launcher.failed: logger.info(f"Agent {agent} terminated with exit code 1") for agent in launcher.not_failed: logger.info(f"Agent {agent} terminated with exit code 0") logger.debug(f"Exit cli. code: {launcher.num_failed}") sys.exit(1 if launcher.num_failed > 0 else 0)
def push_item(ctx: Context, item_type: str, item_id: PublicId) -> None: """ Push item to the Registry. :param item_type: str type of item (connection/protocol/skill). :param item_id: str item name. :return: None """ item_type_plural = item_type + "s" items_folder = os.path.join(ctx.cwd, item_type_plural) item_path = os.path.join(items_folder, item_id.name) item_config_filepath = os.path.join(item_path, "{}.yaml".format(item_type)) logger.debug("Reading {} {} config ...".format(item_id.name, item_type)) item_config = load_yaml(item_config_filepath) check_is_author_logged_in(item_config["author"]) logger.debug("Searching for {} {} in {} ...".format( item_id.name, item_type, items_folder)) if not os.path.exists(item_path): raise click.ClickException( '{} "{}" not found in {}. Make sure you run push command ' "from a correct folder.".format(item_type.title(), item_id.name, items_folder)) output_filename = "{}.tar.gz".format(item_id.name) logger.debug("Compressing {} {} to {} ...".format(item_id.name, item_type, output_filename)) _compress_dir(output_filename, item_path) output_filepath = os.path.join(ctx.cwd, output_filename) data = { "name": item_id.name, "description": item_config["description"], "version": item_config["version"], } # dependencies for key in ["connections", "contracts", "protocols", "skills"]: deps_list = item_config.get(key) if deps_list: data.update({key: deps_list}) path = "/{}/create".format(item_type_plural) logger.debug("Pushing {} {} to Registry ...".format( item_id.name, item_type)) resp = request_api("POST", path, data=data, is_auth=True, filepath=output_filepath) click.echo( "Successfully pushed {} {} to the Registry. Public ID: {}".format( item_type, item_id.name, resp["public_id"]))
def register_item(ctx: Context, item_type: str, item_public_id: PublicId) -> None: """ Register item in agent configuration. :param ctx: click context object. :param item_type: type of item. :param item_public_id: PublicId of item. :return: None. """ logger.debug("Registering the {} into {}".format(item_type, DEFAULT_AEA_CONFIG_FILE)) supported_items = get_items(ctx.agent_config, item_type) supported_items.add(item_public_id) with open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w") as fp: ctx.agent_loader.dump(ctx.agent_config, fp)
def _scaffold_dm_handler(click_context): """Add a scaffolded decision maker handler to the project and configuration.""" ctx = cast(Context, click_context.obj) existing_dm_handler = getattr(ctx.agent_config, "decision_maker_handler") # check if we already have a decision maker in the project if existing_dm_handler != {}: raise click.ClickException( "A decision maker handler specification already exists. Aborting..." ) try: agent_name = ctx.agent_config.agent_name click.echo( "Adding decision maker scaffold to the agent '{}'...".format( agent_name)) # create the file name dest = Path("decision_maker.py") dotted_path = ".decision_maker::DecisionMakerHandler" # copy the item package into the agent project. src = Path(os.path.join(AEA_DIR, "decision_maker", "scaffold.py")) logger.debug("Copying decision maker. src={} dst={}".format(src, dest)) shutil.copyfile(src, dest) # add the item to the configurations. logger.debug("Registering the decision_maker into {}".format( DEFAULT_AEA_CONFIG_FILE)) ctx.agent_config.decision_maker_handler = { "dotted_path": str(dotted_path), "file_path": str(os.path.join(".", dest)), } ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w")) except Exception as e: os.remove(dest) raise click.ClickException(str(e))
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 copy_package_directory(src: Path, dst: str) -> Path: """ Copy a package directory to the agent vendor resources. :param src: source path to the package to be added. :param dst: str package destenation path. :return: copied folder target path. :raises SystemExit: if the copy raises an exception. """ # copy the item package into the agent's supported packages. src_path = str(src.absolute()) logger.debug("Copying modules. src={} dst={}".format(src_path, dst)) try: shutil.copytree(src_path, dst) except Exception as e: raise click.ClickException(str(e)) items_folder = os.path.split(dst)[0] Path(items_folder, "__init__.py").touch() return Path(dst)
def fetch_package(obj_type: str, public_id: PublicId, cwd: str, dest: str) -> Path: """ Fetch connection/protocol/skill from Registry. :param obj_type: str type of object you want to fetch: 'connection', 'protocol', 'skill' :param public_id: str public ID of object. :param cwd: str path to current working directory. :return: package path """ logger.debug("Fetching {obj_type} {public_id} from Registry...".format( 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)) filepath = download_file(file_url, cwd) # next code line is needed because the items are stored in tarball packages as folders dest = os.path.split(dest)[ 0] # TODO: replace this hotfix with a proper solution logger.debug("Extracting {obj_type} {public_id}...".format( public_id=public_id, obj_type=obj_type)) extract(filepath, dest) click.echo("Successfully fetched {obj_type}: {public_id}.".format( public_id=public_id, obj_type=obj_type)) package_path = os.path.join(dest, public_id.name) return Path(package_path)
def _process_connection( path_prefix: str, agent_config_manager: AgentConfigManager, cert_requests: List[CertRequest], connection_id: PublicId, ) -> None: if len(cert_requests) == 0: logger.debug("No certificates to process.") return logger.debug(f"Processing connection '{connection_id}'...") for cert_request in cert_requests: click.echo( f"Issuing certificate '{cert_request.identifier}' for connection {connection_id}..." ) _process_certificate( path_prefix, agent_config_manager.agent_config, cert_request, connection_id, )
def do_install(ctx: Context, requirement: Optional[str] = None) -> None: """ Install necessary dependencies. :param ctx: context object. :param requirement: optional str requirement. :return: None :raises: ClickException if AEAException occurres. """ try: if requirement: logger.debug( "Installing the dependencies in '{}'...".format(requirement)) _install_from_requirement(requirement) else: logger.debug("Installing all the dependencies...") dependencies = ctx.get_dependencies() for name, d in dependencies.items(): _install_dependency(name, d) except AEAException as e: raise click.ClickException(str(e))
def fetch_mixed( ctx: Context, public_id: PublicId, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Fetch an agent in mixed mode. :param ctx: the Context. :param public_id: the public id. :param alias: the alias to the agent. :param target_dir: the target directory. :return: None """ try: fetch_agent_locally(ctx, public_id, alias=alias, target_dir=target_dir) except click.ClickException as e: logger.debug( f"Fetch from local registry failed (reason={str(e)}), trying remote registry..." ) fetch_agent(ctx, public_id, alias=alias, target_dir=target_dir)
def _launch_agents(click_context: click.core.Context, agents: List[str], multithreaded: bool) -> None: """ Run multiple agents. :param click_context: click context object. :param agents: agents names. :param multithreaded: bool flag to run as multithreads. :return: None. """ agents_directories = list(map(Path, list(OrderedDict.fromkeys(agents)))) try: if multithreaded: failed = _launch_threads(agents_directories) else: failed = _launch_subprocesses(click_context, agents_directories) except BaseException: # pragma: no cover logger.exception("Exception in launch agents.") failed = -1 finally: logger.debug(f"Exit cli. code: {failed}") sys.exit(failed)
def _setup_package_folder(path: Path): """Set a package folder up.""" path.mkdir(exist_ok=False) init_module = path / "__init__.py" logger.debug("Creating {}".format(init_module)) Path(init_module).touch(exist_ok=False)
def _generate_item(click_context, item_type, specification_path): """Generate an item based on a specification and add it to the configuration file and agent.""" # check protocol buffer compiler is installed ctx = cast(Context, click_context.obj) res = shutil.which("protoc") if res is None: raise click.ClickException( "Please install protocol buffer first! See the following link: https://developers.google.com/protocol-buffers/" ) # check black code formatter is installed res = shutil.which("black") if res is None: raise click.ClickException( "Please install black code formater first! See the following link: https://black.readthedocs.io/en/stable/installation_and_usage.html" ) # Get existing items existing_id_list = getattr(ctx.agent_config, "{}s".format(item_type)) existing_item_list = [public_id.name for public_id in existing_id_list] item_type_plural = item_type + "s" # Load item specification yaml file try: config_loader = ConfigLoader("protocol-specification_schema.json", ProtocolSpecification) protocol_spec = config_loader.load_protocol_specification( open(specification_path)) except Exception as e: raise click.ClickException(str(e)) protocol_directory_path = os.path.join(ctx.cwd, item_type_plural, protocol_spec.name) # Check if we already have an item with the same name in the agent config logger.debug("{} already supported by the agent: {}".format( item_type_plural, existing_item_list)) if protocol_spec.name in existing_item_list: raise click.ClickException( "A {} with name '{}' already exists. Aborting...".format( item_type, protocol_spec.name)) # Check if we already have a directory with the same name in the resource directory (e.g. protocols) of the agent's directory if os.path.exists(protocol_directory_path): raise click.ClickException( "A directory with name '{}' already exists. Aborting...".format( protocol_spec.name)) ctx.clean_paths.append(protocol_directory_path) try: agent_name = ctx.agent_config.agent_name click.echo( "Generating {} '{}' and adding it to the agent '{}'...".format( item_type, protocol_spec.name, agent_name)) output_path = os.path.join(ctx.cwd, item_type_plural) protocol_generator = ProtocolGenerator(protocol_spec, output_path) protocol_generator.generate() # Add the item to the configurations logger.debug("Registering the {} into {}".format( item_type, DEFAULT_AEA_CONFIG_FILE)) existing_id_list.add( PublicId("fetchai", protocol_spec.name, protocol_spec.version)) ctx.agent_loader.dump( ctx.agent_config, open(os.path.join(ctx.cwd, DEFAULT_AEA_CONFIG_FILE), "w")) except FileExistsError: raise click.ClickException( "A {} with this name already exists. Please choose a different name and try again." .format(item_type)) except ProtocolSpecificationParseError as e: raise click.ClickException( "The following error happened while parsing the protocol specification: " + str(e)) except Exception as e: raise click.ClickException( "There was an error while generating the protocol. The protocol is NOT generated. Exception: " + str(e)) # Run black code formatting try: subp = subprocess.Popen( # nosec [ sys.executable, "-m", "black", os.path.join(item_type_plural, protocol_spec.name), "--quiet", ]) subp.wait(10.0) finally: poll = subp.poll() if poll is None: # pragma: no cover subp.terminate() subp.wait(5) _fingerprint_item(click_context, "protocol", protocol_spec.public_id)