Skip to content

Commit ea7d8fd

Browse files
committed
add package management
1 parent 5956579 commit ea7d8fd

9 files changed

Lines changed: 357 additions & 40 deletions

CHANGELOG.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
99

1010
### Added
1111

12-
- Custom Action: Validate initialization phase - run the init code and report results
13-
- Custom Action: Validate execution phase - run the execute code and report results
14-
- Custom Action: List Packages - Show installed python packages with version
15-
- more documentation
12+
- Python Code Workflow Task
13+
- Custom Action: Validate initialization phase - run the init code and report results
14+
- Custom Action: Validate execution phase - run the execute code and report results
15+
- Custom Action: List Packages - Show installed python packages with version
16+
- Custom Action: Install Missing Dependencies - Install missing dependency packages
17+
- Parameter: Dependencies - Comma-separated list of package names
18+
- more documentation
1619

1720
### Fixed
1821

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Shared Code for Package Management"""
2+
3+
from cmem.cmempy.workspace.python import install_package_by_name, list_packages
4+
from cmem_plugin_base.dataintegration.context import UserContext
5+
from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access
6+
from pydantic import BaseModel
7+
8+
9+
class InstallationResult(BaseModel):
10+
"""Result of the install_package_by_name call"""
11+
12+
success: bool
13+
output: str
14+
forbidden: bool
15+
already_install: bool = False
16+
17+
18+
def install_missing_packages(
19+
package_specs: list[str], context: UserContext
20+
) -> dict[str, InstallationResult]:
21+
"""Install missing packages"""
22+
setup_cmempy_user_access(context=context)
23+
installed_package: dict[str, str] = {
24+
package["name"]: package["name"] for package in list_packages()
25+
}
26+
results = {}
27+
for package_spec in package_specs:
28+
if package_spec not in installed_package:
29+
setup_cmempy_user_access(context=context)
30+
result = InstallationResult(**install_package_by_name(package_name=package_spec))
31+
results[package_spec] = result
32+
else:
33+
version = installed_package[package_spec]
34+
result = InstallationResult(
35+
success=True,
36+
output=f"Package already installed: {package_spec} ({version})",
37+
forbidden=False,
38+
already_install=True,
39+
)
40+
results[package_spec] = result
41+
return results
File renamed without changes.
Lines changed: 58 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
from types import SimpleNamespace
55
from typing import Any
66

7-
from cmem_plugin_base.dataintegration.context import ExecutionContext
7+
from cmem.cmempy.workspace.python import list_packages
8+
from cmem_plugin_base.dataintegration.context import ExecutionContext, PluginContext
89
from cmem_plugin_base.dataintegration.description import Icon, Plugin, PluginAction, PluginParameter
910
from cmem_plugin_base.dataintegration.entity import Entities
1011
from cmem_plugin_base.dataintegration.parameter.code import PythonCode
1112
from cmem_plugin_base.dataintegration.plugins import WorkflowPlugin
12-
from pkg_resources import working_set
13+
from cmem_plugin_base.dataintegration.utils import setup_cmempy_user_access
14+
15+
from cmem_plugin_python.package_management import install_missing_packages
1316

1417
docu_links = SimpleNamespace()
1518
docu_links.entities = (
@@ -202,6 +205,11 @@
202205
label="List Packages",
203206
description="Show installed python packages with version.",
204207
),
208+
PluginAction(
209+
name="install_missing_packages_action",
210+
label="Install missing dependencies",
211+
description="Install missing dependency packages.",
212+
),
205213
],
206214
parameters=[
207215
PluginParameter(
@@ -214,6 +222,12 @@
214222
label="Python source code for the execution phase",
215223
default_value="",
216224
),
225+
PluginParameter(
226+
name="dependencies",
227+
label="Dependencies",
228+
description="Comma-separated list of package names, e.g. 'pandas'.",
229+
default_value="",
230+
),
217231
],
218232
)
219233
class PythonCodeWorkflowPlugin(WorkflowPlugin):
@@ -222,36 +236,26 @@ class PythonCodeWorkflowPlugin(WorkflowPlugin):
222236
init_code: str
223237
execute_code: str
224238
data: dict
239+
dependencies: list[str]
225240

226-
def do_init(self) -> dict[str, Any]:
227-
"""Run initialization phase"""
228-
scope: dict[str, Any] = {"data": {}}
229-
exec(str(self.init_code), scope) # nosec # noqa: S102
230-
return scope
231-
232-
def do_execute(
233-
self, inputs: Sequence[Entities], context: ExecutionContext | None, data: dict
234-
) -> dict[str, Any]:
235-
"""Run execution phase"""
236-
scope: dict[str, Any] = {"inputs": inputs, "context": context, "data": data}
237-
exec(str(self.execute_code), scope) # nosec # noqa: S102
238-
return scope
239-
240-
def __init__(self, init_code: PythonCode, execute_code: PythonCode):
241+
def __init__(self, init_code: PythonCode, execute_code: PythonCode, dependencies: str = ""):
241242
self.init_code = str(init_code)
242243
self.execute_code = str(execute_code)
244+
self.dependencies = (
245+
[] if dependencies.strip() == "" else [_.strip() for _ in dependencies.split(",")]
246+
)
243247
scope = self.do_init()
244248
if "input_ports" in scope:
245249
self.input_ports = scope["input_ports"]
246250
if "output_port" in scope:
247251
self.output_port = scope["output_port"]
248252
self.data = scope.get("data", {})
249253

250-
def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Entities | None:
251-
"""Start the plugin in workflow context."""
252-
self.log.info("Start doing bad things with custom code.")
253-
scope = self.do_execute(inputs, context, self.data)
254-
return scope.get("result")
254+
def do_init(self) -> dict[str, Any]:
255+
"""Run initialization phase"""
256+
scope: dict[str, Any] = {"data": {}}
257+
exec(str(self.init_code), scope) # nosec # noqa: S102
258+
return scope
255259

256260
def validate_init_action(self) -> str:
257261
"""Run the init code and report results."""
@@ -284,6 +288,14 @@ def validate_init_action(self) -> str:
284288
output += "No data provided.\n"
285289
return output
286290

291+
def do_execute(
292+
self, inputs: Sequence[Entities], context: ExecutionContext | None, data: dict
293+
) -> dict[str, Any]:
294+
"""Run execution phase"""
295+
scope: dict[str, Any] = {"inputs": inputs, "context": context, "data": data}
296+
exec(str(self.execute_code), scope) # nosec # noqa: S102
297+
return scope
298+
287299
def validate_execute_action(self) -> str:
288300
"""Run the execute code and report results."""
289301
init_scope = self.do_init()
@@ -304,7 +316,28 @@ def validate_execute_action(self) -> str:
304316
output = "No result provided.\n"
305317
return output
306318

307-
def list_packages_action(self) -> str:
319+
def list_packages_action(self, context: PluginContext) -> str:
308320
"""List Packages action"""
309-
packages = sorted([f"- {i.key} ({i.version})" for i in working_set])
310-
return "\n".join(packages)
321+
setup_cmempy_user_access(context=context.user)
322+
packages: list[dict[str, str]] = list_packages()
323+
output = [f"- {package['name']} ({package['version']})" for package in packages]
324+
return "\n".join(output)
325+
326+
def install_missing_packages_action(self, context: PluginContext) -> str:
327+
"""Install Missing Packages action"""
328+
results = install_missing_packages(package_specs=self.dependencies, context=context.user)
329+
output = []
330+
for package, result in results.items():
331+
output.append(f"# {package}\n\n")
332+
output.append(f"{result.output}\n\n")
333+
if len(output) == 0:
334+
output.append("No packages installed.")
335+
return "\n".join(output)
336+
337+
def execute(self, inputs: Sequence[Entities], context: ExecutionContext) -> Entities | None:
338+
"""Start the plugin in workflow context."""
339+
self.log.info("Start doing bad things with custom code.")
340+
if self.dependencies:
341+
install_missing_packages(package_specs=self.dependencies, context=context.user)
342+
scope = self.do_execute(inputs, context, self.data)
343+
return scope.get("result")

0 commit comments

Comments
 (0)