diff --git a/src/python/jw/pkg/lib/ExecContext.py b/src/python/jw/pkg/lib/ExecContext.py index 164e1f98..eb4321e3 100644 --- a/src/python/jw/pkg/lib/ExecContext.py +++ b/src/python/jw/pkg/lib/ExecContext.py @@ -12,8 +12,9 @@ if TYPE_CHECKING: from .log import * from .base import Input, InputMode, Result +from .FileTransfer import FileTransfer as Base -class ExecContext(abc.ABC): +class ExecContext(Base): class CallContext: @@ -146,39 +147,7 @@ class ExecContext(abc.ABC): return result def __init__(self, uri: str, interactive: bool|None=None, verbose_default=False): - self.__uri = uri - self.__interactive = interactive - self.__verbose_default = verbose_default - self.__log_name: str|None = None - assert verbose_default is not None - - async def __aenter__(self): - return self - - async def __aexit__(self, exc_type, exc, tb): - await self.close() - - @property - def uri(self) -> str: - return self.__uri - - @property - def log_name(self) -> str: - if self.__log_name is None: - from urllib.parse import urlparse - parsed = urlparse(self.__uri) - scheme = 'local' if parsed.scheme is None else parsed.scheme - hostname = '' if parsed.hostname is None else '' - self.__log_name = f'{scheme}://{hostname}' - return self.__log_name - - @property - def interactive(self) -> bool|None: - return self.__interactive - - @property - def verbose_default(self) -> bool: - return self.__verbose_default + super().__init__(uri=uri, interactive=interactive, verbose_default=verbose_default) @abc.abstractmethod async def _run(self, *args, **kwargs) -> Result: @@ -312,19 +281,6 @@ class ExecContext(abc.ABC): cc.check_exit_code(ret) return ret - async def get( - self, - path: str, - wd: str|None = None, - throw: bool = True, - verbose: bool|None = None, - title: str=None, - owner: str|None=None, - group: str|None=None, - mode: str|None=None, - ) -> Result: - return await self._get(path, wd=wd, throw=throw, verbose=verbose, title=title) - async def _put( self, content: bytes, @@ -383,39 +339,3 @@ class ExecContext(abc.ABC): raise return cc.exception(ret, e) return ret - - async def put( - self, - content: str, - path: str, - wd: str|None = None, - throw: bool = True, - verbose: bool|None = None, - title: str=None, - owner: str|None=None, - group: str|None=None, - mode: str|None=None, - ) -> Result: - return await self._put(content, path, wd=wd, throw=throw, verbose=verbose, - title=title, owner=owner, group=group, mode=mode) - - async def _close(self) -> None: - pass - - async def close(self) -> None: - await self._close() - - @classmethod - def create(cls, uri: str, *args, **kwargs) -> Self: - tokens = re.split(r'://', uri) - schema = tokens[0] if tokens[0] != uri else 'file' - match schema: - case 'local' | 'file': - from .ec.Local import Local - return Local(uri, *args, **kwargs) - case 'ssh': - from .ec.SSHClient import ssh_client - return ssh_client(uri, *args, **kwargs) - case _: - pass - raise Exception(f'Can\'t create execution context for {uri} with unknown schema "{schema}"') diff --git a/src/python/jw/pkg/lib/FileTransfer.py b/src/python/jw/pkg/lib/FileTransfer.py new file mode 100644 index 00000000..ee239498 --- /dev/null +++ b/src/python/jw/pkg/lib/FileTransfer.py @@ -0,0 +1,129 @@ +# -*- coding: utf-8 -*- + +from __future__ import annotations + +import abc, re +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import Self + +from .log import * +from .base import Input, InputMode, Result + +class FileTransfer(abc.ABC): + + def __init__(self, uri: str, interactive: bool|None=None, verbose_default=False): + self.__uri = uri + self.__interactive = interactive + self.__verbose_default = verbose_default + self.__log_name: str|None = None + assert verbose_default is not None + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc, tb): + await self.close() + + @property + def uri(self) -> str: + return self.__uri + + @property + def log_name(self) -> str: + if self.__log_name is None: + from urllib.parse import urlparse + parsed = urlparse(self.__uri) + ret: list[str] = [] + if parsed.scheme: + ret.append(parsed.scheme) + if parsed.hostname: + ret.append(parsed.hostname) + self.__log_name = '://'.join(ret) if ret else 'local' + return self.__log_name + + @property + def interactive(self) -> bool|None: + return self.__interactive + + @property + def verbose_default(self) -> bool: + return self.__verbose_default + + @abc.abstractmethod + async def _get( + self, + path: str, + wd: str|None, + throw: bool, + verbose: bool|None, + title: str + ) -> Result: + raise NotImplementedError() + + async def get( + self, + path: str, + wd: str|None = None, + throw: bool = True, + verbose: bool|None = None, + title: str=None, + owner: str|None=None, + group: str|None=None, + mode: str|None=None, + ) -> Result: + return await self._get(path, wd=wd, throw=throw, verbose=verbose, title=title) + + async def _put( + self, + content: bytes, + path: str, + wd: str|None, + throw: bool, + verbose: bool|None, + title: str, + owner: str|None, + group: str|None, + mode: str|None, + ) -> Result: + raise NotImplementedError() + + async def put( + self, + content: str, + path: str, + wd: str|None = None, + throw: bool = True, + verbose: bool|None = None, + title: str=None, + owner: str|None=None, + group: str|None=None, + mode: str|None=None, + ) -> Result: + return await self._put(content, path, wd=wd, throw=throw, verbose=verbose, + title=title, owner=owner, group=group, mode=mode) + + async def _close(self) -> None: + pass + + async def close(self) -> None: + await self._close() + + @classmethod + def create(cls, uri: str, *args, **kwargs) -> Self: + tokens = re.split(r'://', uri) + schema = tokens[0] if tokens[0] != uri else 'file' + match schema: + case 'local' | 'file': + from .ec.Local import Local + return Local(uri, *args, **kwargs) + case 'ssh': + from .ec.SSHClient import ssh_client + return ssh_client(uri, *args, **kwargs) + case 'http' | 'https': + from .ec.Curl import Curl + return Curl(uri, *args, **kwargs) + case _: + pass + raise Exception(f'Can\'t create file transfer instance for {uri} with unknown schema "{schema}"')