openrewrite-migrate-python 0.16.0.dev20261006155312__tar.gz → 0.16.0.dev20261007033737__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 (160) hide show
  1. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/PKG-INFO +1 -1
  2. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/pyproject.toml +1 -1
  3. openrewrite_migrate_python-0.16.0.dev20261007033737/src/openrewrite_code_quality_python/codequality/_side_effects.py +36 -0
  4. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +17 -3
  5. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +69 -5
  6. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +10 -5
  7. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +7 -2
  8. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +6 -2
  9. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +26 -1
  10. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/string_formatting.py +21 -3
  11. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
  12. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +1 -0
  13. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_asyncio_coroutine_to_async.py +27 -0
  14. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_string_formatting.py +51 -1
  15. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/setup.cfg +0 -0
  16. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/__init__.py +0 -0
  17. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
  18. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
  19. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
  20. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/_trivia.py +0 -0
  21. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/code_quality.py +0 -0
  22. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +0 -0
  23. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
  24. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +0 -0
  25. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +0 -0
  26. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
  27. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/__init__.py +0 -0
  28. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
  29. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/_from_imports.py +0 -0
  30. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -0
  31. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
  32. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  33. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/_slots.py +0 -0
  34. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -0
  35. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
  36. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
  37. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  38. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
  39. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
  40. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  41. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
  42. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
  43. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
  44. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
  45. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
  46. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
  47. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
  48. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
  49. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
  50. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
  51. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
  52. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
  53. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  54. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  55. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  56. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
  57. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
  58. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
  59. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
  60. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
  61. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
  62. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
  63. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
  64. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
  65. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
  66. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
  67. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
  68. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
  69. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
  70. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
  71. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
  72. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
  73. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
  74. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
  75. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  76. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
  77. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
  78. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
  79. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
  80. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  81. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
  82. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
  83. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
  84. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  85. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
  86. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  87. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  88. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
  89. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  90. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  91. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
  92. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  93. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  94. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  95. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
  96. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
  97. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
  98. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
  99. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
  100. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  101. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  102. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
  103. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  104. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_aifc_migrations.py +0 -0
  105. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_array_deprecations.py +0 -0
  106. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_ast_deprecations.py +0 -0
  107. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_asyncio_deprecations.py +0 -0
  108. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_avro_migrations.py +0 -0
  109. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_calendar_deprecations.py +0 -0
  110. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_cgi_parse_deprecations.py +0 -0
  111. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_change_import_recipes.py +0 -0
  112. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_collections_abc_migrations.py +0 -0
  113. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_configparser_deprecations.py +0 -0
  114. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_datetime_utc.py +0 -0
  115. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_distutils_deprecations.py +0 -0
  116. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_distutils_migrations.py +0 -0
  117. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_functools_deprecations.py +0 -0
  118. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_future_imports.py +0 -0
  119. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_gettext_deprecations.py +0 -0
  120. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_html_parser_deprecations.py +0 -0
  121. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_imp_migrations.py +0 -0
  122. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_langchain_classic_imports.py +0 -0
  123. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_langchain_community_imports.py +0 -0
  124. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_langchain_provider_imports.py +0 -0
  125. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_locale_deprecations.py +0 -0
  126. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  127. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_macpath_deprecations.py +0 -0
  128. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_mailcap_migrations.py +0 -0
  129. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_markers.py +0 -0
  130. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_os_deprecations.py +0 -0
  131. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pathlib_deprecations.py +0 -0
  132. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pep594_migrations.py +0 -0
  133. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pep594_system_migrations.py +0 -0
  134. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pkgutil_deprecations.py +0 -0
  135. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_platform_deprecations.py +0 -0
  136. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pydantic_deprecations.py +0 -0
  137. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pydantic_fields.py +0 -0
  138. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_pydantic_model_fields.py +0 -0
  139. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_re_deprecations.py +0 -0
  140. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_removed_modules_312.py +0 -0
  141. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_shutil_deprecations.py +0 -0
  142. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_socket_deprecations.py +0 -0
  143. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_ssl_deprecations.py +0 -0
  144. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_sys_deprecations.py +0 -0
  145. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_sys_last_deprecations.py +0 -0
  146. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_tarfile_deprecations.py +0 -0
  147. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_tempfile_deprecations.py +0 -0
  148. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_threading_deprecations.py +0 -0
  149. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_threading_extended.py +0 -0
  150. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_threading_is_alive_deprecation.py +0 -0
  151. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_typing_alias_moves.py +0 -0
  152. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_typing_callable.py +0 -0
  153. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_typing_deprecations.py +0 -0
  154. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_typing_union_to_pipe.py +0 -0
  155. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_unittest_deprecations.py +0 -0
  156. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_upgrade_recipes.py +0 -0
  157. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_upgrade_to_pydantic.py +0 -0
  158. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_urllib_deprecations.py +0 -0
  159. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/tests/test_xdrlib_migrations.py +0 -0
  160. {openrewrite_migrate_python-0.16.0.dev20261006155312 → openrewrite_migrate_python-0.16.0.dev20261007033737}/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.16.0.dev20261006155312
3
+ Version: 0.16.0.dev20261007033737
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.16.0.dev20261006155312" # Updated dynamically during release
8
+ version = "0.16.0.dev20261007033737" # 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"
@@ -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
@@ -27,6 +27,7 @@ from openrewrite_code_quality_python.codequality._reindent import (
27
27
  reindent,
28
28
  )
29
29
  from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
30
+ from openrewrite_code_quality_python.codequality._side_effects import is_side_effect_free
30
31
 
31
32
  _CodeQuality = [
32
33
  *Python,
@@ -68,6 +69,17 @@ def _collect_bodies(if_: If) -> list[Statement]:
68
69
  return bodies
69
70
 
70
71
 
72
+ def _conditions_side_effect_free(if_: If) -> bool:
73
+ current = if_
74
+ while True:
75
+ if not is_side_effect_free(current.if_condition.tree):
76
+ return False
77
+ else_part = current.else_part
78
+ if else_part is None or not isinstance(else_part.body, If):
79
+ return True
80
+ current = else_part.body
81
+
82
+
71
83
  def _all_bodies_equal(bodies: list[Statement]) -> bool:
72
84
  """Return True when every body is semantically equal to the first."""
73
85
  if len(bodies) < 2:
@@ -90,7 +102,8 @@ class AllBranchesIdentical(Recipe):
90
102
  and eliminates a source of confusion for future readers.
91
103
 
92
104
  Only applies when the chain has an explicit ``else`` so that the
93
- branches truly cover all cases.
105
+ branches truly cover all cases, and when no condition may have a
106
+ side effect that removing it would lose.
94
107
 
95
108
  Example:
96
109
  Before:
@@ -116,7 +129,8 @@ class AllBranchesIdentical(Recipe):
116
129
  return (
117
130
  "Replace `if`/`elif`/`else` chains where every branch has "
118
131
  "the same body with just the body, since the condition has "
119
- "no effect on what code executes."
132
+ "no effect on what code executes. A chain whose conditions may "
133
+ "have side effects, such as a call, is left alone."
120
134
  )
121
135
 
122
136
  @property
@@ -163,7 +177,7 @@ def _replace_identical_ifs_padded(padded_stmts) -> Optional[list]:
163
177
  if not isinstance(s, If):
164
178
  result.append(rp)
165
179
  continue
166
- if not _has_explicit_else(s):
180
+ if not _has_explicit_else(s) or not _conditions_side_effect_free(s):
167
181
  result.append(rp)
168
182
  continue
169
183
  bodies = _collect_bodies(s)
@@ -10,16 +10,20 @@ See: https://rules.sonarsource.com/python/RSPEC-S1940/
10
10
 
11
11
  from typing import Any, List, Optional
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.support_types import JavaType, JLeftPadded
19
20
  from rewrite.java.tree import (
21
+ Assert,
20
22
  Binary as JBinary,
23
+ ControlParentheses,
21
24
  Expression,
22
25
  Parentheses,
26
+ Ternary,
23
27
  Unary,
24
28
  )
25
29
 
@@ -37,6 +41,59 @@ _OPERATOR_INVERSE = {
37
41
  JBinary.Type.GreaterThanOrEqual: JBinary.Type.LessThan,
38
42
  }
39
43
 
44
+ _EQUALITY = {JBinary.Type.Equal, JBinary.Type.NotEqual}
45
+
46
+ _COMPARISONS = {
47
+ *_OPERATOR_INVERSE,
48
+ PyBinary.Type.In,
49
+ PyBinary.Type.NotIn,
50
+ PyBinary.Type.Is,
51
+ PyBinary.Type.IsNot,
52
+ }
53
+
54
+ _NUMERIC = {
55
+ JavaType.Primitive.Boolean,
56
+ JavaType.Primitive.Int,
57
+ JavaType.Primitive.Long,
58
+ JavaType.Primitive.Double,
59
+ JavaType.Primitive.Float,
60
+ }
61
+
62
+
63
+ def _is_comparison(expr: Expression) -> bool:
64
+ return isinstance(expr, (JBinary, PyBinary)) and expr.operator in _COMPARISONS
65
+
66
+
67
+ def _is_invertible(comparison: JBinary) -> bool:
68
+ # `a < b < c` is one chained comparison whose last link is the outer node.
69
+ if _is_comparison(comparison.left) or _is_comparison(comparison.right):
70
+ return False
71
+ # Only numbers are totally ordered. A set orders by inclusion.
72
+ return comparison.operator in _EQUALITY or (
73
+ comparison.left.type in _NUMERIC and comparison.right.type in _NUMERIC
74
+ )
75
+
76
+
77
+ def _is_read_as_truth_value(cursor: Cursor) -> bool:
78
+ node = cursor.value
79
+ parent = cursor.parent_tree_cursor()
80
+ while isinstance(parent.value, Parentheses):
81
+ node = parent.value
82
+ parent = parent.parent_tree_cursor()
83
+ value = parent.value
84
+ if isinstance(value, ControlParentheses):
85
+ return True
86
+ if isinstance(value, Unary):
87
+ return value.operator == Unary.Type.Not
88
+ if isinstance(value, Ternary):
89
+ return value.condition is node
90
+ if isinstance(value, Assert):
91
+ return value.condition is node
92
+ # `a or b` yields an operand, so its operands are truth values only if it is one.
93
+ if isinstance(value, JBinary) and value.operator in (JBinary.Type.And, JBinary.Type.Or):
94
+ return _is_read_as_truth_value(parent)
95
+ return False
96
+
40
97
 
41
98
  @categorize(_CodeQuality)
42
99
  class BooleanChecksNotInverted(Recipe):
@@ -71,7 +128,10 @@ class BooleanChecksNotInverted(Recipe):
71
128
  return (
72
129
  "Replace inverted boolean comparisons like `not (a == b)` with the "
73
130
  "equivalent direct operator (`a != b`), and remove double negations "
74
- "like `not (not x)`."
131
+ "like `not (not x)`. An ordering such as `not (a < b)` is only "
132
+ "inverted where both operands' types resolve to numbers, a chained "
133
+ "comparison is left alone, and a double negation is only removed "
134
+ "where its value is read as a truth value, as in a condition."
75
135
  )
76
136
 
77
137
  @property
@@ -103,11 +163,15 @@ class BooleanChecksNotInverted(Recipe):
103
163
  inner = inner.tree
104
164
 
105
165
  # Double negation: not (not x) -> x
106
- if isinstance(inner, Unary) and inner.operator == Unary.Type.Not:
166
+ if (
167
+ isinstance(inner, Unary)
168
+ and inner.operator == Unary.Type.Not
169
+ and _is_read_as_truth_value(self.cursor)
170
+ ):
107
171
  return inner.expression.replace(_prefix=unary.prefix)
108
172
 
109
173
  # Inverted comparison: not (a == b) -> a != b
110
- if isinstance(inner, JBinary):
174
+ if isinstance(inner, JBinary) and _is_invertible(inner):
111
175
  inverse = _OPERATOR_INVERSE.get(inner.operator)
112
176
  if inverse is not None:
113
177
  return inner.padding.replace(
@@ -19,13 +19,16 @@ from rewrite.markers import Markers
19
19
  from rewrite.python.visitor import PythonVisitor
20
20
  from rewrite.java.support_types import JLeftPadded, JRightPadded, Space
21
21
  from rewrite.java.tree import (
22
+ Assignment,
22
23
  Binary as JBinary,
23
24
  Block,
24
25
  ControlParentheses,
25
26
  Expression,
26
27
  If,
28
+ Lambda,
27
29
  Parentheses,
28
30
  Statement,
31
+ Ternary,
29
32
  )
30
33
  from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
31
34
  from rewrite.utils import random_id
@@ -36,8 +39,11 @@ _CodeQuality = [
36
39
  ]
37
40
 
38
41
 
39
- def _contains_and(expr: Expression) -> bool:
40
- """Return True if the top-level expression is an ``and`` binary."""
42
+ def _needs_parens_as_or_operand(expr: Expression) -> bool:
43
+ # A walrus, ternary or lambda binds looser than `or`. An `and` does not,
44
+ # and is parenthesized for readability.
45
+ if isinstance(expr, (Assignment, Ternary, Lambda)):
46
+ return True
41
47
  return isinstance(expr, JBinary) and expr.operator == JBinary.Type.And
42
48
 
43
49
 
@@ -160,8 +166,7 @@ def _merge_conditions(first: If, second: If) -> If:
160
166
  first_cond = first.if_condition.tree
161
167
  second_cond = second.if_condition.tree
162
168
 
163
- # Wrap conditions in parens if they contain ``and`` to preserve precedence
164
- if _contains_and(first_cond):
169
+ if _needs_parens_as_or_operand(first_cond):
165
170
  left = Parentheses(
166
171
  _id=random_id(),
167
172
  _prefix=first_cond.prefix,
@@ -175,7 +180,7 @@ def _merge_conditions(first: If, second: If) -> If:
175
180
  else:
176
181
  left = first_cond
177
182
 
178
- if _contains_and(second_cond):
183
+ if _needs_parens_as_or_operand(second_cond):
179
184
  right = Parentheses(
180
185
  _id=random_id(),
181
186
  _prefix=Space.SINGLE_SPACE,
@@ -18,6 +18,7 @@ from rewrite.marketplace import Python
18
18
  from rewrite.python.visitor import PythonVisitor
19
19
  from rewrite.java.tree import If, Expression
20
20
  from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
21
+ from openrewrite_code_quality_python.codequality._side_effects import is_side_effect_free
21
22
 
22
23
  _CodeQuality = [
23
24
  *Python,
@@ -79,7 +80,9 @@ class RemoveDuplicateConditions(Recipe):
79
80
  return (
80
81
  "Remove `elif` branches whose condition is identical to an earlier "
81
82
  "branch in the same `if`/`elif` chain, since the duplicate branch "
82
- "is dead code that can never execute."
83
+ "is dead code that can never execute. A condition that may have "
84
+ "side effects, such as a call, is kept, since evaluating it again "
85
+ "can give a different result."
83
86
  )
84
87
 
85
88
  @property
@@ -136,7 +139,9 @@ def _prune_chain(root: If, current: If, seen: list[Expression]) -> If:
136
139
  return root
137
140
 
138
141
  cond = branch.if_condition.tree
139
- is_dup = any(SemanticallyEqual.are_equal(cond, prev) for prev in seen)
142
+ is_dup = is_side_effect_free(cond) and any(
143
+ SemanticallyEqual.are_equal(cond, prev) for prev in seen
144
+ )
140
145
 
141
146
  if is_dup:
142
147
  # Skip this elif -- connect current to the duplicate's else
@@ -16,7 +16,7 @@ from rewrite.decorators import categorize
16
16
  from rewrite.marketplace import Python
17
17
  from rewrite.markers import Markers
18
18
  from rewrite.python.visitor import PythonVisitor
19
- from rewrite.java.support_types import JLeftPadded, Space
19
+ from rewrite.java.support_types import JavaType, JLeftPadded, Space
20
20
  from rewrite.java.tree import Binary as JBinary, Expression, Literal, Unary
21
21
  from rewrite.python.tree import Binary as PyBinary
22
22
  from rewrite.utils import random_id
@@ -81,7 +81,8 @@ class SimplifyBooleanLiteral(Recipe):
81
81
  def description(self) -> str:
82
82
  return (
83
83
  "Replace comparisons against boolean literals (`== True`, `!= False`, "
84
- "`is True`, etc.) with the simpler equivalent boolean expression."
84
+ "`is True`, etc.) with the simpler equivalent boolean expression. "
85
+ "Only fires where the other operand's type resolves to `bool`."
85
86
  )
86
87
 
87
88
  @property
@@ -153,6 +154,9 @@ def _simplify_comparison(
153
154
  other = right
154
155
  if bool_val is None:
155
156
  return None
157
+ # `2 == True` is False, but `2` is truthy.
158
+ if other.type != JavaType.Primitive.Boolean:
159
+ return None
156
160
 
157
161
  # Determine whether the result should be negated.
158
162
  # == True / is True -> x (identity)
@@ -168,6 +168,27 @@ class _YieldFromToAwaitVisitor(PythonVisitor[ExecutionContext]):
168
168
  return lambda_
169
169
 
170
170
 
171
+ class _BareYieldFinder(PythonVisitor[List[Yield]]):
172
+ """Collects each ``yield`` that is not a ``yield from``, outside nested functions."""
173
+
174
+ def visit_yield(self, yield_: Yield, p: List[Yield]) -> J:
175
+ if not isinstance(yield_.value, YieldFrom):
176
+ p.append(yield_)
177
+ return super().visit_yield(yield_, p)
178
+
179
+ def visit_method_declaration(self, method: MethodDeclaration, p: List[Yield]) -> J:
180
+ return method
181
+
182
+ def visit_lambda(self, lambda_: Lambda, p: List[Yield]) -> J:
183
+ return lambda_
184
+
185
+
186
+ def _has_bare_yield(body: J) -> bool:
187
+ found: List[Yield] = []
188
+ _BareYieldFinder().visit(body, found)
189
+ return bool(found)
190
+
191
+
171
192
  @categorize(_Python311)
172
193
  class MigrateAsyncioCoroutine(Recipe):
173
194
  """
@@ -205,7 +226,8 @@ class MigrateAsyncioCoroutine(Recipe):
205
226
  return (
206
227
  "Migrate functions using the deprecated `@asyncio.coroutine` decorator "
207
228
  "to use `async def` syntax. Also transforms `yield from` to `await`. "
208
- "The decorator was removed in Python 3.11."
229
+ "The decorator was removed in Python 3.11. A function whose body has "
230
+ "a bare `yield` is left alone, since it would become an async generator."
209
231
  )
210
232
 
211
233
  def editor(self) -> TreeVisitor[Any, ExecutionContext]:
@@ -219,6 +241,9 @@ class MigrateAsyncioCoroutine(Recipe):
219
241
 
220
242
  if not _has_asyncio_coroutine_decorator(method):
221
243
  return method
244
+ # A bare `yield` would make the `async def` an async generator.
245
+ if method.body is not None and _has_bare_yield(method.body):
246
+ return method
222
247
 
223
248
  # Step 1: Remove the @asyncio.coroutine decorator
224
249
  method = _remove_asyncio_coroutine_decorator(method)
@@ -19,9 +19,10 @@ from rewrite.decorators import categorize
19
19
  from rewrite.marketplace import Python
20
20
  from rewrite.markers import Markers
21
21
  from rewrite.python.visitor import PythonVisitor
22
- from rewrite.java.support_types import JRightPadded, Space
22
+ from rewrite.java.support_types import JavaType, JRightPadded, Space
23
23
  from rewrite.java.tree import Binary, Expression, FieldAccess, Identifier, J, Literal, MethodInvocation, Parentheses
24
24
  from rewrite.python.printer import PrintOutputCapture, PythonPrinter
25
+ from rewrite.python.type_utils import is_of_class_type
25
26
  from rewrite.python.tree import (
26
27
  CollectionLiteral,
27
28
  FormattedString,
@@ -126,6 +127,19 @@ def _is_side_effect_free(expr: Any) -> bool:
126
127
  return isinstance(expr, (Identifier, Literal, FieldAccess))
127
128
 
128
129
 
130
+ _NON_TUPLE_BUILTINS = ("list", "dict", "set", "frozenset")
131
+
132
+
133
+ def _resolves_to_non_tuple(expr: Expression) -> bool:
134
+ # A tuple operand is spread over the specifiers, so `"%s" % (1,)` is `"1"`.
135
+ # A class's supertypes may stop short of `tuple`, as a NamedTuple's do,
136
+ # so only types known not to be tuples qualify.
137
+ type_ = expr.type
138
+ return isinstance(type_, JavaType.Primitive) or any(
139
+ is_of_class_type(type_, name) for name in _NON_TUPLE_BUILTINS
140
+ )
141
+
142
+
129
143
  def _has_backslash(value_source: str) -> bool:
130
144
  """Check if the string content (between quotes) contains a backslash."""
131
145
  s = value_source
@@ -470,8 +484,10 @@ def _convert_percent_to_fstring(binary: Binary) -> Optional[FormattedString]:
470
484
  # Get argument list
471
485
  if isinstance(right, CollectionLiteral) and right.kind == CollectionLiteral.Kind.TUPLE:
472
486
  args = list(right.elements)
473
- else:
487
+ elif isinstance(right, Expression) and _resolves_to_non_tuple(right):
474
488
  args = [right]
489
+ else:
490
+ return None
475
491
 
476
492
  # Verify arg count matches specifier count
477
493
  if len(args) != spec_count:
@@ -560,7 +576,9 @@ class ReplacePercentFormatWithFString(Recipe):
560
576
  return (
561
577
  'Replace `"..." % (...)` expressions with f-strings (Python 3.6+). '
562
578
  "Only converts `%s` and `%r` specifiers where the format string is "
563
- "a literal and the conversion is safe."
579
+ "a literal and the conversion is safe. A single operand that is not a "
580
+ "tuple literal must have a type that resolves to a non-tuple, such as "
581
+ "`str` or `int`, since a tuple would be spread over the specifiers."
564
582
  )
565
583
 
566
584
  @property
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.16.0.dev20261006155312
3
+ Version: 0.16.0.dev20261007033737
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
@@ -3,6 +3,7 @@ src/openrewrite_code_quality_python/__init__.py
3
3
  src/openrewrite_code_quality_python/codequality/__init__.py
4
4
  src/openrewrite_code_quality_python/codequality/_reindent.py
5
5
  src/openrewrite_code_quality_python/codequality/_semantically_equal.py
6
+ src/openrewrite_code_quality_python/codequality/_side_effects.py
6
7
  src/openrewrite_code_quality_python/codequality/_trivia.py
7
8
  src/openrewrite_code_quality_python/codequality/all_branches_identical.py
8
9
  src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py
@@ -167,6 +167,7 @@ class TestMigrateAsyncioCoroutine:
167
167
  @asyncio.coroutine
168
168
  def outer():
169
169
  def gen():
170
+ yield 0
170
171
  yield from range(3)
171
172
  g = lambda: (yield from range(3))
172
173
  yield from step()
@@ -176,9 +177,35 @@ class TestMigrateAsyncioCoroutine:
176
177
 
177
178
  async def outer():
178
179
  def gen():
180
+ yield 0
179
181
  yield from range(3)
180
182
  g = lambda: (yield from range(3))
181
183
  await step()
182
184
  """,
183
185
  )
184
186
  )
187
+
188
+ def test_no_change_when_the_body_has_a_bare_yield(self):
189
+ spec = RecipeSpec(recipe=MigrateAsyncioCoroutine())
190
+ spec.rewrite_run(
191
+ python(
192
+ """
193
+ import asyncio
194
+
195
+ @asyncio.coroutine
196
+ def f():
197
+ yield
198
+ """
199
+ ),
200
+
201
+ python(
202
+ """
203
+ import asyncio
204
+
205
+ @asyncio.coroutine
206
+ def g():
207
+ x = yield from step()
208
+ yield x
209
+ """
210
+ ),
211
+ )
@@ -1,5 +1,7 @@
1
1
  """Tests for string formatting recipes."""
2
2
 
3
+ import pytest
4
+
3
5
  from rewrite import InMemoryExecutionContext, InMemoryLargeSourceSet, Recipe
4
6
  from rewrite.test import RecipeSpec, python
5
7
  from openrewrite_migrate_python.migrate.string_formatting import (
@@ -370,18 +372,54 @@ class TestReplaceStrFormatWithFString:
370
372
  class TestReplacePercentFormatWithFString:
371
373
  """Tests for the ReplacePercentFormatWithFString recipe."""
372
374
 
375
+ @pytest.mark.requires_type_attribution
373
376
  def test_simple_percent_s(self):
374
377
  """'Hello %s' % name → f'Hello {name}'"""
375
378
  spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
376
379
  spec.rewrite_run(
377
380
  python(
378
381
  """
382
+ name = "world"
379
383
  message = "Hello %s" % name
380
384
  """,
381
385
  """
386
+ name = "world"
382
387
  message = f"Hello {name}"
383
388
  """,
384
- )
389
+ ),
390
+
391
+ python(
392
+ """
393
+ items = [1]
394
+ message = "Hello %s" % items
395
+ """,
396
+ """
397
+ items = [1]
398
+ message = f"Hello {items}"
399
+ """,
400
+ ),
401
+ )
402
+
403
+ def test_no_change_unless_a_single_operand_resolves_to_a_non_tuple(self):
404
+ spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
405
+ spec.rewrite_run(
406
+ python(
407
+ """
408
+ t = (1,)
409
+ message = "%s" % t
410
+ """
411
+ ),
412
+
413
+ python(
414
+ """
415
+ from typing import NamedTuple
416
+ class P(NamedTuple):
417
+ x: int
418
+ message = "%s" % P(1)
419
+ """
420
+ ),
421
+
422
+ python('message = "Hello %s" % name'),
385
423
  )
386
424
 
387
425
  def test_multiple_percent_s(self):
@@ -398,43 +436,52 @@ class TestReplacePercentFormatWithFString:
398
436
  )
399
437
  )
400
438
 
439
+ @pytest.mark.requires_type_attribution
401
440
  def test_percent_r(self):
402
441
  """'%r' % x → f'{x!r}'"""
403
442
  spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
404
443
  spec.rewrite_run(
405
444
  python(
406
445
  """
446
+ x = 1
407
447
  message = "%r" % x
408
448
  """,
409
449
  """
450
+ x = 1
410
451
  message = f"{x!r}"
411
452
  """,
412
453
  )
413
454
  )
414
455
 
456
+ @pytest.mark.requires_type_attribution
415
457
  def test_escaped_percent(self):
416
458
  """'100%% of %s' % name → f'100% of {name}'"""
417
459
  spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
418
460
  spec.rewrite_run(
419
461
  python(
420
462
  """
463
+ name = "world"
421
464
  message = "100%% of %s" % name
422
465
  """,
423
466
  """
467
+ name = "world"
424
468
  message = f"100% of {name}"
425
469
  """,
426
470
  )
427
471
  )
428
472
 
473
+ @pytest.mark.requires_type_attribution
429
474
  def test_single_quotes(self):
430
475
  """'Hello %s' % name → f'Hello {name}' (single quotes)"""
431
476
  spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
432
477
  spec.rewrite_run(
433
478
  python(
434
479
  """
480
+ name = "world"
435
481
  message = 'Hello %s' % name
436
482
  """,
437
483
  """
484
+ name = "world"
438
485
  message = f'Hello {name}'
439
486
  """,
440
487
  )
@@ -556,6 +603,7 @@ class TestReplacePercentFormatWithFString:
556
603
  )
557
604
  )
558
605
 
606
+ @pytest.mark.requires_type_attribution
559
607
  def test_idempotent(self):
560
608
  """Running the recipe twice produces the same result."""
561
609
  recipe = ReplacePercentFormatWithFString()
@@ -563,9 +611,11 @@ class TestReplacePercentFormatWithFString:
563
611
  spec.rewrite_run(
564
612
  python(
565
613
  """
614
+ name = "world"
566
615
  message = "Hello %s" % name
567
616
  """,
568
617
  """
618
+ name = "world"
569
619
  message = f"Hello {name}"
570
620
  """,
571
621
  after_recipe=_assert_idempotent(recipe),