OLD | NEW |
| (Empty) |
1 # Copyright (C) 2010 Google Inc. | |
2 # | |
3 # Licensed under the Apache License, Version 2.0 (the "License"); | |
4 # you may not use this file except in compliance with the License. | |
5 # You may obtain a copy of the License at | |
6 # | |
7 # http://www.apache.org/licenses/LICENSE-2.0 | |
8 # | |
9 # Unless required by applicable law or agreed to in writing, software | |
10 # distributed under the License is distributed on an "AS IS" BASIS, | |
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 # See the License for the specific language governing permissions and | |
13 # limitations under the License. | |
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 # TODO(jcgregorio) support format, enum, minimum, maximum | |
61 | |
62 __author__ = 'jcgregorio@google.com (Joe Gregorio)' | |
63 | |
64 import copy | |
65 | |
66 from oauth2client import util | |
67 from oauth2client.anyjson import simplejson | |
68 | |
69 | |
70 class Schemas(object): | |
71 """Schemas for an API.""" | |
72 | |
73 def __init__(self, discovery): | |
74 """Constructor. | |
75 | |
76 Args: | |
77 discovery: object, Deserialized discovery document from which we pull | |
78 out the named schema. | |
79 """ | |
80 self.schemas = discovery.get('schemas', {}) | |
81 | |
82 # Cache of pretty printed schemas. | |
83 self.pretty = {} | |
84 | |
85 @util.positional(2) | |
86 def _prettyPrintByName(self, name, seen=None, dent=0): | |
87 """Get pretty printed object prototype from the schema name. | |
88 | |
89 Args: | |
90 name: string, Name of schema in the discovery document. | |
91 seen: list of string, Names of schema already seen. Used to handle | |
92 recursive definitions. | |
93 | |
94 Returns: | |
95 string, A string that contains a prototype object with | |
96 comments that conforms to the given schema. | |
97 """ | |
98 if seen is None: | |
99 seen = [] | |
100 | |
101 if name in seen: | |
102 # Do not fall into an infinite loop over recursive definitions. | |
103 return '# Object with schema name: %s' % name | |
104 seen.append(name) | |
105 | |
106 if name not in self.pretty: | |
107 self.pretty[name] = _SchemaToStruct(self.schemas[name], | |
108 seen, dent=dent).to_str(self._prettyPrintByName) | |
109 | |
110 seen.pop() | |
111 | |
112 return self.pretty[name] | |
113 | |
114 def prettyPrintByName(self, name): | |
115 """Get pretty printed object prototype from the schema name. | |
116 | |
117 Args: | |
118 name: string, Name of schema in the discovery document. | |
119 | |
120 Returns: | |
121 string, A string that contains a prototype object with | |
122 comments that conforms to the given schema. | |
123 """ | |
124 # Return with trailing comma and newline removed. | |
125 return self._prettyPrintByName(name, seen=[], dent=1)[:-2] | |
126 | |
127 @util.positional(2) | |
128 def _prettyPrintSchema(self, schema, seen=None, dent=0): | |
129 """Get pretty printed object prototype of schema. | |
130 | |
131 Args: | |
132 schema: object, Parsed JSON schema. | |
133 seen: list of string, Names of schema already seen. Used to handle | |
134 recursive definitions. | |
135 | |
136 Returns: | |
137 string, A string that contains a prototype object with | |
138 comments that conforms to the given schema. | |
139 """ | |
140 if seen is None: | |
141 seen = [] | |
142 | |
143 return _SchemaToStruct(schema, seen, dent=dent).to_str(self._prettyPrintByNa
me) | |
144 | |
145 def prettyPrintSchema(self, schema): | |
146 """Get pretty printed object prototype of schema. | |
147 | |
148 Args: | |
149 schema: object, Parsed JSON schema. | |
150 | |
151 Returns: | |
152 string, A string that contains a prototype object with | |
153 comments that conforms to the given schema. | |
154 """ | |
155 # Return with trailing comma and newline removed. | |
156 return self._prettyPrintSchema(schema, dent=1)[:-2] | |
157 | |
158 def get(self, name): | |
159 """Get deserialized JSON schema from the schema name. | |
160 | |
161 Args: | |
162 name: string, Schema name. | |
163 """ | |
164 return self.schemas[name] | |
165 | |
166 | |
167 class _SchemaToStruct(object): | |
168 """Convert schema to a prototype object.""" | |
169 | |
170 @util.positional(3) | |
171 def __init__(self, schema, seen, dent=0): | |
172 """Constructor. | |
173 | |
174 Args: | |
175 schema: object, Parsed JSON schema. | |
176 seen: list, List of names of schema already seen while parsing. Used to | |
177 handle recursive definitions. | |
178 dent: int, Initial indentation depth. | |
179 """ | |
180 # The result of this parsing kept as list of strings. | |
181 self.value = [] | |
182 | |
183 # The final value of the parsing. | |
184 self.string = None | |
185 | |
186 # The parsed JSON schema. | |
187 self.schema = schema | |
188 | |
189 # Indentation level. | |
190 self.dent = dent | |
191 | |
192 # Method that when called returns a prototype object for the schema with | |
193 # the given name. | |
194 self.from_cache = None | |
195 | |
196 # List of names of schema already seen while parsing. | |
197 self.seen = seen | |
198 | |
199 def emit(self, text): | |
200 """Add text as a line to the output. | |
201 | |
202 Args: | |
203 text: string, Text to output. | |
204 """ | |
205 self.value.extend([" " * self.dent, text, '\n']) | |
206 | |
207 def emitBegin(self, text): | |
208 """Add text to the output, but with no line terminator. | |
209 | |
210 Args: | |
211 text: string, Text to output. | |
212 """ | |
213 self.value.extend([" " * self.dent, text]) | |
214 | |
215 def emitEnd(self, text, comment): | |
216 """Add text and comment to the output with line terminator. | |
217 | |
218 Args: | |
219 text: string, Text to output. | |
220 comment: string, Python comment. | |
221 """ | |
222 if comment: | |
223 divider = '\n' + ' ' * (self.dent + 2) + '# ' | |
224 lines = comment.splitlines() | |
225 lines = [x.rstrip() for x in lines] | |
226 comment = divider.join(lines) | |
227 self.value.extend([text, ' # ', comment, '\n']) | |
228 else: | |
229 self.value.extend([text, '\n']) | |
230 | |
231 def indent(self): | |
232 """Increase indentation level.""" | |
233 self.dent += 1 | |
234 | |
235 def undent(self): | |
236 """Decrease indentation level.""" | |
237 self.dent -= 1 | |
238 | |
239 def _to_str_impl(self, schema): | |
240 """Prototype object based on the schema, in Python code with comments. | |
241 | |
242 Args: | |
243 schema: object, Parsed JSON schema file. | |
244 | |
245 Returns: | |
246 Prototype object based on the schema, in Python code with comments. | |
247 """ | |
248 stype = schema.get('type') | |
249 if stype == 'object': | |
250 self.emitEnd('{', schema.get('description', '')) | |
251 self.indent() | |
252 if 'properties' in schema: | |
253 for pname, pschema in schema.get('properties', {}).iteritems(): | |
254 self.emitBegin('"%s": ' % pname) | |
255 self._to_str_impl(pschema) | |
256 elif 'additionalProperties' in schema: | |
257 self.emitBegin('"a_key": ') | |
258 self._to_str_impl(schema['additionalProperties']) | |
259 self.undent() | |
260 self.emit('},') | |
261 elif '$ref' in schema: | |
262 schemaName = schema['$ref'] | |
263 description = schema.get('description', '') | |
264 s = self.from_cache(schemaName, seen=self.seen) | |
265 parts = s.splitlines() | |
266 self.emitEnd(parts[0], description) | |
267 for line in parts[1:]: | |
268 self.emit(line.rstrip()) | |
269 elif stype == 'boolean': | |
270 value = schema.get('default', 'True or False') | |
271 self.emitEnd('%s,' % str(value), schema.get('description', '')) | |
272 elif stype == 'string': | |
273 value = schema.get('default', 'A String') | |
274 self.emitEnd('"%s",' % str(value), schema.get('description', '')) | |
275 elif stype == 'integer': | |
276 value = schema.get('default', '42') | |
277 self.emitEnd('%s,' % str(value), schema.get('description', '')) | |
278 elif stype == 'number': | |
279 value = schema.get('default', '3.14') | |
280 self.emitEnd('%s,' % str(value), schema.get('description', '')) | |
281 elif stype == 'null': | |
282 self.emitEnd('None,', schema.get('description', '')) | |
283 elif stype == 'any': | |
284 self.emitEnd('"",', schema.get('description', '')) | |
285 elif stype == 'array': | |
286 self.emitEnd('[', schema.get('description')) | |
287 self.indent() | |
288 self.emitBegin('') | |
289 self._to_str_impl(schema['items']) | |
290 self.undent() | |
291 self.emit('],') | |
292 else: | |
293 self.emit('Unknown type! %s' % stype) | |
294 self.emitEnd('', '') | |
295 | |
296 self.string = ''.join(self.value) | |
297 return self.string | |
298 | |
299 def to_str(self, from_cache): | |
300 """Prototype object based on the schema, in Python code with comments. | |
301 | |
302 Args: | |
303 from_cache: callable(name, seen), Callable that retrieves an object | |
304 prototype for a schema with the given name. Seen is a list of schema | |
305 names already seen as we recursively descend the schema definition. | |
306 | |
307 Returns: | |
308 Prototype object based on the schema, in Python code with comments. | |
309 The lines of the code will all be properly indented. | |
310 """ | |
311 self.from_cache = from_cache | |
312 return self._to_str_impl(self.schema) | |
OLD | NEW |