Coverage for src / template / schema.py: 88%

35 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-21 16:36 +0000

1# vim: foldmarker=[[,]] foldmethod=marker 

2from ..answers import schema as answers 

3 

4# https://docs.python-cerberus.org/en/stable/validation-rules.html 

5SCHEMA = ( 

6 answers.SCHEMA 

7 + r""" 

8actions: # [[ 

9 type: list 

10 default: [] 

11 schema: 

12 type: dict 

13 default: {} 

14 nullable: true 

15 schema: 

16 # actions.hidden 

17 # -------------- 

18 action: 

19 type: string 

20 required: True 

21 allowed: ["move", "remove", "remove-trailing-newline"] 

22 

23 # actions.dst 

24 # ----------- 

25 dst: 

26 type: string 

27 excludes: ["path"] 

28 

29 # actions.else 

30 # ------------ 

31 else: 

32 type: string 

33 allowed: ["remove"] 

34 

35 # actions.if 

36 # ---------- 

37 if: 

38 type: [string, boolean] 

39 required: True 

40 

41 # actions.order 

42 # ------------- 

43 order: 

44 type: integer 

45 default: 0 

46 

47 # actions.path 

48 # ------------ 

49 path: 

50 type: [string, list] 

51 excludes: ["dst", "src"] 

52 

53 # actions.src 

54 # ----------- 

55 src: 

56 type: string 

57 excludes: ["path"] 

58# ]] 

59 

60includes: # [[ 

61 type: list 

62 default: [] 

63 schema: 

64 type: dict 

65 default: {} 

66 nullable: true 

67 

68 schema: 

69 # includes.branch 

70 # -------------- 

71 branch: 

72 type: string 

73 nullable: True 

74 

75 # includes.include 

76 # --------------- 

77 include: 

78 type: string 

79 required: true 

80 check_with: include_minlength 

81 

82 # includes.filename 

83 # --------------- 

84 filename: 

85 type: string 

86 default: "scaffold.yml" 

87# ]] 

88 

89jinja2: # [[ 

90 type: dict 

91 default: {} 

92 schema: 

93 # lstrip_blocks 

94 lstrip_blocks: 

95 type: boolean 

96 default: false 

97 

98 # trim_blocks 

99 trim_blocks: 

100 type: boolean 

101 default: false 

102# ]] 

103 

104questions: # [[ 

105 type: list 

106 default: [] 

107 schema: 

108 type: dict 

109 default: {} 

110 nullable: true 

111 schema: 

112 

113 # questions.description 

114 # --------------------- 

115 description: 

116 type: string 

117 

118 # questions.hidden 

119 # ---------------- 

120 hidden: 

121 type: [string, boolean] 

122 default: false 

123 

124 # questions.if 

125 # ------------ 

126 if: 

127 type: [string, boolean] 

128 

129 # questions.name 

130 # -------------- 

131 name: 

132 type: string 

133 regex: '^\S+$' 

134 required: true 

135 minlength: 1 

136 

137 # questions.order 

138 # --------------- 

139 order: 

140 type: integer 

141 default: 0 

142 

143 # questions.schema 

144 # ---------------- 

145 schema: 

146 type: dict 

147 nullable: true 

148 coerce: asdict 

149 check_with: schema_rules 

150 schema: 

151 

152 # questions.schema.allowed 

153 # ------------------------ 

154 allowed: 

155 type: list 

156 schema: 

157 type: [string, integer] 

158 

159 # questions.schema.default 

160 # ------------------------ 

161 default: 

162 type: [string, boolean, integer] 

163 

164 # questions.schema.nullable 

165 # ------------------------- 

166 nullable: 

167 type: boolean 

168 default: False 

169 

170 # questions.schema.max_length 

171 # --------------------------- 

172 max_length: 

173 type: integer 

174 

175 # questions.schema.max_value 

176 # --------------------------- 

177 max_value: 

178 type: integer 

179 

180 # questions.schema.min_length 

181 # --------------------------- 

182 min_length: 

183 type: integer 

184 min: 1 

185 

186 # questions.schema.min_value 

187 # --------------------------- 

188 min_value: 

189 type: integer 

190 

191 # questions.schema.type 

192 # --------------------- 

193 type: 

194 type: string 

195 default: "string" 

196 allowed: ["string", "integer", "boolean"] 

197# ]] 

198""" 

199) 

200 

201 

202class LocalValidator(answers.LocalValidator): 

203 def _normalize_coerce_asdict(self, value): 

204 if value is None: 204 ↛ 205line 204 didn't jump to line 205 because the condition on line 204 was never true

205 return {} 

206 return value 

207 

208 def _normalize_coerce_tolist(self, value): 

209 if isinstance(value, str): 

210 return [value] 

211 return value 

212 

213 def _check_with_include_minlength(self, field, value): 

214 if value is None or len(value) == 0: 

215 self._error(field, "Length of an 'include' path must be non-zero") 

216 

217 def _check_with_schema_rules(self, field, schema): 

218 if schema.get("type") in ["boolean", "string"] and any(k in schema for k in ["min_value", "max_value"]): 

219 self._error( 

220 field, 

221 'neither "min_value" nor "max_value" can be specified if "type" is either boolean or string', 

222 ) 

223 return 

224 if schema.get("type") in ["boolean", "integer"] and any(k in schema for k in ["min_length", "max_length"]): 

225 # fmt: off 

226 self._error(field, 'neither "min_length" nor "max_length" can be specified if "type" is either boolean or integer') 

227 return 

228 # fmt: on 

229 if "allowed" in schema and schema.get("type") == "boolean": 

230 self._error(field, '"allowed" can not be specified if "type" is boolean') 

231 return 

232 if "allowed" in schema and any(k in schema for k in ["min_length", "max_length", "min_value", "max_value"]): 

233 # fmt: off 

234 self._error(field, '"allowed" can not be specified when one of "min_length", "max_length", "min_value", or "max_value" is also specified') 

235 return 

236 # fmt: on 

237 if "min_value" in schema and "max_value" in schema: 

238 if int(schema["min_value"]) > int(schema["max_value"]): 

239 self._error(field, '"min_value" must be inferior to "max_value"') 

240 return 

241 if "min_length" in schema and "max_length" in schema: 

242 if schema["min_length"] > schema["max_length"]: 

243 self._error(field, '"min_length" must be inferior to "max_length"') 

244 return 

245 

246 

247# def _check_with_file_rules(self, field, value): 

248# if "else" in value and "if" not in value: 

249# self._error(field, '"if" required if a "files" element contains an "else"') 

250# return 

251# if "move" in [value.get("action", ""), value.get("else", "")] and "dest" not in value: 

252# self._error(field, '"dest" required if a "files" element contains an "action" or "else"') 

253# return