Coverage for src/configuraptor/alias.py: 100%

17 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2025-01-09 20:20 +0100

1""" 

2Alias functionality so config keys can have multiple names. 

3""" 

4 

5import typing 

6from dataclasses import dataclass 

7from typing import Any 

8 

9from .abs import AnyType, T 

10 

11 

12@dataclass(frozen=True, slots=True) 

13class Alias: 

14 """ 

15 Internal class used to relate keys. 

16 """ 

17 

18 to: str 

19 

20 

21def alias(to: str) -> Any: 

22 """ 

23 Function to create an alias to a different key in the same class. 

24 """ 

25 return Alias(to) 

26 

27 

28def has_aliases(cls: AnyType, key: str) -> typing.Generator[str, None, None]: 

29 """ 

30 Generate all aliases that point to 'key' in 'cls'. 

31 """ 

32 for field, value in cls.__dict__.items(): 

33 if isinstance(value, Alias) and value.to == key: 

34 yield field 

35 

36 

37def has_alias(cls: AnyType, key: str, data: dict[str, T]) -> typing.Optional[T]: 

38 """ 

39 Get the value of any alias in the same config class that references `key`. 

40 

41 Example: 

42 class Config: 

43 key1: str 

44 key2: str = alias('key1') 

45 

46 load_into(Config, {'key2': 'something'}) 

47 # -> key1 will look up the value of key2 because it's configured as an alias for it. 

48 

49 If multiple aliases point to the same base, they are all iterated until a valid value was found. 

50 """ 

51 # for field, value in cls.__dict__.items(): 

52 # if isinstance(value, Alias) and value.to == key: 

53 # # yay! 

54 # return data.get(field) 

55 # 

56 # return None 

57 

58 return next( 

59 (value for field in has_aliases(cls, key) if (value := data.get(field))), 

60 None, 

61 ) 

62 

63 

64def is_alias(cls: AnyType, prop: str) -> bool: 

65 """ 

66 Returns whether 'prop' is an alias to something else on cls. 

67 """ 

68 return isinstance(cls.__dict__.get(prop), Alias)