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.command as command
import decman.core.output as output 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( def prg(
cmd: list[str], cmd: list[str],
@@ -16,7 +26,7 @@ def prg(
""" """
Shortcut for running a command. Returns the output of that command. Shortcut for running a command. Returns the output of that command.
Args: Arguments:
cmd: cmd:
Command to execute. Command to execute.
@@ -74,7 +84,7 @@ def sh(
""" """
Shortcut for running a shell command. Returns the output of that command. Shortcut for running a shell command. Returns the output of that command.
Args: Arguments:
sh_cmd: sh_cmd:
Shell command to execute. The command is passed to the system shell /bin/sh. Shell command to execute. The command is passed to the system shell /bin/sh.
@@ -20,8 +20,7 @@ class File:
Ownership, permissions, and parent directories are enforced on creation. Missing parent 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. directories are created recursively and assigned the same ownership as the file when specified.
Parameters Parameters:
----------
source_file: source_file:
Path to an existing file to copy from. Mutually exclusive with ``content``. Path to an existing file to copy from. Mutually exclusive with ``content``.
@@ -44,8 +43,7 @@ class File:
permissions: permissions:
File mode applied to the target file (e.g. ``0o644``). File mode applied to the target file (e.g. ``0o644``).
Raises Raises:
------
ValueError ValueError
If both ``source_file`` and ``content`` are ``None`` or if both are set. If both ``source_file`` and ``content`` are ``None`` or if both are set.
@@ -55,8 +53,7 @@ class File:
GroupNotFoundError GroupNotFoundError
If ``group`` does not exist on the system. If ``group`` does not exist on the system.
Notes Notes:
-----
Variable substitution is a simple string replacement where each key in ``variables`` is 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. replaced by its corresponding value. No escaping or templating semantics are applied.
""" """
@@ -98,23 +95,19 @@ class File:
""" """
Copies the contents of this file to the target file if they differ. Copies the contents of this file to the target file if they differ.
Parameters Parameters:
----------
target: target:
Path to the target file on disk. Path to the target file on disk.
variables: variables:
Optional mapping of literal substrings to replace in the text content before writing. Optional mapping of literal substrings to replace in the text content before
Ignored for binary files and when ``bin_file`` is True. writing. Ignored for binary files and when ``bin_file`` is True.
Returns Returns:
-------
bool
True if the file contents were created or modified. True if the file contents were created or modified.
False if the existing file already contained the desired contents. False if the existing file already contained the desired contents.
Raises Raises:
------
OSError OSError
If directory creation, file I/O, permission changes, or ownership changes fail If directory creation, file I/O, permission changes, or ownership changes fail
(e.g. permission denied, missing parent path components, I/O errors). (e.g. permission denied, missing parent path components, I/O errors).
@@ -220,8 +213,7 @@ class Directory:
permissions, encoding, and binary/text behavior. Text files can optionally undergo permissions, encoding, and binary/text behavior. Text files can optionally undergo
variable substitution before being written. variable substitution before being written.
Parameters Parameters:
----------
source_directory: source_directory:
Path to the directory whose contents will be mirrored into the target. Path to the directory whose contents will be mirrored into the target.
@@ -241,8 +233,7 @@ class Directory:
permissions: permissions:
File mode applied to created or updated files (e.g. ``0o644``). File mode applied to created or updated files (e.g. ``0o644``).
Raises Raises:
------
UserNotFoundError UserNotFoundError
If ``owner`` does not exist on the system. If ``owner`` does not exist on the system.
@@ -287,22 +278,20 @@ class Directory:
""" """
Copies the files in this directory to the target directory. Only replaces files that differ. Copies the files in this directory to the target directory. Only replaces files that differ.
Parameters Parameters:
----------
target_directory: target_directory:
Destination directory root. Relative layout from the source is preserved beneath this Destination directory root. Relative layout from the source is preserved beneath
path. this path.
variables: variables:
Optional mapping of literal substrings to replace in text files before writing. Ignored Optional mapping of literal substrings to replace in text files before writing.
for binary files. Ignored for binary files.
dry_run: dry_run:
If ``True``, perform a dry-run: no files are written, but the list of files that *would* If ``True``, perform a dry-run: no files are written, but the list of files that
be processed is returned. *would* be processed is returned.
Returns Returns:
-------
list[str] list[str]
When ``dry_run`` is ``False``, paths of files that were created or whose contents When ``dry_run`` is ``False``, paths of files that were created or whose contents
were modified. were modified.
@@ -310,8 +299,7 @@ class Directory:
When ``dry_run`` is ``True``, paths of all files that would be considered for When ``dry_run`` is ``True``, paths of all files that would be considered for
creation or modification (no changes are actually performed). creation or modification (no changes are actually performed).
Raises Raises:
------
OSError OSError
If directory traversal or file I/O fails (e.g. permission denied). If directory traversal or file I/O fails (e.g. permission denied).
+17 -17
View File
@@ -3,15 +3,15 @@ import stat
from pathlib import Path from pathlib import Path
# Adjust this import to match your actual module location # 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: def test_file_from_content_creates_and_is_idempotent(tmp_path: Path) -> None:
target = tmp_path / "file.txt" 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 # First run: file must be created and reported as changed
changed1 = f.copy_to(str(target)) 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: def test_file_content_with_variables_and_change_detection(tmp_path: Path) -> None:
target = tmp_path / "templated.txt" target = tmp_path / "templated.txt"
f = files.File(content="hello {{NAME}}") f = fs.File(content="hello {{NAME}}")
# First run: NAME=world # First run: NAME=world
changed1 = f.copy_to(str(target), {"{{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" target = tmp_path / "dst.txt"
# Without variables (raw copy) # 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), {}) changed1 = f_raw.copy_to(str(target), {})
assert changed1 is True assert changed1 is True
assert target.read_text(encoding="utf-8") == "VALUE={{X}}" 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 assert changed2 is False
# With variables (substitution) # 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"}) changed3 = f_sub.copy_to(str(target), {"{{X}}": "42"})
assert changed3 is True assert changed3 is True
assert target.read_text(encoding="utf-8") == "VALUE=42" 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" target = tmp_path / "bin.dat"
payload = b"\x00\x01\x02hello" 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)) changed1 = f.copy_to(str(target))
assert changed1 is True assert changed1 is True
@@ -97,7 +97,7 @@ def test_file_binary_copy_from_source(tmp_path: Path) -> None:
src.write_bytes(payload) src.write_bytes(payload)
target = tmp_path / "dst.bin" 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"}) changed1 = f.copy_to(str(target), {"IGNORED": "x"})
assert changed1 is True 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" nested_dir = tmp_path / "a" / "b" / "c"
target = nested_dir / "file.txt" 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)) changed = f.copy_to(str(target))
assert changed is True assert changed is True
@@ -127,7 +127,7 @@ def test_file_creates_parent_directories_and_applies_permissions(tmp_path: Path)
assert mode == 0o644 assert mode == 0o644
# --- files.Directory tests --------------------------------------------------------- # --- fs.Directory tests ---------------------------------------------------------
def _create_sample_source_tree(root: Path) -> None: 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) _create_sample_source_tree(src_dir)
d = files.Directory( d = fs.Directory(
source_directory=str(src_dir), source_directory=str(src_dir),
bin_files=False, bin_files=False,
encoding="utf-8", encoding="utf-8",
permissions=0o644, 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"}) changed1 = d.copy_to(str(dst_dir), variables={"{{X}}": "1"})
expected_paths = { expected_paths = {
str(dst_dir / "a.txt"), 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 / "a.txt").read_text(encoding="utf-8") == "A=1"
assert (dst_dir / "sub" / "b.txt").read_text(encoding="utf-8") == "B=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"}) changed2 = d.copy_to(str(dst_dir), variables={"{{X}}": "1"})
assert changed2 == [] assert changed2 == []
@@ -172,7 +172,7 @@ def test_directory_copy_to_detects_changes_via_variables(tmp_path: Path) -> None
src_dir.mkdir() src_dir.mkdir()
_create_sample_source_tree(src_dir) _create_sample_source_tree(src_dir)
d = files.Directory(source_directory=str(src_dir)) d = fs.Directory(source_directory=str(src_dir))
# Initial materialization # Initial materialization
changed1 = d.copy_to(str(dst_dir), variables={"{{X}}": "alpha"}) 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"), 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"}) changed2 = d.copy_to(str(dst_dir), variables={"{{X}}": "beta"})
assert set(changed2) == { assert set(changed2) == {
str(dst_dir / "a.txt"), str(dst_dir / "a.txt"),
@@ -198,7 +198,7 @@ def test_directory_copy_to_dry_run(tmp_path: Path) -> None:
src_dir.mkdir() src_dir.mkdir()
_create_sample_source_tree(src_dir) _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 # First, actually materialize once
d.copy_to(str(dst_dir), variables={"{{X}}": "1"}) 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() src_dir.mkdir()
_create_sample_source_tree(src_dir) _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() original_cwd = os.getcwd()
try: try: