| OLD | NEW |
| 1 # Copyright 2012 The Chromium Authors. All rights reserved. | 1 # Copyright 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 import fnmatch | 5 import fnmatch |
| 6 import inspect | 6 import inspect |
| 7 import os | 7 import os |
| 8 import re | 8 import re |
| 9 | 9 |
| 10 from telemetry import decorators | |
| 11 from telemetry.internal.util import camel_case | |
| 12 from telemetry.internal.util import classes as classes_module | 10 from telemetry.internal.util import classes as classes_module |
| 13 | 11 |
| 14 | 12 |
| 15 @decorators.Cache | |
| 16 def DiscoverModules(start_dir, top_level_dir, pattern='*'): | 13 def DiscoverModules(start_dir, top_level_dir, pattern='*'): |
| 17 """Discover all modules in |start_dir| which match |pattern|. | 14 """Discover all modules in |start_dir| which match |pattern|. |
| 18 | 15 |
| 19 Args: | 16 Args: |
| 20 start_dir: The directory to recursively search. | 17 start_dir: The directory to recursively search. |
| 21 top_level_dir: The top level of the package, for importing. | 18 top_level_dir: The top level of the package, for importing. |
| 22 pattern: Unix shell-style pattern for filtering the filenames to import. | 19 pattern: Unix shell-style pattern for filtering the filenames to import. |
| 23 | 20 |
| 24 Returns: | 21 Returns: |
| 25 list of modules. | 22 list of modules. |
| (...skipping 16 matching lines...) Expand all Loading... |
| 42 # Find the module. | 39 # Find the module. |
| 43 module_rel_path = os.path.relpath(os.path.join(dir_path, filename), | 40 module_rel_path = os.path.relpath(os.path.join(dir_path, filename), |
| 44 top_level_dir) | 41 top_level_dir) |
| 45 module_name = re.sub(r'[/\\]', '.', os.path.splitext(module_rel_path)[0]) | 42 module_name = re.sub(r'[/\\]', '.', os.path.splitext(module_rel_path)[0]) |
| 46 | 43 |
| 47 # Import the module. | 44 # Import the module. |
| 48 module = __import__(module_name, fromlist=[True]) | 45 module = __import__(module_name, fromlist=[True]) |
| 49 modules.append(module) | 46 modules.append(module) |
| 50 return modules | 47 return modules |
| 51 | 48 |
| 52 | |
| 53 # TODO(dtu): Normalize all discoverable classes to have corresponding module | |
| 54 # and class names, then always index by class name. | |
| 55 @decorators.Cache | |
| 56 def DiscoverClasses(start_dir, top_level_dir, base_class, pattern='*', | 49 def DiscoverClasses(start_dir, top_level_dir, base_class, pattern='*', |
| 57 index_by_class_name=True, directly_constructable=False): | 50 one_class_per_module=False, directly_constructable=False): |
| 58 """Discover all classes in |start_dir| which subclass |base_class|. | 51 """Discover all classes in |start_dir| which subclass |base_class|. |
| 59 | 52 |
| 60 Base classes that contain subclasses are ignored by default. | 53 Base classes that contain subclasses are ignored by default. |
| 61 | 54 |
| 62 Args: | 55 Args: |
| 63 start_dir: The directory to recursively search. | 56 start_dir: The directory to recursively search. |
| 64 top_level_dir: The top level of the package, for importing. | 57 top_level_dir: The top level of the package, for importing. |
| 65 base_class: The base class to search for. | 58 base_class: The base class to search for. |
| 66 pattern: Unix shell-style pattern for filtering the filenames to import. | 59 pattern: Unix shell-style pattern for filtering the filenames to import. |
| 67 index_by_class_name: If True, use class name converted to | 60 one_class_per_module: If True, will only include the first class found in |
| 68 lowercase_with_underscores instead of module name in return dict keys. | 61 each module. |
| 69 directly_constructable: If True, will only return classes that can be | 62 directly_constructable: If True, will only return classes that can be |
| 70 constructed without arguments | 63 constructed without arguments |
| 71 | 64 |
| 72 Returns: | 65 Returns: A list of classes. |
| 73 dict of {module_name: class} or {underscored_class_name: class} | |
| 74 """ | 66 """ |
| 75 modules = DiscoverModules(start_dir, top_level_dir, pattern) | 67 modules = DiscoverModules(start_dir, top_level_dir, pattern) |
| 76 classes = {} | 68 classes = [] |
| 77 for module in modules: | 69 for module in modules: |
| 78 new_classes = DiscoverClassesInModule( | 70 classes.extend(DiscoverClassesInModule( |
| 79 module, base_class, index_by_class_name, directly_constructable) | 71 module, base_class, one_class_per_module, directly_constructable)) |
| 80 classes = dict(classes.items() + new_classes.items()) | |
| 81 return classes | 72 return classes |
| 82 | 73 |
| 83 @decorators.Cache | 74 def DiscoverClassesInModule(module, base_class, one_class_per_module=False, |
| 84 def DiscoverClassesInModule(module, base_class, index_by_class_name=False, | |
| 85 directly_constructable=False): | 75 directly_constructable=False): |
| 86 """Discover all classes in |module| which subclass |base_class|. | 76 """Discover all classes in |module| which subclass |base_class|. |
| 87 | 77 |
| 88 Base classes that contain subclasses are ignored by default. | 78 Base classes that contain subclasses are ignored by default. |
| 89 | 79 |
| 90 Args: | 80 Args: |
| 91 module: The module to search. | 81 module: The module to search. |
| 92 base_class: The base class to search for. | 82 base_class: The base class to search for. |
| 93 index_by_class_name: If True, use class name converted to | 83 one_class_per_module: If True, will only include the first class found in |
| 94 lowercase_with_underscores instead of module name in return dict keys. | 84 each module. |
| 95 | 85 |
| 96 Returns: | 86 Returns: A list of classes. |
| 97 dict of {module_name: class} or {underscored_class_name: class} | |
| 98 """ | 87 """ |
| 99 classes = {} | 88 classes = [] |
| 100 for _, obj in inspect.getmembers(module): | 89 for _, obj in inspect.getmembers(module): |
| 101 # Ensure object is a class. | 90 # Ensure object is a class. |
| 102 if not inspect.isclass(obj): | 91 if not inspect.isclass(obj): |
| 103 continue | 92 continue |
| 104 # Include only subclasses of base_class. | 93 # Include only subclasses of base_class. |
| 105 if not issubclass(obj, base_class): | 94 if not issubclass(obj, base_class): |
| 106 continue | 95 continue |
| 107 # Exclude the base_class itself. | 96 # Exclude the base_class itself. |
| 108 if obj is base_class: | 97 if obj is base_class: |
| 109 continue | 98 continue |
| 110 # Exclude protected or private classes. | 99 # Exclude protected or private classes. |
| 111 if obj.__name__.startswith('_'): | 100 if obj.__name__.startswith('_'): |
| 112 continue | 101 continue |
| 113 # Include only the module in which the class is defined. | 102 # Include only the module in which the class is defined. |
| 114 # If a class is imported by another module, exclude those duplicates. | 103 # If a class is imported by another module, exclude those duplicates. |
| 115 if obj.__module__ != module.__name__: | 104 if obj.__module__ != module.__name__: |
| 116 continue | 105 continue |
| 117 | 106 |
| 118 if index_by_class_name: | |
| 119 key_name = camel_case.ToUnderscore(obj.__name__) | |
| 120 else: | |
| 121 key_name = module.__name__.split('.')[-1] | |
| 122 if (not directly_constructable or | 107 if (not directly_constructable or |
| 123 classes_module.IsDirectlyConstructable(obj)): | 108 classes_module.IsDirectlyConstructable(obj)): |
| 124 classes[key_name] = obj | 109 classes.append(obj) |
| 125 | 110 if one_class_per_module: |
| 111 return classes |
| 126 return classes | 112 return classes |
| 127 | 113 |
| 128 | 114 |
| 129 _counter = [0] | 115 _counter = [0] |
| 130 def _GetUniqueModuleName(): | 116 def _GetUniqueModuleName(): |
| 131 _counter[0] += 1 | 117 _counter[0] += 1 |
| 132 return "module_" + str(_counter[0]) | 118 return "module_" + str(_counter[0]) |
| OLD | NEW |