Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
24 changes: 17 additions & 7 deletions src/volttron/client/commands/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down Expand Up @@ -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)

Expand All @@ -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)
Expand All @@ -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)

Expand Down
18 changes: 16 additions & 2 deletions src/volttron/server/aip.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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"
)
Comment on lines +774 to +780
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wheel filename parser only accepts python tags py3 or py2.py3 and will raise for valid wheels like pkg-1.0.0-cp310-cp310-manylinux_2_17_x86_64.whl (and also doesn’t account for the optional PEP 427 build tag). Since install_agent_or_lib_source can receive arbitrary wheels, consider parsing per PEP 427 more generally (e.g., accept any python/abi/platform tags and handle an optional build tag) so compiled/tagged wheels don’t get rejected.

Copilot uses AI. Check for mistakes.
agent_name_with_version = stripped.replace("_", "-")
Comment on lines +774 to +781
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_construct_package_name_from_wheel now only accepts wheel filenames with a py3 or py2.py3 Python tag and raises for other valid wheel tags (e.g., py310, cp310, etc.). Since install_agent_or_lib_source calls this for any .whl, this can break installing perfectly valid wheels. Consider using a standards-based wheel filename parser (or expanding the parsing to cover PEP 427 wheel tags and optional build tags) rather than hard-coding py3|py2.py3 and erroring out.

Copilot uses AI. Check for mistakes.
agent_name = agent_name_with_version[:agent_name_with_version.rfind("-")]
return agent_name

Expand Down
4 changes: 4 additions & 0 deletions src/volttron/services/control/control_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 16 additions & 6 deletions src/volttron/utils/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Comment on lines +48 to +55
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tomli is only installed for Python <3.11 (per pyproject), but python = ^3.10 allows running this code on 3.11+. In a dev checkout on 3.11+, this branch will raise ModuleNotFoundError when volttron-core metadata isn't present. Consider importing tomllib when available and falling back to tomli (or adjust dependencies accordingly).

Copilot uses AI. Check for mistakes.
# 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"]
Expand Down
Loading