|
22 | 22 | from superset.connectors.sqla.models import SqlaTable |
23 | 23 | from superset.models.core import Database |
24 | 24 | from superset.superset_typing import Metric |
| 25 | +from superset.utils import pandas_postprocessing |
25 | 26 | from superset.utils.core import override_user |
26 | 27 |
|
27 | 28 |
|
@@ -438,3 +439,143 @@ def test_cache_key_cache_impersonation_on_with_different_user_and_db_impersonati |
438 | 439 | ], |
439 | 440 | any_order=True, |
440 | 441 | ) |
| 442 | + |
| 443 | + |
| 444 | +def test_post_processing_drops_unsupported_options(): |
| 445 | + """ |
| 446 | + An option that the operation no longer accepts is dropped, not passed on. |
| 447 | +
|
| 448 | + A chart saved by an older version of Superset stores `flatten_columns` in |
| 449 | + the options of its `pivot` operation. `pivot` lost that parameter when |
| 450 | + flattening became its own operation, so replaying the stored query_context |
| 451 | + raised `TypeError: pivot() got an unexpected keyword argument |
| 452 | + 'flatten_columns'`. |
| 453 | + """ |
| 454 | + query_object = QueryObject( |
| 455 | + row_limit=1, |
| 456 | + post_processing=[ |
| 457 | + { |
| 458 | + "operation": "pivot", |
| 459 | + "options": { |
| 460 | + "index": ["__timestamp"], |
| 461 | + "columns": ["genre"], |
| 462 | + "aggregates": {"count": {"operator": "mean"}}, |
| 463 | + "drop_missing_columns": False, |
| 464 | + "flatten_columns": True, |
| 465 | + "reset_index": True, |
| 466 | + }, |
| 467 | + } |
| 468 | + ], |
| 469 | + ) |
| 470 | + |
| 471 | + options = query_object.post_processing[0]["options"] |
| 472 | + assert "flatten_columns" not in options |
| 473 | + assert "reset_index" not in options |
| 474 | + assert options["drop_missing_columns"] is False |
| 475 | + assert options["index"] == ["__timestamp"] |
| 476 | + |
| 477 | + |
| 478 | +def test_post_processing_keeps_supported_options(): |
| 479 | + """Options the operation accepts are left alone.""" |
| 480 | + post_processing = [ |
| 481 | + { |
| 482 | + "operation": "pivot", |
| 483 | + "options": {"index": ["__timestamp"], "aggregates": {}}, |
| 484 | + } |
| 485 | + ] |
| 486 | + query_object = QueryObject(row_limit=1, post_processing=post_processing) |
| 487 | + |
| 488 | + assert query_object.post_processing == post_processing |
| 489 | + |
| 490 | + |
| 491 | +def test_post_processing_keeps_unknown_operation(): |
| 492 | + """ |
| 493 | + An unknown operation is kept, so that `exec_post_processing` can report it |
| 494 | + as an `InvalidPostProcessingError` rather than being silently dropped here. |
| 495 | + """ |
| 496 | + query_object = QueryObject( |
| 497 | + row_limit=1, |
| 498 | + post_processing=[{"operation": "does_not_exist", "options": {"a": 1}}, None], |
| 499 | + ) |
| 500 | + |
| 501 | + assert query_object.post_processing == [ |
| 502 | + {"operation": "does_not_exist", "options": {"a": 1}} |
| 503 | + ] |
| 504 | + |
| 505 | + |
| 506 | +def test_post_processing_drops_the_dataframe_parameter(): |
| 507 | + """ |
| 508 | + The DataFrame parameter is not an option. |
| 509 | +
|
| 510 | + `exec_post_processing` calls `operation(df, **options)`, so an option named |
| 511 | + after the first parameter would raise `TypeError: pivot() got multiple |
| 512 | + values for argument 'df'`. |
| 513 | + """ |
| 514 | + query_object = QueryObject( |
| 515 | + row_limit=1, |
| 516 | + post_processing=[ |
| 517 | + { |
| 518 | + "operation": "pivot", |
| 519 | + "options": {"df": "malformed", "index": ["a"], "aggregates": {}}, |
| 520 | + } |
| 521 | + ], |
| 522 | + ) |
| 523 | + |
| 524 | + options = query_object.post_processing[0]["options"] |
| 525 | + assert "df" not in options |
| 526 | + assert options["index"] == ["a"] |
| 527 | + |
| 528 | + |
| 529 | +def test_post_processing_keeps_options_of_a_variadic_operation(): |
| 530 | + """An operation that accepts `**kwargs` accepts every option.""" |
| 531 | + |
| 532 | + def variadic(df, **kwargs): |
| 533 | + return df |
| 534 | + |
| 535 | + post_processing = [{"operation": "variadic", "options": {"anything": 1}}] |
| 536 | + with patch.object(pandas_postprocessing, "variadic", variadic, create=True): |
| 537 | + query_object = QueryObject(row_limit=1, post_processing=post_processing) |
| 538 | + |
| 539 | + assert query_object.post_processing == post_processing |
| 540 | + |
| 541 | + |
| 542 | +def test_post_processing_drops_a_variadic_positional_option(): |
| 543 | + """ |
| 544 | + A `*args` parameter cannot be filled by a keyword argument. |
| 545 | +
|
| 546 | + `exec_post_processing` calls the operation as `operation(df, **options)`, |
| 547 | + so an option named after a `*args` parameter would raise `TypeError: |
| 548 | + variadic_positional() got an unexpected keyword argument 'args'` even |
| 549 | + though the name appears in the signature. |
| 550 | + """ |
| 551 | + |
| 552 | + def variadic_positional(df, *args, index=None): # pylint: disable=unused-argument |
| 553 | + return df |
| 554 | + |
| 555 | + with patch.object( |
| 556 | + pandas_postprocessing, "variadic_positional", variadic_positional, create=True |
| 557 | + ): |
| 558 | + query_object = QueryObject( |
| 559 | + row_limit=1, |
| 560 | + post_processing=[ |
| 561 | + { |
| 562 | + "operation": "variadic_positional", |
| 563 | + "options": {"args": [1], "index": ["a"]}, |
| 564 | + } |
| 565 | + ], |
| 566 | + ) |
| 567 | + |
| 568 | + options = query_object.post_processing[0]["options"] |
| 569 | + assert "args" not in options |
| 570 | + assert options["index"] == ["a"] |
| 571 | + |
| 572 | + |
| 573 | +def test_post_processing_keeps_an_entry_without_an_operation(): |
| 574 | + """ |
| 575 | + An entry that names no operation is kept, so that `exec_post_processing` |
| 576 | + reports it as an `InvalidPostProcessingError`. |
| 577 | + """ |
| 578 | + post_processing = [{"options": {"a": 1}}] |
| 579 | + query_object = QueryObject(row_limit=1, post_processing=post_processing) |
| 580 | + |
| 581 | + assert query_object.post_processing == post_processing |
0 commit comments