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

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 

8 

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) 

21 

22PROTOCOLS = ("rsync", "sftp", "file") 

23 

24 

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) 

35 

36 

37SUPPORTED_PROTOCOLS = _determine_available_protocols() 

38 

39DEFAULT_PROTOCOL = SUPPORTED_PROTOCOLS[0] 

40 

41 

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 

50 

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 

72 

73 

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 

82 

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 

104 

105 

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 

109 

110 Parameters 

111 ---------- 

112 uri : parsed URI 

113 The URI of the file to read 

114 

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 

123 

124 

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]