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
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-27 10:56 -0600
1"""
2Core abstractions for sequence rules.
4This module provides the fundamental types and functions for working with
5abstract objects and sequences.
6"""
8from typing import Any, Callable, Dict, List, Protocol, TypeVar
10T = TypeVar("T")
13class AbstractObject:
14 """
15 Represents an abstract object with arbitrary properties.
16 """
18 def __init__(self, **properties: Any):
19 self.properties: Dict[str, Any] = properties
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
28 def __repr__(self) -> str:
29 return f"{self.__class__.__name__}({self.properties})"
31 def __eq__(self, other: object) -> bool:
32 if not isinstance(other, AbstractObject):
33 return NotImplemented
34 return self.properties == other.properties
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
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 )
54class DictAccessProxy:
55 """Proxy class for handling nested dictionary access."""
57 def __init__(self, data: Dict[str, Any]):
58 self._data = data
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
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
75 def __contains__(self, key: Any) -> bool:
76 """Support 'in' operator for checking key existence."""
77 return key in self._data
80# A Sequence is simply a list of AbstractObjects.
81Sequence = List[AbstractObject]
84class FormalRuleProtocol(Protocol):
85 """Protocol defining the interface for formal rules."""
87 def __call__(self, seq: Sequence) -> bool:
88 """
89 Evaluates the rule against a sequence.
91 Args:
92 seq: The sequence to evaluate against
94 Returns:
95 bool: True if the sequence satisfies the rule, False otherwise
96 """
97 ... # pragma: no cover
100# A FormalRule is a predicate over sequences.
101FormalRule = Callable[[Sequence], bool]
104def check_sequence(seq: Sequence, rule: FormalRule) -> bool:
105 """
106 Returns True if the sequence satisfies the formal rule.
108 Args:
109 seq: The sequence to check
110 rule: The formal rule to apply
112 Returns:
113 bool: True if the sequence satisfies the rule, False otherwise
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)