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