Coverage for kye/types.py: 31%

111 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2024-01-16 14:12 -0700

1from __future__ import annotations 

2from typing import Optional 

3from kye.parser.parser import parse_expression 

4import kye.parser.kye_ast as AST 

5import re 

6from collections import OrderedDict 

7 

8TYPE_REF = str 

9EDGE = str 

10 

11class Type: 

12 """ Base Class for Types """ 

13 ref: TYPE_REF 

14 extends: Optional[Type] 

15 indexes: tuple[tuple[EDGE]] 

16 assertions: list[AST.Expression] 

17 _edges: OrderedDict[EDGE, Type] 

18 _multiple: dict[EDGE, bool] 

19 _nullable: dict[EDGE, bool] 

20 

21 def __init__(self, name: TYPE_REF): 

22 assert re.match(r'\b[A-Z]+[a-z]\w+\b', name) 

23 self.ref = name 

24 self.indexes = tuple() 

25 self.assertions = [] 

26 self.extends = None 

27 self._edges = OrderedDict() 

28 self._multiple = {} 

29 self._nullable = {} 

30 

31 def define_edge(self, 

32 name: EDGE, 

33 type: Type, 

34 nullable=False, 

35 multiple=False 

36 ): 

37 assert re.fullmatch(r'[a-z_][a-z0-9_]+', name) 

38 assert isinstance(type, Type) 

39 self._edges[name] = type 

40 self._nullable[name] = nullable 

41 self._multiple[name] = multiple 

42 

43 def define_index(self, index: tuple[EDGE]): 

44 # Convert to tuple if passed in a single string 

45 if type(index) is str: 

46 index = (index,) 

47 else: 

48 index = tuple(index) 

49 

50 # Skip if it is already part of our indexes 

51 if index in self.indexes: 

52 return 

53 

54 # Validate edges within index 

55 for edge in index: 

56 assert self.has_edge(edge), f'Cannot use undefined edge in index: "{edge}"' 

57 assert not self.allows_null(edge), f'Cannot use a nullable edge in index: "{edge}"' 

58 

59 # Remove any existing indexes that are a superset of the new index 

60 self.indexes = tuple( 

61 existing_idx for existing_idx in self.indexes 

62 if not set(index).issubset(set(existing_idx)) 

63 ) + (index,) 

64 

65 def define_parent(self, parent: Type): 

66 assert isinstance(parent, Type) 

67 if self.extends is not None: 

68 assert self.extends == parent, 'Already assigned a parent' 

69 return 

70 self.extends = parent 

71 for edge in parent._edges: 

72 if not self.has_edge(edge): 

73 self.define_edge( 

74 name=edge, 

75 type=parent._edges[edge], 

76 multiple=parent.allows_multiple(edge), 

77 nullable=parent.allows_null(edge), 

78 ) 

79 self.assertions = parent.assertions + self.assertions 

80 

81 def define_assertion(self, assertion: str): 

82 assert type(assertion) is str 

83 ast = parse_expression(assertion) 

84 self.assertions.append(ast) 

85 

86 @property 

87 def index(self) -> set[EDGE]: 

88 """ Flatten the 2d list of indexes """ 

89 return {idx for idxs in self.indexes for idx in idxs} 

90 

91 @property 

92 def has_index(self) -> bool: 

93 return len(self.indexes) > 0 

94 

95 @property 

96 def edges(self) -> list[EDGE]: 

97 return list(self._edges.keys()) 

98 

99 def has_edge(self, edge: EDGE) -> bool: 

100 return edge in self._edges 

101 

102 def get_edge(self, edge: EDGE) -> Type: 

103 assert self.has_edge(edge) 

104 return self._edges[edge] 

105 

106 def edge_origin(self, edge: EDGE) -> Optional[Type]: 

107 assert self.has_edge(edge) 

108 if self.extends and self.extends.has_edge(edge): 

109 return self.extends.edge_origin(edge) 

110 return self 

111 

112 def allows_multiple(self, edge: EDGE) -> bool: 

113 assert self.has_edge(edge) 

114 return self._multiple[edge] 

115 

116 def allows_null(self, edge: EDGE) -> bool: 

117 assert self.has_edge(edge) 

118 return self._nullable[edge] 

119 

120 def __repr__(self): 

121 def get_cardinality_symbol(edge): 

122 nullable = int(self.allows_null(edge)) 

123 multiple = int(self.allows_multiple(edge)) 

124 return ([['' ,'+'], 

125 ['?','*']])[nullable][multiple] 

126 

127 non_index_edges = [ 

128 edge + get_cardinality_symbol(edge) 

129 for edge in self._edges 

130 if edge not in self.index 

131 ] 

132 

133 return "{}{}{}".format( 

134 self.ref or '', 

135 ''.join('(' + ','.join(idx) + ')' for idx in self.indexes), 

136 '{' + ','.join(non_index_edges) + '}' if len(non_index_edges) else '', 

137 ) 

138 

139GLOBALS = { 

140 'Number': {}, 

141 'String': {'edges':{'length':'Number'}}, 

142 'Boolean': {}, 

143} 

144 

145def from_compiled(source, types: dict[TYPE_REF, Type]={}): 

146 source['models'] = {**GLOBALS, **source.get('models',{})} 

147 # 1. Do first iteration creating a stub type for each name 

148 for ref in source.get('models',{}): 

149 types[ref] = Type(ref) 

150 

151 def get_type(type_ref): 

152 assert type_ref in types, f'Undefined type: "{type_ref}"' 

153 return types[type_ref] 

154 

155 zipped_source_and_stub: dict[TYPE_REF, tuple[dict, Type]] = { 

156 ref: (src, types[ref]) 

157 for ref, src in source.get('models',{}).items() 

158 } 

159 

160 # 2. During second iteration define the edges, indexes & assertions 

161 for src, typ in zipped_source_and_stub.values(): 

162 

163 for edge_name, edge_type_ref in src.get('edges', {}).items(): 

164 nullable = edge_name.endswith('?') or edge_name.endswith('*') 

165 multiple = edge_name.endswith('+') or edge_name.endswith('*') 

166 edge_name = edge_name.rstrip('?+*') 

167 typ.define_edge( 

168 name=edge_name, 

169 type=get_type(edge_type_ref), 

170 nullable=nullable, 

171 multiple=multiple, 

172 ) 

173 

174 if 'index' in src: 

175 typ.define_index(src['index']) 

176 if 'indexes' in src: 

177 for idx in src['indexes']: 

178 typ.define_index(idx) 

179 

180 for assertion in src.get('assertions', []): 

181 typ.define_assertion(assertion) 

182 

183 # 3. Wait till the third iteration to define the extends 

184 # so that parent edges & assertions will be known 

185 def recursively_define_parent(type_ref): 

186 src, typ = zipped_source_and_stub[type_ref] 

187 if 'extends' in src: 

188 parent = get_type(src['extends']) 

189 recursively_define_parent(parent.ref) 

190 typ.define_parent(parent) 

191 

192 for type_ref in zipped_source_and_stub.keys(): 

193 recursively_define_parent(type_ref) 

194 

195 

196 # # 4. Now that all edges have been defined, parse the expressions 

197 # for src, typ in zipped_source_and_stub: 

198 # for assertion in src.get('assertions', []): 

199 # # TODO: parse the assertion and add type information 

200 # typ.define_assertion(assertion) 

201 

202 return types