def update_aea_version_specifiers(old_version: Version, new_version: Version) -> bool: """ Update aea_version specifier set in docs. :param old_version: the old version. :param new_version: the new version. :return: True if the update has been done, False otherwise. """ old_specifier_set = compute_specifier_from_version(old_version) new_specifier_set = compute_specifier_from_version(new_version) print(f"Old version specifier: {old_specifier_set}") print(f"New version specifier: {new_specifier_set}") old_specifier_set_regex = re.compile(str(old_specifier_set).replace(" ", " *")) if old_specifier_set == new_specifier_set: print("Not updating version specifier - they haven't changed.") return False for file in filter(lambda p: not p.is_dir(), Path(".").rglob("*")): dir_root = Path(file.parts[0]) if dir_root in IGNORE_DIRS: print(f"Skipping '{file}'...") continue print( f"Replacing '{old_specifier_set}' with '{new_specifier_set}' in '{file}'... ", end="", ) try: content = file.read_text() except UnicodeDecodeError as e: print(f"Cannot read {file}: {str(e)}. Continue...") else: if old_specifier_set_regex.search(content) is not None: content = old_specifier_set_regex.sub(new_specifier_set, content) file.write_text(content) return True
def test_compute_specifier_from_version(): """Test function 'compute_specifier_from_version'.""" version = "0.1.5" expected_range = ">=0.1.0, <0.2.0" assert expected_range == compute_specifier_from_version(Version(version)) version = "1.1.5" expected_range = ">=1.1.0, <1.2.0" assert expected_range == compute_specifier_from_version(Version(version))
def _update_agent_config(ctx: Context): """ Update agent configurations. :param ctx: the context. :return: None """ # update aea_version in case current framework version is different version = Version(aea.__version__) if not ctx.agent_config.aea_version_specifiers.contains(version): new_aea_version = compute_specifier_from_version(version) old_aea_version = ctx.agent_config.aea_version click.echo( f"Updating AEA version specifier from {old_aea_version} to {new_aea_version}." ) ctx.agent_config.aea_version = new_aea_version # update author name if it is different cli_author = ctx.config.get("cli_author") if cli_author and ctx.agent_config.author != cli_author: click.echo( f"Updating author from {ctx.agent_config.author} to {cli_author}") ctx.agent_config._author = cli_author # pylint: disable=protected-access ctx.agent_config.version = DEFAULT_VERSION ctx.dump_agent_config()
def _crete_agent_config(ctx: Context, agent_name: str, set_author: str) -> AgentConfig: """ Create agent config. :param ctx: context object. :param agent_name: agent name. :param set_author: author name to set. :return: AgentConfig object. """ agent_config = AgentConfig( agent_name=agent_name, aea_version=compute_specifier_from_version(Version(aea.__version__)), author=set_author, version=DEFAULT_VERSION, license_=DEFAULT_LICENSE, registry_path=os.path.join("..", DEFAULT_REGISTRY_PATH), description="", default_ledger=DEFAULT_LEDGER, default_connection=str(PublicId.from_str(DEFAULT_CONNECTION).to_any()), ) with open(os.path.join(agent_name, DEFAULT_AEA_CONFIG_FILE), "w") as config_file: ctx.agent_loader.dump(agent_config, config_file) return agent_config
def update_aea_version_range( package_configuration: PackageConfiguration) -> None: """Update 'aea_version' range.""" version = get_current_aea_version() if not package_configuration.aea_version_specifiers.contains(version): new_aea_version = compute_specifier_from_version(version) old_aea_version = package_configuration.aea_version click.echo( f"Updating AEA version specifier from {old_aea_version} to {new_aea_version}." ) package_configuration.aea_version = new_aea_version
def assert_dependency_updated( self, item_type: ComponentType, package_name: str, package_type: str, expected: Set[PublicId], ): """Assert dependency is updated.""" package_path = Path(self._get_cwd(), item_type.to_plural(), package_name) component_config = load_component_configuration(item_type, package_path) assert hasattr(component_config, package_type), "Test is not well-written." assert getattr(component_config, package_type) == expected # type: ignore expected_version_range = compute_specifier_from_version( get_current_aea_version() ) assert component_config.aea_version == expected_version_range
def test_nothing_to_upgrade(self, mock_click_echo): """Test nothing to upgrade, and additionally, that 'aea_version' is correct.""" result = self.run_cli_command("upgrade", cwd=self._get_cwd()) assert result.exit_code == 0 mock_click_echo.assert_any_call("Starting project upgrade...") mock_click_echo.assert_any_call( "Updating AEA version specifier from ==0.1.0 to >=0.10.0, <0.11.0." ) # test 'aea_version' of agent configuration is upgraded expected_aea_version_specifier = compute_specifier_from_version( get_current_aea_version() ) agent_config = self.load_agent_config(self.current_agent_context) assert agent_config.aea_version == expected_aea_version_specifier assert agent_config.author == self.author assert agent_config.version == DEFAULT_VERSION
class TestUpgradeNonVendorDependencies(AEATestCaseEmpty): """ Test that the command 'aea upgrade' correctly updates non-vendor package data. In particular, check that 'aea upgrade' updates: - the public ids of the package dependencies and the 'aea_version' field. - the 'aea_version' field in case it is not compatible with the current version. The test works as follows: - scaffold a package, one for each possible package type; - add the protocol "fetchai/default:0.11.0" as dependency to each of them. - add the skill "fetchai/error:0.11.0"; this will also add the default protocol. add it also as dependency of non-vendor skill. - run 'aea upgrade' - check that the reference to "fetchai/default" in each scaffolded package has the new version. """ capture_log = True IS_EMPTY = True old_default_protocol_id = PublicId( DefaultMessage.protocol_id.author, DefaultMessage.protocol_id.name, "0.11.0" ) old_error_skill_id = PublicId( ERROR_SKILL_PUBLIC_ID.author, ERROR_SKILL_PUBLIC_ID.name, "0.11.0" ) old_aea_version_range = compute_specifier_from_version(Version("0.1.0")) @classmethod def scaffold_item( cls, item_type: str, name: str, skip_consistency_check: bool = False ) -> Result: """Override default behaviour by adding a custom dependency to the scaffolded item.""" result = super(TestUpgradeNonVendorDependencies, cls).scaffold_item( item_type, name, skip_consistency_check ) # add custom dependency (a protocol) to each package # that supports dependencies (only connections and skills) if item_type in {ComponentType.CONNECTION.value, ComponentType.SKILL.value}: cls.nested_set_config( f"{ComponentType(item_type).to_plural()}.{name}.protocols", [str(cls.old_default_protocol_id)], ) # add the vendor skill as dependency of the non-vendor skill if item_type == ComponentType.SKILL.value: cls.nested_set_config( f"{ComponentType(item_type).to_plural()}.{name}.skills", [str(cls.old_error_skill_id)], ) # update 'aea_version' to an old version range. cls.nested_set_config( f"{ComponentType(item_type).to_plural()}.{name}.aea_version", str(cls.old_aea_version_range), ) return result @classmethod def setup_class(cls): """Set up test case.""" super(TestUpgradeNonVendorDependencies, cls).setup_class() cls.scaffold_item("protocol", "my_protocol", skip_consistency_check=True) cls.scaffold_item("connection", "my_connection", skip_consistency_check=True) cls.scaffold_item("contract", "my_contract", skip_consistency_check=True) cls.scaffold_item("skill", "my_skill", skip_consistency_check=True) cls.run_cli_command( "--skip-consistency-check", "add", "skill", str(cls.old_error_skill_id), cwd=cls._get_cwd(), ) cls.run_cli_command( "--skip-consistency-check", "upgrade", "--local", cwd=cls._get_cwd() ) def test_agent_config_updated(self): """Test the agent configuration is updated.""" loader = ConfigLoader.from_configuration_type(PackageType.AGENT) with Path(self._get_cwd(), DEFAULT_AEA_CONFIG_FILE).open() as fp: agent_config = loader.load(fp) assert DefaultMessage.protocol_id in agent_config.protocols assert ERROR_SKILL_PUBLIC_ID in agent_config.skills def test_non_vendor_update_references_to_upgraded_packages( self, ): # pylint: disable=unused-argument """Test that dependencies in non-vendor packages are updated correctly after upgrade.""" self.assert_dependency_updated( ComponentType.CONNECTION, "my_connection", "protocols", {DefaultMessage.protocol_id}, ) self.assert_dependency_updated( ComponentType.SKILL, "my_skill", "protocols", {DefaultMessage.protocol_id} ) self.assert_dependency_updated( ComponentType.SKILL, "my_skill", "skills", {ERROR_SKILL_PUBLIC_ID} ) def assert_dependency_updated( self, item_type: ComponentType, package_name: str, package_type: str, expected: Set[PublicId], ): """Assert dependency is updated.""" package_path = Path(self._get_cwd(), item_type.to_plural(), package_name) component_config = load_component_configuration(item_type, package_path) assert hasattr(component_config, package_type), "Test is not well-written." assert getattr(component_config, package_type) == expected # type: ignore expected_version_range = compute_specifier_from_version( get_current_aea_version() ) assert component_config.aea_version == expected_version_range
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}." )