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

1"""

2 Alias functionality so config keys can have multiple names.

3"""

4

5 import typing

6 from dataclasses import dataclass

7 from typing import Any

8

9 from .abs import AnyType, T

10

11

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

13 class Alias:

14 """

15 Internal class used to relate keys.

16 """

17

18 to: str

19

20

21 def 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

28 def 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

37 def 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

64 def 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)