Coverage for enderchest/sync/__init__.py: 97%
37 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-04 01:41 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-04 01:41 +0000
1"""Low-level functionality for synchronizing across different machines"""
3import importlib
4from contextlib import contextmanager
5from pathlib import Path
6from tempfile import TemporaryDirectory
7from typing import Collection, Generator
8from urllib.parse import ParseResult
10from ..loggers import SYNC_LOGGER
11from .utils import Operation as Op
12from .utils import (
13 abspath_from_uri,
14 diff,
15 filter_contents,
16 generate_sync_report,
17 get_default_netloc,
18 is_identical,
19 render_remote,
20 uri_to_ssh,
21)
23PROTOCOLS = ("rsync", "sftp", "file")
26def _determine_available_protocols() -> tuple[str, ...]:
27 """Determine which protocols are available on this system"""
28 available_protocols: list[str] = []
29 for protocol in PROTOCOLS:
30 try:
31 _ = importlib.import_module(f"{__package__}.{protocol}")
32 available_protocols.append(protocol)
33 except (ModuleNotFoundError, RuntimeError):
34 pass
35 return tuple(available_protocols)
38SUPPORTED_PROTOCOLS = _determine_available_protocols()
40DEFAULT_PROTOCOL = SUPPORTED_PROTOCOLS[0]
43def pull(
44 remote_uri: ParseResult,
45 local_path: Path,
46 exclude: Collection[str] | None = None,
47 dry_run: bool = False,
48 **kwargs,
49) -> None:
50 """Pull all upstream changes from a remote into the specified location
52 Parameters
53 ----------
54 remote_uri : ParseResult
55 The URI for the remote resource to pull
56 local_path : Path
57 The local destination
58 exclude : list of str, optional
59 Any patterns that should be excluded from the sync
60 dry_run : bool, optional
61 Whether to only simulate this sync (report the operations to be performed
62 but not actually perform them). Default is False.
63 **kwargs
64 Any additional options to pass into the sync command
65 """
66 try:
67 protocol = importlib.import_module(f"{__package__}.{remote_uri.scheme.lower()}")
68 protocol.pull(remote_uri, local_path, exclude or (), dry_run, **kwargs)
69 except ModuleNotFoundError as not_installed: # pragma: no cover
70 raise NotImplementedError(
71 f"Protocol {remote_uri.scheme} is not currently implemented"
72 ) from not_installed
75def push(
76 local_path: Path,
77 remote_uri: ParseResult,
78 exclude: Collection[str] | None = None,
79 dry_run: bool = False,
80 **kwargs,
81) -> None:
82 """Push all local changes in the specified directory into the specified remote
84 Parameters
85 ----------
86 local_path
87 The local path to push
88 remote_uri : ParseResult
89 The URI for the remote destination
90 exclude : list of str, optional
91 Any patterns that should be excluded from the sync
92 dry_run : bool, optional
93 Whether to only simulate this sync (report the operations to be performed
94 but not actually perform them). Default is False.
95 **kwargs
96 Any additional options to pass into the sync command
97 """
98 try:
99 protocol = importlib.import_module(f"{__package__}.{remote_uri.scheme.lower()}")
100 protocol.push(local_path, remote_uri, exclude or (), dry_run, **kwargs)
101 except ModuleNotFoundError as not_installed:
102 raise NotImplementedError(
103 f"Protocol {remote_uri.scheme} is not currently implemented"
104 ) from not_installed
107@contextmanager
108def remote_file(uri: ParseResult) -> Generator[Path, None, None]:
109 """Grab a file from a remote filesystem by its URI and read its contents
111 Parameters
112 ----------
113 uri : parsed URI
114 The URI of the file to read
116 Yields
117 ------
118 Path
119 A path to a local (temp) copy of the file
120 """
121 with TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
122 pull(uri, Path(tmpdir), verbosity=-1)
123 yield Path(tmpdir) / Path(uri.path).name
126__all__ = [
127 "SYNC_LOGGER",
128 "PROTOCOLS",
129 "DEFAULT_PROTOCOL",
130 "get_default_netloc",
131 "render_remote",
132 "remote_file",
133 "abspath_from_uri",
134 "pull",
135 "push",
136 "uri_to_ssh",
137]