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