openrewrite-migrate-python 0.16.0.dev20261004180356__tar.gz → 0.16.0.dev20261005072603__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 (159) hide show
  1. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/PKG-INFO +1 -1
  2. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/pyproject.toml +1 -1
  3. openrewrite_migrate_python-0.16.0.dev20261005072603/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +333 -0
  4. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/string_formatting.py +4 -4
  5. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
  6. openrewrite_migrate_python-0.16.0.dev20261004180356/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +0 -258
  7. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/setup.cfg +0 -0
  8. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/__init__.py +0 -0
  9. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
  10. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
  11. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
  12. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/_trivia.py +0 -0
  13. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +0 -0
  14. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +0 -0
  15. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +0 -0
  16. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
  17. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +0 -0
  18. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +0 -0
  19. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +0 -0
  20. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +0 -0
  21. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
  22. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/__init__.py +0 -0
  23. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
  24. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/_from_imports.py +0 -0
  25. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -0
  26. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
  27. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  28. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/_slots.py +0 -0
  29. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -0
  30. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
  31. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
  32. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  33. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
  34. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
  35. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
  36. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  37. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
  38. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
  39. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
  40. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
  41. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
  42. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
  43. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
  44. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
  45. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
  46. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
  47. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
  48. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
  49. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  50. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  51. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  52. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
  53. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
  54. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
  55. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
  56. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
  57. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
  58. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
  59. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
  60. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
  61. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
  62. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
  63. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
  64. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
  65. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
  66. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
  67. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
  68. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
  69. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
  70. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
  71. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  72. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
  73. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
  74. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
  75. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
  76. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  77. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
  78. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
  79. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
  80. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  81. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
  82. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  83. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  84. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
  85. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  86. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
  87. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
  88. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  89. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  90. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
  91. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
  92. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
  93. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
  94. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
  95. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
  96. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +0 -0
  97. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  98. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  99. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
  100. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  101. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_aifc_migrations.py +0 -0
  102. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_array_deprecations.py +0 -0
  103. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_ast_deprecations.py +0 -0
  104. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_asyncio_coroutine_to_async.py +0 -0
  105. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_asyncio_deprecations.py +0 -0
  106. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_avro_migrations.py +0 -0
  107. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_calendar_deprecations.py +0 -0
  108. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_cgi_parse_deprecations.py +0 -0
  109. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_change_import_recipes.py +0 -0
  110. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_collections_abc_migrations.py +0 -0
  111. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_configparser_deprecations.py +0 -0
  112. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_datetime_utc.py +0 -0
  113. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_distutils_deprecations.py +0 -0
  114. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_distutils_migrations.py +0 -0
  115. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_functools_deprecations.py +0 -0
  116. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_future_imports.py +0 -0
  117. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_gettext_deprecations.py +0 -0
  118. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_html_parser_deprecations.py +0 -0
  119. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_imp_migrations.py +0 -0
  120. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_langchain_classic_imports.py +0 -0
  121. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_langchain_community_imports.py +0 -0
  122. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_langchain_provider_imports.py +0 -0
  123. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_locale_deprecations.py +0 -0
  124. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  125. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_macpath_deprecations.py +0 -0
  126. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_mailcap_migrations.py +0 -0
  127. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_markers.py +0 -0
  128. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_os_deprecations.py +0 -0
  129. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pathlib_deprecations.py +0 -0
  130. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pep594_migrations.py +0 -0
  131. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pep594_system_migrations.py +0 -0
  132. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pkgutil_deprecations.py +0 -0
  133. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_platform_deprecations.py +0 -0
  134. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pydantic_deprecations.py +0 -0
  135. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pydantic_fields.py +0 -0
  136. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_pydantic_model_fields.py +0 -0
  137. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_re_deprecations.py +0 -0
  138. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_removed_modules_312.py +0 -0
  139. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_shutil_deprecations.py +0 -0
  140. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_socket_deprecations.py +0 -0
  141. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_ssl_deprecations.py +0 -0
  142. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_string_formatting.py +0 -0
  143. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_sys_deprecations.py +0 -0
  144. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_sys_last_deprecations.py +0 -0
  145. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_tarfile_deprecations.py +0 -0
  146. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_tempfile_deprecations.py +0 -0
  147. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_threading_deprecations.py +0 -0
  148. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_threading_extended.py +0 -0
  149. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_threading_is_alive_deprecation.py +0 -0
  150. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_typing_alias_moves.py +0 -0
  151. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_typing_callable.py +0 -0
  152. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_typing_deprecations.py +0 -0
  153. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_typing_union_to_pipe.py +0 -0
  154. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_unittest_deprecations.py +0 -0
  155. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_upgrade_recipes.py +0 -0
  156. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_upgrade_to_pydantic.py +0 -0
  157. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_urllib_deprecations.py +0 -0
  158. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/tests/test_xdrlib_migrations.py +0 -0
  159. {openrewrite_migrate_python-0.16.0.dev20261004180356 → openrewrite_migrate_python-0.16.0.dev20261005072603}/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.dev20261004180356
3
+ Version: 0.16.0.dev20261005072603
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.dev20261004180356" # Updated dynamically during release
8
+ version = "0.16.0.dev20261005072603" # 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,333 @@
1
+ """
2
+ Recipe to remove dict entry writes that the next statement overwrites.
3
+
4
+ See: https://rules.sonarsource.com/python/RSPEC-S4143/
5
+ """
6
+
7
+ from itertools import islice
8
+ from typing import Any, List, Optional, Tuple
9
+
10
+ from rewrite import Cursor, ExecutionContext, Recipe, TreeVisitor
11
+ from rewrite.category import CategoryDescriptor
12
+ from rewrite.decorators import categorize
13
+ from rewrite.marketplace import Python
14
+ from rewrite.python.method_matcher import MethodMatcher
15
+ from rewrite.python.visitor import PythonVisitor
16
+ from rewrite.python.tree import (
17
+ CollectionLiteral,
18
+ CompilationUnit,
19
+ ComprehensionExpression,
20
+ DictLiteral,
21
+ ExpressionStatement,
22
+ KeyValue,
23
+ NamedArgument,
24
+ TypeHintedExpression,
25
+ VariableScope,
26
+ )
27
+ from rewrite.java.support_types import Space
28
+ from rewrite.java.tree import (
29
+ ArrayAccess,
30
+ Assignment,
31
+ Block,
32
+ ClassDeclaration,
33
+ Empty,
34
+ Expression,
35
+ Identifier,
36
+ J,
37
+ Lambda,
38
+ Literal,
39
+ MethodDeclaration,
40
+ MethodInvocation,
41
+ Parentheses,
42
+ Statement,
43
+ Try,
44
+ Unary,
45
+ )
46
+ from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
47
+ from openrewrite_code_quality_python.codequality._trivia import splice_out, split_line
48
+
49
+ _CodeQuality = [
50
+ *Python,
51
+ CategoryDescriptor(display_name="Code quality"),
52
+ ]
53
+
54
+
55
+ def _get_assignment(stmt: Statement) -> Optional[Assignment]:
56
+ """Extract an Assignment from a statement, if present."""
57
+ if isinstance(stmt, ExpressionStatement):
58
+ expr = stmt.expression
59
+ if isinstance(expr, Assignment):
60
+ return expr
61
+ if isinstance(stmt, Assignment):
62
+ return stmt
63
+ return None
64
+
65
+
66
+ class _Find(PythonVisitor[None]):
67
+ """Records whether any subtree of the visited node satisfies ``predicate``."""
68
+
69
+ def __init__(self, predicate):
70
+ super().__init__()
71
+ self._predicate = predicate
72
+ self.found = False
73
+
74
+ def visit(self, tree, p, parent=None):
75
+ if tree is not None and self._predicate(tree):
76
+ self.found = True
77
+ return super().visit(tree, p, parent)
78
+
79
+
80
+ def _contains(node, predicate) -> bool:
81
+ search = _Find(predicate)
82
+ search.visit(node, None)
83
+ return search.found
84
+
85
+
86
+ def _mentions(node, name: str) -> bool:
87
+ return _contains(node, lambda n: isinstance(n, Identifier) and n.simple_name == name)
88
+
89
+
90
+ def _is_number(expression: J) -> bool:
91
+ return (
92
+ isinstance(expression, Unary)
93
+ and expression.operator in (Unary.Type.Negative, Unary.Type.Positive)
94
+ and isinstance(expression.expression, Literal)
95
+ and isinstance(expression.expression.value, (int, float, complex))
96
+ )
97
+
98
+
99
+ def _is_hashable_constant(expression: J) -> bool:
100
+ """A value whose hashing and comparison run no user code and cannot raise."""
101
+ if isinstance(expression, Parentheses) and isinstance(expression.tree, Expression):
102
+ return _is_hashable_constant(expression.tree)
103
+ if isinstance(expression, Literal) or _is_number(expression):
104
+ return True
105
+ return (
106
+ isinstance(expression, CollectionLiteral)
107
+ and expression.kind == CollectionLiteral.Kind.TUPLE
108
+ and all(_is_hashable_constant(e) for e in expression.elements if not isinstance(e, Empty))
109
+ )
110
+
111
+
112
+ def _is_constant(expression: J) -> bool:
113
+ """A value whose evaluation runs no user code and cannot raise."""
114
+ if isinstance(expression, Parentheses) and isinstance(expression.tree, Expression):
115
+ return _is_constant(expression.tree)
116
+ if isinstance(expression, CollectionLiteral):
117
+ element_ok = _is_hashable_constant if expression.kind == CollectionLiteral.Kind.SET else _is_constant
118
+ return all(element_ok(e) for e in expression.elements if not isinstance(e, Empty))
119
+ if isinstance(expression, DictLiteral):
120
+ return all(
121
+ isinstance(e, KeyValue) and _is_hashable_constant(e.key) and _is_constant(e.value)
122
+ for e in expression.elements
123
+ if not isinstance(e, Empty)
124
+ )
125
+ return _is_hashable_constant(expression)
126
+
127
+
128
+ def _store(stmt: Statement) -> Optional[Tuple[str, Expression, Expression]]:
129
+ """The dict name, key and value of a ``d[key] = value`` statement with a constant key."""
130
+ assign = _get_assignment(stmt)
131
+ if assign is None or not isinstance(assign.variable, ArrayAccess):
132
+ return None
133
+ receiver = assign.variable.indexed
134
+ key = assign.variable.dimension.index
135
+ if not isinstance(receiver, Identifier) or not _is_hashable_constant(key):
136
+ return None
137
+ return receiver.simple_name, key, assign.assignment
138
+
139
+
140
+ _DICT_CONSTRUCTOR = MethodMatcher.create("dict <constructor>(..)")
141
+
142
+
143
+ def _builds_dict(value: Expression) -> bool:
144
+ """True when ``value`` is a new builtin dict holding only constant keys."""
145
+ if isinstance(value, DictLiteral):
146
+ return all(
147
+ isinstance(e, KeyValue) and _is_hashable_constant(e.key)
148
+ for e in value.elements
149
+ if not isinstance(e, Empty)
150
+ )
151
+ return (
152
+ isinstance(value, MethodInvocation)
153
+ and _DICT_CONSTRUCTOR.matches(value)
154
+ and all(isinstance(a, (NamedArgument, Empty)) for a in value.arguments)
155
+ )
156
+
157
+
158
+ def _binds_fresh_dict(stmt: Statement, name: str) -> bool:
159
+ assign = _get_assignment(stmt)
160
+ if assign is None:
161
+ return False
162
+ variable = assign.variable
163
+ if isinstance(variable, TypeHintedExpression):
164
+ variable = variable.expression
165
+ return (
166
+ isinstance(variable, Identifier)
167
+ and variable.simple_name == name
168
+ and _builds_dict(assign.assignment)
169
+ )
170
+
171
+
172
+ def _is_fresh_at(statements: List[Statement], i: int, name: str) -> bool:
173
+ """True when ``name`` holds a dict this block built and has kept to itself up to ``i``.
174
+
175
+ Rebinding the dict on every pass through the block keeps it fresh in a
176
+ loop too. Only constant-key stores may touch it in between, so every key
177
+ it holds hashes and compares without running user code.
178
+ """
179
+ for stmt in reversed(statements[:i]):
180
+ if _binds_fresh_dict(stmt, name):
181
+ return True
182
+ store = _store(stmt)
183
+ if store is not None and store[0] == name and not _mentions(store[2], name):
184
+ continue
185
+ if _mentions(stmt, name):
186
+ return False
187
+ return False
188
+
189
+
190
+ _NESTED_SCOPES = (ClassDeclaration, ComprehensionExpression, Lambda, MethodDeclaration)
191
+
192
+
193
+ def _shared_beyond_frame(function: MethodDeclaration, name: str) -> bool:
194
+ """True when ``name`` in ``function`` is a global, or a cell a nested scope can read."""
195
+ return _contains(
196
+ function.body,
197
+ lambda n: (
198
+ isinstance(n, VariableScope) and any(i.simple_name == name for i in n.names)
199
+ ) or (isinstance(n, _NESTED_SCOPES) and _mentions(n, name)),
200
+ )
201
+
202
+
203
+ def _enclosing_function(cursor: Cursor) -> Optional[MethodDeclaration]:
204
+ """The function whose frame alone can observe a block's locals, if there is one.
205
+
206
+ A ``try`` or ``with`` between them can catch an exception and read the
207
+ dict afterwards, so a block inside one has no such function.
208
+ """
209
+ for value in islice(cursor.get_path(), 1, None):
210
+ if isinstance(value, MethodDeclaration):
211
+ return value
212
+ if isinstance(value, (Try, ClassDeclaration, CompilationUnit)):
213
+ return None
214
+ return None
215
+
216
+
217
+ def _is_dead_write(statements: List[Statement], i: int, function: MethodDeclaration) -> bool:
218
+ """True when the write at ``i`` is overwritten by the next one before anything can see it.
219
+
220
+ The dict has to be one only this frame can reach, which rules out other
221
+ threads, aliases reached through a call, and setter hooks. The dropped
222
+ value has to be a constant, so dropping it drops no code.
223
+ """
224
+ a = _store(statements[i])
225
+ b = _store(statements[i + 1])
226
+ if a is None or b is None:
227
+ return False
228
+ name, key, value = a
229
+ return (
230
+ b[0] == name
231
+ and SemanticallyEqual.are_equal(key, b[1])
232
+ and _is_constant(value)
233
+ and not _mentions(b[2], name)
234
+ and _is_fresh_at(statements, i, name)
235
+ and not _shared_beyond_frame(function, name)
236
+ )
237
+
238
+
239
+ @categorize(_CodeQuality)
240
+ class RemoveUnconditionalValueOverwrite(Recipe):
241
+ """
242
+ Remove a dict entry write that the next statement overwrites.
243
+
244
+ Only a dict the function has just built and kept to itself qualifies.
245
+
246
+ Example:
247
+ Before:
248
+ def f():
249
+ d = {}
250
+ d["key"] = 1
251
+ d["key"] = 2
252
+ return d
253
+
254
+ After:
255
+ def f():
256
+ d = {}
257
+ d["key"] = 2
258
+ return d
259
+ """
260
+
261
+ @property
262
+ def name(self) -> str:
263
+ return "org.openrewrite.python.codequality.RemoveUnconditionalValueOverwrite"
264
+
265
+ @property
266
+ def display_name(self) -> str:
267
+ return "Remove unconditional value overwrites"
268
+
269
+ @property
270
+ def description(self) -> str:
271
+ return (
272
+ "Remove a constant written to a dict key when the next statement "
273
+ "overwrites the same key. Only a dict the enclosing function built "
274
+ "with `{...}` or `dict()` and has not yet passed on qualifies, and "
275
+ "only for constant keys outside `try` and `with` blocks. Writes to "
276
+ "attributes, parameters and globals are left alone, because "
277
+ "another thread, an alias or a setter can observe them."
278
+ )
279
+
280
+ @property
281
+ def tags(self) -> List[str]:
282
+ return ["python", "code-quality", "RSPEC-S4143"]
283
+
284
+ @property
285
+ def estimated_effort_per_occurrence(self) -> int:
286
+ return 5
287
+
288
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
289
+ class Visitor(PythonVisitor[ExecutionContext]):
290
+ def visit_block(
291
+ self, block: Block, p: ExecutionContext
292
+ ) -> Optional[Block]:
293
+ block = super().visit_block(block, p)
294
+ if not isinstance(block, Block):
295
+ return block
296
+ function = _enclosing_function(self.cursor)
297
+ if function is None:
298
+ return block
299
+ removed = _remove_overwrites(block._statements, block.end, function)
300
+ if removed is not None:
301
+ return block.replace(_statements=removed[0], _end=removed[1])
302
+ return block
303
+
304
+ return Visitor()
305
+
306
+
307
+ def _removable(padded_statements: list, i: int, function: MethodDeclaration) -> bool:
308
+ """Return True when the write at ``i`` is dead and can be dropped by splicing lines.
309
+
310
+ Both the write and its overwrite have to start a line of their own,
311
+ which a `;`-joined pair does not.
312
+ """
313
+ if i + 1 >= len(padded_statements):
314
+ return False
315
+ statements = [s.element for s in padded_statements]
316
+ if not _is_dead_write(statements, i, function):
317
+ return False
318
+ if split_line(statements[i].prefix) is None:
319
+ return False
320
+ return split_line(statements[i + 1].prefix) is not None
321
+
322
+
323
+ def _remove_overwrites(
324
+ padded_statements: list, end: Space, function: MethodDeclaration
325
+ ) -> Optional[Tuple[list, Space]]:
326
+ """Return the statements and end trivia with dead writes removed, or None if unchanged."""
327
+ to_remove = {
328
+ i for i in range(len(padded_statements))
329
+ if _removable(padded_statements, i, function)
330
+ }
331
+ if not to_remove:
332
+ return None
333
+ return splice_out(padded_statements, end, to_remove)
@@ -304,9 +304,9 @@ def _convert_format_to_fstring(method: MethodInvocation) -> Optional[FormattedSt
304
304
  _markers=Markers.EMPTY,
305
305
  _expression=padded_expr,
306
306
  _debug=None,
307
- _conversion=conversion_enum,
307
+ _conversion=None,
308
308
  _format=format_node,
309
- ))
309
+ ).with_conversion(conversion_enum))
310
310
 
311
311
  return FormattedString(
312
312
  _id=random_id(),
@@ -515,9 +515,9 @@ def _convert_percent_to_fstring(binary: Binary) -> Optional[FormattedString]:
515
515
  _markers=Markers.EMPTY,
516
516
  _expression=padded_expr,
517
517
  _debug=None,
518
- _conversion=conversion_enum,
518
+ _conversion=None,
519
519
  _format=None,
520
- ))
520
+ ).with_conversion(conversion_enum))
521
521
 
522
522
  return FormattedString(
523
523
  _id=random_id(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.16.0.dev20261004180356
3
+ Version: 0.16.0.dev20261005072603
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
@@ -1,258 +0,0 @@
1
- """
2
- Recipe to remove unconditional overwrites of dict/object values.
3
-
4
- When two consecutive statements assign to the same dict key or object
5
- attribute, the first assignment is dead code -- its value is
6
- immediately overwritten without ever being read. Removing it
7
- eliminates noise and prevents confusion about the author's intent.
8
-
9
- See: https://rules.sonarsource.com/python/RSPEC-S4143/
10
- """
11
-
12
- from typing import Any, List, Optional, Tuple
13
-
14
- from rewrite import ExecutionContext, Recipe, TreeVisitor
15
- from rewrite.category import CategoryDescriptor
16
- from rewrite.decorators import categorize
17
- from rewrite.marketplace import Python
18
- from rewrite.python.visitor import PythonVisitor
19
- from rewrite.python.tree import Await, CompilationUnit, ExpressionStatement, YieldFrom
20
- from rewrite.java.support_types import MethodCall, Space
21
- from rewrite.java.tree import (
22
- ArrayAccess,
23
- Assignment,
24
- Block,
25
- Expression,
26
- FieldAccess,
27
- Statement,
28
- Yield,
29
- )
30
- from openrewrite_code_quality_python.codequality._semantically_equal import SemanticallyEqual
31
- from openrewrite_code_quality_python.codequality._trivia import splice_out, split_line
32
-
33
- _CodeQuality = [
34
- *Python,
35
- CategoryDescriptor(display_name="Code quality"),
36
- ]
37
-
38
-
39
- def _get_assignment(stmt: Statement) -> Optional[Assignment]:
40
- """Extract an Assignment from a statement, if present."""
41
- if isinstance(stmt, ExpressionStatement):
42
- expr = stmt.expression
43
- if isinstance(expr, Assignment):
44
- return expr
45
- if isinstance(stmt, Assignment):
46
- return stmt
47
- return None
48
-
49
-
50
- def _is_subscript_assign(assign: Assignment) -> bool:
51
- """Return True if the assignment target is a subscript (d[key])."""
52
- return isinstance(assign.variable, ArrayAccess)
53
-
54
-
55
- def _is_attribute_assign(assign: Assignment) -> bool:
56
- """Return True if the assignment target is an attribute (obj.x)."""
57
- return isinstance(assign.variable, FieldAccess)
58
-
59
-
60
- def _same_subscript_target(a: Assignment, b: Assignment) -> bool:
61
- """Return True if both assign to the same dict with the same key."""
62
- va = a.variable
63
- vb = b.variable
64
- if not (isinstance(va, ArrayAccess) and isinstance(vb, ArrayAccess)):
65
- return False
66
- # Compare the receiver (dict) and the key
67
- return (
68
- SemanticallyEqual.are_equal(va.indexed, vb.indexed)
69
- and SemanticallyEqual.are_equal(va.dimension.index, vb.dimension.index)
70
- )
71
-
72
-
73
- def _same_attribute_target(a: Assignment, b: Assignment) -> bool:
74
- """Return True if both assign to the same attribute (obj.x)."""
75
- va = a.variable
76
- vb = b.variable
77
- if not (isinstance(va, FieldAccess) and isinstance(vb, FieldAccess)):
78
- return False
79
- return (
80
- va.name.simple_name == vb.name.simple_name
81
- and SemanticallyEqual.are_equal(va.target, vb.target)
82
- )
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
-
135
- def _is_duplicate_write(a: Assignment, b: Assignment) -> bool:
136
- """Return True if a and b write to the same target."""
137
- if _is_subscript_assign(a) and _is_subscript_assign(b):
138
- return _same_subscript_target(a, b)
139
- if _is_attribute_assign(a) and _is_attribute_assign(b):
140
- return _same_attribute_target(a, b)
141
- return False
142
-
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
-
159
- @categorize(_CodeQuality)
160
- class RemoveUnconditionalValueOverwrite(Recipe):
161
- """
162
- Remove consecutive assignments that write to the same dict key or
163
- object attribute.
164
-
165
- When a value written to a collection entry or attribute is
166
- immediately overwritten in the very next statement, the first
167
- write is dead code. Removing it keeps the code focused on the
168
- value that actually persists.
169
-
170
- Example:
171
- Before:
172
- d["key"] = 1
173
- d["key"] = 2
174
-
175
- After:
176
- d["key"] = 2
177
- """
178
-
179
- @property
180
- def name(self) -> str:
181
- return "org.openrewrite.python.codequality.RemoveUnconditionalValueOverwrite"
182
-
183
- @property
184
- def display_name(self) -> str:
185
- return "Remove unconditional value overwrites"
186
-
187
- @property
188
- def description(self) -> str:
189
- return (
190
- "Remove consecutive assignments that write to the same dict key "
191
- "or object attribute, since the first value is immediately "
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."
195
- )
196
-
197
- @property
198
- def tags(self) -> List[str]:
199
- return ["python", "code-quality", "RSPEC-S4143"]
200
-
201
- @property
202
- def estimated_effort_per_occurrence(self) -> int:
203
- return 5
204
-
205
- def editor(self) -> TreeVisitor[Any, ExecutionContext]:
206
- class Visitor(PythonVisitor[ExecutionContext]):
207
- def visit_compilation_unit(
208
- self, cu: CompilationUnit, p: ExecutionContext
209
- ) -> CompilationUnit:
210
- cu = super().visit_compilation_unit(cu, p)
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])
214
- return cu
215
-
216
- def visit_block(
217
- self, block: Block, p: ExecutionContext
218
- ) -> Optional[Block]:
219
- block = super().visit_block(block, p)
220
- if not isinstance(block, Block):
221
- return block
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])
225
- return block
226
-
227
- return Visitor()
228
-
229
-
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.
232
-
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
246
-
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
- }
256
- if not to_remove:
257
- return None
258
- return splice_out(padded_statements, end, to_remove)