|
9 | 9 | then add ``ENABLED = False`` class attribute inside. |
10 | 10 | """ |
11 | 11 | import copy |
| 12 | +import inspect |
12 | 13 | import pathlib |
13 | 14 | import textwrap |
14 | 15 | from itertools import chain |
15 | | -from typing import Dict, List, Optional |
| 16 | +from typing import Dict, Iterable, List, Optional |
16 | 17 |
|
17 | 18 | try: |
18 | 19 | import rich_click as click |
@@ -132,6 +133,13 @@ def add_transformer(self, tr): |
132 | 133 | else: |
133 | 134 | self.transformers[tr.name] = tr |
134 | 135 |
|
| 136 | + def get_args(self, *names) -> Dict: |
| 137 | + for name in names: |
| 138 | + name = str(name) |
| 139 | + if name in self.transformers: |
| 140 | + return self.transformers[name].args |
| 141 | + return dict() |
| 142 | + |
135 | 143 | def transformer_should_be_included(self, name: str) -> bool: |
136 | 144 | """ |
137 | 145 | Check whether --transform option was used. If it was, check if transformer name was used with --transform. |
@@ -170,6 +178,9 @@ def validate_config_names(self): |
170 | 178 | Assert that all --configure NAME are either defaults or from --transform/--load-transformer. |
171 | 179 | Otherwise raise an error with similar names. |
172 | 180 | """ |
| 181 | + # TODO: Currently not used. It enforces that every --config NAME is valid one which may not be desired |
| 182 | + # if the NAME is external transformer which may not be imported. |
| 183 | + # Maybe we can add special flag like --validate-config that would run this method if needed. |
173 | 184 | for transf_name, transformer in self.transformers.items(): |
174 | 185 | if not transformer.is_config_only: |
175 | 186 | continue |
@@ -213,11 +224,12 @@ class TransformerContainer: |
213 | 224 | Stub for transformer container class that holds the transformer instance and its metadata. |
214 | 225 | """ |
215 | 226 |
|
216 | | - def __init__(self, instance, argument_names, spec): |
| 227 | + def __init__(self, instance, argument_names, spec, args): |
217 | 228 | self.instance = instance |
218 | 229 | self.name = instance.__class__.__name__ |
219 | 230 | self.enabled_by_default = getattr(instance, "ENABLED", True) |
220 | 231 | self.parameters = self.get_parameters(argument_names, spec) |
| 232 | + self.args = args |
221 | 233 |
|
222 | 234 | def get_parameters(self, argument_names, spec): |
223 | 235 | params = [] |
@@ -264,23 +276,66 @@ def get_absolute_path_to_transformer(name, short_name): |
264 | 276 | return name |
265 | 277 |
|
266 | 278 |
|
267 | | -def import_transformer(name, args, skip): |
268 | | - short_name = get_transformer_short_name(name) |
269 | | - name = get_absolute_path_to_transformer(name, short_name) |
| 279 | +def load_transformers_from_module(module): |
| 280 | + classes = inspect.getmembers(module, inspect.isclass) |
| 281 | + transformers = dict() |
| 282 | + for name, transformer_class in classes: |
| 283 | + if issubclass(transformer_class, (Transformer, ModelTransformer)) and transformer_class not in ( |
| 284 | + Transformer, |
| 285 | + ModelTransformer, |
| 286 | + ): |
| 287 | + transformers[name] = transformer_class |
| 288 | + return transformers |
| 289 | + |
| 290 | + |
| 291 | +def order_transformers(transformers, module): |
| 292 | + """If the module contains TRANSFORMERS list, order transformers using this list.""" |
| 293 | + transform_list = getattr(module, "TRANSFORMERS", []) |
| 294 | + if not (transform_list and isinstance(transform_list, list)): |
| 295 | + return transformers |
| 296 | + ordered_transformers = dict() |
| 297 | + for name in transform_list: |
| 298 | + if name not in transformers: |
| 299 | + raise ImportTransformerError( |
| 300 | + f"Importing transformer '{name}' declared in TRANSFORMERS list failed. " |
| 301 | + "Verify if correct name was provided." |
| 302 | + ) from None |
| 303 | + ordered_transformers[name] = transformers[name] |
| 304 | + return ordered_transformers |
| 305 | + |
| 306 | + |
| 307 | +def import_transformer(name, config: TransformConfigMap, skip) -> Iterable[TransformerContainer]: |
| 308 | + import_path = resolve_core_import_path(name) |
| 309 | + short_name = get_transformer_short_name(import_path) |
| 310 | + name = get_absolute_path_to_transformer(import_path, short_name) |
270 | 311 | try: |
271 | | - imported_class = IMPORTER.import_class_or_module(name) |
272 | | - spec = IMPORTER._get_arg_spec(imported_class) |
273 | | - handles_skip = getattr(imported_class, "HANDLES_SKIP", {}) |
274 | | - positional, named, argument_names = resolve_args(short_name, spec, args, skip, handles_skip=handles_skip) |
| 312 | + imported = IMPORTER.import_class_or_module(name) |
| 313 | + if inspect.isclass(imported): |
| 314 | + yield create_transformer_instance( |
| 315 | + imported, short_name, config.get_args(name, short_name, import_path), skip |
| 316 | + ) |
| 317 | + else: |
| 318 | + transformers = load_transformers_from_module(imported) |
| 319 | + transformers = order_transformers(transformers, imported) |
| 320 | + for name, transformer_class in transformers.items(): |
| 321 | + yield create_transformer_instance( |
| 322 | + transformer_class, name, config.get_args(name, short_name, import_path), skip |
| 323 | + ) |
275 | 324 | except DataError: |
276 | 325 | similar_finder = RecommendationFinder() |
277 | 326 | similar = similar_finder.find_similar(short_name, TRANSFORMERS) |
278 | 327 | raise ImportTransformerError( |
279 | 328 | f"Importing transformer '{short_name}' failed. " |
280 | 329 | f"Verify if correct name or configuration was provided.{similar}" |
281 | 330 | ) from None |
| 331 | + |
| 332 | + |
| 333 | +def create_transformer_instance(imported_class, short_name, args, skip): |
| 334 | + spec = IMPORTER._get_arg_spec(imported_class) |
| 335 | + handles_skip = getattr(imported_class, "HANDLES_SKIP", {}) |
| 336 | + positional, named, argument_names = resolve_args(short_name, spec, args, skip, handles_skip=handles_skip) |
282 | 337 | instance = imported_class(*positional, **named) |
283 | | - return TransformerContainer(instance, argument_names, spec) |
| 338 | + return TransformerContainer(instance, argument_names, spec, args) |
284 | 339 |
|
285 | 340 |
|
286 | 341 | def split_args_to_class_and_skip(args): |
@@ -373,11 +428,6 @@ def resolve_core_import_path(name): |
373 | 428 | return f"robotidy.transformers.{name}" if name in TRANSFORMERS else name |
374 | 429 |
|
375 | 430 |
|
376 | | -def load_transformer(name, args, skip) -> Optional[TransformerContainer]: |
377 | | - import_name = resolve_core_import_path(name) |
378 | | - return import_transformer(import_name, args, skip) |
379 | | - |
380 | | - |
381 | 431 | def can_run_in_robot_version(transformer, overwritten, target_version): |
382 | 432 | if not hasattr(transformer, "MIN_VERSION"): |
383 | 433 | return True |
@@ -412,31 +462,30 @@ def load_transformers( |
412 | 462 | """Dynamically load all classes from this file with attribute `name` defined in selected_transformers""" |
413 | 463 | loaded_transformers = [] |
414 | 464 | transformers_config.update_with_defaults(TRANSFORMERS) |
415 | | - transformers_config.validate_config_names() |
416 | 465 | if not force_order: |
417 | 466 | transformers_config.order_using_list(TRANSFORMERS) |
418 | 467 | for name, transformer_config in transformers_config.transformers.items(): |
419 | 468 | if not allow_disabled and not transformers_config.transformer_should_be_included(name): |
420 | 469 | continue |
421 | | - container = load_transformer(name, transformer_config.args, skip) |
422 | | - if transformers_config.force_included_only: |
423 | | - enabled = transformer_config.args.get("enabled", True) |
424 | | - else: |
425 | | - if "enabled" in transformer_config.args: |
426 | | - enabled = transformer_config.args["enabled"] |
| 470 | + for container in import_transformer(name, transformers_config, skip): |
| 471 | + if transformers_config.force_included_only: |
| 472 | + enabled = container.args.get("enabled", True) |
427 | 473 | else: |
428 | | - enabled = getattr(container.instance, "ENABLED", True) |
429 | | - if not (enabled or allow_disabled): |
430 | | - continue |
431 | | - if can_run_in_robot_version( |
432 | | - container.instance, |
433 | | - overwritten=transformers_config.transformer_was_forcefully_enabled(name), |
434 | | - target_version=target_version, |
435 | | - ): |
436 | | - container.enabled_by_default = enabled |
437 | | - loaded_transformers.append(container) |
438 | | - elif allow_version_mismatch and allow_disabled: |
439 | | - setattr(container.instance, "ENABLED", False) |
440 | | - container.enabled_by_default = False |
441 | | - loaded_transformers.append(container) |
| 474 | + if "enabled" in container.args: |
| 475 | + enabled = container.args["enabled"] |
| 476 | + else: |
| 477 | + enabled = getattr(container.instance, "ENABLED", True) |
| 478 | + if not (enabled or allow_disabled): |
| 479 | + continue |
| 480 | + if can_run_in_robot_version( |
| 481 | + container.instance, |
| 482 | + overwritten=transformers_config.transformer_was_forcefully_enabled(name), |
| 483 | + target_version=target_version, |
| 484 | + ): |
| 485 | + container.enabled_by_default = enabled |
| 486 | + loaded_transformers.append(container) |
| 487 | + elif allow_version_mismatch and allow_disabled: |
| 488 | + setattr(container.instance, "ENABLED", False) |
| 489 | + container.enabled_by_default = False |
| 490 | + loaded_transformers.append(container) |
442 | 491 | return loaded_transformers |
0 commit comments