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

# Source Code for Module googleapiclient.schema

      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  from __future__ import absolute_import
     60  import six
     61
     62  # TODO(jcgregorio) support format, enum, minimum, maximum
     63
     64  __author__ = 'jcgregorio@google.com (Joe Gregorio)'
     65
     66  import copy
     67
     68  from oauth2client import util
     69
     70
     71 -class Schemas(object):
     72    """Schemas for an API."""
     73
     74 -  def __init__(self, discovery):
     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      # Cache of pretty printed schemas.
     84      self.pretty = {}
     85
     86    @util.positional(2)
     87 -  def _prettyPrintByName(self, name, seen=None, dent=0):
     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        # Do not fall into an infinite loop over recursive definitions.
    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
    115 -  def prettyPrintByName(self, name):
    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      # Return with trailing comma and newline removed.
    126      return self._prettyPrintByName(name, seen=[], dent=1)[:-2]
    127
    128    @util.positional(2)
    129 -  def _prettyPrintSchema(self, schema, seen=None, dent=0):
    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
    146 -  def prettyPrintSchema(self, schema):
    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      # Return with trailing comma and newline removed.
    157      return self._prettyPrintSchema(schema, dent=1)[:-2]
    158
    159 -  def get(self, name):
    160      """Get deserialized JSON schema from the schema name.
    161
    162      Args:
    163        name: string, Schema name.
    164      """
    165      return self.schemas[name]
    166
    167
    168 -class _SchemaToStruct(object):
    169    """Convert schema to a prototype object."""
    170
    171    @util.positional(3)
    172 -  def __init__(self, schema, seen, dent=0):
    173      """Constructor.
    174
    175      Args:
    176        schema: object, Parsed JSON schema.
    177        seen: list, List of names of schema already seen while parsing. Used to
    178          handle recursive definitions.
    179        dent: int, Initial indentation depth.
    180      """
    181      # The result of this parsing kept as list of strings.
    182      self.value = []
    183
    184      # The final value of the parsing.
    185      self.string = None
    186
    187      # The parsed JSON schema.
    188      self.schema = schema
    189
    190      # Indentation level.
    191      self.dent = dent
    192
    193      # Method that when called returns a prototype object for the schema with
    194      # the given name.
    195      self.from_cache = None
    196
    197      # List of names of schema already seen while parsing.
    198      self.seen = seen
    199
    200 -  def emit(self, text):
    201      """Add text as a line to the output.
    202
    203      Args:
    204        text: string, Text to output.
    205      """
    206      self.value.extend(["  " * self.dent, text, '\n'])
    207
    208 -  def emitBegin(self, text):
    209      """Add text to the output, but with no line terminator.
    210
    211      Args:
    212        text: string, Text to output.
    213        """
    214      self.value.extend(["  " * self.dent, text])
    215
    216 -  def emitEnd(self, text, comment):
    217      """Add text and comment to the output with line terminator.
    218
    219      Args:
    220        text: string, Text to output.
    221        comment: string, Python comment.
    222      """
    223      if comment:
    224        divider = '\n' + '  ' * (self.dent + 2) + '# '
    225        lines = comment.splitlines()
    226        lines = [x.rstrip() for x in lines]
    227        comment = divider.join(lines)
    228        self.value.extend([text, ' # ', comment, '\n'])
    229      else:
    230        self.value.extend([text, '\n'])
    231
    232 -  def indent(self):
    233      """Increase indentation level."""
    234      self.dent += 1
    235
    236 -  def undent(self):
    237      """Decrease indentation level."""
    238      self.dent -= 1
    239
    240 -  def _to_str_impl(self, schema):
    241      """Prototype object based on the schema, in Python code with comments.
    242
    243      Args:
    244        schema: object, Parsed JSON schema file.
    245
    246      Returns:
    247        Prototype object based on the schema, in Python code with comments.
    248      """
    249      stype = schema.get('type')
    250      if stype == 'object':
    251        self.emitEnd('{', schema.get('description', ''))
    252        self.indent()
    253        if 'properties' in schema:
    254          for pname, pschema in six.iteritems(schema.get('properties', {})):
    255            self.emitBegin('"%s": ' % pname)
    256            self._to_str_impl(pschema)
    257        elif 'additionalProperties' in schema:
    258          self.emitBegin('"a_key": ')
    259          self._to_str_impl(schema['additionalProperties'])
    260        self.undent()
    261        self.emit('},')
    262      elif '$ref' in schema:
    263        schemaName = schema['$ref']
    264        description = schema.get('description', '')
    265        s = self.from_cache(schemaName, seen=self.seen)
    266        parts = s.splitlines()
    267        self.emitEnd(parts[0], description)
    268        for line in parts[1:]:
    269          self.emit(line.rstrip())
    270      elif stype == 'boolean':
    271        value = schema.get('default', 'True or False')
    272        self.emitEnd('%s,' % str(value), schema.get('description', ''))
    273      elif stype == 'string':
    274        value = schema.get('default', 'A String')
    275        self.emitEnd('"%s",' % str(value), schema.get('description', ''))
    276      elif stype == 'integer':
    277        value = schema.get('default', '42')
    278        self.emitEnd('%s,' % str(value), schema.get('description', ''))
    279      elif stype == 'number':
    280        value = schema.get('default', '3.14')
    281        self.emitEnd('%s,' % str(value), schema.get('description', ''))
    282      elif stype == 'null':
    283        self.emitEnd('None,', schema.get('description', ''))
    284      elif stype == 'any':
    285        self.emitEnd('"",', schema.get('description', ''))
    286      elif stype == 'array':
    287        self.emitEnd('[', schema.get('description'))
    288        self.indent()
    289        self.emitBegin('')
    290        self._to_str_impl(schema['items'])
    291        self.undent()
    292        self.emit('],')
    293      else:
    294        self.emit('Unknown type! %s' % stype)
    295        self.emitEnd('', '')
    296
    297      self.string = ''.join(self.value)
    298      return self.string
    299
    300 -  def to_str(self, from_cache):
    301      """Prototype object based on the schema, in Python code with comments.
    302
    303      Args:
    304        from_cache: callable(name, seen), Callable that retrieves an object
    305           prototype for a schema with the given name. Seen is a list of schema
    306           names already seen as we recursively descend the schema definition.
    307
    308      Returns:
    309        Prototype object based on the schema, in Python code with comments.
    310        The lines of the code will all be properly indented.
    311      """
    312      self.from_cache = from_cache
    313      return self._to_str_impl(self.schema)
    314