Coverage for src / rendercv / schema / models / design / built_in_design.py: 100%

18 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-25 13:49 +0000

1from functools import reduce 

2from operator import or_ 

3from pathlib import Path 

4from typing import Annotated, get_args 

5 

6import pydantic 

7 

8from ...variant_pydantic_model_generator import create_variant_pydantic_model 

9from ...yaml_reader import read_yaml 

10from .classic_theme import ClassicTheme 

11 

12 

13def discover_other_themes() -> list[type[ClassicTheme]]: 

14 """Auto-discover and load theme variant classes from other_themes/ directory. 

15 

16 Why: 

17 Built-in themes beyond classic are defined as YAML files with field 

18 overrides. Dynamic discovery and variant generation keeps theme 

19 system extensible without code changes for each theme. 

20 

21 Returns: 

22 List of dynamically generated theme variant classes. 

23 """ 

24 other_themes_dir = Path(__file__).parent / "other_themes" 

25 discovered: list[type[ClassicTheme]] = [] 

26 

27 for yaml_file in sorted(other_themes_dir.glob("*.yaml")): 

28 theme_class = create_variant_pydantic_model( 

29 variant_name=yaml_file.stem, 

30 defaults=read_yaml(yaml_file)["design"], 

31 base_class=ClassicTheme, 

32 discriminator_field="theme", 

33 class_name_suffix="Theme", 

34 module_name="rendercv.schema.models.design", 

35 ) 

36 discovered.append(theme_class) 

37 

38 return discovered 

39 

40 

41# Build discriminated union dynamically 

42type BuiltInDesign = Annotated[ 

43 reduce(or_, discover_other_themes(), ClassicTheme), # ty: ignore[invalid-type-form] 

44 pydantic.Field(discriminator="theme"), 

45] 

46available_themes: list[str] = [ 

47 ThemeClass.model_fields["theme"].default 

48 for ThemeClass in get_args(get_args(BuiltInDesign.__value__)[0]) 

49] 

50built_in_design_adapter = pydantic.TypeAdapter[BuiltInDesign](BuiltInDesign)