Skip to content

Utils

sereto.utils

assert_file_size_within_range(file, max_bytes, min_bytes=0, interactive=False)

Evaluates if the file size is within the specified range.

If interactive is True, the user is first prompted whether to continue if the file size is not within the range.

Parameters:

Name Type Description Default
file FilePath

The path to the file.

required
max_bytes int

The maximum file size in bytes.

required
min_bytes int

The minimum file size in bytes. Defaults to 0.

0
interactive bool

If True, the user is prompted whether to continue if the file size is not within the range. Defaults to False.

False

Raises:

Type Description
SeretoPathError

If the file does not exist.

SeretoValueError

If the file size is not within the specified range.

Source code in sereto/utils.py
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
@validate_call
def assert_file_size_within_range(
    file: FilePath, max_bytes: int, min_bytes: int = 0, interactive: bool = False
) -> None:
    """Evaluates if the file size is within the specified range.

    If `interactive` is True, the user is first prompted whether to continue if the file size is not within the range.

    Args:
        file: The path to the file.
        max_bytes: The maximum file size in bytes.
        min_bytes: The minimum file size in bytes. Defaults to 0.
        interactive: If True, the user is prompted whether to continue if the file size is not within the range.
            Defaults to False.

    Raises:
        SeretoPathError: If the file does not exist.
        SeretoValueError: If the file size is not within the specified range.
    """
    # Check the input values
    if not file.is_file():
        raise SeretoPathError(f"File '{file}' does not exist")

    if not 0 <= min_bytes <= max_bytes:
        raise SeretoValueError(f"Invalid size threshold range: {min_bytes} - {max_bytes}")

    # Get the file size in bytes
    size = file.stat().st_size

    # Check if the file size is within the specified range
    if min_bytes <= size <= max_bytes:
        return

    # File size is not within the range

    Console().log(
        f"[yellow]File '{file}' size is {naturalsize(size, binary=True)}, which is not within the allowed range "
        f"{naturalsize(min_bytes, binary=True)} - {naturalsize(max_bytes, binary=True)}"
    )

    # In interactive mode, user can choose to continue
    if interactive and click.confirm("Do you want to continue?", default=False):
        return

    # Otherwise, raise an error
    raise SeretoValueError(f"File '{file}' size is not within the range {min_bytes} - {max_bytes} B")

copy_skel(templates, dst, overwrite=False)

Copy the content of a templates skel directory to a destination directory.

A skel directory is a directory that contains a set of files and directories that can be used as a template for creating new projects. This function copies the contents of the skel directory located at the path specified by templates to the destination directory specified by dst.

Parameters:

Name Type Description Default
templates DirectoryPath

The path to the directory containing the skel directory.

required
dst DirectoryPath

The destination directory to copy the skel directory contents to.

required
overwrite bool

Whether to allow overwriting of existing files in the destination directory. If True, existing files will be overwritten. If False (default), a SeretoPathError will be raised if the destination already exists.

False

Raises:

Type Description
SeretoPathError

If the destination directory already exists and overwrite is False.

Source code in sereto/utils.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
@validate_call
def copy_skel(templates: DirectoryPath, dst: DirectoryPath, overwrite: bool = False) -> None:
    """Copy the content of a templates `skel` directory to a destination directory.

    A `skel` directory is a directory that contains a set of files and directories that can be used as a template
    for creating new projects. This function copies the contents of the `skel` directory located at
    the path specified by `templates` to the destination directory specified by `dst`.

    Args:
        templates: The path to the directory containing the `skel` directory.
        dst: The destination directory to copy the `skel` directory contents to.
        overwrite: Whether to allow overwriting of existing files in the destination directory.
            If `True`, existing files will be overwritten. If `False` (default), a `SeretoPathError` will be raised
            if the destination already exists.

    Raises:
        SeretoPathError: If the destination directory already exists and `overwrite` is `False`.
    """
    skel_path: Path = templates / "skel"
    Console().log(f"Copying 'skel' directory: '{skel_path}' -> '{dst}'")

    for item in skel_path.iterdir():
        dst_item: Path = dst / (item.relative_to(skel_path))
        if not overwrite and dst_item.exists():
            raise SeretoPathError("Destination already exists")
        if item.is_file():
            Console().log(f" [green]+[/green] copy file: '{item.relative_to(skel_path)}'")
            copy2(item, dst_item, follow_symlinks=False)
        if item.is_dir():
            Console().log(f" [green]+[/green] copy dir: '{item.relative_to(skel_path)}'")
            copytree(item, dst_item, dirs_exist_ok=overwrite)

lower_alphanum(text)

Converts the input text to lowercase alphanumerical delimited by underscores.

Also all spaces are replaced with underscores.

Parameters:

Name Type Description Default
text str

The input text.

required

Returns:

Type Description
str

The input text with the modifications applied.

Source code in sereto/utils.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@validate_call
def lower_alphanum(text: str) -> str:
    """Converts the input text to lowercase alphanumerical delimited by underscores.

    Also all spaces are replaced with underscores.

    Args:
        text: The input text.

    Returns:
        The input text with the modifications applied.
    """
    normalized = unicodedata.normalize("NFKD", text)
    ascii_text = normalized.encode("ascii", "ignore").decode("ascii").lower()
    ascii_text = re.sub(r"[^a-z0-9_\s]", "", ascii_text)
    return re.sub(r"\s+", "_", ascii_text)

replace_strings(text, replacements)

replace_strings(text: str, replacements: dict[str, str]) -> str
replace_strings(text: list[str], replacements: dict[str, str]) -> list[str]

One-pass string replacement with values from dictionary.

Parameters:

Name Type Description Default
text str | list[str]

The input text.

required
replacements dict[str, str]

Dictionary with replacements. Key-value in dictionary refers to pattern string and replacement string, respectively.

required

Returns:

Type Description
str | list[str]

String (or list of strings, depending on the input value) obtained by applying the replacements from the replacements dictionary.

Source code in sereto/utils.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@validate_call
def replace_strings(text: str | list[str], replacements: dict[str, str]) -> str | list[str]:
    """One-pass string replacement with values from dictionary.

    Args:
        text: The input text.
        replacements: Dictionary with replacements. Key-value in dictionary refers to pattern string and replacement
            string, respectively.

    Returns:
        String (or list of strings, depending on the input value) obtained by applying the replacements from the
            `replacements` dictionary.
    """
    if len(text) == 0 or len(replacements) == 0:
        return text

    pattern = re.compile("|".join([re.escape(rep) for rep in replacements]))

    if isinstance(text, str):
        return pattern.sub(lambda match: replacements[match.group(0)], text)
    else:
        return [pattern.sub(lambda match: replacements[match.group(0)], item) for item in text]

write_if_different(file, content)

Writes content to file only if the content is different from the existing file content.

Parameters:

Name Type Description Default
file Path

The path to the file.

required
content str

The content to write to the file.

required

Returns:

Type Description
bool

True if new content was written to the file, False otherwise.

Source code in sereto/utils.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
@validate_call
def write_if_different(file: Path, content: str) -> bool:
    """Writes content to file only if the content is different from the existing file content.

    Args:
        file: The path to the file.
        content: The content to write to the file.

    Returns:
        True if new content was written to the file, False otherwise.
    """
    # Check if the file exists and has the same size
    if file.is_file() and file.stat().st_size == len(content):
        assert_file_size_within_range(file=file, max_bytes=104_857_600)  # 100 MiB
        # Check if the content is the same
        if file.read_text(encoding="utf-8") == content:
            return False

    # Changes detected, write the content to the file
    file.write_text(content, encoding="utf-8")
    return True