Coverage for src/lexigram/admin/mapping/mapper.py: 100%

21 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1"""AdminObjectMapper — registry-based object-to-object mapper for lexigram-admin. 

2 

3Implements :class:`lexigram.contracts.mapping.ObjectMapperProtocol` so the mapper 

4can be resolved by any consumer that depends on the contract. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import TYPE_CHECKING, Any 

10 

11from lexigram.logging import get_logger 

12 

13if TYPE_CHECKING: 

14 from lexigram.contracts.mapping import ObjectMapperProtocol 

15 

16logger = get_logger(__name__) 

17 

18 

19class AdminObjectMapper: 

20 """Registry-based mapper for admin domain objects. 

21 

22 Satisfies :class:`~lexigram.contracts.mapping.ObjectMapperProtocol`. 

23 

24 Example:: 

25 

26 mapper = AdminObjectMapper() 

27 mapper.register(AdminUserEntity, AdminUserRecord, lambda e: e.to_user()) 

28 record = mapper.map(entity, AdminUserRecord) 

29 """ 

30 

31 def __init__(self) -> None: 

32 self._registry: dict[tuple[type, type], Any] = {} 

33 

34 def register( 

35 self, 

36 source_type: type[Any], 

37 dest_type: type[Any], 

38 mapper_func: Any, 

39 ) -> None: 

40 """Register a mapping function from *source_type* to *dest_type*. 

41 

42 Args: 

43 source_type: The type to map from. 

44 dest_type: The target type to produce. 

45 mapper_func: Callable that accepts a source instance and returns a dest 

46 instance. 

47 """ 

48 self._registry[(source_type, dest_type)] = mapper_func 

49 logger.debug( 

50 "admin_mapper_registered", 

51 source=source_type.__name__, 

52 dest=dest_type.__name__, 

53 ) 

54 

55 def map( 

56 self, 

57 source: Any, 

58 dest_type: type[Any], 

59 *, 

60 validate: bool = False, 

61 validator: Any | None = None, 

62 ) -> Any: 

63 """Map *source* to an instance of *dest_type*. 

64 

65 Args: 

66 source: The source object to transform. 

67 dest_type: The target type to produce. 

68 validate: Whether to validate the result after mapping (unused by 

69 default; pass a *validator* to enable). 

70 validator: Optional callable ``validator(result) -> None`` that raises 

71 on invalid data. 

72 

73 Returns: 

74 A new instance of *dest_type*. 

75 

76 Raises: 

77 KeyError: If no mapping is registered for this type pair. 

78 """ 

79 key = (type(source), dest_type) 

80 mapper_func = self._registry.get(key) 

81 if mapper_func is None: 

82 raise KeyError( 

83 f"No mapping registered from {type(source).__name__!r} " 

84 f"to {dest_type.__name__!r}" 

85 ) 

86 result = mapper_func(source) 

87 if validate and validator is not None: 

88 validator(result) 

89 return result 

90 

91 

92# Ensure the class satisfies the protocol at import time (structural check). 

93_: ObjectMapperProtocol = AdminObjectMapper() 

94 

95__all__ = ["AdminObjectMapper"]