openrewrite-migrate-python 0.14.0.dev20260902102558__tar.gz → 0.14.0.dev20260902105951__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.dev20260902105951}/PKG-INFO +1 -1
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/pyproject.toml +1 -1
- openrewrite_migrate_python-0.14.0.dev20260902105951/src/openrewrite_migrate_python/migrate/datetime_utc.py +274 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
- openrewrite_migrate_python-0.14.0.dev20260902105951/tests/test_datetime_utc.py +225 -0
- 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.dev20260902105951}/setup.cfg +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_code_quality_python/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_code_quality_python/codequality/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_code_quality_python/codequality/_reindent.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_code_quality_python/codequality/_semantically_equal.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/src/openrewrite_code_quality_python/codequality/remove_self_assignment.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/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.dev20260902105951}/src/openrewrite_migrate_python/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/__init__.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/_import_hygiene.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/_typing_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/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.dev20260902105951}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/avro_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/future_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pydantic_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pydantic_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/pydantic_model_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/re_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/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.dev20260902105951}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/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.dev20260902105951}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_pydantic.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_aifc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_array_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_ast_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_asyncio_coroutine_to_async.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_asyncio_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_avro_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_calendar_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_cgi_parse_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_change_import_recipes.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_collections_abc_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_configparser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_distutils_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_future_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_gettext_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_html_parser_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_imp_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_langchain_classic_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_langchain_community_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_langchain_provider_imports.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_locale_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_macpath_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_os_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_pep594_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_pep594_system_migrations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_pkgutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_platform_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_pydantic_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_pydantic_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_pydantic_model_fields.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_re_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_removed_modules_312.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_shutil_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_ssl_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_string_formatting.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_sys_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_sys_last_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_tarfile_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_threading_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_threading_extended.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_threading_is_alive_deprecation.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_typing_alias_moves.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_typing_callable.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_typing_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_typing_union_to_pipe.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_unittest_deprecations.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_upgrade_recipes.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/tests/test_upgrade_to_pydantic.py +0 -0
- {openrewrite_migrate_python-0.14.0.dev20260902102558 → openrewrite_migrate_python-0.14.0.dev20260902105951}/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.dev20260902105951
|
|
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.dev20260902105951" # 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,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.dev20260902105951
|
|
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
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Tests for datetime UTC migration recipes."""
|
|
2
|
+
|
|
3
|
+
from rewrite.test import RecipeSpec, python
|
|
4
|
+
from openrewrite_migrate_python.migrate.datetime_utc import (
|
|
5
|
+
ReplaceDatetimeUtcNow,
|
|
6
|
+
ReplaceDatetimeUtcFromTimestamp,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
class TestReplaceDatetimeUtcNow:
|
|
10
|
+
"""Tests for the ReplaceDatetimeUtcNow recipe."""
|
|
11
|
+
|
|
12
|
+
def test_binds_utc_when_datetime_class_imported(self):
|
|
13
|
+
"""`from datetime import datetime` leaves no name for UTC, so one is imported."""
|
|
14
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
15
|
+
spec.rewrite_run(
|
|
16
|
+
python(
|
|
17
|
+
"""
|
|
18
|
+
from datetime import datetime
|
|
19
|
+
now = datetime.utcnow()
|
|
20
|
+
""",
|
|
21
|
+
"""
|
|
22
|
+
from datetime import datetime, UTC
|
|
23
|
+
now = datetime.now(UTC)
|
|
24
|
+
""",
|
|
25
|
+
)
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def test_reuses_imported_timezone(self):
|
|
29
|
+
"""An existing `datetime.timezone` binding is used instead of a new import."""
|
|
30
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
31
|
+
spec.rewrite_run(
|
|
32
|
+
python(
|
|
33
|
+
"""
|
|
34
|
+
from datetime import datetime, timezone
|
|
35
|
+
now = datetime.utcnow()
|
|
36
|
+
""",
|
|
37
|
+
"""
|
|
38
|
+
from datetime import datetime, timezone
|
|
39
|
+
now = datetime.now(timezone.utc)
|
|
40
|
+
""",
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
def test_qualifies_through_the_module_binding(self):
|
|
45
|
+
"""`import datetime` binds the module, so `UTC` is reachable without an import."""
|
|
46
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
47
|
+
spec.rewrite_run(
|
|
48
|
+
python(
|
|
49
|
+
"""
|
|
50
|
+
import datetime
|
|
51
|
+
now = datetime.datetime.utcnow()
|
|
52
|
+
""",
|
|
53
|
+
"""
|
|
54
|
+
import datetime
|
|
55
|
+
now = datetime.datetime.now(datetime.UTC)
|
|
56
|
+
""",
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def test_keeps_the_alias_of_an_aliased_class_import(self):
|
|
61
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
62
|
+
spec.rewrite_run(
|
|
63
|
+
python(
|
|
64
|
+
"""
|
|
65
|
+
from datetime import datetime as dt
|
|
66
|
+
now = dt.utcnow()
|
|
67
|
+
""",
|
|
68
|
+
"""
|
|
69
|
+
from datetime import datetime as dt, UTC
|
|
70
|
+
now = dt.now(UTC)
|
|
71
|
+
""",
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def test_falls_back_to_timezone_when_utc_names_something_else(self):
|
|
76
|
+
"""Importing `datetime.UTC` here would rebind pytz's `UTC` for the whole file."""
|
|
77
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
78
|
+
spec.rewrite_run(
|
|
79
|
+
python(
|
|
80
|
+
"""
|
|
81
|
+
from datetime import datetime
|
|
82
|
+
from pytz import UTC
|
|
83
|
+
|
|
84
|
+
now = datetime.utcnow()
|
|
85
|
+
""",
|
|
86
|
+
"""
|
|
87
|
+
from datetime import datetime, timezone
|
|
88
|
+
from pytz import UTC
|
|
89
|
+
|
|
90
|
+
now = datetime.now(timezone.utc)
|
|
91
|
+
""",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def test_falls_back_to_timezone_when_utc_is_locally_rebound(self):
|
|
96
|
+
"""`UTC` would reach the local at the call site, whatever the import binds it to."""
|
|
97
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
98
|
+
spec.rewrite_run(
|
|
99
|
+
python(
|
|
100
|
+
"""
|
|
101
|
+
from datetime import datetime
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def stamp():
|
|
105
|
+
UTC = "Z"
|
|
106
|
+
return UTC, datetime.utcnow()
|
|
107
|
+
""",
|
|
108
|
+
"""
|
|
109
|
+
from datetime import datetime, timezone
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def stamp():
|
|
113
|
+
UTC = "Z"
|
|
114
|
+
return UTC, datetime.now(timezone.utc)
|
|
115
|
+
""",
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def test_no_change_when_both_utc_names_are_taken(self):
|
|
120
|
+
"""With no free name and none bound to `datetime`, there is nothing safe to emit."""
|
|
121
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
122
|
+
spec.rewrite_run(
|
|
123
|
+
python(
|
|
124
|
+
"""
|
|
125
|
+
from datetime import datetime
|
|
126
|
+
from django.utils import timezone
|
|
127
|
+
from pytz import UTC
|
|
128
|
+
|
|
129
|
+
now = datetime.utcnow()
|
|
130
|
+
"""
|
|
131
|
+
)
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def test_rewrites_under_a_function_local_import(self):
|
|
135
|
+
"""A deferred `from datetime import datetime` binds the same class the module one does."""
|
|
136
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
137
|
+
spec.rewrite_run(
|
|
138
|
+
python(
|
|
139
|
+
"""
|
|
140
|
+
def cutoff(max_age):
|
|
141
|
+
from datetime import datetime, timedelta
|
|
142
|
+
|
|
143
|
+
return datetime.utcnow() - timedelta(seconds=max_age)
|
|
144
|
+
""",
|
|
145
|
+
"""
|
|
146
|
+
from datetime import UTC
|
|
147
|
+
def cutoff(max_age):
|
|
148
|
+
from datetime import datetime, timedelta
|
|
149
|
+
|
|
150
|
+
return datetime.now(UTC) - timedelta(seconds=max_age)
|
|
151
|
+
""",
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def test_no_change_when_the_receiver_is_locally_rebound(self):
|
|
156
|
+
"""The parameter shadows the import, so `datetime` here is not the class."""
|
|
157
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
158
|
+
spec.rewrite_run(
|
|
159
|
+
python(
|
|
160
|
+
"""
|
|
161
|
+
from datetime import datetime
|
|
162
|
+
|
|
163
|
+
def render(datetime):
|
|
164
|
+
return datetime.utcnow()
|
|
165
|
+
"""
|
|
166
|
+
)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def test_no_change_when_regular_now(self):
|
|
170
|
+
"""Test that datetime.now() is not marked."""
|
|
171
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
172
|
+
spec.rewrite_run(
|
|
173
|
+
python(
|
|
174
|
+
"""
|
|
175
|
+
from datetime import datetime
|
|
176
|
+
now = datetime.now()
|
|
177
|
+
"""
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def test_no_change_when_different_module(self):
|
|
182
|
+
"""Test that utcnow on a different object is not marked."""
|
|
183
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcNow())
|
|
184
|
+
spec.rewrite_run(
|
|
185
|
+
python(
|
|
186
|
+
"""
|
|
187
|
+
class MyClass:
|
|
188
|
+
def utcnow(self):
|
|
189
|
+
return "not datetime"
|
|
190
|
+
|
|
191
|
+
obj = MyClass()
|
|
192
|
+
result = obj.utcnow()
|
|
193
|
+
"""
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
class TestReplaceDatetimeUtcFromTimestamp:
|
|
198
|
+
"""Tests for the ReplaceDatetimeUtcFromTimestamp recipe."""
|
|
199
|
+
|
|
200
|
+
def test_appends_utc_argument(self):
|
|
201
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcFromTimestamp())
|
|
202
|
+
spec.rewrite_run(
|
|
203
|
+
python(
|
|
204
|
+
"""
|
|
205
|
+
from datetime import datetime
|
|
206
|
+
dt = datetime.utcfromtimestamp(1234567890)
|
|
207
|
+
""",
|
|
208
|
+
"""
|
|
209
|
+
from datetime import datetime, UTC
|
|
210
|
+
dt = datetime.fromtimestamp(1234567890, UTC)
|
|
211
|
+
""",
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def test_no_change_when_regular_fromtimestamp(self):
|
|
216
|
+
"""Test that datetime.fromtimestamp() is not marked."""
|
|
217
|
+
spec = RecipeSpec(recipe=ReplaceDatetimeUtcFromTimestamp())
|
|
218
|
+
spec.rewrite_run(
|
|
219
|
+
python(
|
|
220
|
+
"""
|
|
221
|
+
from datetime import datetime
|
|
222
|
+
dt = datetime.fromtimestamp(1234567890)
|
|
223
|
+
"""
|
|
224
|
+
)
|
|
225
|
+
)
|
|
@@ -1,137 +0,0 @@
|
|
|
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(datetime.UTC)
|
|
8
|
-
- datetime.utcfromtimestamp(ts) -> datetime.fromtimestamp(ts, datetime.UTC)
|
|
9
|
-
|
|
10
|
-
See: https://docs.python.org/3/library/datetime.html#datetime.datetime.utcnow
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
from typing import Any, Optional
|
|
14
|
-
|
|
15
|
-
from rewrite import ExecutionContext, Preconditions, Recipe, TreeVisitor
|
|
16
|
-
from rewrite.category import CategoryDescriptor
|
|
17
|
-
from rewrite.decorators import categorize
|
|
18
|
-
from rewrite.marketplace import Python
|
|
19
|
-
from rewrite.python.preconditions import uses_import
|
|
20
|
-
from rewrite.python.visitor import PythonVisitor
|
|
21
|
-
from rewrite.python.template import pattern, template, capture
|
|
22
|
-
from rewrite.java.tree import MethodInvocation
|
|
23
|
-
|
|
24
|
-
# Define category path: Python > Migrate > Python 3.12
|
|
25
|
-
_Python312 = [
|
|
26
|
-
*Python,
|
|
27
|
-
CategoryDescriptor(display_name="Migrate"),
|
|
28
|
-
CategoryDescriptor(display_name="Python 3.12"),
|
|
29
|
-
]
|
|
30
|
-
|
|
31
|
-
# Pattern/template pairs for datetime.utcnow() -> datetime.now(datetime.UTC)
|
|
32
|
-
_utcnow_pattern = pattern("datetime.utcnow()")
|
|
33
|
-
_utcnow_template = template("datetime.now(datetime.UTC)")
|
|
34
|
-
|
|
35
|
-
# Pattern/template pairs for datetime.utcfromtimestamp(ts) -> datetime.fromtimestamp(ts, datetime.UTC)
|
|
36
|
-
_ts = capture('ts')
|
|
37
|
-
_utcfromtimestamp_pattern = pattern("datetime.utcfromtimestamp({ts})", ts=_ts)
|
|
38
|
-
_utcfromtimestamp_template = template("datetime.fromtimestamp({ts}, datetime.UTC)", ts=_ts)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
@categorize(_Python312)
|
|
42
|
-
class ReplaceDatetimeUtcNow(Recipe):
|
|
43
|
-
"""
|
|
44
|
-
Replace `datetime.utcnow()` with `datetime.now(datetime.UTC)`.
|
|
45
|
-
|
|
46
|
-
The `datetime.utcnow()` method is deprecated in Python 3.12 because it returns
|
|
47
|
-
a naive datetime object that doesn't carry timezone information. This can lead
|
|
48
|
-
to subtle bugs when comparing or converting between timezones.
|
|
49
|
-
|
|
50
|
-
This recipe transforms `datetime.utcnow()` to `datetime.now(datetime.UTC)` which
|
|
51
|
-
returns a timezone-aware datetime object.
|
|
52
|
-
|
|
53
|
-
Example:
|
|
54
|
-
Before:
|
|
55
|
-
from datetime import datetime
|
|
56
|
-
now = datetime.utcnow()
|
|
57
|
-
|
|
58
|
-
After:
|
|
59
|
-
from datetime import datetime
|
|
60
|
-
now = datetime.now(datetime.UTC)
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
@property
|
|
64
|
-
def name(self) -> str:
|
|
65
|
-
return "org.openrewrite.python.migrate.ReplaceDatetimeUtcNow"
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def display_name(self) -> str:
|
|
69
|
-
return "Replace `datetime.utcnow()` with `datetime.now(UTC)`"
|
|
70
|
-
|
|
71
|
-
@property
|
|
72
|
-
def description(self) -> str:
|
|
73
|
-
return (
|
|
74
|
-
"The `datetime.utcnow()` method is deprecated in Python 3.12. "
|
|
75
|
-
"Replace it with `datetime.now(datetime.UTC)` for timezone-aware datetime objects."
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
79
|
-
class Visitor(PythonVisitor[ExecutionContext]):
|
|
80
|
-
def visit_method_invocation(
|
|
81
|
-
self, method: MethodInvocation, p: ExecutionContext
|
|
82
|
-
) -> Optional[MethodInvocation]:
|
|
83
|
-
method = super().visit_method_invocation(method, p)
|
|
84
|
-
match = _utcnow_pattern.match(method, self.cursor)
|
|
85
|
-
if match:
|
|
86
|
-
return _utcnow_template.apply(self.cursor, values=match)
|
|
87
|
-
return method
|
|
88
|
-
|
|
89
|
-
return Preconditions.check(uses_import("datetime"), Visitor())
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@categorize(_Python312)
|
|
93
|
-
class ReplaceDatetimeUtcFromTimestamp(Recipe):
|
|
94
|
-
"""
|
|
95
|
-
Replace `datetime.utcfromtimestamp(ts)` with `datetime.fromtimestamp(ts, datetime.UTC)`.
|
|
96
|
-
|
|
97
|
-
The `datetime.utcfromtimestamp()` method is deprecated in Python 3.12 because it
|
|
98
|
-
returns a naive datetime object. This recipe transforms it to use the timezone-aware
|
|
99
|
-
`datetime.fromtimestamp(ts, datetime.UTC)` form.
|
|
100
|
-
|
|
101
|
-
Example:
|
|
102
|
-
Before:
|
|
103
|
-
from datetime import datetime
|
|
104
|
-
dt = datetime.utcfromtimestamp(1234567890)
|
|
105
|
-
|
|
106
|
-
After:
|
|
107
|
-
from datetime import datetime
|
|
108
|
-
dt = datetime.fromtimestamp(1234567890, datetime.UTC)
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def name(self) -> str:
|
|
113
|
-
return "org.openrewrite.python.migrate.ReplaceDatetimeUtcFromTimestamp"
|
|
114
|
-
|
|
115
|
-
@property
|
|
116
|
-
def display_name(self) -> str:
|
|
117
|
-
return "Replace `datetime.utcfromtimestamp()` with `datetime.fromtimestamp(ts, UTC)`"
|
|
118
|
-
|
|
119
|
-
@property
|
|
120
|
-
def description(self) -> str:
|
|
121
|
-
return (
|
|
122
|
-
"The `datetime.utcfromtimestamp()` method is deprecated in Python 3.12. "
|
|
123
|
-
"Replace it with `datetime.fromtimestamp(ts, datetime.UTC)` for timezone-aware datetime objects."
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def editor(self) -> TreeVisitor[Any, ExecutionContext]:
|
|
127
|
-
class Visitor(PythonVisitor[ExecutionContext]):
|
|
128
|
-
def visit_method_invocation(
|
|
129
|
-
self, method: MethodInvocation, p: ExecutionContext
|
|
130
|
-
) -> Optional[MethodInvocation]:
|
|
131
|
-
method = super().visit_method_invocation(method, p)
|
|
132
|
-
match = _utcfromtimestamp_pattern.match(method, self.cursor)
|
|
133
|
-
if match:
|
|
134
|
-
return _utcfromtimestamp_template.apply(self.cursor, values=match)
|
|
135
|
-
return method
|
|
136
|
-
|
|
137
|
-
return Preconditions.check(uses_import("datetime"), Visitor())
|