discover.py revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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 3# found in the LICENSE file. 4 5import fnmatch 6import inspect 7import os 8import re 9 10from telemetry import decorators 11from telemetry.core import camel_case 12 13 14@decorators.Cache 15def DiscoverModules(start_dir, top_level_dir, pattern='*'): 16 """Discover all modules in |start_dir| which match |pattern|. 17 18 Args: 19 start_dir: The directory to recursively search. 20 top_level_dir: The top level of the package, for importing. 21 pattern: Unix shell-style pattern for filtering the filenames to import. 22 23 Returns: 24 list of modules. 25 """ 26 modules = [] 27 for dir_path, _, filenames in os.walk(start_dir): 28 for filename in filenames: 29 # Filter out unwanted filenames. 30 if filename.startswith('.') or filename.startswith('_'): 31 continue 32 if os.path.splitext(filename)[1] != '.py': 33 continue 34 if not fnmatch.fnmatch(filename, pattern): 35 continue 36 37 # Find the module. 38 module_rel_path = os.path.relpath(os.path.join(dir_path, filename), 39 top_level_dir) 40 module_name = re.sub(r'[/\\]', '.', os.path.splitext(module_rel_path)[0]) 41 42 # Import the module. 43 module = __import__(module_name, fromlist=[True]) 44 45 modules.append(module) 46 return modules 47 48 49# TODO(dtu): Normalize all discoverable classes to have corresponding module 50# and class names, then always index by class name. 51@decorators.Cache 52def DiscoverClasses(start_dir, top_level_dir, base_class, pattern='*', 53 index_by_class_name=False): 54 """Discover all classes in |start_dir| which subclass |base_class|. 55 56 Base classes that contain subclasses are ignored by default. 57 58 Args: 59 start_dir: The directory to recursively search. 60 top_level_dir: The top level of the package, for importing. 61 base_class: The base class to search for. 62 pattern: Unix shell-style pattern for filtering the filenames to import. 63 index_by_class_name: If True, use class name converted to 64 lowercase_with_underscores instead of module name in return dict keys. 65 66 Returns: 67 dict of {module_name: class} or {underscored_class_name: class} 68 """ 69 modules = DiscoverModules(start_dir, top_level_dir, pattern) 70 classes = {} 71 for module in modules: 72 for _, obj in inspect.getmembers(module): 73 # Ensure object is a class. 74 if not inspect.isclass(obj): 75 continue 76 # Include only subclasses of base_class. 77 if not issubclass(obj, base_class): 78 continue 79 # Exclude the base_class itself. 80 if obj is base_class: 81 continue 82 # Exclude protected or private classes. 83 if obj.__name__.startswith('_'): 84 continue 85 # Include only the module in which the class is defined. 86 # If a class is imported by another module, exclude those duplicates. 87 if obj.__module__ != module.__name__: 88 continue 89 90 if index_by_class_name: 91 key_name = camel_case.ToUnderscore(obj.__name__) 92 else: 93 key_name = module.__name__.split('.')[-1] 94 classes[key_name] = obj 95 96 return classes 97 98def GetAllPageSetFilenames(dir_path): 99 results = [] 100 start_dir = os.path.dirname(dir_path) 101 for dirpath, _, filenames in os.walk(start_dir): 102 for f in filenames: 103 if os.path.splitext(f)[1] != '.json': 104 continue 105 filename = os.path.join(dirpath, f) 106 results.append(filename) 107 return results 108