xref: /aosp_15_r20/external/autotest/utils/frozen_chromite/third_party/googleapiclient/schema.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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
17Schemas holds an APIs discovery schemas. It can return those schema as
18deserialized JSON objects, or pretty print them as prototype objects that
19conform to the schema.
20
21For 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
57The constructor takes a discovery document in which to look up named schema.
58"""
59from __future__ import absolute_import
60import six
61
62# TODO(jcgregorio) support format, enum, minimum, maximum
63
64__author__ = '[email protected] (Joe Gregorio)'
65
66import copy
67
68# Oauth2client < 3 has the positional helper in 'util', >= 3 has it
69# in '_helpers'.
70try:
71  from oauth2client import util
72except ImportError:
73  from oauth2client import _helpers as util
74
75
76class 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
173class _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