openrewrite-migrate-python 0.15.0.dev20260922170519__tar.gz → 0.15.1__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.
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/PKG-INFO +1 -1
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/pyproject.toml +1 -1
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/__init__.py +2 -0
- openrewrite_migrate_python-0.15.1/src/openrewrite_code_quality_python/codequality/code_quality.py +73 -0
- openrewrite_migrate_python-0.15.1/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +333 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/string_formatting.py +31 -30
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +1 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_string_formatting.py +48 -13
- openrewrite_migrate_python-0.15.0.dev20260922170519/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +0 -258
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/setup.cfg +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/__init__.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/_trivia.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/__init__.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/_from_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/_langchain_tables.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/_slots.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_aifc_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_array_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_ast_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_asyncio_coroutine_to_async.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_asyncio_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_avro_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_calendar_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_cgi_parse_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_change_import_recipes.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_collections_abc_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_configparser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_datetime_utc.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_distutils_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_distutils_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_functools_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_future_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_gettext_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_html_parser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_imp_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_langchain_classic_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_langchain_community_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_langchain_provider_imports.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_locale_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_macpath_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_mailcap_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_markers.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_os_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pathlib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pep594_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pep594_system_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pkgutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_platform_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pydantic_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pydantic_fields.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_pydantic_model_fields.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_re_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_removed_modules_312.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_shutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_socket_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_ssl_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_sys_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_sys_last_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_tarfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_tempfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_threading_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_threading_extended.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_threading_is_alive_deprecation.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_typing_alias_moves.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_typing_callable.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_typing_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_typing_union_to_pipe.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_unittest_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_upgrade_recipes.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_upgrade_to_pydantic.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_urllib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_xdrlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/tests/test_xml_deprecations.py +0 -0
{openrewrite_migrate_python-0.15.0.dev20260922170519 → openrewrite_migrate_python-0.15.1}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrewrite-migrate-python
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.1
|
|
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.15.
|
|
8
|
+
version = "0.15.1" # 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"
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from .all_branches_identical import AllBranchesIdentical
|
|
4
4
|
from .boolean_checks_not_inverted import BooleanChecksNotInverted
|
|
5
|
+
from .code_quality import CodeQuality
|
|
5
6
|
from .collapsible_if_statements import CollapsibleIfStatements
|
|
6
7
|
from .merge_identical_branches import MergeIdenticalBranches
|
|
7
8
|
from .remove_duplicate_conditions import RemoveDuplicateConditions
|
|
@@ -13,6 +14,7 @@ from .simplify_redundant_logical_expression import SimplifyRedundantLogicalExpre
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"AllBranchesIdentical",
|
|
15
16
|
"BooleanChecksNotInverted",
|
|
17
|
+
"CodeQuality",
|
|
16
18
|
"CollapsibleIfStatements",
|
|
17
19
|
"MergeIdenticalBranches",
|
|
18
20
|
"RemoveDuplicateConditions",
|
openrewrite_migrate_python-0.15.1/src/openrewrite_code_quality_python/codequality/code_quality.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""Composite recipe bundling the Python code quality fixes."""
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from rewrite import Recipe
|
|
6
|
+
from rewrite.category import CategoryDescriptor
|
|
7
|
+
from rewrite.decorators import categorize
|
|
8
|
+
from rewrite.marketplace import Python
|
|
9
|
+
|
|
10
|
+
from .all_branches_identical import AllBranchesIdentical
|
|
11
|
+
from .boolean_checks_not_inverted import BooleanChecksNotInverted
|
|
12
|
+
from .collapsible_if_statements import CollapsibleIfStatements
|
|
13
|
+
from .merge_identical_branches import MergeIdenticalBranches
|
|
14
|
+
from .remove_duplicate_conditions import RemoveDuplicateConditions
|
|
15
|
+
from .remove_self_assignment import RemoveSelfAssignment
|
|
16
|
+
from .remove_unconditional_value_overwrite import RemoveUnconditionalValueOverwrite
|
|
17
|
+
from .simplify_boolean_literal import SimplifyBooleanLiteral
|
|
18
|
+
from .simplify_redundant_logical_expression import SimplifyRedundantLogicalExpression
|
|
19
|
+
|
|
20
|
+
_CodeQuality = [
|
|
21
|
+
*Python,
|
|
22
|
+
CategoryDescriptor(display_name="Code quality"),
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@categorize(_CodeQuality)
|
|
27
|
+
class CodeQuality(Recipe):
|
|
28
|
+
"""
|
|
29
|
+
Apply every Python code quality fix.
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
Before:
|
|
33
|
+
if done == True:
|
|
34
|
+
if ready:
|
|
35
|
+
run()
|
|
36
|
+
|
|
37
|
+
After:
|
|
38
|
+
if done and ready:
|
|
39
|
+
run()
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def name(self) -> str:
|
|
44
|
+
return "org.openrewrite.python.codequality.CodeQuality"
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def display_name(self) -> str:
|
|
48
|
+
return "Code quality"
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def description(self) -> str:
|
|
52
|
+
return (
|
|
53
|
+
"Apply all Python code quality recipes that change code: simplify boolean "
|
|
54
|
+
"expressions, merge and collapse `if` chains, and remove dead conditions and "
|
|
55
|
+
"writes. Search-only recipes, which mark code for review, are not included."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def tags(self) -> List[str]:
|
|
60
|
+
return ["python", "code-quality"]
|
|
61
|
+
|
|
62
|
+
def recipe_list(self) -> List[Recipe]:
|
|
63
|
+
return [
|
|
64
|
+
SimplifyBooleanLiteral(),
|
|
65
|
+
BooleanChecksNotInverted(),
|
|
66
|
+
SimplifyRedundantLogicalExpression(),
|
|
67
|
+
RemoveDuplicateConditions(),
|
|
68
|
+
AllBranchesIdentical(),
|
|
69
|
+
MergeIdenticalBranches(),
|
|
70
|
+
CollapsibleIfStatements(),
|
|
71
|
+
RemoveSelfAssignment(),
|
|
72
|
+
RemoveUnconditionalValueOverwrite(),
|
|
73
|
+
]
|
|
@@ -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)
|
|
@@ -21,10 +21,9 @@ from rewrite.markers import Markers
|
|
|
21
21
|
from rewrite.python.visitor import PythonVisitor
|
|
22
22
|
from rewrite.java.support_types import JRightPadded, Space
|
|
23
23
|
from rewrite.java.tree import Binary, Expression, FieldAccess, Identifier, J, Literal, MethodInvocation, Parentheses
|
|
24
|
+
from rewrite.python.printer import PrintOutputCapture, PythonPrinter
|
|
24
25
|
from rewrite.python.tree import (
|
|
25
26
|
CollectionLiteral,
|
|
26
|
-
ComprehensionExpression,
|
|
27
|
-
DictLiteral,
|
|
28
27
|
FormattedString,
|
|
29
28
|
NamedArgument,
|
|
30
29
|
Star,
|
|
@@ -74,17 +73,6 @@ def _get_fstring_delimiter(value_source: str) -> Optional[str]:
|
|
|
74
73
|
return 'f' + quote if quote else None
|
|
75
74
|
|
|
76
75
|
|
|
77
|
-
def _opens_with_brace(expr: Any) -> bool:
|
|
78
|
-
"""Whether ``expr`` prints with a leading ``{`` that the field's own brace makes into ``{{``."""
|
|
79
|
-
if isinstance(expr, DictLiteral):
|
|
80
|
-
return True
|
|
81
|
-
if isinstance(expr, CollectionLiteral):
|
|
82
|
-
return expr.kind == CollectionLiteral.Kind.SET
|
|
83
|
-
if isinstance(expr, ComprehensionExpression):
|
|
84
|
-
return expr.kind in (ComprehensionExpression.Kind.SET, ComprehensionExpression.Kind.DICT)
|
|
85
|
-
return False
|
|
86
|
-
|
|
87
|
-
|
|
88
76
|
def _breaks_replacement_field(expr: Any, quote: str) -> bool:
|
|
89
77
|
"""Whether ``expr`` holds anything a replacement field cannot carry.
|
|
90
78
|
|
|
@@ -124,11 +112,11 @@ def _unsafe_to_interpolate(expr: Any, delimiter: str) -> bool:
|
|
|
124
112
|
"""Whether substituting ``expr`` into a replacement field spoils the f-string.
|
|
125
113
|
|
|
126
114
|
A literal argument nests a string in a string, a rewrite worth declining however it
|
|
127
|
-
is quoted.
|
|
128
|
-
recipes run for -- cannot parse the result.
|
|
115
|
+
is quoted. Other expressions are declined only where 3.8 -- the earliest target
|
|
116
|
+
these recipes run for -- cannot parse the result.
|
|
129
117
|
"""
|
|
130
118
|
value = expr.value if isinstance(expr, NamedArgument) else expr
|
|
131
|
-
if
|
|
119
|
+
if _is_string_literal(value):
|
|
132
120
|
return True
|
|
133
121
|
return _breaks_replacement_field(value, delimiter[1:])
|
|
134
122
|
|
|
@@ -151,6 +139,23 @@ def _has_backslash(value_source: str) -> bool:
|
|
|
151
139
|
return '\\' in content
|
|
152
140
|
|
|
153
141
|
|
|
142
|
+
def _field_expression(arg_expr: Expression, delimiter: str) -> Optional[JRightPadded]:
|
|
143
|
+
"""The argument as a replacement field, or None when it cannot sit inside this f-string."""
|
|
144
|
+
expr = arg_expr.replace(_prefix=Space.EMPTY)
|
|
145
|
+
source = PythonPrinter().print(expr, PrintOutputCapture(None))
|
|
146
|
+
# Only legal in a replacement field from 3.12 (PEP 701)
|
|
147
|
+
if any(c in source for c in (delimiter[1], '\\', '#', '\n')):
|
|
148
|
+
return None
|
|
149
|
+
# A leading or trailing brace would fuse with the field's own and read as an escaped brace
|
|
150
|
+
if source.startswith('{'):
|
|
151
|
+
expr = expr.replace(_prefix=Space.SINGLE_SPACE)
|
|
152
|
+
return JRightPadded(
|
|
153
|
+
_element=expr,
|
|
154
|
+
_after=Space.SINGLE_SPACE if source.endswith('}') else Space.EMPTY,
|
|
155
|
+
_markers=Markers.EMPTY,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
154
159
|
def _convert_format_to_fstring(method: MethodInvocation) -> Optional[FormattedString]:
|
|
155
160
|
"""Convert a ``"...".format(...)`` MethodInvocation to a FormattedString.
|
|
156
161
|
|
|
@@ -267,11 +272,9 @@ def _convert_format_to_fstring(method: MethodInvocation) -> Optional[FormattedSt
|
|
|
267
272
|
return None
|
|
268
273
|
arg_expr = named_args[field_name]
|
|
269
274
|
|
|
270
|
-
padded_expr =
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
_markers=Markers.EMPTY,
|
|
274
|
-
)
|
|
275
|
+
padded_expr = _field_expression(arg_expr, delimiter)
|
|
276
|
+
if padded_expr is None:
|
|
277
|
+
return None
|
|
275
278
|
|
|
276
279
|
# Map conversion flag
|
|
277
280
|
conversion_enum = None
|
|
@@ -301,9 +304,9 @@ def _convert_format_to_fstring(method: MethodInvocation) -> Optional[FormattedSt
|
|
|
301
304
|
_markers=Markers.EMPTY,
|
|
302
305
|
_expression=padded_expr,
|
|
303
306
|
_debug=None,
|
|
304
|
-
_conversion=
|
|
307
|
+
_conversion=None,
|
|
305
308
|
_format=format_node,
|
|
306
|
-
))
|
|
309
|
+
).with_conversion(conversion_enum))
|
|
307
310
|
|
|
308
311
|
return FormattedString(
|
|
309
312
|
_id=random_id(),
|
|
@@ -498,11 +501,9 @@ def _convert_percent_to_fstring(binary: Binary) -> Optional[FormattedString]:
|
|
|
498
501
|
arg_expr = args[arg_index]
|
|
499
502
|
arg_index += 1
|
|
500
503
|
|
|
501
|
-
padded_expr =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
_markers=Markers.EMPTY,
|
|
505
|
-
)
|
|
504
|
+
padded_expr = _field_expression(arg_expr, delimiter)
|
|
505
|
+
if padded_expr is None:
|
|
506
|
+
return None
|
|
506
507
|
|
|
507
508
|
conversion_enum = None
|
|
508
509
|
if part_value == 'r':
|
|
@@ -514,9 +515,9 @@ def _convert_percent_to_fstring(binary: Binary) -> Optional[FormattedString]:
|
|
|
514
515
|
_markers=Markers.EMPTY,
|
|
515
516
|
_expression=padded_expr,
|
|
516
517
|
_debug=None,
|
|
517
|
-
_conversion=
|
|
518
|
+
_conversion=None,
|
|
518
519
|
_format=None,
|
|
519
|
-
))
|
|
520
|
+
).with_conversion(conversion_enum))
|
|
520
521
|
|
|
521
522
|
return FormattedString(
|
|
522
523
|
_id=random_id(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrewrite-migrate-python
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.1
|
|
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
|
|
@@ -6,6 +6,7 @@ src/openrewrite_code_quality_python/codequality/_semantically_equal.py
|
|
|
6
6
|
src/openrewrite_code_quality_python/codequality/_trivia.py
|
|
7
7
|
src/openrewrite_code_quality_python/codequality/all_branches_identical.py
|
|
8
8
|
src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py
|
|
9
|
+
src/openrewrite_code_quality_python/codequality/code_quality.py
|
|
9
10
|
src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py
|
|
10
11
|
src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py
|
|
11
12
|
src/openrewrite_code_quality_python/codequality/merge_identical_branches.py
|
|
@@ -249,6 +249,31 @@ class TestReplaceStrFormatWithFString:
|
|
|
249
249
|
)
|
|
250
250
|
)
|
|
251
251
|
|
|
252
|
+
def test_set_literal_argument_is_spaced_from_the_field_braces(self):
|
|
253
|
+
"""A brace of the argument's own must not fuse with the field's into an escaped brace."""
|
|
254
|
+
spec = RecipeSpec(recipe=ReplaceStrFormatWithFString())
|
|
255
|
+
spec.rewrite_run(
|
|
256
|
+
python(
|
|
257
|
+
"""
|
|
258
|
+
message = "{} outside {} block".format(op, {'for', 'while'})
|
|
259
|
+
""",
|
|
260
|
+
"""
|
|
261
|
+
message = f"{op} outside { {'for', 'while'} } block"
|
|
262
|
+
""",
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def test_no_change_argument_contains_the_delimiter_quote(self):
|
|
267
|
+
"""The quote is only legal inside a replacement field from 3.12."""
|
|
268
|
+
spec = RecipeSpec(recipe=ReplaceStrFormatWithFString())
|
|
269
|
+
spec.rewrite_run(
|
|
270
|
+
python(
|
|
271
|
+
"""
|
|
272
|
+
message = "{} outside {} block".format(op, {"for", "while"})
|
|
273
|
+
""",
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
|
|
252
277
|
def test_no_change_backslash_in_string(self):
|
|
253
278
|
"""'hello\\n{}'.format(name) — backslash escape, no change."""
|
|
254
279
|
spec = RecipeSpec(recipe=ReplaceStrFormatWithFString())
|
|
@@ -326,19 +351,6 @@ class TestReplaceStrFormatWithFString:
|
|
|
326
351
|
)
|
|
327
352
|
)
|
|
328
353
|
|
|
329
|
-
def test_no_change_brace_opening_argument(self):
|
|
330
|
-
"""A brace-opening argument's own brace would read as an escaped `{{`."""
|
|
331
|
-
spec = RecipeSpec(recipe=ReplaceStrFormatWithFString())
|
|
332
|
-
spec.rewrite_run(
|
|
333
|
-
python(
|
|
334
|
-
"""
|
|
335
|
-
keywords = "{}".format({1, 2})
|
|
336
|
-
mapping = "{}".format({1: 2})
|
|
337
|
-
squares = "{}".format({n: n * n for n in sizes})
|
|
338
|
-
"""
|
|
339
|
-
)
|
|
340
|
-
)
|
|
341
|
-
|
|
342
354
|
def test_idempotent(self):
|
|
343
355
|
"""Running the recipe twice produces the same result."""
|
|
344
356
|
recipe = ReplaceStrFormatWithFString()
|
|
@@ -485,6 +497,29 @@ class TestReplacePercentFormatWithFString:
|
|
|
485
497
|
)
|
|
486
498
|
)
|
|
487
499
|
|
|
500
|
+
def test_dict_literal_argument_is_spaced_from_the_field_braces(self):
|
|
501
|
+
spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
|
|
502
|
+
spec.rewrite_run(
|
|
503
|
+
python(
|
|
504
|
+
"""
|
|
505
|
+
message = "config: %s" % ({'a': 1},)
|
|
506
|
+
""",
|
|
507
|
+
"""
|
|
508
|
+
message = f"config: { {'a': 1} }"
|
|
509
|
+
""",
|
|
510
|
+
)
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
def test_no_change_argument_contains_the_delimiter_quote(self):
|
|
514
|
+
spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
|
|
515
|
+
spec.rewrite_run(
|
|
516
|
+
python(
|
|
517
|
+
"""
|
|
518
|
+
message = "name: %s" % d["name"]
|
|
519
|
+
""",
|
|
520
|
+
)
|
|
521
|
+
)
|
|
522
|
+
|
|
488
523
|
def test_no_change_backslash(self):
|
|
489
524
|
r"""'%s\n' % x — backslash in string, no change."""
|
|
490
525
|
spec = RecipeSpec(recipe=ReplacePercentFormatWithFString())
|