OLD | NEW |
| (Empty) |
1 # Copyright 2014 Google Inc. All Rights Reserved. | |
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 | |
68 | |
69 class Schemas(object): | |
70 """Schemas for an API.""" | |
71 | |
72 def __init__(self, discovery): | |
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 # Cache of pretty printed schemas. | |
82 self.pretty = {} | |
83 | |
84 @util.positional(2) | |
85 def _prettyPrintByName(self, name, seen=None, dent=0): | |
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 # Do not fall into an infinite loop over recursive definitions. | |
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 | |
113 def prettyPrintByName(self, name): | |
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 # Return with trailing comma and newline removed. | |
124 return self._prettyPrintByName(name, seen=[], dent=1)[:-2] | |
125 | |
126 @util.positional(2) | |
127 def _prettyPrintSchema(self, schema, seen=None, dent=0): | |
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._prettyPrintByNa
me) | |
143 | |
144 def prettyPrintSchema(self, schema): | |
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 # Return with trailing comma and newline removed. | |
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 | |
165 | |
166 class _SchemaToStruct(object): | |
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 # The result of this parsing kept as list of strings. | |
180 self.value = [] | |
181 | |
182 # The final value of the parsing. | |
183 self.string = None | |
184 | |
185 # The parsed JSON schema. | |
186 self.schema = schema | |
187 | |
188 # Indentation level. | |
189 self.dent = dent | |
190 | |
191 # Method that when called returns a prototype object for the schema with | |
192 # the given name. | |
193 self.from_cache = None | |
194 | |
195 # List of names of schema already seen while parsing. | |
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 | |
206 def emitBegin(self, text): | |
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 | |
214 def emitEnd(self, text, comment): | |
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 | |
230 def indent(self): | |
231 """Increase indentation level.""" | |
232 self.dent += 1 | |
233 | |
234 def undent(self): | |
235 """Decrease indentation level.""" | |
236 self.dent -= 1 | |
237 | |
238 def _to_str_impl(self, schema): | |
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) | |
OLD | NEW |