Coverage for src/seqrule/core.py: 37%

51 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-27 10:56 -0600

1""" 

2Core abstractions for sequence rules. 

3 

4This module provides the fundamental types and functions for working with 

5abstract objects and sequences. 

6""" 

7 

8from typing import Any, Callable, Dict, List, Protocol, TypeVar 

9 

10T = TypeVar("T") 

11 

12 

13class AbstractObject: 

14 """ 

15 Represents an abstract object with arbitrary properties. 

16 """ 

17 

18 def __init__(self, **properties: Any): 

19 self.properties: Dict[str, Any] = properties 

20 

21 def __getitem__(self, key: str) -> Any: 

22 value = self.properties.get(key) 

23 if isinstance(value, dict): 

24 # Return a proxy object that handles nested access 

25 return DictAccessProxy(value) 

26 return value 

27 

28 def __repr__(self) -> str: 

29 return f"{self.__class__.__name__}({self.properties})" 

30 

31 def __eq__(self, other: object) -> bool: 

32 if not isinstance(other, AbstractObject): 

33 return NotImplemented 

34 return self.properties == other.properties 

35 

36 def __hash__(self) -> int: 

37 def make_hashable(value): 

38 if isinstance(value, dict): 

39 return tuple((k, make_hashable(v)) for k, v in sorted(value.items())) 

40 elif isinstance(value, list): 

41 # Preserve list order in hash 

42 return tuple(make_hashable(v) for v in value) 

43 elif isinstance(value, set): 

44 # Convert set to sorted tuple for consistent hash 

45 return tuple(sorted(make_hashable(v) for v in value)) 

46 return value 

47 

48 # Convert properties to a tuple of (key, value) pairs and hash it 

49 return hash( 

50 tuple(sorted((k, make_hashable(v)) for k, v in self.properties.items())) 

51 ) 

52 

53 

54class DictAccessProxy: 

55 """Proxy class for handling nested dictionary access.""" 

56 

57 def __init__(self, data: Dict[str, Any]): 

58 self._data = data 

59 

60 def __getitem__(self, key: str) -> Any: 

61 value = self._data.get(key) 

62 if value is None: 

63 return None 

64 if isinstance(value, dict): 

65 return DictAccessProxy(value) 

66 return value 

67 

68 def get(self, key: Any, default: Any = None) -> Any: 

69 """Get a value from the dictionary with a default if not found.""" 

70 value = self._data.get(key, default) 

71 if isinstance(value, dict): 

72 return DictAccessProxy(value) 

73 return value 

74 

75 def __contains__(self, key: Any) -> bool: 

76 """Support 'in' operator for checking key existence.""" 

77 return key in self._data 

78 

79 

80# A Sequence is simply a list of AbstractObjects. 

81Sequence = List[AbstractObject] 

82 

83 

84class FormalRuleProtocol(Protocol): 

85 """Protocol defining the interface for formal rules.""" 

86 

87 def __call__(self, seq: Sequence) -> bool: 

88 """ 

89 Evaluates the rule against a sequence. 

90 

91 Args: 

92 seq: The sequence to evaluate against 

93 

94 Returns: 

95 bool: True if the sequence satisfies the rule, False otherwise 

96 """ 

97 ... # pragma: no cover 

98 

99 

100# A FormalRule is a predicate over sequences. 

101FormalRule = Callable[[Sequence], bool] 

102 

103 

104def check_sequence(seq: Sequence, rule: FormalRule) -> bool: 

105 """ 

106 Returns True if the sequence satisfies the formal rule. 

107 

108 Args: 

109 seq: The sequence to check 

110 rule: The formal rule to apply 

111 

112 Returns: 

113 bool: True if the sequence satisfies the rule, False otherwise 

114 

115 Raises: 

116 TypeError: If any element in the sequence is not an AbstractObject 

117 """ 

118 if not all(isinstance(obj, AbstractObject) for obj in seq): 

119 raise TypeError("All elements in sequence must be instances of AbstractObject") 

120 return rule(seq)