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 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, "packages", "fetchai", "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.9.0' are not allowed to change: {'class_name'}.", ): skill_config.update(new_configurations)
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) 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 _validate_config_consistency(ctx: Context): """ Validate fingerprints for every agent component. :param ctx: the context :raise ValueError: if there is a missing configuration file. or if the configuration file is not valid. or if the fingerprints do not match """ packages_public_ids_to_types = dict([ *map(lambda x: (x, PackageType.PROTOCOL), ctx.agent_config.protocols), *map( lambda x: (x, PackageType.CONNECTION), ctx.agent_config.connections, ), *map(lambda x: (x, PackageType.SKILL), ctx.agent_config.skills), *map(lambda x: (x, PackageType.CONTRACT), ctx.agent_config.contracts), ]) # type: Dict[PublicId, PackageType] for public_id, item_type in packages_public_ids_to_types.items(): # find the configuration file. try: # either in vendor/ or in personal packages. # we give precedence to custom agent components (i.e. not vendorized). package_directory = Path(item_type.to_plural(), public_id.name) is_vendor = False if not package_directory.exists(): package_directory = Path("vendor", public_id.author, item_type.to_plural(), public_id.name) is_vendor = True # we fail if none of the two alternative works. enforce(package_directory.exists(), "Package directory does not exist!") loader = ConfigLoaders.from_package_type(item_type) config_file_name = _get_default_configuration_file_name_from_type( item_type) configuration_file_path = package_directory / config_file_name enforce( configuration_file_path.exists(), "Configuration file path does not exist!", ) except Exception: raise ValueError("Cannot find {}: '{}'".format( item_type.value, public_id)) # load the configuration file. try: package_configuration = loader.load( configuration_file_path.open("r")) except ValidationError as e: raise ValueError("{} configuration file not valid: {}".format( item_type.value.capitalize(), str(e))) _check_aea_version(package_configuration) _compare_fingerprints(package_configuration, package_directory, is_vendor, item_type)
def verify_or_create_private_keys( aea_project_path: Path, exit_on_error: bool = True, ) -> AgentConfig: """ Verify or create private keys. :param aea_project_path: path to an AEA project. :param exit_on_error: whether we should exit the program on error. :return: the agent configuration. """ path_to_aea_config = aea_project_path / DEFAULT_AEA_CONFIG_FILE agent_loader = ConfigLoaders.from_package_type(PackageType.AGENT) fp = path_to_aea_config.open(mode="r", encoding="utf-8") aea_conf = agent_loader.load(fp) for identifier, _ in aea_conf.private_key_paths.read_all(): if identifier not in crypto_registry.supported_ids: # pragma: nocover raise ValueError( "Unsupported identifier `{}` in private key paths. Supported identifiers: {}." .format(identifier, sorted(crypto_registry.supported_ids))) for identifier in crypto_registry.supported_ids: config_private_key_path = aea_conf.private_key_paths.read(identifier) if config_private_key_path is None: private_key_path = PRIVATE_KEY_PATH_SCHEMA.format(identifier) if identifier == aea_conf.default_ledger: # pragma: nocover create_private_key( identifier, private_key_file=str(aea_project_path / private_key_path), ) aea_conf.private_key_paths.update(identifier, private_key_path) else: try: try_validate_private_key_path( identifier, str(aea_project_path / config_private_key_path), exit_on_error=exit_on_error, ) except FileNotFoundError: # pragma: no cover raise ValueError( "File {} for private key {} not found.".format( repr(config_private_key_path), identifier, )) # update aea config fp = path_to_aea_config.open(mode="w", encoding="utf-8") agent_loader.dump(aea_conf, fp) return aea_conf
def load_agent_config(cls, agent_name: str) -> AgentConfig: """Load agent configuration.""" if agent_name not in cls.agents: raise AEATestingException( f"Cannot find agent '{agent_name}' in the current test case." ) loader = ConfigLoaders.from_package_type(PackageType.AGENT) config_file_name = _get_default_configuration_file_name_from_type( PackageType.AGENT ) configuration_file_path = Path(cls.t, agent_name, config_file_name) with open_file(configuration_file_path) as file_input: agent_config = loader.load(file_input) return agent_config
def test_agent_configuration_loading_multipage_when_type_not_found(): """Test agent configuration loading, multi-page case, when type not found in some component.""" # remove type field manually file = Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() jsons = list(yaml.safe_load_all(file)) jsons[1].pop("type") modified_file = io.StringIO() yaml.safe_dump_all(jsons, modified_file) modified_file.seek(0) with pytest.raises( ValueError, match="There are missing fields in component id 1: {'type'}."): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(modified_file)
def test_update_method(self): """Test the update method.""" skill_config_path = Path(DUMMY_SKILL_PATH) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) dummy_behaviour = skill_config.behaviours.read("dummy") expected_dummy_behaviour_args = copy(dummy_behaviour.args) expected_dummy_behaviour_args["behaviour_arg_1"] = 42 dummy_handler = skill_config.handlers.read("dummy") expected_dummy_handler_args = copy(dummy_handler.args) expected_dummy_handler_args["handler_arg_1"] = 42 dummy_model = skill_config.models.read("dummy") expected_dummy_model_args = copy(dummy_model.args) expected_dummy_model_args["model_arg_1"] = 42 new_configurations = { "behaviours": { "dummy": { "args": dict(behaviour_arg_1=42) } }, "handlers": { "dummy": { "args": dict(handler_arg_1=42) } }, "models": { "dummy": { "args": dict(model_arg_1=42) } }, } directory = "test_directory" skill_config.directory = directory skill_config.update(new_configurations) assert skill_config.directory == directory assert (expected_dummy_behaviour_args == skill_config.behaviours.read( "dummy").args) assert expected_dummy_handler_args == skill_config.handlers.read( "dummy").args assert expected_dummy_model_args == skill_config.models.read( "dummy").args assert len(skill_config.package_dependencies)
def test_agent_configuration_loading_multipage(): """Test agent configuration loading, multi-page case.""" 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 keys = list(agent_config.component_configurations) dummy_skill_public_id = PublicId.from_str("dummy_author/dummy:0.1.0") expected_component_id = ComponentId("skill", dummy_skill_public_id) assert keys[0] == expected_component_id
def setup(cls, **kwargs: Any) -> 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)) _shared_state = cast(Dict[str, Any], kwargs.pop("shared_state", dict())) _skill_config_overrides = cast( Dict[str, Any], kwargs.pop("config_overrides", dict()) ) 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_ledger_id=identity.default_address_key, currency_denominations=DEFAULT_CURRENCY_DENOMINATIONS, default_connection=None, default_routing={}, search_service_address="dummy_search_service_address", decision_maker_address="dummy_decision_maker_address", data_dir=os.getcwd(), ) # This enables pre-populating the 'shared_state' prior to loading the skill if _shared_state != dict(): for key, value in _shared_state.items(): agent_context.shared_state[key] = value skill_configuration_file_path: Path = Path(cls.path_to_skill, "skill.yaml") loader = ConfigLoaders.from_package_type(PackageType.SKILL) with open_file(skill_configuration_file_path) as fp: skill_config: SkillConfig = loader.load(fp) # This enables overriding the skill's config prior to loading if _skill_config_overrides != {}: skill_config.update(_skill_config_overrides) skill_config.directory = cls.path_to_skill cls._skill = Skill.from_config(skill_config, agent_context)
def test_agent_configuration_loading_multipage_when_same_id(): """Test agent configuration loading, multi-page case, when there are two components with the same id.""" file = Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() jsons = list(yaml.safe_load_all(file)) # add twice the last component jsons.append(jsons[-1]) modified_file = io.StringIO() yaml.safe_dump_all(jsons, modified_file) modified_file.seek(0) with pytest.raises( ValueError, match= r"Configuration of component \(skill, dummy_author/dummy:0.1.0\) occurs more than once.", ): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(modified_file)
def validate_item_config(item_type: str, package_path: Path) -> None: """ Validate item configuration. :param item_type: type of item. :param package_path: path to a package folder. :return: None :raises AEAConfigException: if something is wrong with item configuration. """ item_config = load_item_config(item_type, package_path) loader = ConfigLoaders.from_package_type(item_type) for field_name in loader.required_fields: if not getattr(item_config, field_name): raise AEAConfigException( "Parameter '{}' is missing from {} config.".format( field_name, item_type))
def test_agent_configuration_loading_multipage_validation_error(): """Test agent configuration loading, multi-page case, when the configuration is invalid.""" file = Path(CUR_PATH, "data", "aea-config.example_multipage.yaml").open() jsons = list(yaml.safe_load_all(file)) # make invalid the last component configuration jsons[-1]["invalid_attribute"] = "foo" modified_file = io.StringIO() yaml.safe_dump_all(jsons, modified_file) modified_file.seek(0) with pytest.raises( ValueError, match= r"Configuration of component \(skill, dummy_author/dummy:0.1.0\) is not valid.", ): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(modified_file)
def load_configuration(package_type: PackageType, package_path: Path) -> PackageConfiguration: """ Load a configuration, knowing the type and the path to the package root. :param package_type: the package type. :param package_path: the path to the package root. :return: the configuration object. """ configuration_class = type_to_class_config[package_type] configuration_filepath = ( package_path / configuration_class.default_configuration_filename) loader = ConfigLoaders.from_package_type(package_type) with configuration_filepath.open() as fp: configuration_obj = loader.load(fp) configuration_obj._directory = package_path # pylint: disable=protected-access return cast(PackageConfiguration, configuration_obj)
def _make_config( self, project_path: str, agent_overrides: Optional[dict] = None, component_overrides: Optional[List[dict]] = None, ) -> List[dict]: """Make new config based on project's config with overrides applied.""" agent_overrides = agent_overrides or {} component_overrides = component_overrides or [] if any([key in agent_overrides for key in self.AGENT_DO_NOT_OVERRIDE_VALUES]): raise ValueError( 'Do not override any of {" ".join(self.AGENT_DO_NOT_OVERRIDE_VALUES)}' ) agent_configuration_file_path: Path = AEABuilder.get_configuration_file_path( project_path ) loader = ConfigLoaders.from_package_type(PackageType.AGENT) with agent_configuration_file_path.open() as fp: agent_config: AgentConfig = loader.load(fp) # prepare configuration overrides # - agent part agent_update_dictionary: Dict = dict(**agent_overrides) # - components part components_configs: Dict[ComponentId, Dict] = {} for obj in component_overrides: obj = copy.copy(obj) author, name, version = ( obj.pop("author"), obj.pop("name"), obj.pop("version"), ) component_id = ComponentId(obj.pop("type"), PublicId(author, name, version)) components_configs[component_id] = obj agent_update_dictionary["component_configurations"] = components_configs # do the override (and valiation) agent_config.update(agent_update_dictionary) # return the multi-paged JSON object. json_data = agent_config.ordered_json result: List[Dict] = [json_data] + json_data.pop("component_configurations") return result
def update_item_config(item_type: str, package_path: Path, **kwargs) -> None: """ Update item config and item config file. :param item_type: type of item. :param package_path: path to a package folder. :param kwargs: pairs of config key-value to update. :return: None """ item_config = load_item_config(item_type, package_path) for key, value in kwargs.items(): setattr(item_config, key, value) config_filepath = os.path.join(package_path, item_config.default_configuration_filename) loader = ConfigLoaders.from_package_type(item_type) with open(config_filepath, "w") as f: loader.dump(item_config, f)
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]["public_id"] == "dummy_author/dummy:0.1.0" assert agent_config[1]["type"] == "skill"
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)
def _update_non_vendor_package( package_path: Path, replacements: Dict[ComponentType, Dict[Tuple[str, str], PublicId]], ) -> None: """Update a single non-vendor package.""" """ A path to a non-vendor package in an AEA project is of the form: .../aea-project-path/package-type/package-name/ so we need to get the second-to-last part of the path to infer the type. """ type_plural = package_path.parts[-2] loader = ConfigLoaders.from_package_type(PackageType(type_plural[:-1])) path_to_config = ( package_path / loader.configuration_class.default_configuration_filename ) with path_to_config.open() as file_in: component_config = loader.load(file_in) update_dependencies(component_config, replacements) with path_to_config.open(mode="w") as file_out: loader.dump(component_config, file_out)
def test_agent_configuration_loading_multipage_positive_case(component_type): """Test agent configuration loading, multi-page case, positive case.""" public_id = PublicId("dummy_author", "dummy", "0.1.0") file = Path(CUR_PATH, "data", "aea-config.example.yaml").open() json_data = yaml.safe_load(file) json_data[component_type.to_plural()].append(str(public_id)) modified_file = io.StringIO() yaml.safe_dump(json_data, modified_file) modified_file.flush() modified_file.write("---\n") modified_file.write(f"public_id: {public_id}\n") modified_file.write(f"type: {component_type.value}\n") modified_file.seek(0) expected_component_id = ComponentId( component_type, PublicId("dummy_author", "dummy", "0.1.0")) loader = ConfigLoaders.from_package_type(PackageType.AGENT) agent_config = loader.load(modified_file) 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_update_method_raises_error_if_skill_component_not_allowed(self): """Test that we raise error if the custom configuration contain unexpected skill components.""" skill_config_path = Path( ROOT_DIR, "packages", "fetchai", "skills", "error", DEFAULT_SKILL_CONFIG_FILE, ) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) new_configurations = { "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.9.0 includes new behaviours: {'new_behaviour'}. This is not allowed.", ): skill_config.update(new_configurations)
def test_agent_configuration_loading_multipage_when_empty_file(): """Test agent configuration loading, multi-page case, in case of empty file.""" with pytest.raises(ValueError, match="Agent configuration file was empty."): loader = ConfigLoaders.from_package_type(PackageType.AGENT) loader.load(io.StringIO())
def test_check_public_id_consistency_positive(): """Test ComponentId.check_public_id_consistency works.""" skill_config_path = Path(DUMMY_SKILL_PATH) loader = ConfigLoaders.from_package_type(PackageType.SKILL) skill_config = loader.load(skill_config_path.open()) skill_config.check_public_id_consistency(Path(skill_config_path).parent)