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