openrewrite-migrate-python 0.14.0.dev20260902131752__tar.gz → 0.14.0.dev20260902134304__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 (147) hide show
  1. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/PKG-INFO +1 -1
  2. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/pyproject.toml +1 -1
  3. openrewrite_migrate_python-0.14.0.dev20260902134304/src/openrewrite_code_quality_python/codequality/_trivia.py +139 -0
  4. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +15 -118
  5. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +106 -55
  6. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
  7. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +1 -0
  8. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/setup.cfg +0 -0
  9. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/__init__.py +0 -0
  10. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
  11. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
  12. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
  13. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +0 -0
  14. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +0 -0
  15. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +0 -0
  16. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
  17. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +0 -0
  18. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +0 -0
  19. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +0 -0
  20. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
  21. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/__init__.py +0 -0
  22. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
  23. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -0
  24. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
  25. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  26. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -0
  27. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
  28. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
  29. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  30. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
  31. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
  32. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
  33. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  34. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
  35. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
  36. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
  37. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
  38. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
  39. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
  40. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
  41. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
  42. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
  43. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
  44. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
  45. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
  46. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  47. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  48. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  49. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
  50. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
  51. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
  52. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
  53. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
  54. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
  55. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
  56. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
  57. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
  58. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
  59. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
  60. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
  61. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
  62. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
  63. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
  64. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
  65. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
  66. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
  67. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
  68. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
  69. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  70. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
  71. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
  72. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
  73. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
  74. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  75. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
  76. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
  77. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
  78. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  79. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
  80. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  81. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  82. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
  83. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  84. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  85. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
  86. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  87. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  88. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  89. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
  90. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
  91. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
  92. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
  93. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
  94. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  95. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  96. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
  97. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  98. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_aifc_migrations.py +0 -0
  99. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_array_deprecations.py +0 -0
  100. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_ast_deprecations.py +0 -0
  101. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_asyncio_coroutine_to_async.py +0 -0
  102. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_asyncio_deprecations.py +0 -0
  103. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_avro_migrations.py +0 -0
  104. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_calendar_deprecations.py +0 -0
  105. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_cgi_parse_deprecations.py +0 -0
  106. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_change_import_recipes.py +0 -0
  107. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_collections_abc_migrations.py +0 -0
  108. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_configparser_deprecations.py +0 -0
  109. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_datetime_utc.py +0 -0
  110. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_distutils_deprecations.py +0 -0
  111. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_future_imports.py +0 -0
  112. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_gettext_deprecations.py +0 -0
  113. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_html_parser_deprecations.py +0 -0
  114. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_imp_migrations.py +0 -0
  115. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_langchain_classic_imports.py +0 -0
  116. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_langchain_community_imports.py +0 -0
  117. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_langchain_provider_imports.py +0 -0
  118. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_locale_deprecations.py +0 -0
  119. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  120. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_macpath_deprecations.py +0 -0
  121. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_os_deprecations.py +0 -0
  122. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_pep594_migrations.py +0 -0
  123. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_pep594_system_migrations.py +0 -0
  124. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_pkgutil_deprecations.py +0 -0
  125. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_platform_deprecations.py +0 -0
  126. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_pydantic_deprecations.py +0 -0
  127. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_pydantic_fields.py +0 -0
  128. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_pydantic_model_fields.py +0 -0
  129. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_re_deprecations.py +0 -0
  130. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_removed_modules_312.py +0 -0
  131. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_shutil_deprecations.py +0 -0
  132. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_ssl_deprecations.py +0 -0
  133. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_string_formatting.py +0 -0
  134. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_sys_deprecations.py +0 -0
  135. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_sys_last_deprecations.py +0 -0
  136. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_tarfile_deprecations.py +0 -0
  137. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_threading_deprecations.py +0 -0
  138. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_threading_extended.py +0 -0
  139. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_threading_is_alive_deprecation.py +0 -0
  140. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_typing_alias_moves.py +0 -0
  141. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_typing_callable.py +0 -0
  142. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_typing_deprecations.py +0 -0
  143. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_typing_union_to_pipe.py +0 -0
  144. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_unittest_deprecations.py +0 -0
  145. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_upgrade_recipes.py +0 -0
  146. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/tests/test_upgrade_to_pydantic.py +0 -0
  147. {openrewrite_migrate_python-0.14.0.dev20260902131752 → openrewrite_migrate_python-0.14.0.dev20260902134304}/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.14.0.dev20260902131752
3
+ Version: 0.14.0.dev20260902134304
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.14.0.dev20260902131752" # Updated dynamically during release
8
+ version = "0.14.0.dev20260902134304" # 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,139 @@
1
+ """
2
+ Line-level trivia splicing for recipes that delete whole statements.
3
+
4
+ A statement's *trailing* comment is stored on whatever the printer emits
5
+ next -- the following statement's prefix, or, for a block's last statement,
6
+ trivia outside the block. Deleting a statement therefore moves trivia
7
+ between its neighbours.
8
+ """
9
+
10
+ from itertools import islice
11
+ from typing import Optional, Tuple
12
+
13
+ from rewrite import Cursor
14
+ from rewrite.java.support_types import Space
15
+ from rewrite.java.tree import Block, ClassDeclaration, If, MethodDeclaration
16
+ from rewrite.python.tree import CompilationUnit
17
+
18
+
19
+ def split_line(space: Space) -> Optional[Tuple[Space, Space]]:
20
+ """Split ``space`` at its first newline, or return None if it has no newline.
21
+
22
+ The head is the trivia that still belongs to the preceding line -- most
23
+ importantly that line's trailing comment. The tail starts with the
24
+ newline itself.
25
+ """
26
+ newline = space.whitespace.find("\n")
27
+ if newline != -1:
28
+ return (
29
+ Space(_comments=[], _whitespace=space.whitespace[:newline]),
30
+ Space(_comments=space.comments, _whitespace=space.whitespace[newline:]),
31
+ )
32
+ for i, comment in enumerate(space.comments):
33
+ newline = comment.suffix.find("\n")
34
+ if newline != -1:
35
+ return (
36
+ Space(
37
+ _comments=[
38
+ *space.comments[:i],
39
+ comment.replace(_suffix=comment.suffix[:newline]),
40
+ ],
41
+ _whitespace=space.whitespace,
42
+ ),
43
+ Space(
44
+ _comments=space.comments[i + 1:],
45
+ _whitespace=comment.suffix[newline:],
46
+ ),
47
+ )
48
+ return None
49
+
50
+
51
+ def concat(left: Space, right: Space) -> Space:
52
+ """Return the trivia that prints as ``left`` followed by ``right``."""
53
+ if not left.comments:
54
+ return Space(
55
+ _comments=right.comments,
56
+ _whitespace=left.whitespace + right.whitespace,
57
+ )
58
+ last = left.comments[-1]
59
+ return Space(
60
+ _comments=[
61
+ *left.comments[:-1],
62
+ last.replace(_suffix=last.suffix + right.whitespace),
63
+ *right.comments,
64
+ ],
65
+ _whitespace=left.whitespace,
66
+ )
67
+
68
+
69
+ def opens_with_trailing_comment(space: Space) -> bool:
70
+ """Return True when ``space`` opens with a comment on the line it continues."""
71
+ split = split_line(space)
72
+ return bool((split[0] if split else space).comments)
73
+
74
+
75
+ def space_after_block(cursor: Cursor) -> Optional[Space]:
76
+ """The trivia the printer emits after the block at ``cursor``.
77
+
78
+ A block's last statement keeps its trailing comment outside the block --
79
+ on the next statement of an enclosing block, on the `else` of an enclosing
80
+ `if`, or at end of file. Returns None for the containers this does not
81
+ know how to look past.
82
+ """
83
+ child = cursor.value
84
+ for parent in islice(cursor.get_path(), 1, None):
85
+ if isinstance(parent, (Block, CompilationUnit)):
86
+ statements = parent._statements
87
+ index = next(
88
+ (i for i, s in enumerate(statements) if s.element.id == child.id),
89
+ None,
90
+ )
91
+ if index is None:
92
+ return None
93
+ if index + 1 < len(statements):
94
+ return statements[index + 1].element.prefix
95
+ if isinstance(parent, CompilationUnit):
96
+ return parent.eof
97
+ elif isinstance(parent, If):
98
+ if parent.else_part is not None and parent.else_part.id != child.id:
99
+ return parent.else_part.prefix
100
+ elif not isinstance(parent, (ClassDeclaration, If.Else, MethodDeclaration)):
101
+ return None
102
+ child = parent
103
+ return None
104
+
105
+
106
+ def splice_out(
107
+ padded_statements: list, end: Space, to_remove: set[int]
108
+ ) -> Tuple[list, Space]:
109
+ """Return the statements without those at ``to_remove``, and the new end trivia.
110
+
111
+ The survivor takes the removed statement's place on the page: the trivia
112
+ up to the removed statement's own line, which holds the preceding
113
+ statement's trailing comment, then that line's break and indent. Only
114
+ comments standing on their own line before the survivor come from the
115
+ survivor; the removed statement's go with it.
116
+ """
117
+ result = []
118
+ pending: Optional[Tuple[Space, Space]] = None
119
+ for i, padded in enumerate(padded_statements):
120
+ if i in to_remove:
121
+ if pending is None:
122
+ prefix = padded.element.prefix
123
+ # A file's first statement starts a line without a break to show for it.
124
+ pending = split_line(prefix) or (prefix, Space.EMPTY)
125
+ continue
126
+ if pending is not None:
127
+ head, line = pending
128
+ _, tail = split_line(padded.element.prefix)
129
+ padded = padded.replace(
130
+ _element=padded.element.replace(
131
+ _prefix=concat(
132
+ head,
133
+ Space(_comments=tail.comments, _whitespace=line.whitespace),
134
+ )
135
+ )
136
+ )
137
+ pending = None
138
+ result.append(padded)
139
+ return result, end if pending is None else concat(pending[0], end)
@@ -16,18 +16,23 @@ from rewrite import Cursor, ExecutionContext, Recipe, TreeVisitor
16
16
  from rewrite.category import CategoryDescriptor
17
17
  from rewrite.decorators import categorize
18
18
  from rewrite.marketplace import Python
19
+ from rewrite.java.support_types import Space
19
20
  from rewrite.python.tree import CompilationUnit
20
21
  from rewrite.python.visitor import PythonVisitor
21
- from rewrite.java.support_types import Space
22
22
  from rewrite.java.tree import (
23
23
  Assignment,
24
24
  Block,
25
25
  ClassDeclaration,
26
26
  Identifier,
27
- If,
28
27
  MethodDeclaration,
29
28
  Statement,
30
29
  )
30
+ from openrewrite_code_quality_python.codequality._trivia import (
31
+ opens_with_trailing_comment,
32
+ space_after_block,
33
+ split_line,
34
+ splice_out,
35
+ )
31
36
 
32
37
  _CodeQuality = [
33
38
  *Python,
@@ -69,95 +74,7 @@ def _in_function_scope(cursor: Cursor) -> bool:
69
74
  return False
70
75
 
71
76
 
72
- def _space_after_block(cursor: Cursor) -> Optional[Space]:
73
- """The trivia the printer emits after the block at ``cursor``.
74
-
75
- A statement's trailing comment is stored on whatever follows it, so a
76
- block's last statement keeps its comment outside the block -- on the
77
- next statement of an enclosing block, on the `else` of an enclosing
78
- `if`, or at end of file. Returns None for the containers this does not
79
- know how to look past.
80
- """
81
- child = cursor.value
82
- for parent in islice(cursor.get_path(), 1, None):
83
- if isinstance(parent, (Block, CompilationUnit)):
84
- statements = parent._statements
85
- index = next(
86
- (i for i, s in enumerate(statements) if s.element.id == child.id),
87
- None,
88
- )
89
- if index is None:
90
- return None
91
- if index + 1 < len(statements):
92
- return statements[index + 1].element.prefix
93
- if isinstance(parent, CompilationUnit):
94
- return parent.eof
95
- elif isinstance(parent, If):
96
- if parent.else_part is not None and parent.else_part.id != child.id:
97
- return parent.else_part.prefix
98
- elif not isinstance(parent, (ClassDeclaration, If.Else, MethodDeclaration)):
99
- return None
100
- child = parent
101
- return None
102
-
103
-
104
- def _ends_line_with_comment(space: Space) -> bool:
105
- """Return True when ``space`` opens with a comment on the line it continues."""
106
- split = _split_line(space)
107
- return bool((split[0] if split else space).comments)
108
-
109
-
110
- def _split_line(space: Space) -> Optional[Tuple[Space, Space]]:
111
- """Split ``space`` at its first newline, or return None if it has no newline.
112
-
113
- The head is the trivia that still belongs to the preceding line -- most
114
- importantly that line's trailing comment. The tail starts with the
115
- newline itself.
116
- """
117
- newline = space.whitespace.find("\n")
118
- if newline != -1:
119
- return (
120
- Space(_comments=[], _whitespace=space.whitespace[:newline]),
121
- Space(_comments=space.comments, _whitespace=space.whitespace[newline:]),
122
- )
123
- for i, comment in enumerate(space.comments):
124
- newline = comment.suffix.find("\n")
125
- if newline != -1:
126
- return (
127
- Space(
128
- _comments=[
129
- *space.comments[:i],
130
- comment.replace(_suffix=comment.suffix[:newline]),
131
- ],
132
- _whitespace=space.whitespace,
133
- ),
134
- Space(
135
- _comments=space.comments[i + 1:],
136
- _whitespace=comment.suffix[newline:],
137
- ),
138
- )
139
- return None
140
-
141
-
142
- def _concat(left: Space, right: Space) -> Space:
143
- """Return the trivia that prints as ``left`` followed by ``right``."""
144
- if not left.comments:
145
- return Space(
146
- _comments=right.comments,
147
- _whitespace=left.whitespace + right.whitespace,
148
- )
149
- last = left.comments[-1]
150
- return Space(
151
- _comments=[
152
- *left.comments[:-1],
153
- last.replace(_suffix=last.suffix + right.whitespace),
154
- *right.comments,
155
- ],
156
- _whitespace=left.whitespace,
157
- )
158
-
159
-
160
- def _removable(padded_statements: list, i: int, space_after_block: Optional[Space]) -> bool:
77
+ def _removable(padded_statements: list, i: int, after_block: Optional[Space]) -> bool:
161
78
  """Return True when statement ``i`` can be dropped without disturbing its neighbours.
162
79
 
163
80
  The removal is expressed by splicing line-level trivia, so the statement
@@ -168,49 +85,29 @@ def _removable(padded_statements: list, i: int, space_after_block: Optional[Spac
168
85
  """
169
86
  if not _is_local_self_assignment(padded_statements[i].element):
170
87
  return False
171
- if _split_line(padded_statements[i].element.prefix) is None:
88
+ if split_line(padded_statements[i].element.prefix) is None:
172
89
  return False
173
90
  if i + 1 < len(padded_statements):
174
- return _split_line(padded_statements[i + 1].element.prefix) is not None
175
- return space_after_block is not None and not _ends_line_with_comment(space_after_block)
91
+ return split_line(padded_statements[i + 1].element.prefix) is not None
92
+ return after_block is not None and not opens_with_trailing_comment(after_block)
176
93
 
177
94
 
178
95
  def _remove_self_assignments(
179
- padded_statements: list, end: Space, space_after_block: Optional[Space]
96
+ padded_statements: list, end: Space, after_block: Optional[Space]
180
97
  ) -> Optional[Tuple[list, Space]]:
181
98
  """Return the block's statements and end trivia with self-assignments removed, or None if unchanged.
182
99
 
183
- Each removed statement's leading trivia is kept up to its own line (it
184
- holds the preceding statement's trailing comment) and its successor's is
185
- dropped up to the same point (it holds the removed statement's own).
186
100
  Emptying a block would leave its header without a body, so the last
187
101
  surviving statement stays.
188
102
  """
189
103
  to_remove = {
190
104
  i
191
105
  for i in range(len(padded_statements))
192
- if _removable(padded_statements, i, space_after_block)
106
+ if _removable(padded_statements, i, after_block)
193
107
  }
194
108
  if not to_remove or len(to_remove) == len(padded_statements):
195
109
  return None
196
-
197
- result = []
198
- pending_head: Optional[Space] = None
199
- for i, padded in enumerate(padded_statements):
200
- if i in to_remove:
201
- if pending_head is None:
202
- pending_head, _ = _split_line(padded.element.prefix)
203
- continue
204
- if pending_head is not None:
205
- _, tail = _split_line(padded.element.prefix)
206
- padded = padded.replace(
207
- _element=padded.element.replace(
208
- _prefix=_concat(pending_head, tail)
209
- )
210
- )
211
- pending_head = None
212
- result.append(padded)
213
- return result, end if pending_head is None else _concat(pending_head, end)
110
+ return splice_out(padded_statements, end, to_remove)
214
111
 
215
112
 
216
113
  @categorize(_CodeQuality)
@@ -266,7 +163,7 @@ class RemoveSelfAssignment(Recipe):
266
163
  if not isinstance(block, Block) or not _in_function_scope(self.cursor):
267
164
  return block
268
165
  removed = _remove_self_assignments(
269
- block._statements, block.end, _space_after_block(self.cursor)
166
+ block._statements, block.end, space_after_block(self.cursor)
270
167
  )
271
168
  if removed is not None:
272
169
  return block.replace(_statements=removed[0], _end=removed[1])
@@ -9,25 +9,26 @@ eliminates noise and prevents confusion about the author's intent.
9
9
  See: https://rules.sonarsource.com/python/RSPEC-S4143/
10
10
  """
11
11
 
12
- from typing import Any, List, Optional
12
+ from typing import Any, List, Optional, Tuple
13
13
 
14
14
  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
18
  from rewrite.python.visitor import PythonVisitor
19
- from rewrite.python.tree import ExpressionStatement
20
- from rewrite.java.support_types import Space
19
+ from rewrite.python.tree import Await, CompilationUnit, ExpressionStatement, YieldFrom
20
+ from rewrite.java.support_types import MethodCall, Space
21
21
  from rewrite.java.tree import (
22
22
  ArrayAccess,
23
23
  Assignment,
24
24
  Block,
25
25
  Expression,
26
26
  FieldAccess,
27
- Identifier,
28
27
  Statement,
28
+ Yield,
29
29
  )
30
30
  from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
31
+ from openrewrite_code_quality_python.codequality._trivia import splice_out, split_line
31
32
 
32
33
  _CodeQuality = [
33
34
  *Python,
@@ -81,6 +82,56 @@ def _same_attribute_target(a: Assignment, b: Assignment) -> bool:
81
82
  )
82
83
 
83
84
 
85
+ class _Find(PythonVisitor[None]):
86
+ """Records whether any subtree of the visited node satisfies ``predicate``."""
87
+
88
+ def __init__(self, predicate):
89
+ super().__init__()
90
+ self._predicate = predicate
91
+ self.found = False
92
+
93
+ def visit(self, tree, p, parent=None):
94
+ if tree is not None and self._predicate(tree):
95
+ self.found = True
96
+ return super().visit(tree, p, parent)
97
+
98
+
99
+ def _contains(node, predicate) -> bool:
100
+ search = _Find(predicate)
101
+ search.visit(node, None)
102
+ return search.found
103
+
104
+
105
+ # A walrus binds a name; the rest reach code this recipe cannot see.
106
+ _EFFECTFUL = (Assignment, Await, MethodCall, Yield, YieldFrom)
107
+
108
+
109
+ def _has_side_effects(expression: Expression) -> bool:
110
+ """Return True when evaluating ``expression`` can do more than produce a value."""
111
+ return _contains(expression, lambda node: isinstance(node, _EFFECTFUL))
112
+
113
+
114
+ def _receiver(assign: Assignment) -> Expression:
115
+ """The object the assignment writes through."""
116
+ variable = assign.variable
117
+ return variable.indexed if isinstance(variable, ArrayAccess) else variable.target
118
+
119
+
120
+ def _observes_earlier_write(a: Assignment, b: Assignment) -> bool:
121
+ """Return True when evaluating ``b``'s value can see what ``a`` wrote.
122
+
123
+ Reading the target back (``d[k] = d[k].strip()``) is the direct case, but
124
+ the write is equally visible through the object that holds it: whether
125
+ ``d[k] = 0`` added a key or replaced one decides ``len(d)``, and an
126
+ attribute read can run a property computed from the one just written.
127
+ """
128
+ receiver = _receiver(a)
129
+ return _contains(
130
+ b.assignment,
131
+ lambda node: isinstance(node, Expression) and SemanticallyEqual.are_equal(node, receiver),
132
+ )
133
+
134
+
84
135
  def _is_duplicate_write(a: Assignment, b: Assignment) -> bool:
85
136
  """Return True if a and b write to the same target."""
86
137
  if _is_subscript_assign(a) and _is_subscript_assign(b):
@@ -90,6 +141,21 @@ def _is_duplicate_write(a: Assignment, b: Assignment) -> bool:
90
141
  return False
91
142
 
92
143
 
144
+ def _is_dead_write(a: Assignment, b: Assignment) -> bool:
145
+ """Return True when ``b`` leaves nothing ``a`` did observable.
146
+
147
+ Targets are matched as written, so one that names a different location
148
+ each time it is evaluated (``d[next(it)]``) is two targets, not one. A
149
+ value that does more than produce a value keeps its statement alive even
150
+ once the store it makes is dead.
151
+ """
152
+ if not _is_duplicate_write(a, b):
153
+ return False
154
+ if _has_side_effects(a.variable) or _has_side_effects(a.assignment):
155
+ return False
156
+ return not _observes_earlier_write(a, b)
157
+
158
+
93
159
  @categorize(_CodeQuality)
94
160
  class RemoveUnconditionalValueOverwrite(Recipe):
95
161
  """
@@ -123,7 +189,9 @@ class RemoveUnconditionalValueOverwrite(Recipe):
123
189
  return (
124
190
  "Remove consecutive assignments that write to the same dict key "
125
191
  "or object attribute, since the first value is immediately "
126
- "overwritten and never used."
192
+ "overwritten and never used. A write the second one can still "
193
+ "observe -- by reading the target back, or through the object "
194
+ "holding it -- is left alone."
127
195
  )
128
196
 
129
197
  @property
@@ -136,72 +204,55 @@ class RemoveUnconditionalValueOverwrite(Recipe):
136
204
 
137
205
  def editor(self) -> TreeVisitor[Any, ExecutionContext]:
138
206
  class Visitor(PythonVisitor[ExecutionContext]):
139
- def visit_compilation_unit(self, cu, p):
207
+ def visit_compilation_unit(
208
+ self, cu: CompilationUnit, p: ExecutionContext
209
+ ) -> CompilationUnit:
140
210
  cu = super().visit_compilation_unit(cu, p)
141
- new_stmts = _remove_overwrites_padded(cu._statements)
142
- if new_stmts is not None:
143
- return cu.replace(_statements=new_stmts)
211
+ removed = _remove_overwrites(cu._statements, cu.eof, starts_file=True)
212
+ if removed is not None:
213
+ return cu.replace(_statements=removed[0], _eof=removed[1])
144
214
  return cu
145
215
 
146
216
  def visit_block(
147
217
  self, block: Block, p: ExecutionContext
148
218
  ) -> Optional[Block]:
149
219
  block = super().visit_block(block, p)
150
-
151
220
  if not isinstance(block, Block):
152
221
  return block
153
-
154
- new_stmts = _remove_overwrites_padded(block._statements)
155
- if new_stmts is not None:
156
- return block.replace(_statements=new_stmts)
222
+ removed = _remove_overwrites(block._statements, block.end, starts_file=False)
223
+ if removed is not None:
224
+ return block.replace(_statements=removed[0], _end=removed[1])
157
225
  return block
158
226
 
159
227
  return Visitor()
160
228
 
161
229
 
162
- def _remove_overwrites_padded(padded_stmts) -> Optional[list]:
163
- """Return a filtered padded list with dead writes removed, or None if unchanged."""
164
- if len(padded_stmts) < 2:
165
- return None
166
-
167
- stmts = [rp.element for rp in padded_stmts]
168
- to_remove: set[int] = set()
169
- for i in range(len(stmts) - 1):
170
- if i in to_remove:
171
- continue
172
- a_assign = _get_assignment(stmts[i])
173
- if a_assign is None:
174
- continue
175
- if not (_is_subscript_assign(a_assign) or _is_attribute_assign(a_assign)):
176
- continue
230
+ def _removable(padded_statements: list, i: int, starts_file: bool) -> bool:
231
+ """Return True when the write at ``i`` is dead and can be dropped by splicing lines.
177
232
 
178
- b_assign = _get_assignment(stmts[i + 1])
179
- if b_assign is None:
180
- continue
233
+ Both the write and its overwrite have to start a line of their own,
234
+ which a `;`-joined pair does not; a file's first statement starts one
235
+ with no line break to show for it.
236
+ """
237
+ if i + 1 >= len(padded_statements):
238
+ return False
239
+ a = _get_assignment(padded_statements[i].element)
240
+ b = _get_assignment(padded_statements[i + 1].element)
241
+ if a is None or b is None or not _is_dead_write(a, b):
242
+ return False
243
+ if not (starts_file and i == 0) and split_line(padded_statements[i].element.prefix) is None:
244
+ return False
245
+ return split_line(padded_statements[i + 1].element.prefix) is not None
181
246
 
182
- if _is_duplicate_write(a_assign, b_assign):
183
- to_remove.add(i)
184
247
 
248
+ def _remove_overwrites(
249
+ padded_statements: list, end: Space, starts_file: bool
250
+ ) -> Optional[Tuple[list, Space]]:
251
+ """Return the statements and end trivia with dead writes removed, or None if unchanged."""
252
+ to_remove = {
253
+ i for i in range(len(padded_statements))
254
+ if _removable(padded_statements, i, starts_file)
255
+ }
185
256
  if not to_remove:
186
257
  return None
187
-
188
- result = [
189
- rp for idx, rp in enumerate(padded_stmts)
190
- if idx not in to_remove
191
- ]
192
-
193
- # If we removed the first statement(s), strip the leading newline
194
- # from the new first statement's prefix to avoid a blank line.
195
- if result and 0 in to_remove:
196
- first_rp = result[0]
197
- first = first_rp.element
198
- ws = first.prefix.whitespace
199
- if ws.startswith("\n"):
200
- ws = ws[1:]
201
- new_first = first.replace(_prefix=Space(
202
- _comments=first.prefix.comments,
203
- _whitespace=ws,
204
- ))
205
- result[0] = first_rp.replace(_element=new_first)
206
-
207
- return result
258
+ return splice_out(padded_statements, end, to_remove)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.14.0.dev20260902131752
3
+ Version: 0.14.0.dev20260902134304
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/_trivia.py
6
7
  src/openrewrite_code_quality_python/codequality/all_branches_identical.py
7
8
  src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py
8
9
  src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py