Coverage for src / rendercv / schema / models / path.py: 100%
25 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-25 13:49 +0000
1import contextlib
2import pathlib
3from typing import Annotated
5import pydantic
6import pydantic_core
8from ..pydantic_error_handling import CustomPydanticErrorTypes
9from .validation_context import get_input_file_path
12def resolve_relative_path(
13 path: pathlib.Path, info: pydantic.ValidationInfo, *, must_exist: bool = True
14) -> pathlib.Path:
15 """Convert relative path to absolute path based on input file location.
17 Why:
18 Users reference files like `photo: profile.jpg` relative to their CV
19 YAML. This validator resolves such paths to absolute form and validates
20 existence, enabling file access during rendering.
22 Example:
23 ```py
24 # In validators: photo_path = resolve_relative_path(photo, info)
25 # Input: "photo.jpg" in /home/user/cv.yaml
26 # Output: /home/user/photo.jpg (absolute, validated to exist)
27 ```
29 Args:
30 path: Path to resolve (may be relative or absolute).
31 info: Validation context containing input file path.
32 must_exist: Whether to raise error if path doesn't exist.
34 Returns:
35 Absolute path.
36 """
37 if path:
38 input_file_path = get_input_file_path(info)
39 relative_to = input_file_path.parent if input_file_path else pathlib.Path.cwd()
40 if not path.is_absolute():
41 path = relative_to / path
43 if must_exist:
44 if not path.exists():
45 raise pydantic_core.PydanticCustomError(
46 CustomPydanticErrorTypes.other.value,
47 "The file `{file_path}` does not exist.",
48 {"file_path": path.relative_to(relative_to)},
49 )
50 if not path.is_file():
51 raise pydantic_core.PydanticCustomError(
52 CustomPydanticErrorTypes.other.value,
53 "The path `{path}` is not a file.",
54 {"path": path.relative_to(relative_to)},
55 )
57 return path
60def serialize_path(path: pathlib.Path) -> str:
61 with contextlib.suppress(ValueError):
62 return path.relative_to(pathlib.Path.cwd()).as_posix()
64 return str(path)
67type ExistingPathRelativeToInput = Annotated[
68 pathlib.Path,
69 pydantic.AfterValidator(
70 lambda path, info: resolve_relative_path(path, info, must_exist=True)
71 ),
72]
74type PlannedPathRelativeToInput = Annotated[
75 pathlib.Path,
76 pydantic.AfterValidator(
77 lambda path, info: resolve_relative_path(path, info, must_exist=False)
78 ),
79 pydantic.PlainSerializer(serialize_path),
80]