Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/googleapiclient/schema.py: 95%

110 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 06:51 +0000

1# Copyright 2014 Google Inc. All Rights Reserved. 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14 

15"""Schema processing for discovery based APIs 

16 

17Schemas holds an APIs discovery schemas. It can return those schema as 

18deserialized JSON objects, or pretty print them as prototype objects that 

19conform to the schema. 

20 

21For example, given the schema: 

22 

23 schema = \"\"\"{ 

24 "Foo": { 

25 "type": "object", 

26 "properties": { 

27 "etag": { 

28 "type": "string", 

29 "description": "ETag of the collection." 

30 }, 

31 "kind": { 

32 "type": "string", 

33 "description": "Type of the collection ('calendar#acl').", 

34 "default": "calendar#acl" 

35 }, 

36 "nextPageToken": { 

37 "type": "string", 

38 "description": "Token used to access the next 

39 page of this result. Omitted if no further results are available." 

40 } 

41 } 

42 } 

43 }\"\"\" 

44 

45 s = Schemas(schema) 

46 print s.prettyPrintByName('Foo') 

47 

48 Produces the following output: 

49 

50 { 

51 "nextPageToken": "A String", # Token used to access the 

52 # next page of this result. Omitted if no further results are available. 

53 "kind": "A String", # Type of the collection ('calendar#acl'). 

54 "etag": "A String", # ETag of the collection. 

55 }, 

56 

57The constructor takes a discovery document in which to look up named schema. 

58""" 

59from __future__ import absolute_import 

60 

61# TODO(jcgregorio) support format, enum, minimum, maximum 

62 

63__author__ = "jcgregorio@google.com (Joe Gregorio)" 

64 

65 

66from collections import OrderedDict 

67 

68from googleapiclient import _helpers as util 

69 

70 

71class Schemas(object): 

72 """Schemas for an API.""" 

73 

74 def __init__(self, discovery): 

75 """Constructor. 

76 

77 Args: 

78 discovery: object, Deserialized discovery document from which we pull 

79 out the named schema. 

80 """ 

81 self.schemas = discovery.get("schemas", {}) 

82 

83 # Cache of pretty printed schemas. 

84 self.pretty = {} 

85 

86 @util.positional(2) 

87 def _prettyPrintByName(self, name, seen=None, dent=0): 

88 """Get pretty printed object prototype from the schema name. 

89 

90 Args: 

91 name: string, Name of schema in the discovery document. 

92 seen: list of string, Names of schema already seen. Used to handle 

93 recursive definitions. 

94 

95 Returns: 

96 string, A string that contains a prototype object with 

97 comments that conforms to the given schema. 

98 """ 

99 if seen is None: 

100 seen = [] 

101 

102 if name in seen: 

103 # Do not fall into an infinite loop over recursive definitions. 

104 return "# Object with schema name: %s" % name 

105 seen.append(name) 

106 

107 if name not in self.pretty: 

108 self.pretty[name] = _SchemaToStruct( 

109 self.schemas[name], seen, dent=dent 

110 ).to_str(self._prettyPrintByName) 

111 

112 seen.pop() 

113 

114 return self.pretty[name] 

115 

116 def prettyPrintByName(self, name): 

117 """Get pretty printed object prototype from the schema name. 

118 

119 Args: 

120 name: string, Name of schema in the discovery document. 

121 

122 Returns: 

123 string, A string that contains a prototype object with 

124 comments that conforms to the given schema. 

125 """ 

126 # Return with trailing comma and newline removed. 

127 return self._prettyPrintByName(name, seen=[], dent=0)[:-2] 

128 

129 @util.positional(2) 

130 def _prettyPrintSchema(self, schema, seen=None, dent=0): 

131 """Get pretty printed object prototype of schema. 

132 

133 Args: 

134 schema: object, Parsed JSON schema. 

135 seen: list of string, Names of schema already seen. Used to handle 

136 recursive definitions. 

137 

138 Returns: 

139 string, A string that contains a prototype object with 

140 comments that conforms to the given schema. 

141 """ 

142 if seen is None: 

143 seen = [] 

144 

145 return _SchemaToStruct(schema, seen, dent=dent).to_str(self._prettyPrintByName) 

146 

147 def prettyPrintSchema(self, schema): 

148 """Get pretty printed object prototype of schema. 

149 

150 Args: 

151 schema: object, Parsed JSON schema. 

152 

153 Returns: 

154 string, A string that contains a prototype object with 

155 comments that conforms to the given schema. 

156 """ 

157 # Return with trailing comma and newline removed. 

158 return self._prettyPrintSchema(schema, dent=0)[:-2] 

159 

160 def get(self, name, default=None): 

161 """Get deserialized JSON schema from the schema name. 

162 

163 Args: 

164 name: string, Schema name. 

165 default: object, return value if name not found. 

166 """ 

167 return self.schemas.get(name, default) 

168 

169 

170class _SchemaToStruct(object): 

171 """Convert schema to a prototype object.""" 

172 

173 @util.positional(3) 

174 def __init__(self, schema, seen, dent=0): 

175 """Constructor. 

176 

177 Args: 

178 schema: object, Parsed JSON schema. 

179 seen: list, List of names of schema already seen while parsing. Used to 

180 handle recursive definitions. 

181 dent: int, Initial indentation depth. 

182 """ 

183 # The result of this parsing kept as list of strings. 

184 self.value = [] 

185 

186 # The final value of the parsing. 

187 self.string = None 

188 

189 # The parsed JSON schema. 

190 self.schema = schema 

191 

192 # Indentation level. 

193 self.dent = dent 

194 

195 # Method that when called returns a prototype object for the schema with 

196 # the given name. 

197 self.from_cache = None 

198 

199 # List of names of schema already seen while parsing. 

200 self.seen = seen 

201 

202 def emit(self, text): 

203 """Add text as a line to the output. 

204 

205 Args: 

206 text: string, Text to output. 

207 """ 

208 self.value.extend([" " * self.dent, text, "\n"]) 

209 

210 def emitBegin(self, text): 

211 """Add text to the output, but with no line terminator. 

212 

213 Args: 

214 text: string, Text to output. 

215 """ 

216 self.value.extend([" " * self.dent, text]) 

217 

218 def emitEnd(self, text, comment): 

219 """Add text and comment to the output with line terminator. 

220 

221 Args: 

222 text: string, Text to output. 

223 comment: string, Python comment. 

224 """ 

225 if comment: 

226 divider = "\n" + " " * (self.dent + 2) + "# " 

227 lines = comment.splitlines() 

228 lines = [x.rstrip() for x in lines] 

229 comment = divider.join(lines) 

230 self.value.extend([text, " # ", comment, "\n"]) 

231 else: 

232 self.value.extend([text, "\n"]) 

233 

234 def indent(self): 

235 """Increase indentation level.""" 

236 self.dent += 1 

237 

238 def undent(self): 

239 """Decrease indentation level.""" 

240 self.dent -= 1 

241 

242 def _to_str_impl(self, schema): 

243 """Prototype object based on the schema, in Python code with comments. 

244 

245 Args: 

246 schema: object, Parsed JSON schema file. 

247 

248 Returns: 

249 Prototype object based on the schema, in Python code with comments. 

250 """ 

251 stype = schema.get("type") 

252 if stype == "object": 

253 self.emitEnd("{", schema.get("description", "")) 

254 self.indent() 

255 if "properties" in schema: 

256 properties = schema.get("properties", {}) 

257 sorted_properties = OrderedDict(sorted(properties.items())) 

258 for pname, pschema in sorted_properties.items(): 

259 self.emitBegin('"%s": ' % pname) 

260 self._to_str_impl(pschema) 

261 elif "additionalProperties" in schema: 

262 self.emitBegin('"a_key": ') 

263 self._to_str_impl(schema["additionalProperties"]) 

264 self.undent() 

265 self.emit("},") 

266 elif "$ref" in schema: 

267 schemaName = schema["$ref"] 

268 description = schema.get("description", "") 

269 s = self.from_cache(schemaName, seen=self.seen) 

270 parts = s.splitlines() 

271 self.emitEnd(parts[0], description) 

272 for line in parts[1:]: 

273 self.emit(line.rstrip()) 

274 elif stype == "boolean": 

275 value = schema.get("default", "True or False") 

276 self.emitEnd("%s," % str(value), schema.get("description", "")) 

277 elif stype == "string": 

278 value = schema.get("default", "A String") 

279 self.emitEnd('"%s",' % str(value), schema.get("description", "")) 

280 elif stype == "integer": 

281 value = schema.get("default", "42") 

282 self.emitEnd("%s," % str(value), schema.get("description", "")) 

283 elif stype == "number": 

284 value = schema.get("default", "3.14") 

285 self.emitEnd("%s," % str(value), schema.get("description", "")) 

286 elif stype == "null": 

287 self.emitEnd("None,", schema.get("description", "")) 

288 elif stype == "any": 

289 self.emitEnd('"",', schema.get("description", "")) 

290 elif stype == "array": 

291 self.emitEnd("[", schema.get("description")) 

292 self.indent() 

293 self.emitBegin("") 

294 self._to_str_impl(schema["items"]) 

295 self.undent() 

296 self.emit("],") 

297 else: 

298 self.emit("Unknown type! %s" % stype) 

299 self.emitEnd("", "") 

300 

301 self.string = "".join(self.value) 

302 return self.string 

303 

304 def to_str(self, from_cache): 

305 """Prototype object based on the schema, in Python code with comments. 

306 

307 Args: 

308 from_cache: callable(name, seen), Callable that retrieves an object 

309 prototype for a schema with the given name. Seen is a list of schema 

310 names already seen as we recursively descend the schema definition. 

311 

312 Returns: 

313 Prototype object based on the schema, in Python code with comments. 

314 The lines of the code will all be properly indented. 

315 """ 

316 self.from_cache = from_cache 

317 return self._to_str_impl(self.schema)