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