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