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