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(self.schemas[name],
109 seen, dent=dent).to_str(self._prettyPrintByName)
110
111 seen.pop()
112
113 return self.pretty[name]
114
116 """Get pretty printed object prototype from the schema name.
117
118 Args:
119 name: string, Name of schema in the discovery document.
120
121 Returns:
122 string, A string that contains a prototype object with
123 comments that conforms to the given schema.
124 """
125
126 return self._prettyPrintByName(name, seen=[], dent=1)[:-2]
127
128 @util.positional(2)
130 """Get pretty printed object prototype of schema.
131
132 Args:
133 schema: object, Parsed JSON schema.
134 seen: list of string, Names of schema already seen. Used to handle
135 recursive definitions.
136
137 Returns:
138 string, A string that contains a prototype object with
139 comments that conforms to the given schema.
140 """
141 if seen is None:
142 seen = []
143
144 return _SchemaToStruct(schema, seen, dent=dent).to_str(self._prettyPrintByName)
145
147 """Get pretty printed object prototype of schema.
148
149 Args:
150 schema: object, Parsed JSON schema.
151
152 Returns:
153 string, A string that contains a prototype object with
154 comments that conforms to the given schema.
155 """
156
157 return self._prettyPrintSchema(schema, dent=1)[:-2]
158
159 - def get(self, name, default=None):
160 """Get deserialized JSON schema from the schema name.
161
162 Args:
163 name: string, Schema name.
164 default: object, return value if name not found.
165 """
166 return self.schemas.get(name, default)
167
170 """Convert schema to a prototype object."""
171
172 @util.positional(3)
173 - def __init__(self, schema, seen, dent=0):
174 """Constructor.
175
176 Args:
177 schema: object, Parsed JSON schema.
178 seen: list, List of names of schema already seen while parsing. Used to
179 handle recursive definitions.
180 dent: int, Initial indentation depth.
181 """
182
183 self.value = []
184
185
186 self.string = None
187
188
189 self.schema = schema
190
191
192 self.dent = dent
193
194
195
196 self.from_cache = None
197
198
199 self.seen = seen
200
201 - def emit(self, text):
202 """Add text as a line to the output.
203
204 Args:
205 text: string, Text to output.
206 """
207 self.value.extend([" " * self.dent, text, '\n'])
208
210 """Add text to the output, but with no line terminator.
211
212 Args:
213 text: string, Text to output.
214 """
215 self.value.extend([" " * self.dent, text])
216
218 """Add text and comment to the output with line terminator.
219
220 Args:
221 text: string, Text to output.
222 comment: string, Python comment.
223 """
224 if comment:
225 divider = '\n' + ' ' * (self.dent + 2) + '# '
226 lines = comment.splitlines()
227 lines = [x.rstrip() for x in lines]
228 comment = divider.join(lines)
229 self.value.extend([text, ' # ', comment, '\n'])
230 else:
231 self.value.extend([text, '\n'])
232
234 """Increase indentation level."""
235 self.dent += 1
236
238 """Decrease indentation level."""
239 self.dent -= 1
240
242 """Prototype object based on the schema, in Python code with comments.
243
244 Args:
245 schema: object, Parsed JSON schema file.
246
247 Returns:
248 Prototype object based on the schema, in Python code with comments.
249 """
250 stype = schema.get('type')
251 if stype == 'object':
252 self.emitEnd('{', schema.get('description', ''))
253 self.indent()
254 if 'properties' in schema:
255 for pname, pschema in six.iteritems(schema.get('properties', {})):
256 self.emitBegin('"%s": ' % pname)
257 self._to_str_impl(pschema)
258 elif 'additionalProperties' in schema:
259 self.emitBegin('"a_key": ')
260 self._to_str_impl(schema['additionalProperties'])
261 self.undent()
262 self.emit('},')
263 elif '$ref' in schema:
264 schemaName = schema['$ref']
265 description = schema.get('description', '')
266 s = self.from_cache(schemaName, seen=self.seen)
267 parts = s.splitlines()
268 self.emitEnd(parts[0], description)
269 for line in parts[1:]:
270 self.emit(line.rstrip())
271 elif stype == 'boolean':
272 value = schema.get('default', 'True or False')
273 self.emitEnd('%s,' % str(value), schema.get('description', ''))
274 elif stype == 'string':
275 value = schema.get('default', 'A String')
276 self.emitEnd('"%s",' % str(value), schema.get('description', ''))
277 elif stype == 'integer':
278 value = schema.get('default', '42')
279 self.emitEnd('%s,' % str(value), schema.get('description', ''))
280 elif stype == 'number':
281 value = schema.get('default', '3.14')
282 self.emitEnd('%s,' % str(value), schema.get('description', ''))
283 elif stype == 'null':
284 self.emitEnd('None,', schema.get('description', ''))
285 elif stype == 'any':
286 self.emitEnd('"",', schema.get('description', ''))
287 elif stype == 'array':
288 self.emitEnd('[', schema.get('description'))
289 self.indent()
290 self.emitBegin('')
291 self._to_str_impl(schema['items'])
292 self.undent()
293 self.emit('],')
294 else:
295 self.emit('Unknown type! %s' % stype)
296 self.emitEnd('', '')
297
298 self.string = ''.join(self.value)
299 return self.string
300
301 - def to_str(self, from_cache):
302 """Prototype object based on the schema, in Python code with comments.
303
304 Args:
305 from_cache: callable(name, seen), Callable that retrieves an object
306 prototype for a schema with the given name. Seen is a list of schema
307 names already seen as we recursively descend the schema definition.
308
309 Returns:
310 Prototype object based on the schema, in Python code with comments.
311 The lines of the code will all be properly indented.
312 """
313 self.from_cache = from_cache
314 return self._to_str_impl(self.schema)
315