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

1import contextlib 

2import pathlib 

3from typing import Annotated 

4 

5import pydantic 

6import pydantic_core 

7 

8from ..pydantic_error_handling import CustomPydanticErrorTypes 

9from .validation_context import get_input_file_path 

10 

11 

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. 

16 

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. 

21 

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 ``` 

28 

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. 

33 

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 

42 

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 ) 

56 

57 return path 

58 

59 

60def serialize_path(path: pathlib.Path) -> str: 

61 with contextlib.suppress(ValueError): 

62 return path.relative_to(pathlib.Path.cwd()).as_posix() 

63 

64 return str(path) 

65 

66 

67type ExistingPathRelativeToInput = Annotated[ 

68 pathlib.Path, 

69 pydantic.AfterValidator( 

70 lambda path, info: resolve_relative_path(path, info, must_exist=True) 

71 ), 

72] 

73 

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]