Warning

This document is for an in-development version of Galaxy. You can alternatively view this page in the latest release if it exists or view the top of the latest release's documentation.

Source code for galaxy.files.sources.ftp

import urllib.parse
from typing import Union

try:
    from fs.ftpfs import FTPFS
except ImportError:
    FTPFS = None  # type: ignore[misc,assignment]


from galaxy.files.models import (
    BaseFileSourceConfiguration,
    BaseFileSourceTemplateConfiguration,
    FilesSourceRuntimeContext,
)
from galaxy.util.config_templates import TemplateExpansion
from ._pyfilesystem2 import PyFilesystem2FilesSource


class FTPFileSourceTemplateConfiguration(BaseFileSourceTemplateConfiguration):
    host: Union[str, TemplateExpansion] = ""
    port: Union[int, TemplateExpansion] = 21
    user: Union[str, TemplateExpansion] = "anonymous"
    passwd: Union[str, TemplateExpansion] = ""
    acct: Union[str, TemplateExpansion] = ""
    timeout: Union[int, TemplateExpansion] = 10
    proxy: Union[str, TemplateExpansion, None] = None
    tls: Union[bool, TemplateExpansion] = False


class FTPFileSourceConfiguration(BaseFileSourceConfiguration):
    host: str = ""
    port: int = 21
    user: str = "anonymous"
    passwd: str = ""
    acct: str = ""
    timeout: int = 10
    proxy: Union[str, None] = None
    tls: bool = False


[docs] class FtpFilesSource(PyFilesystem2FilesSource[FTPFileSourceTemplateConfiguration, FTPFileSourceConfiguration]): plugin_type = "ftp" required_module = FTPFS required_package = "fs.ftpfs" template_config_class = FTPFileSourceTemplateConfiguration resolved_config_class = FTPFileSourceConfiguration def _open_fs(self, context: FilesSourceRuntimeContext[FTPFileSourceConfiguration]): if FTPFS is None: raise self.required_package_exception config = context.config return FTPFS( host=config.host, port=config.port, user=config.user, passwd=config.passwd, timeout=config.timeout, acct=config.acct, tls=config.tls, proxy=config.proxy, ) def _realize_to( self, source_path: str, native_path: str, context: FilesSourceRuntimeContext[FTPFileSourceConfiguration] ): path = self._parse_url_and_get_path(source_path, context.config) super()._realize_to(path, native_path, context) def _write_from( self, target_path: str, native_path: str, context: FilesSourceRuntimeContext[FTPFileSourceConfiguration] ): path = self._parse_url_and_get_path(target_path, context.config) super()._write_from(path, native_path, context) def _parse_url_and_get_path(self, url: str, config: FTPFileSourceConfiguration) -> str: host = config.host port = config.port user = config.user passwd = config.passwd rel_path = url if url.startswith(f"ftp://{host or ''}"): props = self._extract_url_props(url) config.host = host or props["host"] config.port = port or props["port"] config.user = user or props["user"] config.passwd = passwd or props["passwd"] rel_path = props["path"] or url return rel_path def _extract_url_props(self, url: str): result = urllib.parse.urlparse(url) return { "host": result.hostname, "port": result.port or 21, "user": result.username, "passwd": result.password, "path": result.path, }
[docs] def score_url_match(self, url: str): # We need to use template_config here because this is called before the template is expanded. host = self.template_config.host port = self.template_config.port if host and port and url.startswith(f"ftp://{host}:{port}"): return len(f"ftp://{host}:{port}") # For security, we need to ensure that a partial match doesn't work e.g. ftp://{host}something/myfiles elif host and (url.startswith(f"ftp://{host}/") or url == f"ftp://{host}"): return len(f"ftp://{host}") elif not host and url.startswith("ftp://"): return len("ftp://") else: return super().score_url_match(url)
__all__ = ("FtpFilesSource",)