openrewrite-migrate-python 0.5.0.dev20260407172149__tar.gz → 0.6.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 (135) hide show
  1. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/PKG-INFO +1 -1
  2. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/pyproject.toml +3 -2
  3. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/__init__.py +35 -0
  4. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/__init__.py +23 -0
  5. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +32 -0
  6. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +206 -0
  7. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +123 -0
  8. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +195 -0
  9. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +242 -0
  10. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +190 -0
  11. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +152 -0
  12. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +207 -0
  13. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +167 -0
  14. openrewrite_migrate_python-0.6.0/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +93 -0
  15. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/__init__.py +12 -0
  16. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +34 -46
  17. openrewrite_migrate_python-0.6.0/src/openrewrite_migrate_python/migrate/typing_callable.py +62 -0
  18. openrewrite_migrate_python-0.6.0/src/openrewrite_migrate_python/migrate/typing_deprecations.py +406 -0
  19. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
  20. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +12 -0
  21. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python.egg-info/entry_points.txt +1 -0
  22. openrewrite_migrate_python-0.6.0/src/openrewrite_migrate_python.egg-info/top_level.txt +2 -0
  23. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_pkgutil_deprecations.py +8 -10
  24. openrewrite_migrate_python-0.6.0/tests/test_typing_callable.py +34 -0
  25. openrewrite_migrate_python-0.6.0/tests/test_typing_deprecations.py +236 -0
  26. openrewrite_migrate_python-0.5.0.dev20260407172149/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -142
  27. openrewrite_migrate_python-0.5.0.dev20260407172149/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -326
  28. openrewrite_migrate_python-0.5.0.dev20260407172149/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -1
  29. openrewrite_migrate_python-0.5.0.dev20260407172149/tests/test_typing_callable.py +0 -57
  30. openrewrite_migrate_python-0.5.0.dev20260407172149/tests/test_typing_deprecations.py +0 -36
  31. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/setup.cfg +0 -0
  32. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/__init__.py +0 -0
  33. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  34. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
  35. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
  36. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  37. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
  38. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
  39. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  40. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
  41. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
  42. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
  43. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
  44. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
  45. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
  46. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
  47. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
  48. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
  49. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
  50. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
  51. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
  52. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  53. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  54. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  55. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
  56. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
  57. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
  58. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
  59. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
  60. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
  61. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
  62. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
  63. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
  64. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
  65. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
  66. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
  67. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
  68. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
  69. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
  70. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
  71. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  72. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
  73. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
  74. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
  75. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
  76. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  77. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
  78. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  79. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
  80. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  81. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  82. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  83. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  84. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
  85. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  86. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  87. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  88. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
  89. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
  90. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
  91. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
  92. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
  93. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  94. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
  95. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_aifc_migrations.py +0 -0
  96. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_array_deprecations.py +0 -0
  97. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_ast_deprecations.py +0 -0
  98. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_asyncio_coroutine_to_async.py +0 -0
  99. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_asyncio_deprecations.py +0 -0
  100. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_calendar_deprecations.py +0 -0
  101. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_cgi_parse_deprecations.py +0 -0
  102. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_change_import_recipes.py +0 -0
  103. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_collections_abc_migrations.py +0 -0
  104. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_configparser_deprecations.py +0 -0
  105. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_datetime_utc.py +0 -0
  106. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_distutils_deprecations.py +0 -0
  107. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_future_imports.py +0 -0
  108. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_gettext_deprecations.py +0 -0
  109. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_html_parser_deprecations.py +0 -0
  110. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_imp_migrations.py +0 -0
  111. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_langchain_classic_imports.py +0 -0
  112. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_langchain_community_imports.py +0 -0
  113. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_langchain_provider_imports.py +0 -0
  114. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_locale_deprecations.py +0 -0
  115. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  116. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_macpath_deprecations.py +0 -0
  117. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_os_deprecations.py +0 -0
  118. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_pep594_migrations.py +0 -0
  119. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_pep594_system_migrations.py +0 -0
  120. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_platform_deprecations.py +0 -0
  121. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_re_deprecations.py +0 -0
  122. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_removed_modules_312.py +0 -0
  123. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_shutil_deprecations.py +0 -0
  124. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_ssl_deprecations.py +0 -0
  125. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_string_formatting.py +0 -0
  126. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_sys_deprecations.py +0 -0
  127. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_sys_last_deprecations.py +0 -0
  128. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_tarfile_deprecations.py +0 -0
  129. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_threading_deprecations.py +0 -0
  130. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_threading_extended.py +0 -0
  131. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_threading_is_alive_deprecation.py +0 -0
  132. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_typing_union_to_pipe.py +0 -0
  133. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_unittest_deprecations.py +0 -0
  134. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.0}/tests/test_upgrade_recipes.py +0 -0
  135. {openrewrite_migrate_python-0.5.0.dev20260407172149 → openrewrite_migrate_python-0.6.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.5.0.dev20260407172149
3
+ Version: 0.6.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
@@ -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.5.0.dev20260407172149" # Updated dynamically during release
8
+ version = "0.6.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"
@@ -49,10 +49,11 @@ Issues = "https://github.com/openrewrite/rewrite-migrate-python/issues"
49
49
 
50
50
  [project.entry-points."openrewrite.recipes"]
51
51
  migrate_python = "openrewrite_migrate_python:activate"
52
+ code_quality_python = "openrewrite_code_quality_python:activate"
52
53
 
53
54
  [tool.setuptools.packages.find]
54
55
  where = ["src"]
55
- include = ["openrewrite_migrate_python", "openrewrite_migrate_python.*"]
56
+ include = ["openrewrite_migrate_python", "openrewrite_migrate_python.*", "openrewrite_code_quality_python", "openrewrite_code_quality_python.*"]
56
57
 
57
58
  [tool.setuptools.package-data]
58
59
  "*" = ["py.typed", "*.pyi"]
@@ -0,0 +1,35 @@
1
+ """OpenRewrite recipes for Python code quality improvements."""
2
+
3
+ import importlib
4
+ import inspect
5
+ import pkgutil
6
+
7
+ from rewrite import Recipe, RecipeMarketplace
8
+
9
+ __all__ = ["activate"]
10
+
11
+
12
+ def activate(marketplace: RecipeMarketplace) -> None:
13
+ """
14
+ Install all code quality recipes into the marketplace.
15
+
16
+ This function is called by the OpenRewrite discovery mechanism when the
17
+ openrewrite-code-quality-python package is found via entry points.
18
+
19
+ Args:
20
+ marketplace: The RecipeMarketplace to install recipes into
21
+ """
22
+ from rewrite.decorators import get_recipe_category
23
+
24
+ from . import codequality as codequality_pkg
25
+
26
+ # Auto-discover all Recipe subclasses from the codequality package
27
+ for module_info in pkgutil.walk_packages(
28
+ codequality_pkg.__path__, prefix=codequality_pkg.__name__ + "."
29
+ ):
30
+ module = importlib.import_module(module_info.name)
31
+ for _name, obj in inspect.getmembers(module, inspect.isclass):
32
+ if issubclass(obj, Recipe) and obj is not Recipe and obj.__module__ == module.__name__:
33
+ category = get_recipe_category(obj)
34
+ if category is not None:
35
+ marketplace.install(obj, category)
@@ -0,0 +1,23 @@
1
+ """Python code quality recipes."""
2
+
3
+ from .all_branches_identical import AllBranchesIdentical
4
+ from .boolean_checks_not_inverted import BooleanChecksNotInverted
5
+ from .collapsible_if_statements import CollapsibleIfStatements
6
+ from .merge_identical_branches import MergeIdenticalBranches
7
+ from .remove_duplicate_conditions import RemoveDuplicateConditions
8
+ from .remove_self_assignment import RemoveSelfAssignment
9
+ from .remove_unconditional_value_overwrite import RemoveUnconditionalValueOverwrite
10
+ from .simplify_boolean_literal import SimplifyBooleanLiteral
11
+ from .simplify_redundant_logical_expression import SimplifyRedundantLogicalExpression
12
+
13
+ __all__ = [
14
+ "AllBranchesIdentical",
15
+ "BooleanChecksNotInverted",
16
+ "CollapsibleIfStatements",
17
+ "MergeIdenticalBranches",
18
+ "RemoveDuplicateConditions",
19
+ "RemoveSelfAssignment",
20
+ "RemoveUnconditionalValueOverwrite",
21
+ "SimplifyBooleanLiteral",
22
+ "SimplifyRedundantLogicalExpression",
23
+ ]
@@ -0,0 +1,32 @@
1
+ """
2
+ Structural equality comparison for LST nodes.
3
+
4
+ Compares two tree nodes by printing them with normalized whitespace
5
+ and checking that the printed representations match. This provides
6
+ the same functionality as ``SemanticallyEqual`` from the Java SDK.
7
+ """
8
+
9
+ from rewrite.java.support_types import Space
10
+ from rewrite.python.printer import PythonPrinter
11
+
12
+ _printer = PythonPrinter()
13
+
14
+
15
+ class SemanticallyEqual:
16
+ """Compare two LST nodes for structural equality."""
17
+
18
+ @staticmethod
19
+ def are_equal(a, b) -> bool:
20
+ """Return True if *a* and *b* are semantically equal.
21
+
22
+ Both nodes are printed after stripping their leading prefix so
23
+ that insignificant whitespace differences are ignored.
24
+ """
25
+ if a is None and b is None:
26
+ return True
27
+ if a is None or b is None:
28
+ return False
29
+ return (
30
+ _printer.print(a.replace(_prefix=Space.EMPTY))
31
+ == _printer.print(b.replace(_prefix=Space.EMPTY))
32
+ )
@@ -0,0 +1,206 @@
1
+ """
2
+ Recipe to replace if/elif/else chains where every branch has the same body.
3
+
4
+ When all branches of a conditional execute identical code the condition
5
+ is meaningless. The entire ``if``/``elif``/``else`` chain can be
6
+ replaced with a single copy of the body, removing unnecessary
7
+ complexity and making it obvious that the code always runs.
8
+
9
+ See: https://rules.sonarsource.com/python/RSPEC-S3923/
10
+ """
11
+
12
+ from typing import Any, List, Optional
13
+
14
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
15
+ from rewrite.category import CategoryDescriptor
16
+ from rewrite.decorators import categorize
17
+ from rewrite.marketplace import Python
18
+ from rewrite.python.visitor import PythonVisitor
19
+ from rewrite.java.support_types import Space
20
+ from rewrite.java.tree import (
21
+ Block,
22
+ If,
23
+ Statement,
24
+ )
25
+ from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
26
+
27
+ _CodeQuality = [
28
+ *Python,
29
+ CategoryDescriptor(display_name="Code quality"),
30
+ ]
31
+
32
+
33
+ def _has_explicit_else(if_: If) -> bool:
34
+ """Return True if the chain ends with a plain ``else`` (not elif)."""
35
+ current = if_
36
+ while True:
37
+ else_part = current.else_part
38
+ if else_part is None:
39
+ return False
40
+ branch = else_part.body
41
+ if isinstance(branch, If):
42
+ current = branch
43
+ else:
44
+ # Reached a plain else block
45
+ return True
46
+
47
+
48
+ def _collect_bodies(if_: If) -> list[Statement]:
49
+ """Collect the then-part of every branch including the final else."""
50
+ bodies: list[Statement] = [if_.then_part]
51
+ current = if_
52
+ while True:
53
+ else_part = current.else_part
54
+ if else_part is None:
55
+ break
56
+ branch = else_part.body
57
+ if isinstance(branch, If):
58
+ bodies.append(branch.then_part)
59
+ current = branch
60
+ else:
61
+ # Plain else body
62
+ bodies.append(branch)
63
+ break
64
+ return bodies
65
+
66
+
67
+ def _all_bodies_equal(bodies: list[Statement]) -> bool:
68
+ """Return True when every body is semantically equal to the first."""
69
+ if len(bodies) < 2:
70
+ return False
71
+ first = bodies[0]
72
+ return all(
73
+ SemanticallyEqual.are_equal(first, body)
74
+ for body in bodies[1:]
75
+ )
76
+
77
+
78
+ @categorize(_CodeQuality)
79
+ class AllBranchesIdentical(Recipe):
80
+ """
81
+ Replace ``if``/``elif``/``else`` chains where every branch has
82
+ the same body with just the body.
83
+
84
+ When every branch of a conditional executes identical code, the
85
+ condition serves no purpose. Removing it makes the code shorter
86
+ and eliminates a source of confusion for future readers.
87
+
88
+ Only applies when the chain has an explicit ``else`` so that the
89
+ branches truly cover all cases.
90
+
91
+ Example:
92
+ Before:
93
+ if cond:
94
+ x()
95
+ else:
96
+ x()
97
+
98
+ After:
99
+ x()
100
+ """
101
+
102
+ @property
103
+ def name(self) -> str:
104
+ return "org.openrewrite.python.codequality.AllBranchesIdentical"
105
+
106
+ @property
107
+ def display_name(self) -> str:
108
+ return "Remove conditional with identical branches"
109
+
110
+ @property
111
+ def description(self) -> str:
112
+ return (
113
+ "Replace `if`/`elif`/`else` chains where every branch has "
114
+ "the same body with just the body, since the condition has "
115
+ "no effect on what code executes."
116
+ )
117
+
118
+ @property
119
+ def tags(self) -> List[str]:
120
+ return ["python", "code-quality", "RSPEC-S3923"]
121
+
122
+ @property
123
+ def estimated_effort_per_occurrence(self) -> int:
124
+ return 5
125
+
126
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
127
+ class Visitor(PythonVisitor[ExecutionContext]):
128
+ def visit_compilation_unit(self, cu, p):
129
+ cu = super().visit_compilation_unit(cu, p)
130
+ new_stmts = _replace_identical_ifs_padded(cu._statements)
131
+ if new_stmts is not None:
132
+ return cu.replace(_statements=new_stmts)
133
+ return cu
134
+
135
+ def visit_block(
136
+ self, block: Block, p: ExecutionContext
137
+ ) -> Optional[Block]:
138
+ block = super().visit_block(block, p)
139
+ if not isinstance(block, Block):
140
+ return block
141
+ new_stmts = _replace_identical_ifs_padded(block._statements)
142
+ if new_stmts is not None:
143
+ return block.replace(_statements=new_stmts)
144
+ return block
145
+
146
+ return Visitor()
147
+
148
+
149
+ def _replace_identical_ifs_padded(padded_stmts) -> Optional[list]:
150
+ """Replace if/elif/else with identical branches with just the body.
151
+
152
+ Works on a list of JRightPadded[Statement] and returns a new padded list,
153
+ or None if nothing changed.
154
+ """
155
+ from rewrite.java.support_types import JRightPadded
156
+ from rewrite.markers import Markers
157
+
158
+ result = []
159
+ changed = False
160
+ for rp in padded_stmts:
161
+ s = rp.element
162
+ if not isinstance(s, If):
163
+ result.append(rp)
164
+ continue
165
+ if not _has_explicit_else(s):
166
+ result.append(rp)
167
+ continue
168
+ bodies = _collect_bodies(s)
169
+ if not _all_bodies_equal(bodies):
170
+ result.append(rp)
171
+ continue
172
+
173
+ # Extract the body statements from the then_part Block.
174
+ body = s.then_part
175
+ if isinstance(body, Block) and body._statements:
176
+ first = True
177
+ for body_rp in body._statements:
178
+ bs = body_rp.element
179
+ if first:
180
+ # First body statement gets the if statement's prefix
181
+ new_elem = bs.replace(_prefix=s.prefix)
182
+ result.append(rp.replace(_element=new_elem))
183
+ first = False
184
+ else:
185
+ # Subsequent body statements keep their whitespace
186
+ # but with one indent level removed
187
+ ws = bs.prefix.whitespace
188
+ if_ws = s.prefix.whitespace
189
+ base = if_ws.split("\n")[-1] if "\n" in if_ws else if_ws
190
+ body_indent = ws.split("\n")[-1] if "\n" in ws else ws
191
+ if len(body_indent) > len(base):
192
+ new_ws = ws.replace(body_indent, base, 1)
193
+ else:
194
+ new_ws = ws
195
+ new_elem = bs.replace(_prefix=Space(
196
+ _comments=bs.prefix.comments,
197
+ _whitespace=new_ws,
198
+ ))
199
+ result.append(body_rp.replace(_element=new_elem))
200
+ changed = True
201
+ else:
202
+ result.append(rp)
203
+
204
+ if not changed:
205
+ return None
206
+ return result
@@ -0,0 +1,123 @@
1
+ """
2
+ Recipe to simplify inverted boolean checks.
3
+
4
+ Negating a comparison operator is harder to read than using the
5
+ opposite operator directly. For example, ``not (a == b)`` is clearer
6
+ when written as ``a != b``, and ``not (not x)`` is just ``x``.
7
+
8
+ See: https://rules.sonarsource.com/python/RSPEC-S1940/
9
+ """
10
+
11
+ from typing import Any, List, Optional
12
+
13
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
14
+ from rewrite.category import CategoryDescriptor
15
+ from rewrite.decorators import categorize
16
+ from rewrite.marketplace import Python
17
+ from rewrite.python.visitor import PythonVisitor
18
+ from rewrite.java.support_types import JLeftPadded, Space
19
+ from rewrite.java.tree import (
20
+ Binary as JBinary,
21
+ Expression,
22
+ Parentheses,
23
+ Unary,
24
+ )
25
+
26
+ _CodeQuality = [
27
+ *Python,
28
+ CategoryDescriptor(display_name="Code quality"),
29
+ ]
30
+
31
+ _OPERATOR_INVERSE = {
32
+ JBinary.Type.Equal: JBinary.Type.NotEqual,
33
+ JBinary.Type.NotEqual: JBinary.Type.Equal,
34
+ JBinary.Type.LessThan: JBinary.Type.GreaterThanOrEqual,
35
+ JBinary.Type.GreaterThan: JBinary.Type.LessThanOrEqual,
36
+ JBinary.Type.LessThanOrEqual: JBinary.Type.GreaterThan,
37
+ JBinary.Type.GreaterThanOrEqual: JBinary.Type.LessThan,
38
+ }
39
+
40
+
41
+ @categorize(_CodeQuality)
42
+ class BooleanChecksNotInverted(Recipe):
43
+ """
44
+ Replace inverted boolean checks with the equivalent direct comparison.
45
+
46
+ Writing ``not (a == b)`` obscures the intent when ``a != b`` says the
47
+ same thing more directly. Likewise ``not (not x)`` is simply ``x``.
48
+ Flipping the operator or removing the double negation makes the
49
+ condition immediately obvious to the reader.
50
+
51
+ Example:
52
+ Before:
53
+ if not (a == b):
54
+ pass
55
+
56
+ After:
57
+ if a != b:
58
+ pass
59
+ """
60
+
61
+ @property
62
+ def name(self) -> str:
63
+ return "org.openrewrite.python.codequality.BooleanChecksNotInverted"
64
+
65
+ @property
66
+ def display_name(self) -> str:
67
+ return "Boolean checks should not be inverted"
68
+
69
+ @property
70
+ def description(self) -> str:
71
+ return (
72
+ "Replace inverted boolean comparisons like `not (a == b)` with the "
73
+ "equivalent direct operator (`a != b`), and remove double negations "
74
+ "like `not (not x)`."
75
+ )
76
+
77
+ @property
78
+ def tags(self) -> List[str]:
79
+ return ["python", "code-quality", "RSPEC-S1940"]
80
+
81
+ @property
82
+ def estimated_effort_per_occurrence(self) -> int:
83
+ return 2
84
+
85
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
86
+ class Visitor(PythonVisitor[ExecutionContext]):
87
+ def visit_unary(
88
+ 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
94
+
95
+ if unary.operator != Unary.Type.Not:
96
+ return unary
97
+
98
+ expr = unary.expression
99
+
100
+ # Unwrap parentheses to inspect inner expression
101
+ inner = expr
102
+ if isinstance(inner, Parentheses):
103
+ inner = inner.tree
104
+
105
+ # Double negation: not (not x) -> x
106
+ if isinstance(inner, Unary) and inner.operator == Unary.Type.Not:
107
+ return inner.expression.replace(_prefix=unary.prefix)
108
+
109
+ # Inverted comparison: not (a == b) -> a != b
110
+ if isinstance(inner, JBinary):
111
+ inverse = _OPERATOR_INVERSE.get(inner.operator)
112
+ if inverse is not None:
113
+ return inner.padding.replace(
114
+ _operator=JLeftPadded(
115
+ _before=inner.padding.operator.before,
116
+ _element=inverse,
117
+ _markers=inner.padding.operator.markers,
118
+ )
119
+ ).replace(_prefix=unary.prefix)
120
+
121
+ return unary
122
+
123
+ return Visitor()
@@ -0,0 +1,195 @@
1
+ """
2
+ Recipe to merge collapsible ``if`` statements.
3
+
4
+ When an ``if`` block contains only another ``if`` and neither has an
5
+ ``else`` branch, the two conditions can be combined with ``and`` into
6
+ a single ``if``. This reduces nesting and makes the control flow
7
+ easier to follow.
8
+
9
+ See: https://rules.sonarsource.com/python/RSPEC-S1066/
10
+ """
11
+
12
+ from typing import Any, List, Optional
13
+
14
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
15
+ from rewrite.category import CategoryDescriptor
16
+ from rewrite.decorators import categorize
17
+ from rewrite.marketplace import Python
18
+ from rewrite.markers import Markers
19
+ from rewrite.python.visitor import PythonVisitor
20
+ from rewrite.java.support_types import JLeftPadded, JRightPadded, Space
21
+ from rewrite.java.tree import (
22
+ Binary as JBinary,
23
+ Block,
24
+ ControlParentheses,
25
+ Expression,
26
+ If,
27
+ Parentheses,
28
+ )
29
+ from rewrite.utils import random_id
30
+
31
+ _CodeQuality = [
32
+ *Python,
33
+ CategoryDescriptor(display_name="Code quality"),
34
+ ]
35
+
36
+
37
+ def _contains_or(expr: Expression) -> bool:
38
+ """Return True if the top-level expression is an ``or`` binary."""
39
+ return isinstance(expr, JBinary) and expr.operator == JBinary.Type.Or
40
+
41
+
42
+ def _dedent_block(block: Block, indent: str) -> Block:
43
+ """Remove one level of *indent* from every statement in *block*."""
44
+ if not indent:
45
+ return block
46
+ new_stmts = []
47
+ for rp in block._statements:
48
+ s = rp.element
49
+ ws = s.prefix.whitespace
50
+ if indent in ws:
51
+ ws = ws.replace(indent, "", 1)
52
+ s = s.replace(_prefix=Space(_comments=s.prefix.comments, _whitespace=ws))
53
+ rp = rp.replace(_element=s)
54
+ new_stmts.append(rp)
55
+ return block.replace(_statements=new_stmts)
56
+
57
+
58
+ @categorize(_CodeQuality)
59
+ class CollapsibleIfStatements(Recipe):
60
+ """
61
+ Merge nested ``if`` statements into a single ``if`` with ``and``.
62
+
63
+ An ``if`` whose body is nothing but another ``if``--and neither has
64
+ an ``else`` clause--can always be rewritten as one ``if`` that
65
+ combines both conditions with ``and``. The merged form is shorter,
66
+ has less indentation, and communicates the same logic.
67
+
68
+ Example:
69
+ Before:
70
+ if a:
71
+ if b:
72
+ do_something()
73
+
74
+ After:
75
+ if a and b:
76
+ do_something()
77
+ """
78
+
79
+ @property
80
+ def name(self) -> str:
81
+ return "org.openrewrite.python.codequality.CollapsibleIfStatements"
82
+
83
+ @property
84
+ def display_name(self) -> str:
85
+ return "Merge collapsible if statements"
86
+
87
+ @property
88
+ def description(self) -> str:
89
+ return (
90
+ "Combine nested `if` statements that have no `else` branch into "
91
+ "a single `if` joined with `and`."
92
+ )
93
+
94
+ @property
95
+ def tags(self) -> List[str]:
96
+ return ["python", "code-quality", "RSPEC-S1066"]
97
+
98
+ @property
99
+ def estimated_effort_per_occurrence(self) -> int:
100
+ return 5
101
+
102
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
103
+ class Visitor(PythonVisitor[ExecutionContext]):
104
+ def visit_if(
105
+ self, if_: If, p: ExecutionContext
106
+ ) -> Optional[If]:
107
+ if_ = super().visit_if(if_, p)
108
+
109
+ if not isinstance(if_, If):
110
+ return if_
111
+
112
+ # Outer if must have no else/elif
113
+ if if_.else_part is not None:
114
+ return if_
115
+
116
+ then = if_.then_part
117
+ if not isinstance(then, Block):
118
+ return if_
119
+
120
+ stmts = then.statements
121
+ if len(stmts) != 1:
122
+ return if_
123
+
124
+ inner = stmts[0]
125
+ if not isinstance(inner, If):
126
+ return if_
127
+
128
+ # Inner if must have no else/elif
129
+ if inner.else_part is not None:
130
+ return if_
131
+
132
+ outer_cond = if_.if_condition.tree
133
+ inner_cond = inner.if_condition.tree
134
+
135
+ # Wrap inner condition in parens if it contains `or`
136
+ # to preserve precedence with the new `and`
137
+ if _contains_or(inner_cond):
138
+ inner_cond = Parentheses(
139
+ _id=random_id(),
140
+ _prefix=Space.SINGLE_SPACE,
141
+ _markers=Markers.EMPTY,
142
+ _tree=JRightPadded(
143
+ _element=inner_cond.replace(_prefix=Space.EMPTY),
144
+ _after=Space.EMPTY,
145
+ _markers=Markers.EMPTY,
146
+ ),
147
+ )
148
+ else:
149
+ inner_cond = inner_cond.replace(_prefix=Space.SINGLE_SPACE)
150
+
151
+ combined = JBinary(
152
+ _id=random_id(),
153
+ _prefix=outer_cond.prefix,
154
+ _markers=Markers.EMPTY,
155
+ _left=outer_cond.replace(_prefix=Space.EMPTY),
156
+ _operator=JLeftPadded(
157
+ _before=Space.SINGLE_SPACE,
158
+ _element=JBinary.Type.And,
159
+ _markers=Markers.EMPTY,
160
+ ),
161
+ _right=inner_cond,
162
+ _type=None,
163
+ )
164
+
165
+ new_condition = ControlParentheses(
166
+ _id=if_.if_condition.id,
167
+ _prefix=if_.if_condition.prefix,
168
+ _markers=if_.if_condition.markers,
169
+ _tree=JRightPadded(
170
+ _element=combined,
171
+ _after=if_.if_condition.padding.tree.after,
172
+ _markers=if_.if_condition.padding.tree.markers,
173
+ ),
174
+ )
175
+
176
+ # Dedent the inner body by one level since it was
177
+ # nested inside the inner if that we're removing.
178
+ inner_body = inner.then_part
179
+ if isinstance(inner_body, Block):
180
+ # The inner if's prefix tells us the indent unit
181
+ ws = inner.prefix.whitespace
182
+ indent = ws.split("\n")[-1] if "\n" in ws else ws
183
+ inner_body = _dedent_block(inner_body, indent)
184
+
185
+ return if_.replace(
186
+ _if_condition=new_condition,
187
+ ).padding.replace(
188
+ _then_part=JRightPadded(
189
+ _element=inner_body,
190
+ _after=if_.padding.then_part.after,
191
+ _markers=if_.padding.then_part.markers,
192
+ ),
193
+ )
194
+
195
+ return Visitor()