1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Schema processing for discovery based APIs
16
17 Schemas holds an APIs discovery schemas. It can return those schema as
18 deserialized JSON objects, or pretty print them as prototype objects that
19 conform to the schema.
20
21 For 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
57 The constructor takes a discovery document in which to look up named schema.
58 """
59 from __future__ import absolute_import
60 import six
61
62
63
64 __author__ = "jcgregorio@google.com (Joe Gregorio)"
65
66 import copy
67
68 from googleapiclient import _helpers as util
72 """Schemas for an API."""
73
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
84 self.pretty = {}
85
86 @util.positional(2)
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
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
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
127 return self._prettyPrintByName(name, seen=[], dent=1)[:-2]
128
129 @util.positional(2)
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
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
158 return self._prettyPrintSchema(schema, dent=1)[:-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
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
184 self.value = []
185
186
187 self.string = None
188
189
190 self.schema = schema
191
192
193 self.dent = dent
194
195
196
197 self.from_cache = None
198
199
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
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
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
235 """Increase indentation level."""
236 self.dent += 1
237
239 """Decrease indentation level."""
240 self.dent -= 1
241
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 for pname, pschema in six.iteritems(schema.get("properties", {})):
257 self.emitBegin('"%s": ' % pname)
258 self._to_str_impl(pschema)
259 elif "additionalProperties" in schema:
260 self.emitBegin('"a_key": ')
261 self._to_str_impl(schema["additionalProperties"])
262 self.undent()
263 self.emit("},")
264 elif "$ref" in schema:
265 schemaName = schema["$ref"]
266 description = schema.get("description", "")
267 s = self.from_cache(schemaName, seen=self.seen)
268 parts = s.splitlines()
269 self.emitEnd(parts[0], description)
270 for line in parts[1:]:
271 self.emit(line.rstrip())
272 elif stype == "boolean":
273 value = schema.get("default", "True or False")
274 self.emitEnd("%s," % str(value), schema.get("description", ""))
275 elif stype == "string":
276 value = schema.get("default", "A String")
277 self.emitEnd('"%s",' % str(value), schema.get("description", ""))
278 elif stype == "integer":
279 value = schema.get("default", "42")
280 self.emitEnd("%s," % str(value), schema.get("description", ""))
281 elif stype == "number":
282 value = schema.get("default", "3.14")
283 self.emitEnd("%s," % str(value), schema.get("description", ""))
284 elif stype == "null":
285 self.emitEnd("None,", schema.get("description", ""))
286 elif stype == "any":
287 self.emitEnd('"",', schema.get("description", ""))
288 elif stype == "array":
289 self.emitEnd("[", schema.get("description"))
290 self.indent()
291 self.emitBegin("")
292 self._to_str_impl(schema["items"])
293 self.undent()
294 self.emit("],")
295 else:
296 self.emit("Unknown type! %s" % stype)
297 self.emitEnd("", "")
298
299 self.string = "".join(self.value)
300 return self.string
301
302 - def to_str(self, from_cache):
303 """Prototype object based on the schema, in Python code with comments.
304
305 Args:
306 from_cache: callable(name, seen), Callable that retrieves an object
307 prototype for a schema with the given name. Seen is a list of schema
308 names already seen as we recursively descend the schema definition.
309
310 Returns:
311 Prototype object based on the schema, in Python code with comments.
312 The lines of the code will all be properly indented.
313 """
314 self.from_cache = from_cache
315 return self._to_str_impl(self.schema)
316