Unify docstring formats

This commit is contained in:
Kivi Kaitaniemi
2025-12-12 23:17:18 +02:00
parent 2b1bbdb884
commit 6047cad690
3 changed files with 116 additions and 118 deletions
+12 -2
View File
@@ -4,6 +4,16 @@ import typing
import decman.core.command as command
import decman.core.output as output
# Re-export File and Directory
from decman.core.fs import Directory, File # noqa: F401
__all__ = [
"File",
"Directory",
"prg",
"sh",
]
def prg(
cmd: list[str],
@@ -16,7 +26,7 @@ def prg(
"""
Shortcut for running a command. Returns the output of that command.
Args:
Arguments:
cmd:
Command to execute.
@@ -74,7 +84,7 @@ def sh(
"""
Shortcut for running a shell command. Returns the output of that command.
Args:
Arguments:
sh_cmd:
Shell command to execute. The command is passed to the system shell /bin/sh.
@@ -20,45 +20,42 @@ class File:
Ownership, permissions, and parent directories are enforced on creation. Missing parent
directories are created recursively and assigned the same ownership as the file when specified.
Parameters
----------
source_file:
Path to an existing file to copy from. Mutually exclusive with ``content``.
Parameters:
source_file:
Path to an existing file to copy from. Mutually exclusive with ``content``.
content:
In-memory file contents to write. Mutually exclusive with ``source_file``.
content:
In-memory file contents to write. Mutually exclusive with ``source_file``.
bin_file:
If ``True``, treat the file as binary. Disables variable substitution and writes bytes
verbatim.
bin_file:
If ``True``, treat the file as binary. Disables variable substitution and writes bytes
verbatim.
encoding:
Text encoding used when reading or writing non-binary files.
encoding:
Text encoding used when reading or writing non-binary files.
owner:
System user name to own the file and created parent directories.
owner:
System user name to own the file and created parent directories.
group:
System group name to own the file and created parent directories.
group:
System group name to own the file and created parent directories.
permissions:
File mode applied to the target file (e.g. ``0o644``).
permissions:
File mode applied to the target file (e.g. ``0o644``).
Raises
------
ValueError
If both ``source_file`` and ``content`` are ``None`` or if both are set.
Raises:
ValueError
If both ``source_file`` and ``content`` are ``None`` or if both are set.
UserNotFoundError
If ``owner`` does not exist on the system.
UserNotFoundError
If ``owner`` does not exist on the system.
GroupNotFoundError
If ``group`` does not exist on the system.
GroupNotFoundError
If ``group`` does not exist on the system.
Notes
-----
Variable substitution is a simple string replacement where each key in ``variables`` is
replaced by its corresponding value. No escaping or templating semantics are applied.
Notes:
Variable substitution is a simple string replacement where each key in ``variables`` is
replaced by its corresponding value. No escaping or templating semantics are applied.
"""
def __init__(
@@ -98,35 +95,31 @@ class File:
"""
Copies the contents of this file to the target file if they differ.
Parameters
----------
target:
Path to the target file on disk.
Parameters:
target:
Path to the target file on disk.
variables:
Optional mapping of literal substrings to replace in the text content before writing.
Ignored for binary files and when ``bin_file`` is True.
variables:
Optional mapping of literal substrings to replace in the text content before
writing. Ignored for binary files and when ``bin_file`` is True.
Returns
-------
bool
Returns:
True if the file contents were created or modified.
False if the existing file already contained the desired contents.
Raises
------
OSError
If directory creation, file I/O, permission changes, or ownership changes fail
(e.g. permission denied, missing parent path components, I/O errors).
Raises:
OSError
If directory creation, file I/O, permission changes, or ownership changes fail
(e.g. permission denied, missing parent path components, I/O errors).
FileNotFoundError
If ``source_file`` is set and does not exist.
FileNotFoundError
If ``source_file`` is set and does not exist.
UnicodeDecodeError
If a text file cannot be decoded using ``encoding``.
UnicodeDecodeError
If a text file cannot be decoded using ``encoding``.
UnicodeEncodeError
If text content cannot be encoded using ``encoding``.
UnicodeEncodeError
If text content cannot be encoded using ``encoding``.
"""
if variables is None:
variables = {}
@@ -220,34 +213,32 @@ class Directory:
permissions, encoding, and binary/text behavior. Text files can optionally undergo
variable substitution before being written.
Parameters
----------
source_directory:
Path to the directory whose contents will be mirrored into the target.
Parameters:
source_directory:
Path to the directory whose contents will be mirrored into the target.
bin_files:
If ``True``, treat all files as binary; disables variable substitution and copies bytes
verbatim.
bin_files:
If ``True``, treat all files as binary; disables variable substitution and copies bytes
verbatim.
encoding:
Text encoding used when reading or writing non-binary files.
encoding:
Text encoding used when reading or writing non-binary files.
owner:
System user name to own created files and directories.
owner:
System user name to own created files and directories.
group:
System group name to own created files and directories.
group:
System group name to own created files and directories.
permissions:
File mode applied to created or updated files (e.g. ``0o644``).
permissions:
File mode applied to created or updated files (e.g. ``0o644``).
Raises
------
UserNotFoundError
If ``owner`` does not exist on the system.
Raises:
UserNotFoundError
If ``owner`` does not exist on the system.
GroupNotFoundError
If ``group`` does not exist on the system.
GroupNotFoundError
If ``group`` does not exist on the system.
"""
def __init__(
@@ -287,42 +278,39 @@ class Directory:
"""
Copies the files in this directory to the target directory. Only replaces files that differ.
Parameters
----------
target_directory:
Destination directory root. Relative layout from the source is preserved beneath this
path.
Parameters:
target_directory:
Destination directory root. Relative layout from the source is preserved beneath
this path.
variables:
Optional mapping of literal substrings to replace in text files before writing. Ignored
for binary files.
variables:
Optional mapping of literal substrings to replace in text files before writing.
Ignored for binary files.
dry_run:
If ``True``, perform a dry-run: no files are written, but the list of files that *would*
be processed is returned.
dry_run:
If ``True``, perform a dry-run: no files are written, but the list of files that
*would* be processed is returned.
Returns
-------
list[str]
When ``dry_run`` is ``False``, paths of files that were created or whose contents
were modified.
Returns:
list[str]
When ``dry_run`` is ``False``, paths of files that were created or whose contents
were modified.
When ``dry_run`` is ``True``, paths of all files that would be considered for
creation or modification (no changes are actually performed).
When ``dry_run`` is ``True``, paths of all files that would be considered for
creation or modification (no changes are actually performed).
Raises
------
OSError
If directory traversal or file I/O fails (e.g. permission denied).
Raises:
OSError
If directory traversal or file I/O fails (e.g. permission denied).
FileNotFoundError
If ``source_directory`` does not exist or becomes unavailable.
FileNotFoundError
If ``source_directory`` does not exist or becomes unavailable.
UnicodeDecodeError
If a text file cannot be decoded using ``encoding``.
UnicodeDecodeError
If a text file cannot be decoded using ``encoding``.
UnicodeEncodeError
If text content cannot be encoded using ``encoding``.
UnicodeEncodeError
If text content cannot be encoded using ``encoding``.
"""
changed_or_created = []
original_wd = os.getcwd()
+17 -17
View File
@@ -3,15 +3,15 @@ import stat
from pathlib import Path
# Adjust this import to match your actual module location
import decman.core.files as files
import decman.core.fs as fs
# --- files.File tests --------------------------------------------------------------
# --- fs.File tests --------------------------------------------------------------
def test_file_from_content_creates_and_is_idempotent(tmp_path: Path) -> None:
target = tmp_path / "file.txt"
f = files.File(content="hello", permissions=0o600)
f = fs.File(content="hello", permissions=0o600)
# First run: file must be created and reported as changed
changed1 = f.copy_to(str(target))
@@ -31,7 +31,7 @@ def test_file_from_content_creates_and_is_idempotent(tmp_path: Path) -> None:
def test_file_content_with_variables_and_change_detection(tmp_path: Path) -> None:
target = tmp_path / "templated.txt"
f = files.File(content="hello {{NAME}}")
f = fs.File(content="hello {{NAME}}")
# First run: NAME=world
changed1 = f.copy_to(str(target), {"{{NAME}}": "world"})
@@ -55,7 +55,7 @@ def test_file_from_source_text_with_and_without_variables(tmp_path: Path) -> Non
target = tmp_path / "dst.txt"
# Without variables (raw copy)
f_raw = files.File(source_file=str(src))
f_raw = fs.File(source_file=str(src))
changed1 = f_raw.copy_to(str(target), {})
assert changed1 is True
assert target.read_text(encoding="utf-8") == "VALUE={{X}}"
@@ -65,7 +65,7 @@ def test_file_from_source_text_with_and_without_variables(tmp_path: Path) -> Non
assert changed2 is False
# With variables (substitution)
f_sub = files.File(source_file=str(src))
f_sub = fs.File(source_file=str(src))
changed3 = f_sub.copy_to(str(target), {"{{X}}": "42"})
assert changed3 is True
assert target.read_text(encoding="utf-8") == "VALUE=42"
@@ -79,7 +79,7 @@ def test_file_binary_from_content(tmp_path: Path) -> None:
target = tmp_path / "bin.dat"
payload = b"\x00\x01\x02hello"
f = files.File(content=payload.decode("latin1"), bin_file=True)
f = fs.File(content=payload.decode("latin1"), bin_file=True)
changed1 = f.copy_to(str(target))
assert changed1 is True
@@ -97,7 +97,7 @@ def test_file_binary_copy_from_source(tmp_path: Path) -> None:
src.write_bytes(payload)
target = tmp_path / "dst.bin"
f = files.File(source_file=str(src), bin_file=True)
f = fs.File(source_file=str(src), bin_file=True)
changed1 = f.copy_to(str(target), {"IGNORED": "x"})
assert changed1 is True
@@ -113,7 +113,7 @@ def test_file_creates_parent_directories_and_applies_permissions(tmp_path: Path)
nested_dir = tmp_path / "a" / "b" / "c"
target = nested_dir / "file.txt"
f = files.File(content="data", permissions=0o644)
f = fs.File(content="data", permissions=0o644)
changed = f.copy_to(str(target))
assert changed is True
@@ -127,7 +127,7 @@ def test_file_creates_parent_directories_and_applies_permissions(tmp_path: Path)
assert mode == 0o644
# --- files.Directory tests ---------------------------------------------------------
# --- fs.Directory tests ---------------------------------------------------------
def _create_sample_source_tree(root: Path) -> None:
@@ -143,14 +143,14 @@ def test_directory_copy_to_creates_and_is_idempotent(tmp_path: Path) -> None:
_create_sample_source_tree(src_dir)
d = files.Directory(
d = fs.Directory(
source_directory=str(src_dir),
bin_files=False,
encoding="utf-8",
permissions=0o644,
)
# First run: both files should be created and reported as changed
# First run: both fs should be created and reported as changed
changed1 = d.copy_to(str(dst_dir), variables={"{{X}}": "1"})
expected_paths = {
str(dst_dir / "a.txt"),
@@ -161,7 +161,7 @@ def test_directory_copy_to_creates_and_is_idempotent(tmp_path: Path) -> None:
assert (dst_dir / "a.txt").read_text(encoding="utf-8") == "A=1"
assert (dst_dir / "sub" / "b.txt").read_text(encoding="utf-8") == "B=1"
# Second run with same variables: no files should be reported as changed
# Second run with same variables: no fs should be reported as changed
changed2 = d.copy_to(str(dst_dir), variables={"{{X}}": "1"})
assert changed2 == []
@@ -172,7 +172,7 @@ def test_directory_copy_to_detects_changes_via_variables(tmp_path: Path) -> None
src_dir.mkdir()
_create_sample_source_tree(src_dir)
d = files.Directory(source_directory=str(src_dir))
d = fs.Directory(source_directory=str(src_dir))
# Initial materialization
changed1 = d.copy_to(str(dst_dir), variables={"{{X}}": "alpha"})
@@ -181,7 +181,7 @@ def test_directory_copy_to_detects_changes_via_variables(tmp_path: Path) -> None
str(dst_dir / "sub" / "b.txt"),
}
# Change variables -> both files change
# Change variables -> both fs change
changed2 = d.copy_to(str(dst_dir), variables={"{{X}}": "beta"})
assert set(changed2) == {
str(dst_dir / "a.txt"),
@@ -198,7 +198,7 @@ def test_directory_copy_to_dry_run(tmp_path: Path) -> None:
src_dir.mkdir()
_create_sample_source_tree(src_dir)
d = files.Directory(source_directory=str(src_dir))
d = fs.Directory(source_directory=str(src_dir))
# First, actually materialize once
d.copy_to(str(dst_dir), variables={"{{X}}": "1"})
@@ -230,7 +230,7 @@ def test_directory_copy_to_restores_working_directory(tmp_path: Path) -> None:
src_dir.mkdir()
_create_sample_source_tree(src_dir)
d = files.Directory(source_directory=str(src_dir))
d = fs.Directory(source_directory=str(src_dir))
original_cwd = os.getcwd()
try: