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
     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._prettyPrintByName)
    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)
    312