Skip to content

Commit 75bce9c

Browse files
authored
Merge branch 'master' into typehints
2 parents 192f9da + 977d3c2 commit 75bce9c

File tree

14 files changed

+226
-17
lines changed

14 files changed

+226
-17
lines changed

end-to-end-tests/tests.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Test end-to-end functionality for sysrsync."""
12
import inspect
23
import os
34
import sys
@@ -11,7 +12,10 @@
1112

1213

1314
class TestE2E(unittest.TestCase):
15+
"""Test end-to-end functionality."""
16+
1417
def test_send_file(self) -> None:
18+
"""Test sending a file from local to remote."""
1519
sysrsync.run(source="end-to-end-tests/test-cases/test_file",
1620
destination="/tmp/target_test_file",
1721
destination_ssh="test@openssh-server",
@@ -20,6 +24,7 @@ def test_send_file(self) -> None:
2024
strict_host_key_checking=False)
2125

2226
def test_send_file_with_spaces(self) -> None:
27+
"""Test sending a file with spaces from local to remote."""
2328
sysrsync.run(source="end-to-end-tests/test-cases/file with spaces",
2429
destination="/tmp/target_test_file",
2530
destination_ssh="test@openssh-server",

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Setup script for sysrsync."""
12
import setuptools
23
import toml
34

sysrsync/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
"""sysrsync: A Python wrapper for rsync."""
12
from .command_maker import *
23
from .runner import run

sysrsync/command_maker.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Generates the rsync command."""
12
import os
23
import os.path
34
from typing import Iterable, List, Optional
@@ -19,6 +20,33 @@ def get_rsync_command(
1920
rsh_port: Optional[int] = None,
2021
strict_host_key_checking: Optional[bool] = None,
2122
) -> List[str]:
23+
"""Generate rsync command with the specified options for synchronizing files and directories.
24+
25+
Args:
26+
source (str): The source directory or file path.
27+
destination (str): The destination directory or file path.
28+
source_ssh (Optional[str], optional): The SSH prefix for the source. Defaults
29+
to None.
30+
destination_ssh (Optional[str], optional): The SSH prefix for the destination.
31+
Defaults to None.
32+
exclusions (Optional[Iterable[str]], optional): The exclusions to be applied
33+
during synchronization. Defaults to None.
34+
sync_source_contents (bool, optional): Whether to sync the contents of the
35+
source directory. Defaults to True.
36+
options (Optional[Iterable[str]], optional): Additional rsync options. Defaults
37+
to None.
38+
private_key (Optional[str], optional): The path to the private key file for SSH
39+
authentication. Defaults to None.
40+
rsh_port (Optional[int], optional): The port number to use for the SSH
41+
connection. Defaults to None.
42+
strict_host_key_checking (Optional[bool], optional): Whether to perform strict
43+
host key checking. Defaults to None.
44+
45+
Returns:
46+
List[str]: A list containing the rsync command and its options for
47+
synchronizing files and directories.
48+
"""
49+
2250
if source_ssh is not None and destination_ssh is not None:
2351
raise RemotesError()
2452

sysrsync/exceptions.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,37 @@
1+
"""Exceptions for sysrsync."""
12
class RemotesError(Exception):
3+
"""
4+
Exception raised when both the source and destination are remote.
5+
6+
Attributes:
7+
message: The error message indicating that the source and destination cannot
8+
both be remote.
9+
"""
10+
211
def __init__(self) -> None:
12+
"""Initialize the RemotesError exception."""
313
message = 'source and destination cannot both be remote'
414
super().__init__(message)
515

616

717
class RsyncError(Exception):
18+
"""Exception raised for errors related to rsync operations."""
19+
820
pass
921

1022

1123
class PrivateKeyError(Exception):
24+
"""
25+
Exception raised when a private key file does not exist.
26+
27+
Args:
28+
key_file: The path to the non-existent private key file.
29+
30+
Attributes:
31+
message: The error message indicating that the private key file does not exist.
32+
"""
33+
1234
def __init__(self, key_file: str) -> None:
35+
"""Initialize the PrivateKeyError exception."""
1336
message = f'Private Key File "{key_file}" does not exist'
1437
super().__init__(message)

sysrsync/helpers/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Helpers for sysrsync."""

sysrsync/helpers/directories.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,19 @@
1+
"""Directory helper functions for sysrsync."""
12
from typing import Tuple, Optional
23

34

45
def get_directory_with_ssh(directory: str, ssh: Optional[str]) -> str:
6+
"""
7+
Return the directory path with SSH prefix if SSH is provided.
8+
9+
Args:
10+
directory (str): The directory path.
11+
ssh (Optional[str]): The SSH prefix. Defaults to None.
12+
13+
Returns:
14+
str: The directory path with SSH prefix if SSH is provided, otherwise the
15+
directory path itself.
16+
"""
517
if ssh is None:
618
return directory
719

@@ -13,6 +25,20 @@ def sanitize_trailing_slash(
1325
target_dir: str,
1426
sync_sourcedir_contents: Optional[bool] = True,
1527
) -> Tuple[str, str]:
28+
"""
29+
Sanitizes the trailing slashes in the source and target directories.
30+
31+
Args:
32+
source_dir (str): The source directory path.
33+
target_dir (str): The target directory path.
34+
sync_sourcedir_contents (bool, optional): Whether to sync the contents of the
35+
source directory. Defaults to True.
36+
37+
Returns:
38+
Tuple[str, str]: A tuple containing the sanitized source directory path and the
39+
sanitized target directory path.
40+
"""
41+
1642
target_dir = strip_trailing_slash(target_dir)
1743

1844
if sync_sourcedir_contents is True:
@@ -24,12 +50,30 @@ def sanitize_trailing_slash(
2450

2551

2652
def strip_trailing_slash(directory: str) -> str:
53+
"""
54+
Strip the trailing slash from the directory path if it exists.
55+
56+
Args:
57+
directory (str): The directory path.
58+
59+
Returns:
60+
str: The directory path without the trailing slash, if present. Otherwise,
61+
returns the directory path as is.
62+
"""
2763
return (directory[:-1]
2864
if directory.endswith('/')
2965
else directory)
3066

31-
3267
def add_trailing_slash(directory: str) -> str:
68+
"""Add a trailing slash to the directory path if it doesn't already have one.
69+
70+
Args:
71+
directory (str): The directory path.
72+
73+
Returns:
74+
str: The directory path with a trailing slash, if it doesn't already have one.
75+
Otherwise, returns the directory path as is.
76+
"""
3377
return (directory
3478
if directory.endswith('/')
3579
else f'{directory}/')

sysrsync/helpers/iterators.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Iterator helper functions for sysrsync."""
12
import collections
23

34
if 'Iterable' in dir(collections):
@@ -11,6 +12,16 @@
1112

1213

1314
def flatten(input_iter: Iterable[Any]) -> List[Any]:
15+
"""
16+
Flattens an iterable by converting nested iterables into a single flat list.
17+
18+
Args:
19+
input_iter (Iterable[Any]): The input iterable.
20+
21+
Returns:
22+
List[Any]: A list containing all the elements from the input iterable, with
23+
nested iterables flattened.
24+
"""
1425
list_of_lists = (element if isinstance(element, Iterable)
1526
else [element]
1627
for element in input_iter)

sysrsync/helpers/rsync.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Generates rsync arguments based on the provided options for sysrsync."""
12
import os
23
from pathlib import Path
34
from typing import Iterable, List, Optional
@@ -7,6 +8,16 @@
78

89

910
def get_exclusions(exclusions: Iterable[str]) -> Iterable[str]:
11+
"""Generate a list of rsync exclusion arguments based on the provided exclusions.
12+
13+
Args:
14+
exclusions (Iterable[str]): The exclusions to be used for generating the rsync
15+
exclusion arguments.
16+
17+
Returns:
18+
Iterable[str]: A list of rsync exclusion arguments, where each exclusion is
19+
prefixed with '--exclude'.
20+
"""
1021
return flatten((('--exclude', exclusion)
1122
for exclusion in exclusions
1223
if exclusion != '--exclude'))
@@ -16,6 +27,20 @@ def get_rsh_command(
1627
port: Optional[int] = None,
1728
strict_host_key_checking: Optional[bool] = None,
1829
) -> List[str]:
30+
"""Generate rsync remote shell (rsh) command with the specified options.
31+
32+
Args:
33+
private_key (Optional[str], optional): The path to the private key file.
34+
Defaults to None.
35+
port (Optional[int], optional): The port number to use for the SSH connection.
36+
Defaults to None.
37+
strict_host_key_checking (Optional[bool], optional): Whether to perform strict
38+
host key checking. Defaults to None.
39+
40+
Returns:
41+
List[str]: A list containing the rsync rsh command and its options.
42+
"""
43+
1944
args: List[str] = []
2045

2146
if private_key is not None:

sysrsync/runner.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Runs the rsync command with the specified options."""
12
import os
23
import subprocess
34

@@ -6,13 +7,28 @@
67

78
from typing import Optional
89

9-
1010
def run(
1111
cwd: Optional[str] = os.getcwd(),
1212
strict: Optional[bool] = True,
1313
verbose: Optional[bool] = False,
1414
**kwargs,
1515
) -> subprocess.CompletedProcess:
16+
"""Run the rsync command with the specified options.
17+
18+
Args:
19+
cwd (str, optional): The current working directory. Defaults to the current
20+
directory.
21+
strict (bool, optional): Whether to raise an exception if the rsync command
22+
returns a non-zero exit code. Defaults to True.
23+
verbose (bool, optional): Whether to print the rsync command before executing
24+
it. Defaults to False.
25+
**kwargs: Additional options to be passed to the `get_rsync_command` function.
26+
27+
Returns:
28+
subprocess.CompletedProcess: The completed process object representing the
29+
execution of the rsync command.
30+
"""
31+
1632
rsync_command = get_rsync_command(**kwargs)
1733

1834
rsync_string = ' '.join(rsync_command)
@@ -33,5 +49,16 @@ def _check_return_code(
3349
return_code: int,
3450
action: str,
3551
) -> None:
52+
"""Check the return code of an action and raises an exception if it is non-zero.
53+
54+
Args:
55+
return_code (int): The return code of the action.
56+
action (str): The description of the action.
57+
58+
Raises:
59+
RsyncError: If the return code is non-zero, an exception is raised with an
60+
error message indicating the action and the return code.
61+
"""
62+
3663
if return_code != 0:
3764
raise RsyncError(f"[sysrsync runner] {action} exited with code {return_code}")

0 commit comments

Comments
 (0)