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