openrewrite-migrate-python 0.14.0.dev20260902102558__tar.gz → 0.14.0.dev20260902122728__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.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/PKG-INFO +1 -1
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/pyproject.toml +1 -1
- openrewrite_migrate_python-0.14.0.dev20260902122728/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +275 -0
- openrewrite_migrate_python-0.14.0.dev20260902122728/src/openrewrite_migrate_python/migrate/datetime_utc.py +274 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
- openrewrite_migrate_python-0.14.0.dev20260902122728/tests/test_datetime_utc.py +225 -0
- openrewrite_migrate_python-0.14.0.dev20260902102558/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +0 -152
- openrewrite_migrate_python-0.14.0.dev20260902102558/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -137
- openrewrite_migrate_python-0.14.0.dev20260902102558/tests/test_datetime_utc.py +0 -85
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/setup.cfg +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/all_branches_identical.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/boolean_checks_not_inverted.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/collapsible_if_statements.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/find_suspicious_chained_comparison.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/merge_identical_branches.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/remove_duplicate_conditions.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/remove_unconditional_value_overwrite.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/simplify_boolean_literal.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_code_quality_python/codequality/simplify_redundant_logical_expression.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_aifc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_array_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_ast_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_asyncio_coroutine_to_async.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_asyncio_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_avro_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_calendar_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_cgi_parse_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_change_import_recipes.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_collections_abc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_configparser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_distutils_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_future_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_gettext_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_html_parser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_imp_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_langchain_classic_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_langchain_community_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_langchain_provider_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_locale_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_macpath_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_os_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_pep594_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_pep594_system_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_pkgutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_platform_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_pydantic_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_pydantic_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_pydantic_model_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_re_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_removed_modules_312.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_shutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_ssl_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_string_formatting.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_sys_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_sys_last_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_tarfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_threading_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_threading_extended.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_threading_is_alive_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_typing_alias_moves.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_typing_callable.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_typing_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_typing_union_to_pipe.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_unittest_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_upgrade_recipes.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_upgrade_to_pydantic.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902122728}/tests/test_xml_deprecations.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrewrite-migrate-python
|
|
3
|
-
Version: 0.14.0.
|
|
3
|
+
Version: 0.14.0.dev20260902122728
|
|
4
4
|
Summary: OpenRewrite recipes for migrating Python codebases to newer Python versions.
|
|
5
5
|
Author-email: "Moderne Inc." <support@moderne.io>
|
|
6
6
|
License: Moderne Proprietary
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openrewrite-migrate-python"
|
|
7
7
|
description = "OpenRewrite recipes for migrating Python codebases to newer Python versions."
|
|
8
|
-
version = "0.14.0.
|
|
8
|
+
version = "0.14.0.dev20260902122728" # 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,275 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipe to remove self-assignments.
|
|
3
|
+
|
|
4
|
+
Assigning a plain local name to itself (``x = x``) has no effect and is
|
|
5
|
+
almost always a copy-paste error or leftover from a refactoring.
|
|
6
|
+
Removing these statements makes the code cleaner and avoids
|
|
7
|
+
confusion about the author's intent.
|
|
8
|
+
|
|
9
|
+
See: https://rules.sonarsource.com/python/RSPEC-S1656/
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from itertools import islice
|
|
13
|
+
from typing import Any, List, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
from rewrite import Cursor, ExecutionContext, Recipe, TreeVisitor
|
|
16
|
+
from rewrite.category import CategoryDescriptor
|
|
17
|
+
from rewrite.decorators import categorize
|
|
18
|
+
from rewrite.marketplace import Python
|
|
19
|
+
from rewrite.python.tree import CompilationUnit
|
|
20
|
+
from rewrite.python.visitor import PythonVisitor
|
|
21
|
+
from rewrite.java.support_types import Space
|
|
22
|
+
from rewrite.java.tree import (
|
|
23
|
+
Assignment,
|
|
24
|
+
Block,
|
|
25
|
+
ClassDeclaration,
|
|
26
|
+
Identifier,
|
|
27
|
+
If,
|
|
28
|
+
MethodDeclaration,
|
|
29
|
+
Statement,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
_CodeQuality = [
|
|
33
|
+
*Python,
|
|
34
|
+
CategoryDescriptor(display_name="Code quality"),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _is_local_self_assignment(statement: Statement) -> bool:
|
|
39
|
+
"""Return True for ``x = x`` on a plain name.
|
|
40
|
+
|
|
41
|
+
Only a plain name is a no-op. ``obj.x = obj.x`` and ``d[k] = d[k]``
|
|
42
|
+
read *and* write through ``__set__``/``__setitem__``, and the other
|
|
43
|
+
assignment shapes (``x: int = x``, ``x = y = y``, ``x += x``) parse to
|
|
44
|
+
node types this deliberately does not match.
|
|
45
|
+
"""
|
|
46
|
+
if not isinstance(statement, Assignment):
|
|
47
|
+
return False
|
|
48
|
+
target, value = statement.variable, statement.assignment
|
|
49
|
+
return (
|
|
50
|
+
isinstance(target, Identifier)
|
|
51
|
+
and isinstance(value, Identifier)
|
|
52
|
+
and target.simple_name == value.simple_name
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _in_function_scope(cursor: Cursor) -> bool:
|
|
57
|
+
"""Return True when the block at ``cursor`` belongs to a function.
|
|
58
|
+
|
|
59
|
+
``X = X`` is a no-op only where both sides bind the same name. In a
|
|
60
|
+
class body or at module scope the right-hand side reads an enclosing
|
|
61
|
+
binding while the left-hand side creates a new one, so the statement
|
|
62
|
+
is what publishes the name.
|
|
63
|
+
"""
|
|
64
|
+
for value in islice(cursor.get_path(), 1, None):
|
|
65
|
+
if isinstance(value, MethodDeclaration):
|
|
66
|
+
return True
|
|
67
|
+
if isinstance(value, (ClassDeclaration, CompilationUnit)):
|
|
68
|
+
return False
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _space_after_block(cursor: Cursor) -> Optional[Space]:
|
|
73
|
+
"""The trivia the printer emits after the block at ``cursor``.
|
|
74
|
+
|
|
75
|
+
A statement's trailing comment is stored on whatever follows it, so a
|
|
76
|
+
block's last statement keeps its comment outside the block -- on the
|
|
77
|
+
next statement of an enclosing block, on the `else` of an enclosing
|
|
78
|
+
`if`, or at end of file. Returns None for the containers this does not
|
|
79
|
+
know how to look past.
|
|
80
|
+
"""
|
|
81
|
+
child = cursor.value
|
|
82
|
+
for parent in islice(cursor.get_path(), 1, None):
|
|
83
|
+
if isinstance(parent, (Block, CompilationUnit)):
|
|
84
|
+
statements = parent._statements
|
|
85
|
+
index = next(
|
|
86
|
+
(i for i, s in enumerate(statements) if s.element.id == child.id),
|
|
87
|
+
None,
|
|
88
|
+
)
|
|
89
|
+
if index is None:
|
|
90
|
+
return None
|
|
91
|
+
if index + 1 < len(statements):
|
|
92
|
+
return statements[index + 1].element.prefix
|
|
93
|
+
if isinstance(parent, CompilationUnit):
|
|
94
|
+
return parent.eof
|
|
95
|
+
elif isinstance(parent, If):
|
|
96
|
+
if parent.else_part is not None and parent.else_part.id != child.id:
|
|
97
|
+
return parent.else_part.prefix
|
|
98
|
+
elif not isinstance(parent, (ClassDeclaration, If.Else, MethodDeclaration)):
|
|
99
|
+
return None
|
|
100
|
+
child = parent
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _ends_line_with_comment(space: Space) -> bool:
|
|
105
|
+
"""Return True when ``space`` opens with a comment on the line it continues."""
|
|
106
|
+
split = _split_line(space)
|
|
107
|
+
return bool((split[0] if split else space).comments)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _split_line(space: Space) -> Optional[Tuple[Space, Space]]:
|
|
111
|
+
"""Split ``space`` at its first newline, or return None if it has no newline.
|
|
112
|
+
|
|
113
|
+
The head is the trivia that still belongs to the preceding line -- most
|
|
114
|
+
importantly that line's trailing comment. The tail starts with the
|
|
115
|
+
newline itself.
|
|
116
|
+
"""
|
|
117
|
+
newline = space.whitespace.find("\n")
|
|
118
|
+
if newline != -1:
|
|
119
|
+
return (
|
|
120
|
+
Space(_comments=[], _whitespace=space.whitespace[:newline]),
|
|
121
|
+
Space(_comments=space.comments, _whitespace=space.whitespace[newline:]),
|
|
122
|
+
)
|
|
123
|
+
for i, comment in enumerate(space.comments):
|
|
124
|
+
newline = comment.suffix.find("\n")
|
|
125
|
+
if newline != -1:
|
|
126
|
+
return (
|
|
127
|
+
Space(
|
|
128
|
+
_comments=[
|
|
129
|
+
*space.comments[:i],
|
|
130
|
+
comment.replace(_suffix=comment.suffix[:newline]),
|
|
131
|
+
],
|
|
132
|
+
_whitespace=space.whitespace,
|
|
133
|
+
),
|
|
134
|
+
Space(
|
|
135
|
+
_comments=space.comments[i + 1:],
|
|
136
|
+
_whitespace=comment.suffix[newline:],
|
|
137
|
+
),
|
|
138
|
+
)
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _concat(left: Space, right: Space) -> Space:
|
|
143
|
+
"""Return the trivia that prints as ``left`` followed by ``right``."""
|
|
144
|
+
if not left.comments:
|
|
145
|
+
return Space(
|
|
146
|
+
_comments=right.comments,
|
|
147
|
+
_whitespace=left.whitespace + right.whitespace,
|
|
148
|
+
)
|
|
149
|
+
last = left.comments[-1]
|
|
150
|
+
return Space(
|
|
151
|
+
_comments=[
|
|
152
|
+
*left.comments[:-1],
|
|
153
|
+
last.replace(_suffix=last.suffix + right.whitespace),
|
|
154
|
+
*right.comments,
|
|
155
|
+
],
|
|
156
|
+
_whitespace=left.whitespace,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _removable(padded_statements: list, i: int, space_after_block: Optional[Space]) -> bool:
|
|
161
|
+
"""Return True when statement ``i`` can be dropped without disturbing its neighbours.
|
|
162
|
+
|
|
163
|
+
The removal is expressed by splicing line-level trivia, so the statement
|
|
164
|
+
has to occupy a line of its own, and so does whatever the printer emits
|
|
165
|
+
next -- either the following statement or, for the last one, the trivia
|
|
166
|
+
after the block. A last statement whose line ends in a comment stays:
|
|
167
|
+
that comment is held outside the block, out of reach.
|
|
168
|
+
"""
|
|
169
|
+
if not _is_local_self_assignment(padded_statements[i].element):
|
|
170
|
+
return False
|
|
171
|
+
if _split_line(padded_statements[i].element.prefix) is None:
|
|
172
|
+
return False
|
|
173
|
+
if i + 1 < len(padded_statements):
|
|
174
|
+
return _split_line(padded_statements[i + 1].element.prefix) is not None
|
|
175
|
+
return space_after_block is not None and not _ends_line_with_comment(space_after_block)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _remove_self_assignments(
|
|
179
|
+
padded_statements: list, end: Space, space_after_block: Optional[Space]
|
|
180
|
+
) -> Optional[Tuple[list, Space]]:
|
|
181
|
+
"""Return the block's statements and end trivia with self-assignments removed, or None if unchanged.
|
|
182
|
+
|
|
183
|
+
Each removed statement's leading trivia is kept up to its own line (it
|
|
184
|
+
holds the preceding statement's trailing comment) and its successor's is
|
|
185
|
+
dropped up to the same point (it holds the removed statement's own).
|
|
186
|
+
Emptying a block would leave its header without a body, so the last
|
|
187
|
+
surviving statement stays.
|
|
188
|
+
"""
|
|
189
|
+
to_remove = {
|
|
190
|
+
i
|
|
191
|
+
for i in range(len(padded_statements))
|
|
192
|
+
if _removable(padded_statements, i, space_after_block)
|
|
193
|
+
}
|
|
194
|
+
if not to_remove or len(to_remove) == len(padded_statements):
|
|
195
|
+
return None
|
|
196
|
+
|
|
197
|
+
result = []
|
|
198
|
+
pending_head: Optional[Space] = None
|
|
199
|
+
for i, padded in enumerate(padded_statements):
|
|
200
|
+
if i in to_remove:
|
|
201
|
+
if pending_head is None:
|
|
202
|
+
pending_head, _ = _split_line(padded.element.prefix)
|
|
203
|
+
continue
|
|
204
|
+
if pending_head is not None:
|
|
205
|
+
_, tail = _split_line(padded.element.prefix)
|
|
206
|
+
padded = padded.replace(
|
|
207
|
+
_element=padded.element.replace(
|
|
208
|
+
_prefix=_concat(pending_head, tail)
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
pending_head = None
|
|
212
|
+
result.append(padded)
|
|
213
|
+
return result, end if pending_head is None else _concat(pending_head, end)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@categorize(_CodeQuality)
|
|
217
|
+
class RemoveSelfAssignment(Recipe):
|
|
218
|
+
"""
|
|
219
|
+
Remove assignments of a plain local name to itself.
|
|
220
|
+
|
|
221
|
+
A self-assignment like ``x = x`` inside a function does nothing at
|
|
222
|
+
runtime. These statements typically result from copy-paste mistakes
|
|
223
|
+
or incomplete refactorings and should be deleted to keep the code
|
|
224
|
+
clear.
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
Before:
|
|
228
|
+
x = x
|
|
229
|
+
|
|
230
|
+
After:
|
|
231
|
+
(statement removed)
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def name(self) -> str:
|
|
236
|
+
return "org.openrewrite.python.codequality.RemoveSelfAssignment"
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def display_name(self) -> str:
|
|
240
|
+
return "Remove self-assignments"
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def description(self) -> str:
|
|
244
|
+
return (
|
|
245
|
+
"Remove statements inside a function that assign a plain local name "
|
|
246
|
+
"to itself (`x = x`), since they have no effect. Class-body and "
|
|
247
|
+
"module-level `X = X` bind a new name from an enclosing one and are "
|
|
248
|
+
"left alone, as are attribute and subscript targets, which run "
|
|
249
|
+
"`__set__` and `__setitem__`."
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def tags(self) -> List[str]:
|
|
254
|
+
return ["python", "code-quality", "RSPEC-S1656"]
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def estimated_effort_per_occurrence(self) -> int:
|
|
258
|
+
return 3
|
|
259
|
+
|
|
260
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
261
|
+
class Visitor(PythonVisitor[ExecutionContext]):
|
|
262
|
+
def visit_block(
|
|
263
|
+
self, block: Block, p: ExecutionContext
|
|
264
|
+
) -> Optional[Block]:
|
|
265
|
+
block = super().visit_block(block, p)
|
|
266
|
+
if not isinstance(block, Block) or not _in_function_scope(self.cursor):
|
|
267
|
+
return block
|
|
268
|
+
removed = _remove_self_assignments(
|
|
269
|
+
block._statements, block.end, _space_after_block(self.cursor)
|
|
270
|
+
)
|
|
271
|
+
if removed is not None:
|
|
272
|
+
return block.replace(_statements=removed[0], _end=removed[1])
|
|
273
|
+
return block
|
|
274
|
+
|
|
275
|
+
return Visitor()
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Recipes for migrating deprecated datetime.utcnow() and datetime.utcfromtimestamp() calls.
|
|
3
|
+
|
|
4
|
+
These methods are deprecated in Python 3.12+ because they return naive datetime objects
|
|
5
|
+
that don't carry timezone information. The recommended replacements are:
|
|
6
|
+
|
|
7
|
+
- datetime.utcnow() -> datetime.now(UTC)
|
|
8
|
+
- datetime.utcfromtimestamp(ts) -> datetime.fromtimestamp(ts, UTC)
|
|
9
|
+
|
|
10
|
+
The replacements are timezone-aware where the deprecated calls were naive, so values already
|
|
11
|
+
stored or compared as naive UTC need migrating alongside the call sites.
|
|
12
|
+
|
|
13
|
+
See: https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from functools import lru_cache
|
|
17
|
+
from typing import Any, Dict, Iterable, List, Optional, Tuple, cast
|
|
18
|
+
|
|
19
|
+
from rewrite import Cursor, ExecutionContext, Preconditions, Recipe, TreeVisitor
|
|
20
|
+
from rewrite.category import CategoryDescriptor
|
|
21
|
+
from rewrite.decorators import categorize
|
|
22
|
+
from rewrite.marketplace import Python
|
|
23
|
+
from rewrite.python.add_import import AddImportOptions, maybe_add_import
|
|
24
|
+
from rewrite.python.import_utils import get_alias_name, get_name_string, get_qualid_name
|
|
25
|
+
from rewrite.python.preconditions import uses_import
|
|
26
|
+
from rewrite.python.scope_utils import LocalBindings
|
|
27
|
+
from rewrite.python.visitor import PythonVisitor
|
|
28
|
+
from rewrite.python.template import Template, capture, raw, template
|
|
29
|
+
from rewrite.python.tree import CompilationUnit, MultiImport, NamedArgument
|
|
30
|
+
from rewrite.java.tree import (
|
|
31
|
+
Block, Empty, FieldAccess, Identifier, Import, J, MethodDeclaration, MethodInvocation,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Define category path: Python > Migrate > Python 3.12
|
|
35
|
+
_Python312 = [
|
|
36
|
+
*Python,
|
|
37
|
+
CategoryDescriptor(display_name="Migrate"),
|
|
38
|
+
CategoryDescriptor(display_name="Python 3.12"),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
_sel = capture('sel')
|
|
42
|
+
_ts = capture('ts')
|
|
43
|
+
|
|
44
|
+
# Names `datetime` exports for UTC, each with the expression reaching it, in preference
|
|
45
|
+
# order: `timezone` is the one Django code routinely binds to `django.utils.timezone`.
|
|
46
|
+
_UTC_NAMES: Tuple[Tuple[str, str], ...] = (("UTC", "UTC"), ("timezone", "timezone.utc"))
|
|
47
|
+
|
|
48
|
+
_DATETIME_CLASS = ("datetime", "datetime")
|
|
49
|
+
_DATETIME_MODULE = (None, "datetime")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@lru_cache(maxsize=None)
|
|
53
|
+
def _now_template(utc: str) -> Template:
|
|
54
|
+
return template(f"{_sel}.now({raw(utc)})")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@lru_cache(maxsize=None)
|
|
58
|
+
def _fromtimestamp_template(utc: str) -> Template:
|
|
59
|
+
return template(f"{_sel}.fromtimestamp({_ts}, {raw(utc)})")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _import_bindings(statements: Iterable[J]) -> Dict[str, Tuple[Optional[str], str]]:
|
|
63
|
+
"""Every name the imports among ``statements`` bind, mapped to the ``(from, name)`` pair it
|
|
64
|
+
was imported as -- ``(None, 'datetime')`` for ``import datetime``."""
|
|
65
|
+
bindings: Dict[str, Tuple[Optional[str], str]] = {}
|
|
66
|
+
|
|
67
|
+
def record(imp: Import, from_: Optional[str]) -> None:
|
|
68
|
+
name = get_qualid_name(imp.qualid)
|
|
69
|
+
bindings[get_alias_name(imp) or name.split('.')[0]] = (from_, name)
|
|
70
|
+
|
|
71
|
+
for stmt in statements:
|
|
72
|
+
if isinstance(stmt, MultiImport):
|
|
73
|
+
from_ = get_name_string(stmt.from_) if stmt.from_ is not None else None
|
|
74
|
+
for imp in stmt.names:
|
|
75
|
+
record(imp, from_)
|
|
76
|
+
elif isinstance(stmt, Import):
|
|
77
|
+
record(stmt, None)
|
|
78
|
+
return bindings
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _enclosing_function_body(cursor: Optional[Cursor]) -> List[J]:
|
|
82
|
+
"""The statements of the innermost function enclosing ``cursor``, empty outside one."""
|
|
83
|
+
while cursor is not None:
|
|
84
|
+
value = cursor.value
|
|
85
|
+
if isinstance(value, MethodDeclaration):
|
|
86
|
+
return list(value.body.statements) if isinstance(value.body, Block) else []
|
|
87
|
+
cursor = cursor.parent
|
|
88
|
+
return []
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class _DatetimeUtcVisitor(PythonVisitor[ExecutionContext]):
|
|
92
|
+
"""Rewrites a naive UTC constructor to its aware counterpart, spelling UTC the way the
|
|
93
|
+
file's own ``datetime`` imports allow. Those imports also identify the receiver, which a
|
|
94
|
+
parse that attributes no types still supplies.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
_target = ""
|
|
98
|
+
|
|
99
|
+
def __init__(self) -> None:
|
|
100
|
+
super().__init__()
|
|
101
|
+
self._bindings: Dict[str, Tuple[Optional[str], str]] = {}
|
|
102
|
+
self._locals = LocalBindings()
|
|
103
|
+
|
|
104
|
+
def visit_compilation_unit(self, cu: CompilationUnit, p: ExecutionContext) -> J:
|
|
105
|
+
self._bindings = _import_bindings(cu.statements)
|
|
106
|
+
self._locals = LocalBindings()
|
|
107
|
+
return super().visit_compilation_unit(cu, p)
|
|
108
|
+
|
|
109
|
+
def targets_datetime(self, method: MethodInvocation) -> bool:
|
|
110
|
+
if not isinstance(method.name, Identifier) or method.name.simple_name != self._target:
|
|
111
|
+
return False
|
|
112
|
+
select = method.select
|
|
113
|
+
if isinstance(select, Identifier):
|
|
114
|
+
return self._resolves(select.simple_name, _DATETIME_CLASS)
|
|
115
|
+
if isinstance(select, FieldAccess) and select.name.simple_name == "datetime":
|
|
116
|
+
return self._resolves(get_name_string(select.target), _DATETIME_MODULE)
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
def _resolves(self, name: str, binding: Tuple[Optional[str], str]) -> bool:
|
|
120
|
+
if not self._locals.is_bound(self.cursor, name):
|
|
121
|
+
return self._bindings.get(name) == binding
|
|
122
|
+
# A deferred `from datetime import datetime` shadows the module-level name with the
|
|
123
|
+
# same class, so the receiver is still `datetime.datetime`.
|
|
124
|
+
return _import_bindings(_enclosing_function_body(self.cursor)).get(name) == binding
|
|
125
|
+
|
|
126
|
+
def utc_reference(self, select: Optional[J]) -> Optional[str]:
|
|
127
|
+
"""An expression evaluating to ``datetime.UTC`` at ``select``'s call site, registering an
|
|
128
|
+
import if one is needed, or None when no name is free to bind it to."""
|
|
129
|
+
if isinstance(select, FieldAccess):
|
|
130
|
+
# `import datetime` puts UTC on the same module the class was reached through.
|
|
131
|
+
return f"{get_name_string(select.target)}.UTC"
|
|
132
|
+
|
|
133
|
+
# The expression is emitted at the call site, where an enclosing scope's binding of
|
|
134
|
+
# the name wins over the module-level import it would otherwise reach.
|
|
135
|
+
free = [(n, e) for n, e in _UTC_NAMES if not self._locals.is_bound(self.cursor, n)]
|
|
136
|
+
|
|
137
|
+
for name, expression in free:
|
|
138
|
+
if self._bindings.get(name) == ("datetime", name):
|
|
139
|
+
return expression
|
|
140
|
+
|
|
141
|
+
for name, expression in free:
|
|
142
|
+
if name not in self._bindings:
|
|
143
|
+
maybe_add_import(
|
|
144
|
+
self,
|
|
145
|
+
AddImportOptions(module="datetime", name=name, only_if_referenced=False),
|
|
146
|
+
)
|
|
147
|
+
return expression
|
|
148
|
+
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@categorize(_Python312)
|
|
153
|
+
class ReplaceDatetimeUtcNow(Recipe):
|
|
154
|
+
"""
|
|
155
|
+
Replace `datetime.utcnow()` with `datetime.now(UTC)`.
|
|
156
|
+
|
|
157
|
+
The `datetime.utcnow()` method is deprecated in Python 3.12 because it returns
|
|
158
|
+
a naive datetime object that doesn't carry timezone information. This can lead
|
|
159
|
+
to subtle bugs when comparing or converting between timezones.
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
Before:
|
|
163
|
+
from datetime import datetime
|
|
164
|
+
now = datetime.utcnow()
|
|
165
|
+
|
|
166
|
+
After:
|
|
167
|
+
from datetime import datetime, UTC
|
|
168
|
+
now = datetime.now(UTC)
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def name(self) -> str:
|
|
173
|
+
return "org.openrewrite.python.migrate.ReplaceDatetimeUtcNow"
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def display_name(self) -> str:
|
|
177
|
+
return "Replace `datetime.utcnow()` with `datetime.now(UTC)`"
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def description(self) -> str:
|
|
181
|
+
return (
|
|
182
|
+
"The `datetime.utcnow()` method is deprecated in Python 3.12. "
|
|
183
|
+
"Replace it with `datetime.now(UTC)`, importing `UTC` from `datetime` where the "
|
|
184
|
+
"name is not already in scope. The result is timezone-aware where `utcnow()` was "
|
|
185
|
+
"naive, so stored or compared values may need migrating alongside the call sites."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def tags(self) -> List[str]:
|
|
190
|
+
return ["python", "migration", "3.12", "datetime"]
|
|
191
|
+
|
|
192
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
193
|
+
class Visitor(_DatetimeUtcVisitor):
|
|
194
|
+
_target = "utcnow"
|
|
195
|
+
|
|
196
|
+
def visit_method_invocation(
|
|
197
|
+
self, method: MethodInvocation, p: ExecutionContext
|
|
198
|
+
) -> J:
|
|
199
|
+
method = cast(MethodInvocation, super().visit_method_invocation(method, p))
|
|
200
|
+
if not self.targets_datetime(method):
|
|
201
|
+
return method
|
|
202
|
+
utc = self.utc_reference(method.select)
|
|
203
|
+
if utc is None:
|
|
204
|
+
return method
|
|
205
|
+
replaced = _now_template(utc).apply(self.cursor, values={'sel': method.select})
|
|
206
|
+
return replaced if replaced is not None else method
|
|
207
|
+
|
|
208
|
+
return Preconditions.check(uses_import("datetime"), Visitor())
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@categorize(_Python312)
|
|
212
|
+
class ReplaceDatetimeUtcFromTimestamp(Recipe):
|
|
213
|
+
"""
|
|
214
|
+
Replace `datetime.utcfromtimestamp(ts)` with `datetime.fromtimestamp(ts, UTC)`.
|
|
215
|
+
|
|
216
|
+
The `datetime.utcfromtimestamp()` method is deprecated in Python 3.12 because it
|
|
217
|
+
returns a naive datetime object.
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
Before:
|
|
221
|
+
from datetime import datetime
|
|
222
|
+
dt = datetime.utcfromtimestamp(1234567890)
|
|
223
|
+
|
|
224
|
+
After:
|
|
225
|
+
from datetime import datetime, UTC
|
|
226
|
+
dt = datetime.fromtimestamp(1234567890, UTC)
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def name(self) -> str:
|
|
231
|
+
return "org.openrewrite.python.migrate.ReplaceDatetimeUtcFromTimestamp"
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def display_name(self) -> str:
|
|
235
|
+
return "Replace `datetime.utcfromtimestamp()` with `datetime.fromtimestamp(ts, UTC)`"
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def description(self) -> str:
|
|
239
|
+
return (
|
|
240
|
+
"The `datetime.utcfromtimestamp()` method is deprecated in Python 3.12. "
|
|
241
|
+
"Replace it with `datetime.fromtimestamp(ts, UTC)`, importing `UTC` from "
|
|
242
|
+
"`datetime` where the name is not already in scope. The result is timezone-aware "
|
|
243
|
+
"where `utcfromtimestamp()` was naive, so stored or compared values may need "
|
|
244
|
+
"migrating alongside the call sites."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
@property
|
|
248
|
+
def tags(self) -> List[str]:
|
|
249
|
+
return ["python", "migration", "3.12", "datetime"]
|
|
250
|
+
|
|
251
|
+
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
252
|
+
class Visitor(_DatetimeUtcVisitor):
|
|
253
|
+
_target = "utcfromtimestamp"
|
|
254
|
+
|
|
255
|
+
def visit_method_invocation(
|
|
256
|
+
self, method: MethodInvocation, p: ExecutionContext
|
|
257
|
+
) -> J:
|
|
258
|
+
method = cast(MethodInvocation, super().visit_method_invocation(method, p))
|
|
259
|
+
if not self.targets_datetime(method):
|
|
260
|
+
return method
|
|
261
|
+
# The tz the template appends is positional, so it cannot follow a keyword one.
|
|
262
|
+
if len(method.arguments) != 1 or isinstance(
|
|
263
|
+
method.arguments[0], (Empty, NamedArgument)
|
|
264
|
+
):
|
|
265
|
+
return method
|
|
266
|
+
utc = self.utc_reference(method.select)
|
|
267
|
+
if utc is None:
|
|
268
|
+
return method
|
|
269
|
+
replaced = _fromtimestamp_template(utc).apply(
|
|
270
|
+
self.cursor, values={'sel': method.select, 'ts': method.arguments[0]}
|
|
271
|
+
)
|
|
272
|
+
return replaced if replaced is not None else method
|
|
273
|
+
|
|
274
|
+
return Preconditions.check(uses_import("datetime"), Visitor())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openrewrite-migrate-python
|
|
3
|
-
Version: 0.14.0.
|
|
3
|
+
Version: 0.14.0.dev20260902122728
|
|
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
|