Warning
This document is for an old release 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"]
# A configured port is the default 21 unless set, so it cannot be told
# apart from an unset one. An explicit port in the url wins instead.
config.port = props["port"] or 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,
"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
# For security, we need to ensure that a partial match doesn't work e.g. ftp://{host}something/myfiles
# or ftp://{host}:{port}1/myfiles, which would be realized with this source's credentials.
if host and port and _matches_authority(url, f"ftp://{host}:{port}"):
return len(f"ftp://{host}:{port}")
elif host and _matches_authority(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)
def _matches_authority(url: str, authority: str) -> bool:
return url == authority or url.startswith(f"{authority}/")
__all__ = ("FtpFilesSource",)