openrewrite-migrate-python 0.18.0__tar.gz → 0.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/PKG-INFO +2 -2
  2. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/pyproject.toml +2 -2
  3. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/boolean_simplification.py +153 -51
  4. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/pandas_cleanup.py +10 -2
  5. openrewrite_migrate_python-0.20.0/src/openrewrite_code_quality_python/codequality/_grouping.py +31 -0
  6. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +6 -1
  7. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +6 -1
  8. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_method_renames.py +13 -5
  9. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/backoff_to_tenacity.py +74 -33
  10. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python.egg-info/PKG-INFO +2 -2
  11. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +1 -1
  12. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python.egg-info/requires.txt +1 -1
  13. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_backoff_to_tenacity.py +291 -2
  14. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_unittest_deprecations.py +32 -1
  15. openrewrite_migrate_python-0.20.0/tests/test_upgrade_recipes.py +743 -0
  16. openrewrite_migrate_python-0.18.0/tests/test_markers.py +0 -19
  17. openrewrite_migrate_python-0.18.0/tests/test_upgrade_recipes.py +0 -235
  18. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/setup.cfg +0 -0
  19. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/__init__.py +0 -0
  20. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/__init__.py +0 -0
  21. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/_literals.py +0 -0
  22. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/_negation.py +0 -0
  23. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/assign_return.py +0 -0
  24. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/attr_access.py +0 -0
  25. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/collection_literals.py +0 -0
  26. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/comparison_simplification.py +0 -0
  27. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/complex_transforms.py +0 -0
  28. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/comprehension_cleanup.py +0 -0
  29. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/conditionals.py +0 -0
  30. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/datetime_cleanup.py +0 -0
  31. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/dict_cleanup.py +0 -0
  32. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/exception_cleanup.py +0 -0
  33. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/expression_simplification.py +0 -0
  34. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/logger_warn.py +0 -0
  35. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/loop_cleanup.py +0 -0
  36. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/merge_nested_with.py +0 -0
  37. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/merge_startswith_endswith.py +0 -0
  38. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/naming_conventions.py +0 -0
  39. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/negated_operators.py +0 -0
  40. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/none_and_identity.py +0 -0
  41. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/object_inheritance.py +0 -0
  42. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/python_best_practices.py +0 -0
  43. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/range_cleanup.py +0 -0
  44. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/redundant_code.py +0 -0
  45. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/remove_trailing_return_none.py +0 -0
  46. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/string_cleanup.py +0 -0
  47. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/unreachable_code.py +0 -0
  48. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/use_items.py +0 -0
  49. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/use_list_clear.py +0 -0
  50. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/cleanup/yoda_conditions.py +0 -0
  51. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
  52. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
  53. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
  54. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/_side_effects.py +0 -0
  55. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/_trivia.py +0 -0
  56. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/_truth_value.py +0 -0
  57. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +0 -0
  58. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +0 -0
  59. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/code_quality.py +0 -0
  60. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
  61. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +0 -0
  62. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +0 -0
  63. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +0 -0
  64. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +0 -0
  65. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
  66. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/__init__.py +0 -0
  67. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
  68. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_decorators.py +0 -0
  69. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_finders.py +0 -0
  70. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
  71. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  72. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_module_imports.py +0 -0
  73. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/_slots.py +0 -0
  74. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
  75. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
  76. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  77. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
  78. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
  79. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
  80. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/builtins_deprecations.py +0 -0
  81. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  82. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/cgi_escape_deprecations.py +0 -0
  83. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
  84. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
  85. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
  86. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
  87. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/ctypes_deprecations.py +0 -0
  88. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
  89. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
  90. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
  91. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
  92. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
  93. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
  94. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/glob_deprecations.py +0 -0
  95. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
  96. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/http_server_deprecations.py +0 -0
  97. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
  98. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/importlib_deprecations.py +0 -0
  99. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/importlib_resources_deprecations.py +0 -0
  100. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/inspect_deprecations.py +0 -0
  101. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  102. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  103. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  104. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
  105. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
  106. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
  107. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
  108. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/module_cached_attribute.py +0 -0
  109. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
  110. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
  111. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
  112. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
  113. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
  114. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
  115. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
  116. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/profile_deprecations.py +0 -0
  117. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
  118. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
  119. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
  120. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
  121. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
  122. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
  123. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
  124. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/sre_migrations.py +0 -0
  125. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
  126. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
  127. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  128. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
  129. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
  130. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
  131. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
  132. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/text_encoding.py +0 -0
  133. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  134. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
  135. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/types_deprecations.py +0 -0
  136. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
  137. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
  138. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/typing_functional_syntax.py +0 -0
  139. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  140. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
  141. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  142. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  143. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_pandas.py +0 -0
  144. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
  145. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  146. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  147. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
  148. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  149. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  150. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python315.py +0 -0
  151. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  152. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
  153. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
  154. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
  155. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/wave_deprecations.py +0 -0
  156. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/webbrowser_deprecations.py +0 -0
  157. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
  158. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
  159. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  160. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  161. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  162. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_aifc_migrations.py +0 -0
  163. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_array_deprecations.py +0 -0
  164. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_ast_deprecations.py +0 -0
  165. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_asyncio_coroutine_to_async.py +0 -0
  166. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_asyncio_deprecations.py +0 -0
  167. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_avro_migrations.py +0 -0
  168. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_builtins_deprecations.py +0 -0
  169. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_calendar_deprecations.py +0 -0
  170. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_cgi_escape_deprecations.py +0 -0
  171. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_cgi_parse_deprecations.py +0 -0
  172. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_change_import_recipes.py +0 -0
  173. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_collections_abc_migrations.py +0 -0
  174. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_configparser_deprecations.py +0 -0
  175. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_ctypes_deprecations.py +0 -0
  176. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_datetime_utc.py +0 -0
  177. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_distutils_deprecations.py +0 -0
  178. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_distutils_migrations.py +0 -0
  179. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_functools_deprecations.py +0 -0
  180. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_future_imports.py +0 -0
  181. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_gettext_deprecations.py +0 -0
  182. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_glob_deprecations.py +0 -0
  183. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_html_parser_deprecations.py +0 -0
  184. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_http_server_deprecations.py +0 -0
  185. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_imp_migrations.py +0 -0
  186. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_importlib_deprecations.py +0 -0
  187. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_importlib_resources_deprecations.py +0 -0
  188. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_inspect_deprecations.py +0 -0
  189. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_langchain_classic_imports.py +0 -0
  190. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_langchain_community_imports.py +0 -0
  191. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_langchain_provider_imports.py +0 -0
  192. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_locale_deprecations.py +0 -0
  193. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  194. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_macpath_deprecations.py +0 -0
  195. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_mailcap_migrations.py +0 -0
  196. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_module_cached_attribute.py +0 -0
  197. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_os_deprecations.py +0 -0
  198. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pathlib_deprecations.py +0 -0
  199. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pep594_migrations.py +0 -0
  200. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pep594_system_migrations.py +0 -0
  201. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pkgutil_deprecations.py +0 -0
  202. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_platform_deprecations.py +0 -0
  203. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_profile_deprecations.py +0 -0
  204. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pydantic_deprecations.py +0 -0
  205. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pydantic_fields.py +0 -0
  206. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_pydantic_model_fields.py +0 -0
  207. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_re_deprecations.py +0 -0
  208. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_recipe_conventions.py +0 -0
  209. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_recipe_reachability.py +0 -0
  210. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_removed_modules_312.py +0 -0
  211. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_shutil_deprecations.py +0 -0
  212. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_socket_deprecations.py +0 -0
  213. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_sre_migrations.py +0 -0
  214. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_ssl_deprecations.py +0 -0
  215. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_string_formatting.py +0 -0
  216. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_sys_deprecations.py +0 -0
  217. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_sys_last_deprecations.py +0 -0
  218. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_tarfile_deprecations.py +0 -0
  219. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_tempfile_deprecations.py +0 -0
  220. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_text_encoding.py +0 -0
  221. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_threading_deprecations.py +0 -0
  222. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_threading_extended.py +0 -0
  223. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_threading_is_alive_deprecation.py +0 -0
  224. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_types_deprecations.py +0 -0
  225. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_typing_alias_moves.py +0 -0
  226. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_typing_callable.py +0 -0
  227. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_typing_deprecations.py +0 -0
  228. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_typing_functional_syntax.py +0 -0
  229. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_typing_union_to_pipe.py +0 -0
  230. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_upgrade_to_pydantic.py +0 -0
  231. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_urllib_deprecations.py +0 -0
  232. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_wave_deprecations.py +0 -0
  233. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_webbrowser_deprecations.py +0 -0
  234. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_xdrlib_migrations.py +0 -0
  235. {openrewrite_migrate_python-0.18.0 → openrewrite_migrate_python-0.20.0}/tests/test_xml_deprecations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: OpenRewrite recipes for migrating Python codebases to newer Python versions.
5
5
  Author-email: "Moderne Inc." <support@moderne.io>
6
6
  License: Moderne Proprietary
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.12
20
20
  Description-Content-Type: text/markdown
21
21
  Provides-Extra: dev
22
- Requires-Dist: openrewrite>=8.94.0; extra == "dev"
22
+ Requires-Dist: openrewrite>=8.96.3; extra == "dev"
23
23
  Requires-Dist: pydantic>=2.11; extra == "dev"
24
24
  Requires-Dist: pytest>=8.0.0; extra == "dev"
25
25
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "openrewrite-migrate-python"
7
7
  description = "OpenRewrite recipes for migrating Python codebases to newer Python versions."
8
- version = "0.18.0" # Updated dynamically during release
8
+ version = "0.20.0" # Updated dynamically during release
9
9
  authors = [{ name = "Moderne Inc.", email = "support@moderne.io" }]
10
10
  license = { text = "Moderne Proprietary" }
11
11
  readme = "README.md"
@@ -28,7 +28,7 @@ dependencies = []
28
28
 
29
29
  [project.optional-dependencies]
30
30
  dev = [
31
- "openrewrite>=8.94.0",
31
+ "openrewrite>=8.96.3",
32
32
  # Needed so type-attribution tests can resolve `pydantic` types.
33
33
  "pydantic>=2.11",
34
34
  "pytest>=8.0.0",
@@ -14,7 +14,8 @@ unnecessary operators, literals, or indirection:
14
14
  See: https://peps.python.org/pep-0008/#programming-recommendations
15
15
  """
16
16
 
17
- from typing import Any, List
17
+ from typing import AbstractSet, Any, Dict, List, NamedTuple, Tuple
18
+ from uuid import UUID
18
19
 
19
20
  from rewrite import ExecutionContext, Preconditions, Recipe, TreeVisitor
20
21
  from rewrite.markers import Markers
@@ -28,11 +29,11 @@ from rewrite.python.preconditions import uses_method
28
29
  from rewrite.python.scope_utils import scope_of
29
30
  from rewrite.python.type_utils import is_of_class_type
30
31
  from rewrite.python.visitor import PythonVisitor
31
- from rewrite.python.template import pattern, template, capture
32
+ from rewrite.python.template import Capture, Template, pattern, template, capture
32
33
  from rewrite.python.template.pattern import MatchResult
33
34
  from rewrite.java.tree import (
34
35
  Binary as JBinary, Block, ControlParentheses, Empty, Expression, If as JIf, J,
35
- Literal, MethodInvocation, Return, Ternary as JTernary, Unary,
36
+ Literal, MethodInvocation, Parentheses, Return, Ternary as JTernary, Unary,
36
37
  WhileLoop as JWhileLoop,
37
38
  )
38
39
  from rewrite.java.support_types import JavaType, Space
@@ -80,22 +81,43 @@ _lc_not_x_template = template("not {lc_x}", lc_x=_lc_x)
80
81
 
81
82
 
82
83
  # --- DeMorgan patterns/templates ---
84
+ class _Distributed(NamedTuple):
85
+ """A De Morgan result, written alone or continuing a chain of its own operator."""
86
+
87
+ alone: Template
88
+ chained: Template
89
+ """``{dm_z} and`` the result, which the parser nests to the left as it does ``z and a and b``."""
90
+ operator: JBinary.Type
91
+
92
+
93
+ _dm_z = capture('dm_z')
94
+
95
+
96
+ def _distributed(operator: str, left: str, right: str, captures: Dict[str, Capture]) -> _Distributed:
97
+ return _Distributed(
98
+ Template(f"{left} {operator} {right}", captures),
99
+ Template(f"{{dm_z}} {operator} {left} {operator} {right}", {'dm_z': _dm_z, **captures}),
100
+ JBinary.Type.And if operator == "and" else JBinary.Type.Or,
101
+ )
102
+
103
+
83
104
  _dm_x = capture('dm_x')
84
105
  _dm_y = capture('dm_y')
106
+ _dm_xy = {'dm_x': _dm_x, 'dm_y': _dm_y}
85
107
  _not_and_pattern = pattern("not ({dm_x} and {dm_y})", dm_x=_dm_x, dm_y=_dm_y)
86
108
  _not_or_pattern = pattern("not ({dm_x} or {dm_y})", dm_x=_dm_x, dm_y=_dm_y)
87
- _not_x_or_not_y_template = template("not {dm_x} or not {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
88
- _not_x_and_not_y_template = template("not {dm_x} and not {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
109
+ _not_x_or_not_y = _distributed("or", "not {dm_x}", "not {dm_y}", _dm_xy)
110
+ _not_x_and_not_y = _distributed("and", "not {dm_x}", "not {dm_y}", _dm_xy)
89
111
  # Partial-negation templates: one operand already negated by operator flip (in→not in)
90
- _not_x_or_y_template = template("not {dm_x} or {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
91
- _x_or_not_y_template = template("{dm_x} or not {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
92
- _not_x_and_y_template = template("not {dm_x} and {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
93
- _x_and_not_y_template = template("{dm_x} and not {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
112
+ _not_x_or_y = _distributed("or", "not {dm_x}", "{dm_y}", _dm_xy)
113
+ _x_or_not_y = _distributed("or", "{dm_x}", "not {dm_y}", _dm_xy)
114
+ _not_x_and_y = _distributed("and", "not {dm_x}", "{dm_y}", _dm_xy)
115
+ _x_and_not_y = _distributed("and", "{dm_x}", "not {dm_y}", _dm_xy)
94
116
  # Fused DeMorgan + double-negation: not (not x and not y) -> x or y
95
117
  _not_notx_and_noty_pattern = pattern("not (not {dm_x} and not {dm_y})", dm_x=_dm_x, dm_y=_dm_y)
96
- _x_or_y_template = template("{dm_x} or {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
118
+ _x_or_y = _distributed("or", "{dm_x}", "{dm_y}", _dm_xy)
97
119
  _not_notx_or_noty_pattern = pattern("not (not {dm_x} or not {dm_y})", dm_x=_dm_x, dm_y=_dm_y)
98
- _x_and_y_template = template("{dm_x} and {dm_y}", dm_x=_dm_x, dm_y=_dm_y)
120
+ _x_and_y = _distributed("and", "{dm_x}", "{dm_y}", _dm_xy)
99
121
 
100
122
  # DeMorgan with comparison operators: not (p == q and r == s) -> p != q or r != s
101
123
  _dm_p = capture('dm_p')
@@ -105,8 +127,8 @@ _dm_s = capture('dm_s')
105
127
  _not_eq_and_eq_pattern = pattern(
106
128
  "not ({dm_p} == {dm_q} and {dm_r} == {dm_s})", dm_p=_dm_p, dm_q=_dm_q, dm_r=_dm_r, dm_s=_dm_s
107
129
  )
108
- _neq_or_neq_template = template(
109
- "{dm_p} != {dm_q} or {dm_r} != {dm_s}", dm_p=_dm_p, dm_q=_dm_q, dm_r=_dm_r, dm_s=_dm_s
130
+ _neq_or_neq = _distributed(
131
+ "or", "{dm_p} != {dm_q}", "{dm_r} != {dm_s}", {'dm_p': _dm_p, 'dm_q': _dm_q, 'dm_r': _dm_r, 'dm_s': _dm_s}
110
132
  )
111
133
 
112
134
  def _is_bool_literal(node, expected_source: str) -> bool:
@@ -299,8 +321,11 @@ class RemoveRedundantBoolean(Recipe):
299
321
  return Visitor()
300
322
 
301
323
 
302
- def _apply_demorgan(cursor, match, truth_value, both_not, left_not, right_not, neither_not):
303
- """Apply a DeMorgan template, folding negations where possible.
324
+ _Distribution = Tuple[_Distributed, MatchResult]
325
+
326
+
327
+ def _choose_demorgan(match, truth_value, both_not, left_not, right_not, neither_not) -> _Distribution | None:
328
+ """Choose a DeMorgan template and its values, folding negations where possible.
304
329
 
305
330
  For each operand, tries to absorb the ``not`` introduced by DeMorgan:
306
331
 
@@ -319,19 +344,58 @@ def _apply_demorgan(cursor, match, truth_value, both_not, left_not, right_not, n
319
344
  y_negated = negated_without_not(dm_y_val, truth_value)
320
345
 
321
346
  if x_negated and y_negated:
322
- values = MatchResult({'dm_x': x_negated, 'dm_y': y_negated})
323
- return neither_not.apply(cursor, values=values)
347
+ return neither_not, MatchResult({'dm_x': x_negated, 'dm_y': y_negated})
324
348
  if x_negated:
325
- values = MatchResult({'dm_x': x_negated, 'dm_y': dm_y_val})
326
- return right_not.apply(cursor, values=values)
349
+ return right_not, MatchResult({'dm_x': x_negated, 'dm_y': dm_y_val})
327
350
  if y_negated:
328
- values = MatchResult({'dm_x': dm_x_val, 'dm_y': y_negated})
329
- return left_not.apply(cursor, values=values)
330
- return both_not.apply(cursor, values=match)
351
+ return left_not, MatchResult({'dm_x': dm_x_val, 'dm_y': y_negated})
352
+ return both_not, match
353
+
354
+
355
+ def _negated_chain(unary: Unary, operator: JBinary.Type) -> JBinary | None:
356
+ """The parenthesized chain of three or more ``operator`` operands that ``unary`` negates, or None."""
357
+ inner = unary.expression
358
+ if isinstance(inner, Parentheses):
359
+ inner = inner.tree
360
+ if isinstance(inner, JBinary) and inner.operator == operator \
361
+ and isinstance(inner.left, JBinary) and inner.left.operator == operator:
362
+ return inner
363
+ return None
364
+
365
+
366
+ def _operands(chain: Expression, operator: JBinary.Type) -> List[Expression]:
367
+ """The operands of ``chain``, a chain of ``operator`` nested to the left."""
368
+ operands = []
369
+ while isinstance(chain, JBinary) and chain.operator == operator:
370
+ operands.append(chain.right)
371
+ chain = chain.left
372
+ return [chain, *operands]
373
+
374
+
375
+ def _distributes(unary: Unary, written: Unary, made: AbstractSet[UUID], operator: JBinary.Type,
376
+ truth_value: bool) -> bool:
377
+ """Whether ``unary``'s ``not`` distributes over its ``operator``: never over a chain ``written`` spells, unless
378
+ ``made`` holds it, and over a chain a rewrite made only where each operand can be negated."""
379
+ spelled = _negated_chain(written, operator)
380
+ if spelled is not None and spelled.id not in made:
381
+ return False
382
+ chain = _negated_chain(unary, operator)
383
+ return chain is None or truth_value or not any(is_not(operand) for operand in _operands(chain, operator))
384
+
331
385
 
386
+ def _and_head(made: AbstractSet[UUID], unary: Unary, distributed: _Distributed) -> AbstractSet[UUID]:
387
+ """``made`` and the left part of the chain ``unary`` negates, which ``distributed`` negates whole."""
388
+ negated = JBinary.Type.And if distributed.operator == JBinary.Type.Or else JBinary.Type.Or
389
+ chain = _negated_chain(unary, negated)
390
+ return made if chain is None else made | {chain.left.id}
332
391
 
333
- def _distribute_not(unary: Unary, cursor: Cursor) -> J:
334
- """``unary`` with its ``not`` distributed one level in, or ``unary`` itself."""
392
+
393
+ def _distribution(unary: Unary, cursor: Cursor, written: Unary, made: AbstractSet[UUID]) -> _Distribution | None:
394
+ """How ``unary``'s ``not`` distributes one level in, or None where it does not.
395
+
396
+ ``written`` is ``unary`` as the code spells it, before its operands were rewritten, and ``made`` holds the ids
397
+ of the chains that rewrites made.
398
+ """
335
399
  truth_value = is_read_as_truth_value(cursor)
336
400
 
337
401
  # not (p == q and r == s) -> p != q or r != s
@@ -340,46 +404,52 @@ def _distribute_not(unary: Unary, cursor: Cursor) -> J:
340
404
  isinstance(operand, Expression) and is_comparison(operand)
341
405
  for operand in (match[k] for k in ('dm_p', 'dm_q', 'dm_r', 'dm_s'))
342
406
  ):
343
- if not truth_value:
344
- return unary
345
- return _neq_or_neq_template.apply(cursor, values=match) or unary
407
+ return (_neq_or_neq, match) if truth_value else None
346
408
 
347
409
  if truth_value:
348
410
  # Fused: not (not x and not y) -> x or y
349
411
  match = _not_notx_and_noty_pattern.match(unary, cursor)
350
412
  if match:
351
- return _x_or_y_template.apply(cursor, values=match) or unary
413
+ return _x_or_y, match
352
414
 
353
415
  # Fused: not (not x or not y) -> x and y
354
416
  match = _not_notx_or_noty_pattern.match(unary, cursor)
355
417
  if match:
356
- return _x_and_y_template.apply(cursor, values=match) or unary
418
+ return _x_and_y, match
357
419
 
358
420
  # not (x and y) -> not x or not y
359
- # Skip multi-way chains: not (a and b and c) where dm_x
360
- # captures (a and b) — partial expansion is messy.
421
+ # A chain the code spells, as not (a and b and c), is left alone, but one that rewriting an operand
422
+ # made distributes: not (a or not (b and c)) has become not (a or not b or not c) by now.
361
423
  match = _not_and_pattern.match(unary, cursor)
362
- if match:
363
- dm_x_val = match['dm_x']
364
- if not (isinstance(dm_x_val, JBinary) and dm_x_val.operator == JBinary.Type.And):
365
- return _apply_demorgan(
366
- cursor, match, truth_value, _not_x_or_not_y_template,
367
- _not_x_or_y_template, _x_or_not_y_template,
368
- _x_or_y_template,
369
- ) or unary
424
+ if match and _distributes(unary, written, made, JBinary.Type.And, truth_value):
425
+ return _choose_demorgan(match, truth_value, _not_x_or_not_y, _not_x_or_y, _x_or_not_y, _x_or_y)
370
426
 
371
427
  # not (x or y) -> not x and not y
372
428
  match = _not_or_pattern.match(unary, cursor)
373
- if match:
374
- dm_x_val = match['dm_x']
375
- if not (isinstance(dm_x_val, JBinary) and dm_x_val.operator == JBinary.Type.Or):
376
- return _apply_demorgan(
377
- cursor, match, truth_value, _not_x_and_not_y_template,
378
- _not_x_and_y_template, _x_and_not_y_template,
379
- _x_and_y_template,
380
- ) or unary
429
+ if match and _distributes(unary, written, made, JBinary.Type.Or, truth_value):
430
+ return _choose_demorgan(match, truth_value, _not_x_and_not_y, _not_x_and_y, _x_and_not_y, _x_and_y)
431
+
432
+ return None
433
+
434
+
435
+ def _ends_chain(cursor: Cursor, operator: JBinary.Type) -> bool:
436
+ """Whether the value at ``cursor`` is the right operand of an ``and``/``or`` of ``operator``."""
437
+ node, parent = cursor.value, cursor.parent_tree_cursor().value
438
+ return isinstance(node, J) and isinstance(parent, JBinary) and parent.operator == operator \
439
+ and parent.right.id == node.id
440
+
381
441
 
382
- return unary
442
+ def _respaced(chain: J, binary: JBinary) -> J:
443
+ """``chain``, written by a template in place of ``binary``, with the spaces and comments ``binary`` had
444
+ around its left operand and its operator."""
445
+ head = chain.left if isinstance(chain, JBinary) else None
446
+ if not isinstance(head, JBinary):
447
+ return chain
448
+ return chain.replace(_left=head.replace(
449
+ _left=head.left.replace(_prefix=binary.left.prefix),
450
+ _operator=binary.padding.operator,
451
+ _right=head.right.replace(_prefix=binary.right.prefix),
452
+ ))
383
453
 
384
454
 
385
455
  @categorize(_Cleanup)
@@ -423,6 +493,11 @@ class DeMorgan(Recipe):
423
493
 
424
494
  def editor(self) -> TreeVisitor[Any, ExecutionContext]:
425
495
  class Visitor(PythonVisitor[ExecutionContext]):
496
+ def __init__(self, made: AbstractSet[UUID] = frozenset()):
497
+ super().__init__()
498
+ # The ids of chains that rewrites made, which this visit meets again in what a template wrote.
499
+ self._made = made
500
+
426
501
  def visit_unary(
427
502
  self, unary: Unary, p: ExecutionContext
428
503
  ) -> J:
@@ -431,11 +506,38 @@ class DeMorgan(Recipe):
431
506
  visited.replace(_prefix=Space.EMPTY)
432
507
  ):
433
508
  return visited
434
- result = _distribute_not(visited, Cursor(self.cursor.parent, visited))
435
- if result is visited:
509
+ cursor = Cursor(self.cursor.parent, visited)
510
+ distribution = _distribution(visited, cursor, unary, self._made)
511
+ # The `and` holding `x and not (a or b)` rewrites it whole, in visit_binary.
512
+ if distribution is None or _ends_chain(cursor, distribution[0].operator):
513
+ return visited
514
+ distributed, values = distribution
515
+ result = distributed.alone.apply(cursor, values=values)
516
+ if result is None:
436
517
  return visited
437
518
  # The negations a rewrite creates may distribute in turn.
438
- return Visitor().visit(result, p, self.cursor.parent_or_throw) or result
519
+ made = _and_head(self._made, visited, distributed)
520
+ return Visitor(made).visit(result, p, self.cursor.parent_or_throw) or result
521
+
522
+ def visit_binary(self, binary: JBinary, p: ExecutionContext) -> J:
523
+ visited = super().visit_binary(binary, p)
524
+ if not isinstance(visited, JBinary) or not isinstance(visited.right, Unary) or has_comments(
525
+ visited.right.replace(_prefix=Space.EMPTY)
526
+ ):
527
+ return visited
528
+ cursor = Cursor(self.cursor.parent, visited)
529
+ # Read under the unvisited `and`/`or`, as visit_unary read it before leaving it here.
530
+ written = binary.right if isinstance(binary.right, Unary) else visited.right
531
+ distribution = _distribution(visited.right, Cursor(self.cursor, visited.right), written, self._made)
532
+ if distribution is None or distribution[0].operator != visited.operator:
533
+ return visited
534
+ distributed, values = distribution
535
+ result = distributed.chained.apply(cursor, values={**values.as_dict(), 'dm_z': visited.left})
536
+ if result is None:
537
+ return visited
538
+ result = _respaced(result, visited)
539
+ made = _and_head(self._made, visited.right, distributed)
540
+ return Visitor(made).visit(result, p, self.cursor.parent_or_throw) or result
439
541
 
440
542
  return Visitor()
441
543
 
@@ -9,6 +9,7 @@ Both fire only where the receiver's pandas type resolves.
9
9
  See: https://pandas.pydata.org/docs/whatsnew/v1.4.0.html#deprecations
10
10
  """
11
11
 
12
+ import dataclasses
12
13
  from typing import Any, List
13
14
 
14
15
  from rewrite import ExecutionContext, Preconditions, Recipe, TreeVisitor
@@ -103,12 +104,19 @@ class DataframeAppendToConcat(Recipe):
103
104
  ):
104
105
  return m
105
106
  try:
106
- return _concat_template.apply(
107
+ concat = _concat_template.apply(
107
108
  self.cursor, visitor=self, values={'df': select, 'x': args[0]}
108
- ) or m
109
+ )
109
110
  except ValueError:
110
111
  # The template refuses a file where `pd` is bound to something else.
111
112
  return m
113
+ if not isinstance(concat, MethodInvocation):
114
+ return concat or m
115
+ # Typed as the receiver, so that an `append` on this result or taking it is migrated too.
116
+ method_type = concat.method_type
117
+ if method_type is None:
118
+ return concat
119
+ return concat.replace(_method_type=dataclasses.replace(method_type, _return_type=select.type))
112
120
 
113
121
  # `append` does not resolve under pandas 2, so the gate names the receiver.
114
122
  return Preconditions.check(uses_type(_DATAFRAME), Visitor())
@@ -0,0 +1,31 @@
1
+ """Grouping an ``and`` or ``or`` chain the way the parser groups it."""
2
+
3
+ from rewrite.java.support_types import Space
4
+ from rewrite.java.tree import Binary
5
+ from rewrite.utils import random_id
6
+
7
+ from openrewrite_code_quality_python.codequality._trivia import concat
8
+
9
+ _CHAINED = (Binary.Type.And, Binary.Type.Or)
10
+
11
+
12
+ def grouped_left(binary: Binary) -> Binary:
13
+ """``binary`` with a chain of its own operator on its right, unparenthesized, regrouped to the left.
14
+
15
+ ``a and (b and c)`` without the parentheses prints as ``a and b and c``, which the parser
16
+ groups as ``(a and b) and c``. Both evaluate the same, but a recipe matching the tree sees
17
+ other operands in each.
18
+ """
19
+ right = binary.right
20
+ if binary.operator not in _CHAINED or not isinstance(right, Binary) or right.operator != binary.operator:
21
+ return binary
22
+ left = binary.replace(
23
+ _id=random_id(),
24
+ _prefix=Space.EMPTY,
25
+ _right=right.left.replace(_prefix=concat(right.prefix, right.left.prefix)),
26
+ )
27
+ return grouped_left(binary.replace(
28
+ _left=grouped_left(left),
29
+ _operator=right.padding.operator,
30
+ _right=right.right,
31
+ ))
@@ -28,6 +28,7 @@ from rewrite.java.tree import (
28
28
  )
29
29
  from rewrite.utils import random_id
30
30
 
31
+ from openrewrite_code_quality_python.codequality._grouping import grouped_left
31
32
  from openrewrite_code_quality_python.codequality._reindent import (
32
33
  line_indent,
33
34
  reindent,
@@ -190,8 +191,12 @@ class CollapsibleIfStatements(Recipe):
190
191
  ),
191
192
  _type=None,
192
193
  )
193
- combined = combined.replace(
194
+ # An `and` chain in the inner condition continues the merged one, nested to the left
195
+ # as the parser nests `a and b and c`, so only its last operand is left to parenthesize.
196
+ combined = grouped_left(combined.replace(
194
197
  _left=maybe_parenthesize(combined, combined.left.id, combined.left),
198
+ ))
199
+ combined = combined.replace(
195
200
  _right=maybe_parenthesize(combined, combined.right.id, combined.right),
196
201
  )
197
202
 
@@ -27,6 +27,7 @@ from rewrite.java.tree import (
27
27
  If,
28
28
  Statement,
29
29
  )
30
+ from openrewrite_code_quality_python.codequality._grouping import grouped_left
30
31
  from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
31
32
  from rewrite.utils import random_id
32
33
 
@@ -180,8 +181,12 @@ def _merge_conditions(first: If, second: If) -> If:
180
181
  _right=_or_operand(second_cond, Space.SINGLE_SPACE),
181
182
  _type=None,
182
183
  )
183
- combined = combined.replace(
184
+ # An `or` chain in the second condition continues the merged one, nested to the left as the
185
+ # parser nests `a or b or c`, so only its last operand is left to parenthesize.
186
+ combined = grouped_left(combined.replace(
184
187
  _left=maybe_parenthesize(combined, combined.left.id, combined.left),
188
+ ))
189
+ combined = combined.replace(
185
190
  _right=maybe_parenthesize(combined, combined.right.id, combined.right),
186
191
  )
187
192
 
@@ -19,16 +19,22 @@ def rename_methods(
19
19
  method gives it. The patterns match overrides, since a call through a subclass to a member
20
20
  typeshed no longer declares resolves to the subclass.
21
21
  """
22
- patterns = {old: f"{declaring_type} {old}(..)" for old in renames}
23
- calls = {old: MethodMatcher.create(pattern, match_overrides=True) for old, pattern in patterns.items()}
22
+ # Where Python still declares `old` as an alias, `old = new`, the call resolves to `new`.
23
+ patterns = {
24
+ old: [f"{declaring_type} {old}(..)", f"{declaring_type} {new}(..)"] for old, new in renames.items()
25
+ }
26
+ calls = {
27
+ old: [MethodMatcher.create(pattern, match_overrides=True) for pattern in alternatives]
28
+ for old, alternatives in patterns.items()
29
+ }
24
30
 
25
31
  class Visitor(PythonVisitor[ExecutionContext]):
26
32
  def visit_method_invocation(self, method: MethodInvocation, p: ExecutionContext) -> J:
27
33
  m = super().visit_method_invocation(method, p)
28
34
  if not isinstance(m, MethodInvocation):
29
35
  return m
30
- call = calls.get(m.name.simple_name)
31
- if call is None or not call.matches(m, cursor=self.cursor):
36
+ matchers = calls.get(m.name.simple_name, [])
37
+ if not any(matcher.matches(m, cursor=self.cursor) for matcher in matchers):
32
38
  return m
33
39
  # The old method type names the old method, which no longer describes the call.
34
40
  m = m.replace(
@@ -41,7 +47,9 @@ def rename_methods(
41
47
  ]
42
48
  return m.padding.replace(_arguments=arguments.padding.replace(elements=elements))
43
49
 
44
- gates = [uses_method(pattern, match_overrides=True) for pattern in patterns.values()]
50
+ gates = [
51
+ uses_method(pattern, match_overrides=True) for alternatives in patterns.values() for pattern in alternatives
52
+ ]
45
53
  return Preconditions.check(gates[0] if len(gates) == 1 else Preconditions.or_(*gates), Visitor())
46
54
 
47
55