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  from googleapiclient import _helpers as 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, 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
    168
    169 -class _SchemaToStruct(object):
    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      # The result of this parsing kept as list of strings.
    183      self.value = []
    184
    185      # The final value of the parsing.
    186      self.string = None
    187
    188      # The parsed JSON schema.
    189      self.schema = schema
    190
    191      # Indentation level.
    192      self.dent = dent
    193
    194      # Method that when called returns a prototype object for the schema with
    195      # the given name.
    196      self.from_cache = None
    197
    198      # List of names of schema already seen while parsing.
    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
    209 -  def emitBegin(self, text):
    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
    217 -  def emitEnd(self, text, comment):
    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
    233 -  def indent(self):
    234      """Increase indentation level."""
    235      self.dent += 1
    236
    237 -  def undent(self):
    238      """Decrease indentation level."""
    239      self.dent -= 1
    240
    241 -  def _to_str_impl(self, schema):
    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