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 |