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