Package apiclient :: Module schema
[hide private]
[frames] | no frames]

Source Code for Module apiclient.schema

  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  from oauth2client.anyjson import simplejson 
 66   
 67   
68 -class Schemas(object):
69 """Schemas for an API.""" 70
71 - def __init__(self, discovery):
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 # Cache of pretty printed schemas. 81 self.pretty = {}
82
83 - def _prettyPrintByName(self, name, seen=None, dent=0):
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 # Do not fall into an infinite loop over recursive definitions. 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
111 - def prettyPrintByName(self, name):
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 # Return with trailing comma and newline removed. 122 return self._prettyPrintByName(name, seen=[], dent=1)[:-2]
123
124 - def _prettyPrintSchema(self, schema, seen=None, dent=0):
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
141 - def prettyPrintSchema(self, schema):
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 # Return with trailing comma and newline removed. 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
163 -class _SchemaToStruct(object):
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 # The result of this parsing kept as list of strings. 176 self.value = [] 177 178 # The final value of the parsing. 179 self.string = None 180 181 # The parsed JSON schema. 182 self.schema = schema 183 184 # Indentation level. 185 self.dent = dent 186 187 # Method that when called returns a prototype object for the schema with 188 # the given name. 189 self.from_cache = None 190 191 # List of names of schema already seen while parsing. 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
202 - def emitBegin(self, text):
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
210 - def emitEnd(self, text, comment):
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
226 - def indent(self):
227 """Increase indentation level.""" 228 self.dent += 1
229
230 - def undent(self):
231 """Decrease indentation level.""" 232 self.dent -= 1
233
234 - def _to_str_impl(self, schema):
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 if 'properties' in schema: 248 for pname, pschema in schema.get('properties', {}).iteritems(): 249 self.emitBegin('"%s": ' % pname) 250 self._to_str_impl(pschema) 251 elif 'additionalProperties' in schema: 252 self.emitBegin('"a_key": ') 253 self._to_str_impl(schema['additionalProperties']) 254 self.undent() 255 self.emit('},') 256 elif '$ref' in schema: 257 schemaName = schema['$ref'] 258 description = schema.get('description', '') 259 s = self.from_cache(schemaName, self.seen) 260 parts = s.splitlines() 261 self.emitEnd(parts[0], description) 262 for line in parts[1:]: 263 self.emit(line.rstrip()) 264 elif stype == 'boolean': 265 value = schema.get('default', 'True or False') 266 self.emitEnd('%s,' % str(value), schema.get('description', '')) 267 elif stype == 'string': 268 value = schema.get('default', 'A String') 269 self.emitEnd('"%s",' % str(value), schema.get('description', '')) 270 elif stype == 'integer': 271 value = schema.get('default', '42') 272 self.emitEnd('%s,' % str(value), schema.get('description', '')) 273 elif stype == 'number': 274 value = schema.get('default', '3.14') 275 self.emitEnd('%s,' % str(value), schema.get('description', '')) 276 elif stype == 'null': 277 self.emitEnd('None,', schema.get('description', '')) 278 elif stype == 'any': 279 self.emitEnd('"",', schema.get('description', '')) 280 elif stype == 'array': 281 self.emitEnd('[', schema.get('description')) 282 self.indent() 283 self.emitBegin('') 284 self._to_str_impl(schema['items']) 285 self.undent() 286 self.emit('],') 287 else: 288 self.emit('Unknown type! %s' % stype) 289 self.emitEnd('', '') 290 291 self.string = ''.join(self.value) 292 return self.string
293
294 - def to_str(self, from_cache):
295 """Prototype object based on the schema, in Python code with comments. 296 297 Args: 298 from_cache: callable(name, seen), Callable that retrieves an object 299 prototype for a schema with the given name. Seen is a list of schema 300 names already seen as we recursively descend the schema definition. 301 302 Returns: 303 Prototype object based on the schema, in Python code with comments. 304 The lines of the code will all be properly indented. 305 """ 306 self.from_cache = from_cache 307 return self._to_str_impl(self.schema)
308