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