Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(138)

Side by Side Diff: chrome/common/extensions/docs/server2/api_data_source.py

Issue 354073004: Docserver: Add template support for object level availability (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Make abstractions Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | chrome/common/extensions/docs/server2/api_data_source_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from copy import copy 5 from copy import copy
6 import logging 6 import logging
7 import os 7 import os
8 import posixpath 8 import posixpath
9 9
10 from data_source import DataSource 10 from data_source import DataSource
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
58 type Event. Returns a dictionary mapping the name of a member to that 58 type Event. Returns a dictionary mapping the name of a member to that
59 member's definition. 59 member's definition.
60 ''' 60 '''
61 assert 'types' in events, \ 61 assert 'types' in events, \
62 'The dictionary |events| must contain the key "types".' 62 'The dictionary |events| must contain the key "types".'
63 event_list = [t for t in events['types'] if t.get('name') == 'Event'] 63 event_list = [t for t in events['types'] if t.get('name') == 'Event']
64 assert len(event_list) == 1, 'Exactly one type must be called "Event".' 64 assert len(event_list) == 1, 'Exactly one type must be called "Event".'
65 return _GetByNameDict(event_list[0]) 65 return _GetByNameDict(event_list[0])
66 66
67 67
68 class _APINodeCursor(object):
69 '''An abstract representation of a node in an APISchemaGraph.
70 The current position in the graph is represented by a path into the
71 underlying dictionary. So if the APISchemaGraph is:
72
73 {
74 'tabs': {
75 'types': {
76 'Tab': {
77 'properties': {
78 'url': {
79 ...
80 }
81 }
82 }
83 }
84 }
85 }
86
87 then the 'url' property would be represented by:
88
89 ['tabs', 'types', 'Tab', 'properties', 'url']
90 '''
91 def __init__(self, availability_finder, namespace_name):
92 # The cursor begins life at the root.
93 self._lookup_path = [namespace_name]
94 self._node_availabilities = availability_finder.GetAPINodeAvailability(
95 namespace_name)
96 self._namespace_name = namespace_name
97
98 def _GetParentPath(self):
99 '''Returns the path pointing to this node's parent.
100 '''
101 assert len(self._lookup_path) > 2, \
102 'Tried to look up parent for the top-level node.'
103
104 # lookup_path[-1] is the name of the current node.
105 # lookup_path[-2] is this node's category (e.g. types, events, etc.).
106 # Thus, the parent node is described by lookup_path[:-2].
107 return self._lookup_path[:-2]
108
109 def _LookupNodeAvailability(self):
110 '''Returns the ChannelInfo object for this node.
111 '''
112 return self._node_availabilities.Lookup(*self._lookup_path).annotation
113
114 def _LookupParentNodeAvailability(self):
115 '''Returns the ChannelInfo object for this node's parent.
116 '''
117 return self._node_availabilities.Lookup(*self._GetParentPath()).annotation
118
119 def _CheckNamespacePrefix(self):
120 '''API schemas may prepend the namespace name to top-level types
121 (e.g. declarativeWebRequest > types > declarativeWebRequest.IgnoreRules),
122 but just the base name (here, 'IgnoreRules') will be in the |lookup_path|.
123 Try creating an alternate |lookup_path| by adding the namespace name.
124 '''
125 # lookup_path[0] is always the API namespace, and
126 # lookup_path[1] is always the node category (e.g. types, functions, etc.).
127 # Thus, lookup_path[2] is always the top-level node name.
128 base_name = self._lookup_path[2]
129 self._lookup_path[2] = '%s.%s' % (self._namespace_name, base_name)
130 node_availability = self._LookupNodeAvailability()
131 if node_availability is not None:
132 return node_availability
133 # We want to maintain a working lookup_path, so only restore it
134 # if modifying the lookup_path did not work.
135 self._lookup_path[2] = base_name
136 return None
137
138 def _CheckEventCallback(self):
139 '''Within API schemas, an event has a list of 'properties' that the event's
140 callback expects. The callback itself is not explicitly represented in the
141 schema. However, when creating an event node in _JSCModel, a callback node
142 is generated and acts as the parent for the event's properties.
143 Modify |lookup_path| to check the original schema format.
144 '''
145 if 'events' in self._lookup_path and 'callback' in self._lookup_path:
146 self._lookup_path.remove('callback')
ahernandez 2014/06/27 03:00:01 I'm not sure if this should be restored or not.
147 return self._LookupNodeAvailability()
148 return None
149
150 def GetAvailability(self):
151 '''Returns availability information for this node.
152 '''
153 if self._lookup_path[0] != self._namespace_name:
154 # |lookup_path| won't be lookup up if it doesn't start with the API name.
155 return None
156
157 for lookup in (self._LookupNodeAvailability,
158 self._CheckEventCallback,
159 self._CheckNamespacePrefix):
160 node_availability = lookup()
161 if node_availability is not None:
162 break
163
164 if node_availability is None:
165 logging.warning('No availability found for: %s' % ' > '.join(
166 self._lookup_path))
167 return None
168
169 # Only render this node's availability if it differs from the parent
170 # node's availability.
171 if node_availability == self._LookupParentNodeAvailability():
172 return None
173 return node_availability
174
175 def DescendTo(self, *path):
176 '''Moves down the APISchemaGraph, following |path|.
177 '''
178 self._lookup_path.extend(path)
179
180 def Ascend(self):
181 '''Moves to the parent node.
182 '''
183 self._lookup_path = self._GetParentPath()
184
185
68 class _JSCModel(object): 186 class _JSCModel(object):
69 '''Uses a Model from the JSON Schema Compiler and generates a dict that 187 '''Uses a Model from the JSON Schema Compiler and generates a dict that
70 a Handlebar template can use for a data source. 188 a Handlebar template can use for a data source.
71 ''' 189 '''
72 190
73 def __init__(self, 191 def __init__(self,
74 namespace, 192 namespace,
75 availability, 193 availability_finder,
76 json_cache, 194 json_cache,
77 template_cache, 195 template_cache,
78 features_bundle, 196 features_bundle,
79 event_byname_future): 197 event_byname_future):
80 self._availability = availability 198 self._availability = availability_finder.GetAPIAvailability(namespace.name)
199 self._current_node = _APINodeCursor(availability_finder, namespace.name)
81 self._api_availabilities = json_cache.GetFromFile( 200 self._api_availabilities = json_cache.GetFromFile(
82 posixpath.join(JSON_TEMPLATES, 'api_availabilities.json')) 201 posixpath.join(JSON_TEMPLATES, 'api_availabilities.json'))
83 self._intro_tables = json_cache.GetFromFile( 202 self._intro_tables = json_cache.GetFromFile(
84 posixpath.join(JSON_TEMPLATES, 'intro_tables.json')) 203 posixpath.join(JSON_TEMPLATES, 'intro_tables.json'))
85 self._api_features = features_bundle.GetAPIFeatures() 204 self._api_features = features_bundle.GetAPIFeatures()
86 self._template_cache = template_cache 205 self._template_cache = template_cache
87 self._event_byname_future = event_byname_future 206 self._event_byname_future = event_byname_future
88 self._namespace = namespace 207 self._namespace = namespace
89 208
90 def _GetLink(self, link): 209 def _GetLink(self, link):
(...skipping 28 matching lines...) Expand all
119 def _GetChannelWarning(self): 238 def _GetChannelWarning(self):
120 if not self._IsExperimental(): 239 if not self._IsExperimental():
121 return { 240 return {
122 self._availability.channel_info.channel: True 241 self._availability.channel_info.channel: True
123 } 242 }
124 return None 243 return None
125 244
126 def _IsExperimental(self): 245 def _IsExperimental(self):
127 return self._namespace.name.startswith('experimental') 246 return self._namespace.name.startswith('experimental')
128 247
129 def _GenerateTypes(self, types): 248 def _GenerateTypes(self, types, no_gen=False):
130 return [self._GenerateType(t) for t in types] 249 return [self._GenerateType(t, no_gen=no_gen) for t in types]
131 250
132 def _GenerateType(self, type_): 251 def _GenerateType(self, type_, no_gen=False):
252 '''If |no_gen| is True, don't generate availability info.
253 '''
254 self._current_node.DescendTo('types', type_.simple_name)
133 type_dict = { 255 type_dict = {
134 'name': type_.simple_name, 256 'name': type_.simple_name,
135 'description': type_.description, 257 'description': type_.description,
136 'properties': self._GenerateProperties(type_.properties), 258 'properties': self._GenerateProperties(type_.properties),
137 'functions': self._GenerateFunctions(type_.functions), 259 'functions': self._GenerateFunctions(type_.functions),
138 'events': self._GenerateEvents(type_.events), 260 'events': self._GenerateEvents(type_.events),
139 'id': _CreateId(type_, 'type') 261 'id': _CreateId(type_, 'type'),
262 'availability': None if no_gen else self._GetAvailabilityTemplate()
140 } 263 }
264 self._current_node.Ascend()
ahernandez 2014/06/27 03:00:01 I'm not sure if the whole purpose of the APINodeCu
141 self._RenderTypeInformation(type_, type_dict) 265 self._RenderTypeInformation(type_, type_dict)
142 return type_dict 266 return type_dict
143 267
144 def _GenerateFunctions(self, functions): 268 def _GenerateFunctions(self, functions):
145 return [self._GenerateFunction(f) for f in functions.values()] 269 return [self._GenerateFunction(f) for f in functions.values()]
146 270
147 def _GenerateFunction(self, function): 271 def _GenerateFunction(self, function):
148 function_dict = { 272 function_dict = {
149 'name': function.simple_name, 273 'name': function.simple_name,
150 'description': function.description, 274 'description': function.description,
151 'callback': self._GenerateCallback(function.callback), 275 'callback': self._GenerateCallback(function.callback),
152 'parameters': [], 276 'parameters': [],
153 'returns': None, 277 'returns': None,
154 'id': _CreateId(function, 'method') 278 'id': _CreateId(function, 'method')
155 } 279 }
156 self._AddCommonProperties(function_dict, function) 280 self._AddCommonProperties(function_dict, function)
157 if function.returns: 281 if function.returns:
158 function_dict['returns'] = self._GenerateType(function.returns) 282 function_dict['returns'] = self._GenerateType(function.returns,
283 no_gen=True)
ahernandez 2014/06/27 03:00:01 I think I now understand what the deal with the bl
159 for param in function.params: 284 for param in function.params:
160 function_dict['parameters'].append(self._GenerateProperty(param)) 285 function_dict['parameters'].append(self._GenerateProperty(param))
161 if function.callback is not None: 286 if function.callback is not None:
162 # Show the callback as an extra parameter. 287 # Show the callback as an extra parameter.
163 function_dict['parameters'].append( 288 function_dict['parameters'].append(
164 self._GenerateCallbackProperty(function.callback)) 289 self._GenerateCallbackProperty(function.callback))
165 if len(function_dict['parameters']) > 0: 290 if len(function_dict['parameters']) > 0:
166 function_dict['parameters'][-1]['last'] = True 291 function_dict['parameters'][-1]['last'] = True
167 return function_dict 292 return function_dict
168 293
(...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after
259 'returns': None, 384 'returns': None,
260 'id': _CreateId(property_, 'property') 385 'id': _CreateId(property_, 'property')
261 } 386 }
262 self._AddCommonProperties(property_dict, property_) 387 self._AddCommonProperties(property_dict, property_)
263 388
264 if type_.property_type == model.PropertyType.FUNCTION: 389 if type_.property_type == model.PropertyType.FUNCTION:
265 function = type_.function 390 function = type_.function
266 for param in function.params: 391 for param in function.params:
267 property_dict['parameters'].append(self._GenerateProperty(param)) 392 property_dict['parameters'].append(self._GenerateProperty(param))
268 if function.returns: 393 if function.returns:
269 property_dict['returns'] = self._GenerateType(function.returns) 394 property_dict['returns'] = self._GenerateType(function.returns,
395 no_gen=True)
270 396
271 value = property_.value 397 value = property_.value
272 if value is not None: 398 if value is not None:
273 if isinstance(value, int): 399 if isinstance(value, int):
274 property_dict['value'] = _FormatValue(value) 400 property_dict['value'] = _FormatValue(value)
275 else: 401 else:
276 property_dict['value'] = value 402 property_dict['value'] = value
277 else: 403 else:
278 self._RenderTypeInformation(type_, property_dict) 404 self._RenderTypeInformation(type_, property_dict)
279 405
280 return property_dict 406 return property_dict
281 407
282 def _GenerateCallbackProperty(self, callback): 408 def _GenerateCallbackProperty(self, callback):
283 property_dict = { 409 property_dict = {
284 'name': callback.simple_name, 410 'name': callback.simple_name,
285 'description': callback.description, 411 'description': callback.description,
286 'optional': callback.optional, 412 'optional': callback.optional,
287 'is_callback': True, 413 'is_callback': True,
288 'id': _CreateId(callback, 'property'), 414 'id': _CreateId(callback, 'property'),
289 'simple_type': 'function', 415 'simple_type': 'function',
290 } 416 }
291 if (callback.parent is not None and 417 if (callback.parent is not None and
292 not isinstance(callback.parent, model.Namespace)): 418 not isinstance(callback.parent, model.Namespace)):
293 property_dict['parentName'] = callback.parent.simple_name 419 property_dict['parentName'] = callback.parent.simple_name
294 return property_dict 420 return property_dict
295 421
296 def _RenderTypeInformation(self, type_, dst_dict): 422 def _RenderTypeInformation(self, type_, dst_dict):
297 dst_dict['is_object'] = type_.property_type == model.PropertyType.OBJECT 423 dst_dict['is_object'] = type_.property_type == model.PropertyType.OBJECT
298 if type_.property_type == model.PropertyType.CHOICES: 424 if type_.property_type == model.PropertyType.CHOICES:
299 dst_dict['choices'] = self._GenerateTypes(type_.choices) 425 dst_dict['choices'] = self._GenerateTypes(type_.choices, no_gen=True)
300 # We keep track of which == last for knowing when to add "or" between 426 # We keep track of which == last for knowing when to add "or" between
301 # choices in templates. 427 # choices in templates.
302 if len(dst_dict['choices']) > 0: 428 if len(dst_dict['choices']) > 0:
303 dst_dict['choices'][-1]['last'] = True 429 dst_dict['choices'][-1]['last'] = True
304 elif type_.property_type == model.PropertyType.REF: 430 elif type_.property_type == model.PropertyType.REF:
305 dst_dict['link'] = self._GetLink(type_.ref_type) 431 dst_dict['link'] = self._GetLink(type_.ref_type)
306 elif type_.property_type == model.PropertyType.ARRAY: 432 elif type_.property_type == model.PropertyType.ARRAY:
307 dst_dict['array'] = self._GenerateType(type_.item_type) 433 dst_dict['array'] = self._GenerateType(type_.item_type, no_gen=True)
308 elif type_.property_type == model.PropertyType.ENUM: 434 elif type_.property_type == model.PropertyType.ENUM:
309 dst_dict['enum_values'] = [ 435 dst_dict['enum_values'] = [
310 {'name': value.name, 'description': value.description} 436 {'name': value.name, 'description': value.description}
311 for value in type_.enum_values] 437 for value in type_.enum_values]
312 if len(dst_dict['enum_values']) > 0: 438 if len(dst_dict['enum_values']) > 0:
313 dst_dict['enum_values'][-1]['last'] = True 439 dst_dict['enum_values'][-1]['last'] = True
314 elif type_.instance_of is not None: 440 elif type_.instance_of is not None:
315 dst_dict['simple_type'] = type_.instance_of 441 dst_dict['simple_type'] = type_.instance_of
316 else: 442 else:
317 dst_dict['simple_type'] = type_.property_type.name 443 dst_dict['simple_type'] = type_.property_type.name
(...skipping 11 matching lines...) Expand all
329 # if they share the same 'title' attribute. 455 # if they share the same 'title' attribute.
330 row_titles = [row['title'] for row in intro_rows] 456 row_titles = [row['title'] for row in intro_rows]
331 for misc_row in self._GetMiscIntroRows(): 457 for misc_row in self._GetMiscIntroRows():
332 if misc_row['title'] in row_titles: 458 if misc_row['title'] in row_titles:
333 intro_rows[row_titles.index(misc_row['title'])] = misc_row 459 intro_rows[row_titles.index(misc_row['title'])] = misc_row
334 else: 460 else:
335 intro_rows.append(misc_row) 461 intro_rows.append(misc_row)
336 462
337 return intro_rows 463 return intro_rows
338 464
465 def _GetAvailabilityTemplate(self, status=None, version=None, scheduled=None):
466 '''Returns an object that the templates use to display availability
467 information.
468 '''
469 if status is None:
470 availability_info = self._current_node.GetAvailability()
471 if availability_info is None:
472 return None
473 status = availability_info.channel
474 version = availability_info.version
475 return {
476 'partial': self._template_cache.GetFromFile(
477 '%sintro_tables/%s_message.html' % (PRIVATE_TEMPLATES, status)).Get(),
478 'version': version,
479 'scheduled': scheduled
480 }
481
339 def _GetIntroDescriptionRow(self): 482 def _GetIntroDescriptionRow(self):
340 ''' Generates the 'Description' row data for an API intro table. 483 ''' Generates the 'Description' row data for an API intro table.
341 ''' 484 '''
342 return { 485 return {
343 'title': 'Description', 486 'title': 'Description',
344 'content': [ 487 'content': [
345 { 'text': self._namespace.description } 488 { 'text': self._namespace.description }
346 ] 489 ]
347 } 490 }
348 491
349 def _GetIntroAvailabilityRow(self): 492 def _GetIntroAvailabilityRow(self):
350 ''' Generates the 'Availability' row data for an API intro table. 493 ''' Generates the 'Availability' row data for an API intro table.
351 ''' 494 '''
352 if self._IsExperimental(): 495 if self._IsExperimental():
353 status = 'experimental' 496 status = 'experimental'
354 version = None 497 version = None
355 scheduled = None 498 scheduled = None
356 else: 499 else:
357 status = self._availability.channel_info.channel 500 status = self._availability.channel_info.channel
358 version = self._availability.channel_info.version 501 version = self._availability.channel_info.version
359 scheduled = self._availability.scheduled 502 scheduled = self._availability.scheduled
360 return { 503 return {
361 'title': 'Availability', 504 'title': 'Availability',
362 'content': [{ 505 'content': [
363 'partial': self._template_cache.GetFromFile( 506 self._GetAvailabilityTemplate(status=status,
364 posixpath.join(PRIVATE_TEMPLATES, 507 version=version,
365 'intro_tables', 508 scheduled=scheduled)
366 '%s_message.html' % status)).Get(), 509 ]
367 'version': version,
368 'scheduled': scheduled
369 }]
370 } 510 }
371 511
372 def _GetIntroDependencyRows(self): 512 def _GetIntroDependencyRows(self):
373 # Devtools aren't in _api_features. If we're dealing with devtools, bail. 513 # Devtools aren't in _api_features. If we're dealing with devtools, bail.
374 if 'devtools' in self._namespace.name: 514 if 'devtools' in self._namespace.name:
375 return [] 515 return []
376 516
377 api_feature = self._api_features.Get().get(self._namespace.name) 517 api_feature = self._api_features.Get().get(self._namespace.name)
378 if not api_feature: 518 if not api_feature:
379 logging.error('"%s" not found in _api_features.json' % 519 logging.error('"%s" not found in _api_features.json' %
(...skipping 119 matching lines...) Expand 10 before | Expand all | Expand 10 after
499 def _GetSchemaModel(self, platform, api_name): 639 def _GetSchemaModel(self, platform, api_name):
500 object_store_key = '/'.join((platform, api_name)) 640 object_store_key = '/'.join((platform, api_name))
501 jsc_model_future = self._model_cache.Get(object_store_key) 641 jsc_model_future = self._model_cache.Get(object_store_key)
502 model_future = self._platform_bundle.GetAPIModels(platform).GetModel( 642 model_future = self._platform_bundle.GetAPIModels(platform).GetModel(
503 api_name) 643 api_name)
504 def resolve(): 644 def resolve():
505 jsc_model = jsc_model_future.Get() 645 jsc_model = jsc_model_future.Get()
506 if jsc_model is None: 646 if jsc_model is None:
507 jsc_model = _JSCModel( 647 jsc_model = _JSCModel(
508 model_future.Get(), 648 model_future.Get(),
509 self._platform_bundle.GetAvailabilityFinder( 649 self._platform_bundle.GetAvailabilityFinder(platform),
510 platform).GetAPIAvailability(api_name),
511 self._json_cache, 650 self._json_cache,
512 self._template_cache, 651 self._template_cache,
513 self._platform_bundle.GetFeaturesBundle(platform), 652 self._platform_bundle.GetFeaturesBundle(platform),
514 self._LoadEventByName(platform)).ToDict() 653 self._LoadEventByName(platform)).ToDict()
515 self._model_cache.Set(object_store_key, jsc_model) 654 self._model_cache.Set(object_store_key, jsc_model)
516 return jsc_model 655 return jsc_model
517 return Future(callback=resolve) 656 return Future(callback=resolve)
518 657
519 def _GetImpl(self, platform, api_name): 658 def _GetImpl(self, platform, api_name):
520 handlebar_dict_future = self._GetSchemaModel(platform, api_name) 659 handlebar_dict_future = self._GetSchemaModel(platform, api_name)
(...skipping 15 matching lines...) Expand all
536 getter = lambda: 0 675 getter = lambda: 0
537 getter.get = lambda api_name: self._GetImpl(platform, api_name).Get() 676 getter.get = lambda api_name: self._GetImpl(platform, api_name).Get()
538 return getter 677 return getter
539 678
540 def Cron(self): 679 def Cron(self):
541 futures = [] 680 futures = []
542 for platform in GetPlatforms(): 681 for platform in GetPlatforms():
543 futures += [self._GetImpl(platform, name) 682 futures += [self._GetImpl(platform, name)
544 for name in self._platform_bundle.GetAPIModels(platform).GetNames()] 683 for name in self._platform_bundle.GetAPIModels(platform).GetNames()]
545 return Collect(futures, except_pass=FileNotFoundError) 684 return Collect(futures, except_pass=FileNotFoundError)
OLDNEW
« no previous file with comments | « no previous file | chrome/common/extensions/docs/server2/api_data_source_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698