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 |