def load( cls, working_dir: str, public_id: PublicId, is_local: bool = False, registry_path: str = "packages", skip_consistency_check: bool = False, ) -> "Project": """ Load project with given public_id to working_dir. :param working_dir: the working directory :param public_id: the public id :param is_local: whether to fetch from local or remote :param registry_path: the path to the registry locally :param skip_consistency_check: consistency checks flag """ ctx = Context(cwd=working_dir, registry_path=registry_path) ctx.set_config("skip_consistency_check", skip_consistency_check) path = os.path.join(working_dir, public_id.author, public_id.name) target_dir = os.path.join(public_id.author, public_id.name) if is_local: fetch_agent_locally(ctx, public_id, target_dir=target_dir) else: fetch_agent(ctx, public_id, target_dir=target_dir) return cls(public_id, path)
def fetch_local_or_mixed(ctx: Context, item_type: str, item_id: PublicId) -> None: """ Fetch item, either local or mixed, depending on the parameters/context configuration. It will first try to fetch from local registry; in case of failure, if the 'is_mixed' flag is set, it will try to fetch from remote registry. Context expects 'is_local' and 'is_mixed' to be set. :param ctx: the CLI context. :param item_type: the type of the package. :param item_id: the public id of the item. :return: None """ def _raise(item_type_: str, item_id_: PublicId, exception): """Temporary function to raise exception (used below twice).""" raise click.ClickException( f"Failed to add {item_type_} dependency {item_id_}: {str(exception)}" ) try: add_item(ctx, item_type, item_id) except click.ClickException as e: if not ctx.config.get("is_mixed", False): _raise(item_type, item_id, e) ctx.set_config("is_local", False) try: add_item(ctx, item_type, item_id) except click.ClickException as e: _raise(item_type, item_id, e) ctx.set_config("is_local", True)
def do_fetch( ctx: Context, public_id: PublicId, local: bool, remote: bool, alias: Optional[str] = None, target_dir: Optional[str] = None, ) -> None: """ Run the Fetch command. :param ctx: the CLI context. :param public_id: the public id. :param local: whether to fetch from local :param remote whether to fetch from remote :param alias: the agent alias. :param target_dir: the target directory, in case fetching locally. :return: None """ enforce( not (local and remote), "'local' and 'remote' options are mutually exclusive." ) is_mixed = not local and not remote ctx.set_config("is_local", local and not remote) ctx.set_config("is_mixed", is_mixed) if remote: fetch_agent(ctx, public_id, alias=alias, target_dir=target_dir) elif local: fetch_agent_locally(ctx, public_id, alias=alias, target_dir=target_dir) else: fetch_mixed(ctx, public_id, alias=alias, target_dir=target_dir)
def _fetch_agent_deps(ctx: Context, is_mixed: bool = True) -> None: """ Fetch agent dependencies. :param ctx: context object. :param is_mixed: flag to enable mixed mode (try first local, then remote). :return: None :raises: ClickException re-raises if occurs in add_item call. """ ctx.set_config("is_local", True) ctx.set_config("is_mixed", is_mixed) for item_type in ("protocol", "contract", "connection", "skill"): item_type_plural = "{}s".format(item_type) required_items = getattr(ctx.agent_config, item_type_plural) for item_id in required_items: fetch_local_or_mixed(ctx, item_type, item_id)
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 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 _fetch_agent_deps(ctx: Context) -> None: """ Fetch agent dependencies. :param ctx: context object. :return: None :raises: ClickException re-raises if occures in add_item call. """ ctx.set_config("is_local", True) for item_type in ("protocol", "contract", "connection", "skill"): 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(ctx, item_type, item_id) except click.ClickException as e: raise click.ClickException( "Failed to add {} dependency {}: {}".format( item_type, item_id, str(e)))
def load( cls, working_dir: str, public_id: PublicId, is_local: bool = False, is_remote: bool = False, is_restore: bool = False, registry_path: str = DEFAULT_REGISTRY_NAME, skip_consistency_check: bool = False, ) -> "Project": """ Load project with given public_id 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 working_dir: the working directory :param public_id: the public id :param is_local: whether to fetch from local :param is_remote whether to fetch from remote :param registry_path: the path to the registry locally :param skip_consistency_check: consistency checks flag """ ctx = Context(cwd=working_dir, registry_path=registry_path) ctx.set_config("skip_consistency_check", skip_consistency_check) path = os.path.join(working_dir, public_id.author, public_id.name) target_dir = os.path.join(public_id.author, public_id.name) if not is_restore and not os.path.exists(target_dir): do_fetch(ctx, public_id, is_local, is_remote, target_dir=target_dir) return cls(public_id, path)
def test_create_and_run_agent(): """Test for running and agent, reading TTY and errors.""" # Set up a temporary current working directory in which to make agents with TempCWD() as temp_cwd: app = create_app() # copy the 'packages' directory in the parent of the agent folder. shutil.copytree(Path(CUR_PATH, "..", "packages"), Path(temp_cwd.temp_dir, "packages")) agent_id = "test_agent" # Make an agent # We do it programmatically as we need to create an agent with default author # that was prevented from GUI. ctx = Context(cwd=temp_cwd.temp_dir) ctx.set_config("is_local", True) create_aea(ctx, agent_id, local=True, author=DEFAULT_AUTHOR) # Add the local connection with patch("aea.cli_gui.app_context.local", True): response_add = app.post( "api/agent/" + agent_id + "/connection", content_type="application/json", data=json.dumps(str(LOCAL_PUBLIC_ID)), ) assert response_add.status_code == 201 # Get the running status before we have run it response_status = app.get( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_status.status_code == 200 data = json.loads(response_status.get_data(as_text=True)) assert "NOT_STARTED" in data["status"] # run the agent with a non existent connection response_run = app.post( "api/agent/" + agent_id + "/run", content_type="application/json", data=json.dumps("author/non-existent-connection:0.1.0"), ) assert response_run.status_code == 400 # run the agent with default connection - should be something in the error output? response_run = app.post( "api/agent/" + agent_id + "/run", content_type="application/json", data=json.dumps(""), ) assert response_run.status_code == 201 time.sleep(2) # Stop the agent running response_stop = app.delete( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_stop.status_code == 200 time.sleep(2) # run the agent with stub connection response_run = app.post( "api/agent/" + agent_id + "/run", content_type="application/json", data=json.dumps(str(STUB_CONNECTION_PUBLIC_ID)), ) assert response_run.status_code == 201 time.sleep(2) # Try running it again (this should fail) response_run = app.post( "api/agent/" + agent_id + "/run", content_type="application/json", data=json.dumps(str(STUB_CONNECTION_PUBLIC_ID)), ) assert response_run.status_code == 400 # Get the running status response_status = app.get( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_status.status_code == 200 data = json.loads(response_status.get_data(as_text=True)) assert data["error"] == "" assert "RUNNING" in data["status"] app.delete( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) time.sleep(1) # Get the running status response_status = app.get( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_status.status_code == 200 data = json.loads(response_status.get_data(as_text=True)) assert "process terminate" in data["error"] assert "NOT_STARTED" in data["status"] # run the agent again (takes a different path through code) response_run = app.post( "api/agent/" + agent_id + "/run", content_type="application/json", data=json.dumps(str(STUB_CONNECTION_PUBLIC_ID)), ) assert response_run.status_code == 201 time.sleep(2) # Get the running status response_status = app.get( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_status.status_code == 200 data = json.loads(response_status.get_data(as_text=True)) assert data["error"] == "" assert "RUNNING" in data["status"] # Stop the agent running response_stop = app.delete( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_stop.status_code == 200 time.sleep(2) # Get the running status response_status = app.get( "api/agent/" + agent_id + "/run", data=None, content_type="application/json", ) assert response_status.status_code == 200 data = json.loads(response_status.get_data(as_text=True)) assert "process terminate" in data["error"] assert "NOT_STARTED" in data["status"] # Stop a none existent agent running response_stop = app.delete( "api/agent/" + agent_id + "_NOT" + "/run", data=None, content_type="application/json", ) assert response_stop.status_code == 400 time.sleep(2) genuine_func = aea.cli_gui.call_aea_async def _dummycall_aea_async(param_list, dir_arg): assert param_list[0] == sys.executable assert param_list[1] == "-m" assert param_list[2] == "aea.cli" if param_list[3] == "run": return None else: return genuine_func(param_list, dir_arg) # Run when process files (but other call - such as status should not fail) with patch("aea.cli_gui.call_aea_async", _dummycall_aea_async): response_run = app.post( "api/agent/" + agent_id + "/run", content_type="application/json", data=json.dumps(str(STUB_CONNECTION_PUBLIC_ID)), ) assert response_run.status_code == 400
def create_aea( ctx: Context, agent_name: str, local: bool, author: Optional[str] = None, empty: bool = False, ) -> None: """ Create AEA project. :param ctx: Context object. :param local: boolean flag for local folder usage. :param agent_name: agent name. :param author: optional author name (valid with local=True only). :param empty: optional boolean flag for skip adding default dependencies. :return: None :raises: ClickException if an error occured. """ try: _check_is_parent_folders_are_aea_projects_recursively() except Exception: raise click.ClickException( "The current folder is already an AEA project. Please move to the parent folder." ) if author is not None: if local: do_init(author, False, False) else: raise click.ClickException( "Author is not set up. Please use 'aea init' to initialize.") config = get_or_create_cli_config() set_author = config.get(AUTHOR_KEY, None) if set_author is None: raise click.ClickException( "The AEA configurations are not initialized. Uses `aea init` before continuing or provide optional argument `--author`." ) if Path(agent_name).exists(): raise click.ClickException("Directory already exist. Aborting...") click.echo("Initializing AEA project '{}'".format(agent_name)) click.echo("Creating project directory './{}'".format(agent_name)) path = Path(agent_name) ctx.clean_paths.append(str(path)) # we have already checked that the directory does not exist. path.mkdir(exist_ok=False) try: # set up packages directories. _setup_package_folder(Path(agent_name, "protocols")) _setup_package_folder(Path(agent_name, "contracts")) _setup_package_folder(Path(agent_name, "connections")) _setup_package_folder(Path(agent_name, "skills")) # set up a vendor directory Path(agent_name, "vendor").mkdir(exist_ok=False) Path(agent_name, "vendor", "__init__.py").touch(exist_ok=False) # create a config file inside it click.echo("Creating config file {}".format(DEFAULT_AEA_CONFIG_FILE)) agent_config = _crete_agent_config(ctx, agent_name, set_author) # next commands must be done from the agent's directory -> overwrite ctx.cwd ctx.agent_config = agent_config ctx.cwd = agent_config.agent_name if not empty: click.echo("Adding default packages ...") if local: ctx.set_config("is_local", True) add_item(ctx, "connection", DEFAULT_CONNECTION) add_item(ctx, "skill", DEFAULT_SKILL) except Exception as e: raise click.ClickException(str(e))
def protocol(ctx: Context, protocol_specification_path: str, language: str) -> None: """Generate a protocol based on a specification and add it to the configuration file and agent.""" ctx.set_config("language", language) _generate_protocol(ctx, protocol_specification_path)