Skip to content

Commit 5e1aa1e

Browse files
committed
Add uvx_galaxy engine type
Skips cloning and run.sh to go directly for the galaxy entrypoint. Is much faster and cleaner to boot. Does not yet install conditional dependencies.
1 parent b927261 commit 5e1aa1e

File tree

5 files changed

+311
-5
lines changed

5 files changed

+311
-5
lines changed

planemo/engine/factory.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
ExternalGalaxyEngine,
1111
LocalManagedGalaxyEngine,
1212
LocalManagedGalaxyEngineWithSingularityDB,
13+
UvxManagedGalaxyEngine,
1314
)
1415
from .toil import ToilEngine
1516

@@ -19,7 +20,7 @@
1920
def is_galaxy_engine(**kwds):
2021
"""Return True iff the engine configured is :class:`GalaxyEngine`."""
2122
engine_type_str = kwds.get("engine", "galaxy")
22-
return engine_type_str in ["galaxy", "docker_galaxy", "external_galaxy"]
23+
return engine_type_str in ["galaxy", "docker_galaxy", "external_galaxy", "uvx_galaxy"]
2324

2425

2526
def build_engine(ctx, **kwds):
@@ -34,6 +35,8 @@ def build_engine(ctx, **kwds):
3435
engine_type = DockerizedManagedGalaxyEngine
3536
elif engine_type_str == "external_galaxy":
3637
engine_type = ExternalGalaxyEngine
38+
elif engine_type_str == "uvx_galaxy":
39+
engine_type = UvxManagedGalaxyEngine
3740
elif engine_type_str == "cwltool":
3841
engine_type = CwlToolEngine
3942
elif engine_type_str == "toil":

planemo/engine/galaxy.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,26 @@ def _serve_kwds(self):
191191
return serve_kwds
192192

193193

194+
class UvxManagedGalaxyEngine(LocalManagedGalaxyEngine):
195+
"""An :class:`Engine` implementation backed by Galaxy running via uvx.
196+
197+
More information on Galaxy can be found at http://galaxyproject.org/.
198+
"""
199+
200+
def _serve_kwds(self):
201+
serve_kwds = self._kwds.copy()
202+
serve_kwds["uvx_galaxy"] = True
203+
return serve_kwds
204+
205+
@contextlib.contextmanager
206+
def ensure_runnables_served(self, runnables):
207+
"""Ensure runnables are served using uvx Galaxy configuration."""
208+
with serve_daemon(self._ctx, runnables, **self._serve_kwds()) as config:
209+
if "install_args_list" in self._serve_kwds():
210+
self.shed_install(config)
211+
yield config
212+
213+
194214
class ExternalGalaxyEngine(GalaxyEngine):
195215
"""An :class:`Engine` implementation backed by an external Galaxy instance."""
196216

planemo/galaxy/config.py

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ def galaxy_config(ctx, runnables, **kwds):
158158
c = docker_galaxy_config
159159
elif kwds.get("external", False):
160160
c = external_galaxy_config
161+
elif kwds.get("uvx_galaxy", False):
162+
c = uvx_galaxy_config
161163
log_thread = None
162164
e = threading.Event()
163165
try:
@@ -1421,7 +1423,286 @@ def _ensure_directory(path):
14211423
os.makedirs(path)
14221424

14231425

1426+
class UvxGalaxyConfig(BaseManagedGalaxyConfig):
1427+
"""A uvx-managed implementation of :class:`GalaxyConfig`."""
1428+
1429+
def __init__(
1430+
self,
1431+
ctx,
1432+
config_directory,
1433+
env,
1434+
test_data_dir,
1435+
port,
1436+
server_name,
1437+
master_api_key,
1438+
runnables,
1439+
kwds,
1440+
):
1441+
super().__init__(
1442+
ctx,
1443+
config_directory,
1444+
env,
1445+
test_data_dir,
1446+
port,
1447+
server_name,
1448+
master_api_key,
1449+
runnables,
1450+
kwds,
1451+
)
1452+
# Use config directory as placeholder for galaxy_root since uvx manages Galaxy
1453+
self.galaxy_root = config_directory
1454+
1455+
@property
1456+
def host(self):
1457+
"""Host for uvx Galaxy instance."""
1458+
return self._kwds.get("host", "127.0.0.1")
1459+
1460+
@property
1461+
def galaxy_config_file(self):
1462+
"""Path to galaxy configuration file."""
1463+
return self.env.get("GALAXY_CONFIG_FILE", os.path.join(self.config_directory, "galaxy.yml"))
1464+
1465+
def kill(self):
1466+
"""Kill uvx Galaxy process."""
1467+
if self._ctx.verbose:
1468+
shell(["ps", "ax"])
1469+
exists = os.path.exists(self.pid_file)
1470+
print(f"Killing pid file [{self.pid_file}]")
1471+
print(f"pid_file exists? [{exists}]")
1472+
if exists:
1473+
with open(self.pid_file) as f:
1474+
print(f"pid_file contents are [{f.read()}]")
1475+
1476+
# Kill process using existing utility
1477+
kill_pid_file(self.pid_file)
1478+
1479+
def startup_command(self, ctx, **kwds):
1480+
"""Return a shell command used to startup this uvx Galaxy instance."""
1481+
daemon = kwds.get("daemon", False)
1482+
uvx_cmd = self._build_uvx_command(**kwds)
1483+
1484+
if daemon:
1485+
# Use shell background execution for daemon mode - return as single string for shell execution
1486+
return f"nohup {shell_join(uvx_cmd)} > {self.log_file} 2>&1 & echo $! > {self.pid_file}"
1487+
else:
1488+
# Direct foreground execution
1489+
return shell_join(uvx_cmd)
1490+
1491+
def _build_uvx_command(self, **kwds):
1492+
"""Build uvx galaxy command with appropriate flags."""
1493+
cmd = ["uvx", "galaxy"]
1494+
1495+
# Only pass config file - host and port are configured in galaxy.yml
1496+
cmd.extend(["-c", self.galaxy_config_file])
1497+
1498+
return cmd
1499+
1500+
@property
1501+
def log_file(self):
1502+
"""Log file used when planemo serves this uvx Galaxy instance."""
1503+
file_name = f"{self.server_name}.log"
1504+
return os.path.join(self.config_directory, file_name)
1505+
1506+
@property
1507+
def pid_file(self):
1508+
"""PID file for uvx Galaxy process."""
1509+
pid_file_name = f"{self.server_name}.pid"
1510+
return os.path.join(self.config_directory, pid_file_name)
1511+
1512+
@property
1513+
def log_contents(self):
1514+
"""Return contents of log file."""
1515+
if not os.path.exists(self.log_file):
1516+
return ""
1517+
with open(self.log_file) as f:
1518+
return f.read()
1519+
1520+
def cleanup(self):
1521+
"""Clean up uvx Galaxy configuration."""
1522+
shutil.rmtree(self.config_directory, CLEANUP_IGNORE_ERRORS)
1523+
1524+
@property
1525+
def default_use_path_paste(self):
1526+
"""Default path paste setting for uvx Galaxy."""
1527+
return self.user_is_admin
1528+
1529+
def _install_galaxy(self):
1530+
"""Override to skip Galaxy installation - uvx manages this."""
1531+
# No-op for uvx since it manages Galaxy installation
1532+
return True
1533+
1534+
def _ensure_galaxy_repository_available(self):
1535+
"""Override to skip repository cloning - not needed for uvx."""
1536+
# No-op for uvx since no repository is needed
1537+
return True
1538+
1539+
1540+
@contextlib.contextmanager
1541+
def uvx_galaxy_config(ctx, runnables, for_tests=False, **kwds):
1542+
"""Set up a ``UvxGalaxyConfig`` in an auto-cleaned context."""
1543+
test_data_dir = _find_test_data(runnables, **kwds)
1544+
1545+
with _config_directory(ctx, **kwds) as config_directory:
1546+
1547+
def config_join(*args):
1548+
return os.path.join(config_directory, *args)
1549+
1550+
server_name = "main"
1551+
1552+
# Ensure dependency resolvers are configured
1553+
ensure_dependency_resolvers_conf_configured(ctx, kwds, os.path.join(config_directory, "resolvers_conf.xml"))
1554+
1555+
# Handle basic galaxy configuration without installation
1556+
galaxy_root = config_directory # Use config directory as galaxy root for uvx
1557+
# Skip refgenie config for uvx since Galaxy is managed by uvx
1558+
1559+
# Setup tool paths (but don't require galaxy_root)
1560+
all_tool_paths = _all_tool_paths(runnables, galaxy_root=None, extra_tools=kwds.get("extra_tools"))
1561+
kwds["all_in_one_handling"] = True
1562+
_handle_job_config_file(config_directory, server_name, test_data_dir, all_tool_paths, kwds)
1563+
_handle_file_sources(config_directory, test_data_dir, kwds)
1564+
1565+
# Basic paths setup
1566+
file_path = kwds.get("file_path") or config_join("files")
1567+
_ensure_directory(file_path)
1568+
1569+
tool_dependency_dir = kwds.get("tool_dependency_dir") or config_join("deps")
1570+
_ensure_directory(tool_dependency_dir)
1571+
1572+
shed_tool_conf = kwds.get("shed_tool_conf") or config_join("shed_tools_conf.xml")
1573+
empty_tool_conf = config_join("empty_tool_conf.xml")
1574+
tool_conf = config_join("tool_conf.xml")
1575+
shed_data_manager_config_file = config_join("shed_data_manager_conf.xml")
1576+
1577+
shed_tool_path = kwds.get("shed_tool_path") or config_join("shed_tools")
1578+
_ensure_directory(shed_tool_path)
1579+
1580+
sheds_config_path = _configure_sheds_config_file(ctx, config_directory, runnables, **kwds)
1581+
1582+
database_location = config_join("galaxy.sqlite")
1583+
master_api_key = _get_master_api_key(kwds)
1584+
dependency_dir = os.path.join(config_directory, "deps")
1585+
_ensure_directory(dependency_dir)
1586+
port = _get_port(kwds)
1587+
1588+
# Template args for file generation
1589+
# Use fallback for tool shed URL if none configured
1590+
shed_target_url = tool_shed_url(ctx, **kwds) or MAIN_TOOLSHED_URL
1591+
1592+
template_args = dict(
1593+
shed_tool_path=shed_tool_path,
1594+
shed_tool_conf=shed_tool_conf,
1595+
shed_data_manager_config_file=shed_data_manager_config_file,
1596+
test_data_dir=test_data_dir,
1597+
shed_target_url=shed_target_url,
1598+
dependency_dir=dependency_dir,
1599+
file_path=file_path,
1600+
temp_directory=config_directory,
1601+
)
1602+
1603+
# Galaxy properties
1604+
properties = _shared_galaxy_properties(config_directory, kwds, for_tests=for_tests)
1605+
properties.update(
1606+
dict(
1607+
server_name=server_name,
1608+
host=kwds.get("host", "127.0.0.1"),
1609+
port=str(port),
1610+
enable_celery_tasks="true",
1611+
ftp_upload_dir_template="${ftp_upload_dir}",
1612+
ftp_upload_purge="false",
1613+
ftp_upload_dir=test_data_dir or os.path.abspath("."),
1614+
ftp_upload_site="Test Data",
1615+
check_upload_content="false",
1616+
tool_dependency_dir=dependency_dir,
1617+
file_path=file_path,
1618+
new_file_path="${temp_directory}/tmp",
1619+
tool_config_file=f"{tool_conf},{shed_tool_conf}",
1620+
tool_sheds_config_file=sheds_config_path,
1621+
manage_dependency_relationships="false",
1622+
job_working_directory="${temp_directory}/job_working_directory",
1623+
template_cache_path="${temp_directory}/compiled_templates",
1624+
citation_cache_type="file",
1625+
citation_cache_data_dir="${temp_directory}/citations/data",
1626+
citation_cache_lock_dir="${temp_directory}/citations/lock",
1627+
database_auto_migrate="true",
1628+
enable_beta_tool_formats="true",
1629+
id_secret="${id_secret}",
1630+
log_level="DEBUG" if ctx.verbose else "INFO",
1631+
debug="true" if ctx.verbose else "false",
1632+
watch_tools="auto",
1633+
default_job_shell="/bin/bash",
1634+
integrated_tool_panel_config=("${temp_directory}/integrated_tool_panel_conf.xml"),
1635+
migrated_tools_config=empty_tool_conf,
1636+
test_data_dir=test_data_dir,
1637+
shed_data_manager_config_file=shed_data_manager_config_file,
1638+
outputs_to_working_directory="true",
1639+
object_store_store_by="uuid",
1640+
)
1641+
)
1642+
1643+
_handle_container_resolution(ctx, kwds, properties)
1644+
properties["database_connection"] = _database_connection(database_location, **kwds)
1645+
1646+
if kwds.get("mulled_containers", False):
1647+
properties["mulled_channels"] = kwds.get("conda_ensure_channels", "")
1648+
1649+
_handle_kwd_overrides(properties, kwds)
1650+
1651+
# Build environment
1652+
env = _build_env_for_galaxy(properties, template_args)
1653+
env["PLANEMO"] = "1"
1654+
env["GALAXY_DEVELOPMENT_ENVIRONMENT"] = "1"
1655+
1656+
# Write configuration files (but skip Galaxy installation)
1657+
# Assume uvx Galaxy is modern (>= 22.01) and write YAML config directly
1658+
env["GALAXY_CONFIG_FILE"] = config_join("galaxy.yml")
1659+
env["GRAVITY_STATE_DIR"] = config_join("gravity")
1660+
with NamedTemporaryFile(suffix=".sock", delete=True) as nt:
1661+
env["SUPERVISORD_SOCKET"] = nt.name
1662+
write_file(
1663+
env["GALAXY_CONFIG_FILE"],
1664+
json.dumps(
1665+
{
1666+
"galaxy": properties,
1667+
"gravity": {
1668+
"galaxy_root": galaxy_root,
1669+
"gunicorn": {
1670+
"bind": f"{kwds.get('host', 'localhost')}:{port}",
1671+
"preload": False,
1672+
},
1673+
"gx-it-proxy": {
1674+
"enable": False,
1675+
},
1676+
},
1677+
},
1678+
indent=2,
1679+
),
1680+
)
1681+
1682+
# Write tool configurations
1683+
tool_definition = _tool_conf_entry_for(all_tool_paths)
1684+
write_file(tool_conf, _sub(TOOL_CONF_TEMPLATE, dict(tool_definition=tool_definition)))
1685+
1686+
shed_tool_conf_contents = _sub(SHED_TOOL_CONF_TEMPLATE, template_args)
1687+
write_file(shed_tool_conf, shed_tool_conf_contents, force=False)
1688+
write_file(shed_data_manager_config_file, SHED_DATA_MANAGER_CONF_TEMPLATE)
1689+
1690+
yield UvxGalaxyConfig(
1691+
ctx,
1692+
config_directory,
1693+
env,
1694+
test_data_dir,
1695+
port,
1696+
server_name,
1697+
master_api_key,
1698+
runnables,
1699+
kwds,
1700+
)
1701+
1702+
14241703
__all__ = (
14251704
"DATABASE_LOCATION_TEMPLATE",
14261705
"galaxy_config",
1706+
"UvxGalaxyConfig",
1707+
"uvx_galaxy_config",
14271708
)

planemo/galaxy/serve.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ def _serve(ctx, runnables, **kwds):
3131
engine = kwds.get("engine", "galaxy")
3232
if engine == "docker_galaxy":
3333
kwds["dockerize"] = True
34+
elif engine == "uvx_galaxy":
35+
kwds["uvx_galaxy"] = True
3436

3537
daemon = kwds.get("daemon", False)
3638
if daemon:

planemo/options.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ def run_engine_option():
7070
"""Annotate click command as consume the --engine option."""
7171
return planemo_option(
7272
"--engine",
73-
type=click.Choice(["galaxy", "docker_galaxy", "cwltool", "toil", "external_galaxy"]),
73+
type=click.Choice(["galaxy", "docker_galaxy", "cwltool", "toil", "external_galaxy", "uvx_galaxy"]),
7474
default=None,
7575
use_global_config=True,
7676
help=(
7777
"Select an engine to run or test artifacts such as tools "
7878
"and workflows. Defaults to a local Galaxy, but running Galaxy within "
79-
"a Docker container or the CWL reference implementation 'cwltool' and "
79+
"a Docker container, via uvx, or the CWL reference implementation 'cwltool' and "
8080
"'toil' be selected."
8181
),
8282
)
@@ -100,14 +100,14 @@ def serve_engine_option():
100100
"""
101101
return planemo_option(
102102
"--engine",
103-
type=click.Choice(["galaxy", "docker_galaxy", "external_galaxy"]),
103+
type=click.Choice(["galaxy", "docker_galaxy", "external_galaxy", "uvx_galaxy"]),
104104
default="galaxy",
105105
use_global_config=True,
106106
use_env_var=True,
107107
help=(
108108
"Select an engine to serve artifacts such as tools "
109109
"and workflows. Defaults to a local Galaxy, but running Galaxy within "
110-
"a Docker container."
110+
"a Docker container or via uvx is also supported."
111111
),
112112
)
113113

0 commit comments

Comments
 (0)