openrewrite-migrate-python 0.15.0.dev20260922170519__tar.gz → 0.15.2__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 (188) hide show
  1. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/PKG-INFO +2 -2
  2. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/pyproject.toml +2 -2
  3. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/__init__.py +2 -0
  4. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/_reindent.py +20 -3
  5. openrewrite_migrate_python-0.15.2/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +38 -0
  6. openrewrite_migrate_python-0.15.2/src/openrewrite_code_quality_python/codequality/_side_effects.py +36 -0
  7. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/_trivia.py +6 -3
  8. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +34 -14
  9. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +76 -11
  10. openrewrite_migrate_python-0.15.2/src/openrewrite_code_quality_python/codequality/code_quality.py +73 -0
  11. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +67 -46
  12. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +14 -21
  13. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +21 -43
  14. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +13 -7
  15. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +8 -4
  16. openrewrite_migrate_python-0.15.2/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +334 -0
  17. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +28 -34
  18. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +8 -7
  19. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/__init__.py +8 -0
  20. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/_decorators.py +24 -0
  21. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/_method_renames.py +37 -0
  22. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/_module_imports.py +50 -0
  23. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/aifc_migrations.py +271 -0
  24. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/array_deprecations.py +9 -50
  25. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +13 -110
  26. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +56 -54
  27. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +26 -25
  28. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/backoff_to_tenacity.py +707 -0
  29. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +8 -38
  30. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/cgi_migrations.py +115 -0
  31. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +109 -0
  32. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +6 -11
  33. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +115 -0
  34. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/datetime_utc.py +23 -41
  35. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +9 -28
  36. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +7 -3
  37. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +17 -23
  38. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/future_imports.py +111 -0
  39. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +80 -0
  40. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +89 -0
  41. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/imp_migrations.py +76 -0
  42. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +29 -48
  43. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +9 -4
  44. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +10 -107
  45. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/locale_deprecations.py +85 -0
  46. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +8 -7
  47. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +66 -0
  48. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +70 -0
  49. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +68 -0
  50. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/os_deprecations.py +29 -48
  51. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +11 -14
  52. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +230 -0
  53. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/pipes_migrations.py +71 -0
  54. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +30 -40
  55. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/platform_deprecations.py +91 -0
  56. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +85 -85
  57. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +9 -21
  58. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +5 -5
  59. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/re_deprecations.py +57 -54
  60. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/removed_modules_312.py +81 -0
  61. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +8 -4
  62. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +8 -4
  63. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +13 -42
  64. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/string_formatting.py +70 -50
  65. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +17 -27
  66. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +41 -21
  67. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +64 -0
  68. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +74 -0
  69. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +12 -20
  70. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +67 -172
  71. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +6 -28
  72. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +25 -45
  73. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +52 -41
  74. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +6 -45
  75. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -2
  76. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +22 -51
  77. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/uu_migrations.py +70 -0
  78. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +70 -0
  79. openrewrite_migrate_python-0.15.2/src/openrewrite_migrate_python/migrate/xml_deprecations.py +131 -0
  80. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python.egg-info/PKG-INFO +2 -2
  81. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +7 -3
  82. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python.egg-info/requires.txt +1 -1
  83. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_ast_deprecations.py +15 -0
  84. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_asyncio_coroutine_to_async.py +60 -2
  85. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_asyncio_deprecations.py +37 -0
  86. openrewrite_migrate_python-0.15.2/tests/test_backoff_to_tenacity.py +828 -0
  87. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_calendar_deprecations.py +15 -0
  88. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_cgi_parse_deprecations.py +17 -2
  89. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_configparser_deprecations.py +25 -0
  90. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_datetime_utc.py +31 -0
  91. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_distutils_deprecations.py +15 -0
  92. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_functools_deprecations.py +34 -0
  93. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_gettext_deprecations.py +9 -2
  94. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_html_parser_deprecations.py +56 -0
  95. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_langchain_classic_imports.py +11 -0
  96. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_langchain_provider_imports.py +19 -4
  97. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_locale_deprecations.py +21 -0
  98. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_os_deprecations.py +32 -2
  99. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pathlib_deprecations.py +21 -0
  100. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pep594_migrations.py +12 -0
  101. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pkgutil_deprecations.py +44 -0
  102. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_platform_deprecations.py +47 -2
  103. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pydantic_deprecations.py +146 -0
  104. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_re_deprecations.py +57 -0
  105. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_removed_modules_312.py +13 -0
  106. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_ssl_deprecations.py +22 -0
  107. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_string_formatting.py +149 -15
  108. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_sys_deprecations.py +9 -0
  109. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_sys_last_deprecations.py +44 -0
  110. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_tarfile_deprecations.py +29 -2
  111. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_tempfile_deprecations.py +21 -0
  112. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_threading_deprecations.py +27 -0
  113. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_typing_alias_moves.py +0 -23
  114. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_typing_deprecations.py +32 -0
  115. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_typing_union_to_pipe.py +68 -1
  116. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_unittest_deprecations.py +35 -0
  117. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_urllib_deprecations.py +20 -0
  118. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_xml_deprecations.py +19 -0
  119. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -32
  120. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +0 -258
  121. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/_from_imports.py +0 -68
  122. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -55
  123. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -71
  124. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -508
  125. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -204
  126. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -205
  127. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -254
  128. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/future_imports.py +0 -155
  129. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -148
  130. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -106
  131. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -125
  132. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -190
  133. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -116
  134. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -120
  135. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -118
  136. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -420
  137. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -121
  138. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -173
  139. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -145
  140. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -113
  141. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -123
  142. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -120
  143. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -120
  144. openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -188
  145. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/setup.cfg +0 -0
  146. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_code_quality_python/__init__.py +0 -0
  147. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/__init__.py +0 -0
  148. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
  149. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  150. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/_slots.py +0 -0
  151. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
  152. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
  153. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  154. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  155. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
  156. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  157. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  158. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
  159. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  160. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  161. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  162. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  163. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  164. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  165. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_aifc_migrations.py +0 -0
  166. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_array_deprecations.py +0 -0
  167. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_avro_migrations.py +0 -0
  168. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_change_import_recipes.py +0 -0
  169. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_collections_abc_migrations.py +0 -0
  170. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_distutils_migrations.py +0 -0
  171. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_future_imports.py +0 -0
  172. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_imp_migrations.py +0 -0
  173. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_langchain_community_imports.py +0 -0
  174. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  175. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_macpath_deprecations.py +0 -0
  176. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_mailcap_migrations.py +0 -0
  177. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_markers.py +0 -0
  178. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pep594_system_migrations.py +0 -0
  179. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pydantic_fields.py +0 -0
  180. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_pydantic_model_fields.py +0 -0
  181. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_shutil_deprecations.py +0 -0
  182. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_socket_deprecations.py +0 -0
  183. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_threading_extended.py +0 -0
  184. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_threading_is_alive_deprecation.py +0 -0
  185. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_typing_callable.py +0 -0
  186. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_upgrade_recipes.py +0 -0
  187. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_upgrade_to_pydantic.py +0 -0
  188. {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.2}/tests/test_xdrlib_migrations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.15.0.dev20260922170519
3
+ Version: 0.15.2
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.92.3; extra == "dev"
22
+ Requires-Dist: openrewrite>=8.92.18; 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.15.0.dev20260922170519" # Updated dynamically during release
8
+ version = "0.15.2" # 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.92.3",
31
+ "openrewrite>=8.92.18",
32
32
  # Needed so type-attribution tests can resolve `pydantic` types.
33
33
  "pydantic>=2.11",
34
34
  "pytest>=8.0.0",
@@ -2,6 +2,7 @@
2
2
 
3
3
  from .all_branches_identical import AllBranchesIdentical
4
4
  from .boolean_checks_not_inverted import BooleanChecksNotInverted
5
+ from .code_quality import CodeQuality
5
6
  from .collapsible_if_statements import CollapsibleIfStatements
6
7
  from .merge_identical_branches import MergeIdenticalBranches
7
8
  from .remove_duplicate_conditions import RemoveDuplicateConditions
@@ -13,6 +14,7 @@ from .simplify_redundant_logical_expression import SimplifyRedundantLogicalExpre
13
14
  __all__ = [
14
15
  "AllBranchesIdentical",
15
16
  "BooleanChecksNotInverted",
17
+ "CodeQuality",
16
18
  "CollapsibleIfStatements",
17
19
  "MergeIdenticalBranches",
18
20
  "RemoveDuplicateConditions",
@@ -4,12 +4,15 @@ Python indentation lives in the ``Space`` before each token, so moving a block
4
4
  to a shallower nesting level means rewriting every line it spans.
5
5
  """
6
6
 
7
- from typing import Optional, TypeVar
7
+ from typing import Optional, TypeVar, cast
8
8
 
9
+ from rewrite.java.markers import TrailingComma
9
10
  from rewrite.java.support_types import Space
11
+ from rewrite.java.tree import J
12
+ from rewrite.markers import Marker
10
13
  from rewrite.python.visitor import PythonVisitor
11
14
 
12
- T = TypeVar("T")
15
+ T = TypeVar("T", bound=J)
13
16
 
14
17
 
15
18
  def inherited_prefix(dropped: Space, body: Space) -> Space:
@@ -65,6 +68,20 @@ class _Reindent(PythonVisitor[None]):
65
68
  return space
66
69
  return Space(_comments=comments, _whitespace=whitespace)
67
70
 
71
+ def visit_marker(self, marker: Marker, p: None) -> Marker:
72
+ # The line break before the bracket closing a trailing-comma list
73
+ # lives on the comma.
74
+ if isinstance(marker, TrailingComma):
75
+ suffix = self.visit_space(marker.suffix, p)
76
+ if suffix is not marker.suffix:
77
+ return marker.replace(_suffix=suffix)
78
+ return marker
79
+
80
+
81
+ def reindent_space(space: Space, old_base: str, new_base: str) -> Space:
82
+ """:func:`reindent` for trivia that has no tree to travel with."""
83
+ return _Reindent(old_base, new_base).visit_space(space, None)
84
+
68
85
 
69
86
  def reindent(tree: T, old_base: str, new_base: str) -> T:
70
87
  """Re-base every line in *tree* that is indented at least to *old_base*.
@@ -74,4 +91,4 @@ def reindent(tree: T, old_base: str, new_base: str) -> T:
74
91
  """
75
92
  if old_base == new_base:
76
93
  return tree
77
- return _Reindent(old_base, new_base).visit(tree, None)
94
+ return cast(T, _Reindent(old_base, new_base).visit(tree, None))
@@ -0,0 +1,38 @@
1
+ """Semantic equality of LST nodes, for recipes that keep one of two equal subtrees."""
2
+
3
+ from typing import List, Optional
4
+
5
+ from rewrite.java.support_types import J, Space
6
+ from rewrite.python.template.comparator import PythonComparatorVisitor
7
+ from rewrite.python.visitor import PythonVisitor
8
+ from rewrite.visitor import Cursor
9
+
10
+
11
+ class _CommentTexts(PythonVisitor[List[str]]):
12
+ def visit_space(self, space: Optional[Space], p: List[str]) -> Space:
13
+ if space is not None:
14
+ p.extend(comment.text for comment in space.comments)
15
+ return super().visit_space(space, p)
16
+
17
+
18
+ def _comment_texts(tree: J) -> List[str]:
19
+ texts: List[str] = []
20
+ _CommentTexts().visit(tree, texts)
21
+ return texts
22
+
23
+
24
+ class SemanticallyEqual:
25
+ """Compare two LST nodes for structural equality."""
26
+
27
+ @staticmethod
28
+ def are_equal(a: Optional[J], b: Optional[J]) -> bool:
29
+ """Whether *a* and *b* are the same code carrying the same comments.
30
+
31
+ Whitespace is ignored. Comments must match, so a recipe dropping either copy loses none.
32
+ """
33
+ if a is None or b is None:
34
+ return a is b
35
+ return (
36
+ PythonComparatorVisitor().compare(a, b, Cursor(None, Cursor.ROOT_VALUE))
37
+ and _comment_texts(a) == _comment_texts(b)
38
+ )
@@ -0,0 +1,36 @@
1
+ from rewrite.java.tree import (
2
+ Binary as JBinary,
3
+ Expression,
4
+ FieldAccess,
5
+ Identifier,
6
+ Literal,
7
+ Parentheses,
8
+ Ternary,
9
+ Unary,
10
+ )
11
+ from rewrite.python.tree import Binary as PyBinary
12
+
13
+
14
+ def is_side_effect_free(expr: Expression) -> bool:
15
+ """Whether evaluating ``expr`` is taken to have no side effects.
16
+
17
+ Names, literals, attribute reads and operators over them are taken as pure.
18
+ A call, subscript, walrus, ``await`` or any other node may run code that matters.
19
+ """
20
+ if isinstance(expr, (Identifier, Literal)):
21
+ return True
22
+ if isinstance(expr, FieldAccess):
23
+ return is_side_effect_free(expr.target)
24
+ if isinstance(expr, Parentheses):
25
+ return isinstance(expr.tree, Expression) and is_side_effect_free(expr.tree)
26
+ if isinstance(expr, Unary):
27
+ return is_side_effect_free(expr.expression)
28
+ if isinstance(expr, (JBinary, PyBinary)):
29
+ return is_side_effect_free(expr.left) and is_side_effect_free(expr.right)
30
+ if isinstance(expr, Ternary):
31
+ return (
32
+ is_side_effect_free(expr.condition)
33
+ and is_side_effect_free(expr.true_part)
34
+ and is_side_effect_free(expr.false_part)
35
+ )
36
+ return False
@@ -12,7 +12,7 @@ from typing import Optional, Tuple
12
12
 
13
13
  from rewrite import Cursor
14
14
  from rewrite.java.support_types import Space
15
- from rewrite.java.tree import Block, ClassDeclaration, If, MethodDeclaration
15
+ from rewrite.java.tree import Block, ClassDeclaration, If, J, MethodDeclaration
16
16
  from rewrite.python.tree import CompilationUnit
17
17
 
18
18
 
@@ -81,9 +81,11 @@ def space_after_block(cursor: Cursor) -> Optional[Space]:
81
81
  know how to look past.
82
82
  """
83
83
  child = cursor.value
84
+ if not isinstance(child, J):
85
+ return None
84
86
  for parent in islice(cursor.get_path(), 1, None):
85
87
  if isinstance(parent, (Block, CompilationUnit)):
86
- statements = parent._statements
88
+ statements = parent.padding.statements
87
89
  index = next(
88
90
  (i for i, s in enumerate(statements) if s.element.id == child.id),
89
91
  None,
@@ -125,7 +127,8 @@ def splice_out(
125
127
  continue
126
128
  if pending is not None:
127
129
  head, line = pending
128
- _, tail = split_line(padded.element.prefix)
130
+ prefix = padded.element.prefix
131
+ _, tail = split_line(prefix) or (Space.EMPTY, prefix)
129
132
  padded = padded.replace(
130
133
  _element=padded.element.replace(
131
134
  _prefix=concat(
@@ -15,7 +15,9 @@ from rewrite import ExecutionContext, Recipe, TreeVisitor
15
15
  from rewrite.category import CategoryDescriptor
16
16
  from rewrite.decorators import categorize
17
17
  from rewrite.marketplace import Python
18
+ from rewrite.python.tree import CompilationUnit
18
19
  from rewrite.python.visitor import PythonVisitor
20
+ from rewrite.java.tree import J
19
21
  from rewrite.java.tree import (
20
22
  Block,
21
23
  If,
@@ -27,6 +29,7 @@ from openrewrite_code_quality_python.codequality._reindent import (
27
29
  reindent,
28
30
  )
29
31
  from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
32
+ from openrewrite_code_quality_python.codequality._side_effects import is_side_effect_free
30
33
 
31
34
  _CodeQuality = [
32
35
  *Python,
@@ -68,6 +71,17 @@ def _collect_bodies(if_: If) -> list[Statement]:
68
71
  return bodies
69
72
 
70
73
 
74
+ def _conditions_side_effect_free(if_: If) -> bool:
75
+ current = if_
76
+ while True:
77
+ if not is_side_effect_free(current.if_condition.tree):
78
+ return False
79
+ else_part = current.else_part
80
+ if else_part is None or not isinstance(else_part.body, If):
81
+ return True
82
+ current = else_part.body
83
+
84
+
71
85
  def _all_bodies_equal(bodies: list[Statement]) -> bool:
72
86
  """Return True when every body is semantically equal to the first."""
73
87
  if len(bodies) < 2:
@@ -90,7 +104,8 @@ class AllBranchesIdentical(Recipe):
90
104
  and eliminates a source of confusion for future readers.
91
105
 
92
106
  Only applies when the chain has an explicit ``else`` so that the
93
- branches truly cover all cases.
107
+ branches truly cover all cases, and when no condition may have a
108
+ side effect that removing it would lose.
94
109
 
95
110
  Example:
96
111
  Before:
@@ -116,7 +131,8 @@ class AllBranchesIdentical(Recipe):
116
131
  return (
117
132
  "Replace `if`/`elif`/`else` chains where every branch has "
118
133
  "the same body with just the body, since the condition has "
119
- "no effect on what code executes."
134
+ "no effect on what code executes. A chain whose conditions may "
135
+ "have side effects, such as a call, is left alone."
120
136
  )
121
137
 
122
138
  @property
@@ -130,19 +146,23 @@ class AllBranchesIdentical(Recipe):
130
146
  def editor(self) -> TreeVisitor[Any, ExecutionContext]:
131
147
  class Visitor(PythonVisitor[ExecutionContext]):
132
148
  def visit_compilation_unit(self, cu, p):
133
- cu = super().visit_compilation_unit(cu, p)
134
- new_stmts = _replace_identical_ifs_padded(cu._statements)
149
+ visited = super().visit_compilation_unit(cu, p)
150
+ if not isinstance(visited, CompilationUnit):
151
+ return visited
152
+ cu = visited
153
+ new_stmts = _replace_identical_ifs_padded(cu.padding.statements)
135
154
  if new_stmts is not None:
136
155
  return cu.replace(_statements=new_stmts)
137
156
  return cu
138
157
 
139
158
  def visit_block(
140
159
  self, block: Block, p: ExecutionContext
141
- ) -> Optional[Block]:
142
- block = super().visit_block(block, p)
143
- if not isinstance(block, Block):
144
- return block
145
- new_stmts = _replace_identical_ifs_padded(block._statements)
160
+ ) -> J:
161
+ visited = super().visit_block(block, p)
162
+ if not isinstance(visited, Block):
163
+ return visited
164
+ block = visited
165
+ new_stmts = _replace_identical_ifs_padded(block.padding.statements)
146
166
  if new_stmts is not None:
147
167
  return block.replace(_statements=new_stmts)
148
168
  return block
@@ -163,7 +183,7 @@ def _replace_identical_ifs_padded(padded_stmts) -> Optional[list]:
163
183
  if not isinstance(s, If):
164
184
  result.append(rp)
165
185
  continue
166
- if not _has_explicit_else(s):
186
+ if not _has_explicit_else(s) or not _conditions_side_effect_free(s):
167
187
  result.append(rp)
168
188
  continue
169
189
  bodies = _collect_bodies(s)
@@ -172,12 +192,12 @@ def _replace_identical_ifs_padded(padded_stmts) -> Optional[list]:
172
192
  continue
173
193
 
174
194
  body = s.then_part
175
- if not isinstance(body, Block) or not body._statements:
195
+ if not isinstance(body, Block) or not body.padding.statements:
176
196
  result.append(rp)
177
197
  continue
178
198
 
179
199
  if_indent = line_indent(s.prefix)
180
- body_indent = line_indent(body._statements[0].element.prefix)
200
+ body_indent = line_indent(body.padding.statements[0].element.prefix)
181
201
  if (
182
202
  if_indent is None
183
203
  or body_indent is None
@@ -187,7 +207,7 @@ def _replace_identical_ifs_padded(padded_stmts) -> Optional[list]:
187
207
  continue
188
208
 
189
209
  body = reindent(body, body_indent, if_indent)
190
- first = body._statements[0]
210
+ first = body.padding.statements[0]
191
211
  # Each lifted statement keeps its own padding, which is where a `;`
192
212
  # separating it from the next one is recorded.
193
213
  result.append(
@@ -197,7 +217,7 @@ def _replace_identical_ifs_padded(padded_stmts) -> Optional[list]:
197
217
  )
198
218
  )
199
219
  )
200
- result.extend(body._statements[1:])
220
+ result.extend(body.padding.statements[1:])
201
221
  changed = True
202
222
 
203
223
  if not changed:
@@ -8,18 +8,23 @@ when written as ``a != b``, and ``not (not x)`` is just ``x``.
8
8
  See: https://rules.sonarsource.com/python/RSPEC-S1940/
9
9
  """
10
10
 
11
- from typing import Any, List, Optional
11
+ from typing import Any, List
12
12
 
13
- from rewrite import ExecutionContext, Recipe, TreeVisitor
13
+ from rewrite import Cursor, ExecutionContext, Recipe, TreeVisitor
14
14
  from rewrite.category import CategoryDescriptor
15
15
  from rewrite.decorators import categorize
16
16
  from rewrite.marketplace import Python
17
+ from rewrite.python.tree import Binary as PyBinary
17
18
  from rewrite.python.visitor import PythonVisitor
18
- from rewrite.java.support_types import JLeftPadded, Space
19
+ from rewrite.java.tree import J
20
+ from rewrite.java.support_types import JavaType, JLeftPadded
19
21
  from rewrite.java.tree import (
22
+ Assert,
20
23
  Binary as JBinary,
24
+ ControlParentheses,
21
25
  Expression,
22
26
  Parentheses,
27
+ Ternary,
23
28
  Unary,
24
29
  )
25
30
 
@@ -37,6 +42,59 @@ _OPERATOR_INVERSE = {
37
42
  JBinary.Type.GreaterThanOrEqual: JBinary.Type.LessThan,
38
43
  }
39
44
 
45
+ _EQUALITY = {JBinary.Type.Equal, JBinary.Type.NotEqual}
46
+
47
+ _COMPARISONS = {
48
+ *_OPERATOR_INVERSE,
49
+ PyBinary.Type.In,
50
+ PyBinary.Type.NotIn,
51
+ PyBinary.Type.Is,
52
+ PyBinary.Type.IsNot,
53
+ }
54
+
55
+ _NUMERIC = {
56
+ JavaType.Primitive.Boolean,
57
+ JavaType.Primitive.Int,
58
+ JavaType.Primitive.Long,
59
+ JavaType.Primitive.Double,
60
+ JavaType.Primitive.Float,
61
+ }
62
+
63
+
64
+ def _is_comparison(expr: Expression) -> bool:
65
+ return isinstance(expr, (JBinary, PyBinary)) and expr.operator in _COMPARISONS
66
+
67
+
68
+ def _is_invertible(comparison: JBinary) -> bool:
69
+ # `a < b < c` is one chained comparison whose last link is the outer node.
70
+ if _is_comparison(comparison.left) or _is_comparison(comparison.right):
71
+ return False
72
+ # Only numbers are totally ordered. A set orders by inclusion.
73
+ return comparison.operator in _EQUALITY or (
74
+ comparison.left.type in _NUMERIC and comparison.right.type in _NUMERIC
75
+ )
76
+
77
+
78
+ def _is_read_as_truth_value(cursor: Cursor) -> bool:
79
+ node = cursor.value
80
+ parent = cursor.parent_tree_cursor()
81
+ while isinstance(parent.value, Parentheses):
82
+ node = parent.value
83
+ parent = parent.parent_tree_cursor()
84
+ value = parent.value
85
+ if isinstance(value, ControlParentheses):
86
+ return True
87
+ if isinstance(value, Unary):
88
+ return value.operator == Unary.Type.Not
89
+ if isinstance(value, Ternary):
90
+ return value.condition is node
91
+ if isinstance(value, Assert):
92
+ return value.condition is node
93
+ # `a or b` yields an operand, so its operands are truth values only if it is one.
94
+ if isinstance(value, JBinary) and value.operator in (JBinary.Type.And, JBinary.Type.Or):
95
+ return _is_read_as_truth_value(parent)
96
+ return False
97
+
40
98
 
41
99
  @categorize(_CodeQuality)
42
100
  class BooleanChecksNotInverted(Recipe):
@@ -71,7 +129,10 @@ class BooleanChecksNotInverted(Recipe):
71
129
  return (
72
130
  "Replace inverted boolean comparisons like `not (a == b)` with the "
73
131
  "equivalent direct operator (`a != b`), and remove double negations "
74
- "like `not (not x)`."
132
+ "like `not (not x)`. An ordering such as `not (a < b)` is only "
133
+ "inverted where both operands' types resolve to numbers, a chained "
134
+ "comparison is left alone, and a double negation is only removed "
135
+ "where its value is read as a truth value, as in a condition."
75
136
  )
76
137
 
77
138
  @property
@@ -86,11 +147,11 @@ class BooleanChecksNotInverted(Recipe):
86
147
  class Visitor(PythonVisitor[ExecutionContext]):
87
148
  def visit_unary(
88
149
  self, unary: Unary, p: ExecutionContext
89
- ) -> Optional[Expression]:
90
- unary = super().visit_unary(unary, p)
91
-
92
- if not isinstance(unary, Unary):
93
- return unary
150
+ ) -> J:
151
+ visited = super().visit_unary(unary, p)
152
+ if not isinstance(visited, Unary):
153
+ return visited
154
+ unary = visited
94
155
 
95
156
  if unary.operator != Unary.Type.Not:
96
157
  return unary
@@ -103,11 +164,15 @@ class BooleanChecksNotInverted(Recipe):
103
164
  inner = inner.tree
104
165
 
105
166
  # Double negation: not (not x) -> x
106
- if isinstance(inner, Unary) and inner.operator == Unary.Type.Not:
167
+ if (
168
+ isinstance(inner, Unary)
169
+ and inner.operator == Unary.Type.Not
170
+ and _is_read_as_truth_value(self.cursor)
171
+ ):
107
172
  return inner.expression.replace(_prefix=unary.prefix)
108
173
 
109
174
  # Inverted comparison: not (a == b) -> a != b
110
- if isinstance(inner, JBinary):
175
+ if isinstance(inner, JBinary) and _is_invertible(inner):
111
176
  inverse = _OPERATOR_INVERSE.get(inner.operator)
112
177
  if inverse is not None:
113
178
  return inner.padding.replace(
@@ -0,0 +1,73 @@
1
+ """Composite recipe bundling the Python code quality fixes."""
2
+
3
+ from typing import List
4
+
5
+ from rewrite import Recipe
6
+ from rewrite.category import CategoryDescriptor
7
+ from rewrite.decorators import categorize
8
+ from rewrite.marketplace import Python
9
+
10
+ from .all_branches_identical import AllBranchesIdentical
11
+ from .boolean_checks_not_inverted import BooleanChecksNotInverted
12
+ from .collapsible_if_statements import CollapsibleIfStatements
13
+ from .merge_identical_branches import MergeIdenticalBranches
14
+ from .remove_duplicate_conditions import RemoveDuplicateConditions
15
+ from .remove_self_assignment import RemoveSelfAssignment
16
+ from .remove_unconditional_value_overwrite import RemoveUnconditionalValueOverwrite
17
+ from .simplify_boolean_literal import SimplifyBooleanLiteral
18
+ from .simplify_redundant_logical_expression import SimplifyRedundantLogicalExpression
19
+
20
+ _CodeQuality = [
21
+ *Python,
22
+ CategoryDescriptor(display_name="Code quality"),
23
+ ]
24
+
25
+
26
+ @categorize(_CodeQuality)
27
+ class CodeQuality(Recipe):
28
+ """
29
+ Apply every Python code quality fix.
30
+
31
+ Example:
32
+ Before:
33
+ if done == True:
34
+ if ready:
35
+ run()
36
+
37
+ After:
38
+ if done and ready:
39
+ run()
40
+ """
41
+
42
+ @property
43
+ def name(self) -> str:
44
+ return "org.openrewrite.python.codequality.CodeQuality"
45
+
46
+ @property
47
+ def display_name(self) -> str:
48
+ return "Code quality"
49
+
50
+ @property
51
+ def description(self) -> str:
52
+ return (
53
+ "Apply all Python code quality recipes that change code: simplify boolean "
54
+ "expressions, merge and collapse `if` chains, and remove dead conditions and "
55
+ "writes. Search-only recipes, which mark code for review, are not included."
56
+ )
57
+
58
+ @property
59
+ def tags(self) -> List[str]:
60
+ return ["python", "code-quality"]
61
+
62
+ def recipe_list(self) -> List[Recipe]:
63
+ return [
64
+ SimplifyBooleanLiteral(),
65
+ BooleanChecksNotInverted(),
66
+ SimplifyRedundantLogicalExpression(),
67
+ RemoveDuplicateConditions(),
68
+ AllBranchesIdentical(),
69
+ MergeIdenticalBranches(),
70
+ CollapsibleIfStatements(),
71
+ RemoveSelfAssignment(),
72
+ RemoveUnconditionalValueOverwrite(),
73
+ ]