googleapiclient.schema

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  # Oauth2client < 3 has the positional helper in 'util', >= 3 has it
     69  # in '_helpers'.
     70  try:
     71    from oauth2client import util
     72  except ImportError:
     73    from oauth2client import _helpers as util
     74
     75
     76 -class Schemas(object):
     77    """Schemas for an API."""
     78
     79 -  def __init__(self, discovery):
     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      # Cache of pretty printed schemas.
     89      self.pretty = {}
     90
     91    @util.positional(2)
     92 -  def _prettyPrintByName(self, name, seen=None, dent=0):
     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        # Do not fall into an infinite loop over recursive definitions.
    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
    120 -  def prettyPrintByName(self, name):
    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      # Return with trailing comma and newline removed.
    131      return self._prettyPrintByName(name, seen=[], dent=1)[:-2]
    132
    133    @util.positional(2)
    134 -  def _prettyPrintSchema(self, schema, seen=None, dent=0):
    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
    151 -  def prettyPrintSchema(self, schema):
    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      # Return with trailing comma and newline removed.
    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
    172
    173 -class _SchemaToStruct(object):
    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      # The result of this parsing kept as list of strings.
    187      self.value = []
    188
    189      # The final value of the parsing.
    190      self.string = None
    191
    192      # The parsed JSON schema.
    193      self.schema = schema
    194
    195      # Indentation level.
    196      self.dent = dent
    197
    198      # Method that when called returns a prototype object for the schema with
    199      # the given name.
    200      self.from_cache = None
    201
    202      # List of names of schema already seen while parsing.
    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
    213 -  def emitBegin(self, text):
    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
    221 -  def emitEnd(self, text, comment):
    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
    237 -  def indent(self):
    238      """Increase indentation level."""
    239      self.dent += 1
    240
    241 -  def undent(self):
    242      """Decrease indentation level."""
    243      self.dent -= 1
    244
    245 -  def _to_str_impl(self, schema):
    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