diff --git a/pyproject.toml b/pyproject.toml index bf0a99fa1..bf753d8ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ poetry = "^2.0" python = "^3.10" gevent = ">=24.2.1,<24.3" PyYAML = "^6.0" -toml = "^0.10.2" dateutils = "^0.6.12" tzlocal = "^4.1" psutil = "^5.9.0" @@ -48,12 +47,12 @@ blinker = "^1.7.0" dataclass-wizard = "^0.22.3" returns = "^0.22.0" cattrs = "^23.2.3" +tomli = { version = "^2.0.1", python = "<3.11" } [tool.poetry.group.dev.dependencies] pytest = "^8.3.3" pre-commit = "^4.0.1" yapf = "^0.32.0" -toml = "^0.10.2" mypy = "^1.2.0" pytest-timeout = "^2.3.1" pytest-mock = "^3.10.0" diff --git a/src/volttron/client/commands/control.py b/src/volttron/client/commands/control.py index 3a2ed1376..0bc76e9c3 100644 --- a/src/volttron/client/commands/control.py +++ b/src/volttron/client/commands/control.py @@ -274,6 +274,11 @@ def remove_agent(opts, remove_auth=True): opts.connection.call("remove_agent", agent.uuid, remove_auth=remove_auth) +def remove_lib(opts): + opts.connection.call("remove_library", opts.library) + sys.stdout.write(f"Removed {opts.library} \n") + + def _calc_min_uuid_length(agents: list[AgentMeta]): n = 0 for agent1 in agents: @@ -2138,7 +2143,7 @@ def main(): os.environ["VOLTTRON_HOME"] = volttron_home global_args = config.ArgumentParser(description="global options", add_help=False) - + # Connection options connection_group = global_args.add_argument_group('Connection Options') connection_group.add_argument( @@ -2154,7 +2159,7 @@ def main(): timeout=120.0, help="timeout in seconds for remote calls (default: %(default)g)", ) - + # Debug options debug_group = global_args.add_argument_group('Debug Options') debug_group.add_argument( @@ -2164,7 +2169,7 @@ def main(): ) filterable = config.ArgumentParser(add_help=False) - + # Filter/Search options filter_group = filterable.add_argument_group('Filter Options') filter_group.add_argument( @@ -2198,7 +2203,7 @@ def main(): argument_default=argparse.SUPPRESS, parents=[global_args], ) - + # Output formatting options output_group = parser.add_argument_group('Output Options') output_group.add_argument( @@ -2207,7 +2212,7 @@ def main(): default=False, help="format output to json", ) - + # Logging options logging_group = parser.add_argument_group('Logging Options') logging_group.add_argument( @@ -2246,7 +2251,7 @@ def main(): default=logging.WARNING, help="set logger verboseness", ) - + # Hidden options parser.add_argument("--show-config", action="store_true", help=argparse.SUPPRESS) @@ -2269,7 +2274,7 @@ def add_parser(*args, **kwargs) -> argparse.ArgumentParser: kwargs["parents"] = parents subparser = kwargs.pop("subparser", top_level_subparsers) return subparser.add_parser(*args, **kwargs) - + add_publish_parser(add_parser) add_subscribe_parser(add_parser) add_install_agent_parser(add_parser) @@ -2296,6 +2301,11 @@ def add_parser(*args, **kwargs) -> argparse.ArgumentParser: ) remove.set_defaults(func=remove_agent, force=False) + remove_lib_parser = add_parser("remove-lib", help="remove library") + remove_lib_parser.add_argument("library", help="name of the library") + remove_lib_parser.set_defaults(func=remove_lib) + + peers = add_parser("peerlist", help="list the peers connected to the platform") peers.set_defaults(func=list_peers) diff --git a/src/volttron/server/aip.py b/src/volttron/server/aip.py index b17cdc57c..c6b5d7cc8 100644 --- a/src/volttron/server/aip.py +++ b/src/volttron/server/aip.py @@ -588,7 +588,14 @@ def install_library(self, library, force=False, pre_release=False): """ name, name_with_version, _ = self.install_agent_or_lib_source(library, force, pre_release) return name_with_version - + + def remove_library(self, library): + """ + Removes the library from current pyproject toml project, which in turn uninstalls the library from current + venv + """ + cmd = ["poetry", "--directory", self._server_opts.poetry_project_path.as_posix(), "remove", library] + execute_command(cmd) def _raise_error_if_identity_exists_without_force(self, vip_identity: str, force: bool): """ @@ -764,7 +771,14 @@ def _construct_package_name_from_wheel(agent_wheel): agent_name = agent_wheel if agent_wheel.endswith(".whl"): wheel = agent_wheel.split("/")[-1] - agent_name_with_version = wheel.replace("-py3-none-any.whl", "").replace("_", "-") + # handle both py3 and python2&3 compatible wheels + stripped = re.sub(r'-(py2\.py3|py3)-[^-]+-[^-]+\.whl$', '', wheel) + if stripped == wheel: + raise RuntimeError( + f"Unable to parse package name from wheel filename '{wheel}'. " + f"Expected format: {{name}}-{{version}}-(py3|py2.py3)-{{abi}}-{{platform}}.whl" + ) + agent_name_with_version = stripped.replace("_", "-") agent_name = agent_name_with_version[:agent_name_with_version.rfind("-")] return agent_name diff --git a/src/volttron/services/control/control_service.py b/src/volttron/services/control/control_service.py index 3ec480de2..6f342a68b 100644 --- a/src/volttron/services/control/control_service.py +++ b/src/volttron/services/control/control_service.py @@ -528,6 +528,10 @@ def install_library(self, source, data, force, allow_prerelease) -> str: force=force, pre_release=allow_prerelease) + @RPC.export + def remove_library(self, library: str) -> None: + self._aip.remove_library(library=library) + def _raise_error_if_identity_exists_without_force(self, vip_identity: str, force: bool) -> Identity: """ This will raise a ValueError if the identity passed exists but diff --git a/src/volttron/utils/version.py b/src/volttron/utils/version.py index a5a20474a..12bb28e99 100644 --- a/src/volttron/utils/version.py +++ b/src/volttron/utils/version.py @@ -45,15 +45,25 @@ try: # We should be in a develop environment therefore # we can get the version from the toml pyproject.toml - root = Path(__file__).parent.parent.parent - tomle_file = root.joinpath("pyproject.toml") - if not tomle_file.exists(): + root = Path(__file__).parent.parent.parent.parent + toml_file = root.joinpath("pyproject.toml") + if not toml_file.exists(): raise ValueError( - f"Couldn't find pyproject.toml file for finding version. ({str(tomle_file)})") - import toml + f"Couldn't find pyproject.toml file for finding version. ({str(toml_file)})") - pyproject = toml.load(tomle_file) + # Prefer stdlib tomllib when available (Python 3.11+), fall back to tomli if installed. + try: + # works if python3.11+ + import tomllib as toml_loader # type: ignore[attr-defined] + except ModuleNotFoundError: + try: + import tomli as toml_loader # type: ignore[import] + except ModuleNotFoundError: + # Neither tomllib nor tomli is available; fall back to pip list mechanism. + raise ValueError("No TOML parser available") + with open(toml_file, "rb") as f: + pyproject = toml_loader.load(f) __version__ = pyproject["tool"]["poetry"]["version"] except ValueError: cmd = [sys.executable, "-m", "pip", "list"]