Skip to content

Commit 9bfdea9

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 9bfdea9

File tree

5 files changed

+318
-6
lines changed

5 files changed

+318
-6
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: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
execute_rerun,
1919
GalaxyBaseRunResponse,
2020
)
21-
from planemo.galaxy.config import external_galaxy_config
21+
from planemo.galaxy.config import (
22+
external_galaxy_config,
23+
uvx_galaxy_config,
24+
)
2225
from planemo.galaxy.serve import serve_daemon
2326
from planemo.runnable import (
2427
DelayedGalaxyToolTestCase,
@@ -191,6 +194,26 @@ def _serve_kwds(self):
191194
return serve_kwds
192195

193196

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

planemo/galaxy/config.py

Lines changed: 284 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,289 @@ 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+
try:
1591+
shed_target_url = tool_shed_url(ctx, **kwds) or MAIN_TOOLSHED_URL
1592+
except:
1593+
shed_target_url = MAIN_TOOLSHED_URL
1594+
1595+
template_args = dict(
1596+
shed_tool_path=shed_tool_path,
1597+
shed_tool_conf=shed_tool_conf,
1598+
shed_data_manager_config_file=shed_data_manager_config_file,
1599+
test_data_dir=test_data_dir,
1600+
shed_target_url=shed_target_url,
1601+
dependency_dir=dependency_dir,
1602+
file_path=file_path,
1603+
temp_directory=config_directory,
1604+
)
1605+
1606+
# Galaxy properties
1607+
properties = _shared_galaxy_properties(config_directory, kwds, for_tests=for_tests)
1608+
properties.update(
1609+
dict(
1610+
server_name=server_name,
1611+
host=kwds.get("host", "127.0.0.1"),
1612+
port=str(port),
1613+
enable_celery_tasks="true",
1614+
ftp_upload_dir_template="${ftp_upload_dir}",
1615+
ftp_upload_purge="false",
1616+
ftp_upload_dir=test_data_dir or os.path.abspath("."),
1617+
ftp_upload_site="Test Data",
1618+
check_upload_content="false",
1619+
tool_dependency_dir=dependency_dir,
1620+
file_path=file_path,
1621+
new_file_path="${temp_directory}/tmp",
1622+
tool_config_file=f"{tool_conf},{shed_tool_conf}",
1623+
tool_sheds_config_file=sheds_config_path,
1624+
manage_dependency_relationships="false",
1625+
job_working_directory="${temp_directory}/job_working_directory",
1626+
template_cache_path="${temp_directory}/compiled_templates",
1627+
citation_cache_type="file",
1628+
citation_cache_data_dir="${temp_directory}/citations/data",
1629+
citation_cache_lock_dir="${temp_directory}/citations/lock",
1630+
database_auto_migrate="true",
1631+
enable_beta_tool_formats="true",
1632+
id_secret="${id_secret}",
1633+
log_level="DEBUG" if ctx.verbose else "INFO",
1634+
debug="true" if ctx.verbose else "false",
1635+
watch_tools="auto",
1636+
default_job_shell="/bin/bash",
1637+
integrated_tool_panel_config=("${temp_directory}/integrated_tool_panel_conf.xml"),
1638+
migrated_tools_config=empty_tool_conf,
1639+
test_data_dir=test_data_dir,
1640+
shed_data_manager_config_file=shed_data_manager_config_file,
1641+
outputs_to_working_directory="true",
1642+
object_store_store_by="uuid",
1643+
)
1644+
)
1645+
1646+
_handle_container_resolution(ctx, kwds, properties)
1647+
properties["database_connection"] = _database_connection(database_location, **kwds)
1648+
1649+
if kwds.get("mulled_containers", False):
1650+
properties["mulled_channels"] = kwds.get("conda_ensure_channels", "")
1651+
1652+
_handle_kwd_overrides(properties, kwds)
1653+
1654+
# Build environment
1655+
env = _build_env_for_galaxy(properties, template_args)
1656+
env["PLANEMO"] = "1"
1657+
env["GALAXY_DEVELOPMENT_ENVIRONMENT"] = "1"
1658+
1659+
# Write configuration files (but skip Galaxy installation)
1660+
# Assume uvx Galaxy is modern (>= 22.01) and write YAML config directly
1661+
env["GALAXY_CONFIG_FILE"] = config_join("galaxy.yml")
1662+
env["GRAVITY_STATE_DIR"] = config_join("gravity")
1663+
with NamedTemporaryFile(suffix=".sock", delete=True) as nt:
1664+
env["SUPERVISORD_SOCKET"] = nt.name
1665+
write_file(
1666+
env["GALAXY_CONFIG_FILE"],
1667+
json.dumps(
1668+
{
1669+
"galaxy": properties,
1670+
"gravity": {
1671+
"galaxy_root": galaxy_root,
1672+
"gunicorn": {
1673+
"bind": f"{kwds.get('host', 'localhost')}:{port}",
1674+
"preload": False,
1675+
},
1676+
"gx-it-proxy": {
1677+
"enable": False,
1678+
},
1679+
},
1680+
},
1681+
indent=2,
1682+
),
1683+
)
1684+
1685+
# Write tool configurations
1686+
tool_definition = _tool_conf_entry_for(all_tool_paths)
1687+
write_file(tool_conf, _sub(TOOL_CONF_TEMPLATE, dict(tool_definition=tool_definition)))
1688+
1689+
shed_tool_conf_contents = _sub(SHED_TOOL_CONF_TEMPLATE, template_args)
1690+
write_file(shed_tool_conf, shed_tool_conf_contents, force=False)
1691+
write_file(shed_data_manager_config_file, SHED_DATA_MANAGER_CONF_TEMPLATE)
1692+
1693+
yield UvxGalaxyConfig(
1694+
ctx,
1695+
config_directory,
1696+
env,
1697+
test_data_dir,
1698+
port,
1699+
server_name,
1700+
master_api_key,
1701+
runnables,
1702+
kwds,
1703+
)
1704+
1705+
14241706
__all__ = (
14251707
"DATABASE_LOCATION_TEMPLATE",
14261708
"galaxy_config",
1709+
"UvxGalaxyConfig",
1710+
"uvx_galaxy_config",
14271711
)

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:

0 commit comments

Comments
 (0)