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