openrewrite-migrate-python 0.2.0.dev20260304105122__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/PKG-INFO +1 -1
  2. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/pyproject.toml +1 -1
  3. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/__init__.py +17 -15
  4. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/cgi_parse_deprecations.py +73 -29
  5. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/configparser_deprecations.py +7 -24
  6. openrewrite_migrate_python-0.3.0/src/openrewrite_migrate_python/migrate/future_imports.py +153 -0
  7. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/gettext_deprecations.py +7 -21
  8. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/html_parser_deprecations.py +25 -20
  9. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/locale_deprecations.py +7 -22
  10. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/pkgutil_deprecations.py +7 -22
  11. openrewrite_migrate_python-0.3.0/src/openrewrite_migrate_python/migrate/platform_deprecations.py +138 -0
  12. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/re_deprecations.py +18 -12
  13. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/ssl_deprecations.py +7 -16
  14. openrewrite_migrate_python-0.3.0/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +121 -0
  15. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/tarfile_deprecations.py +15 -19
  16. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python311.py +3 -3
  17. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python312.py +3 -3
  18. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python38.py +23 -13
  19. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python39.py +6 -6
  20. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/xml_deprecations.py +57 -14
  21. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python.egg-info/PKG-INFO +1 -1
  22. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python.egg-info/SOURCES.txt +2 -0
  23. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_aifc_migrations.py +0 -14
  24. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_array_deprecations.py +0 -4
  25. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_ast_deprecations.py +0 -11
  26. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_asyncio_coroutine_to_async.py +0 -2
  27. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_asyncio_deprecations.py +0 -3
  28. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_calendar_deprecations.py +0 -3
  29. openrewrite_migrate_python-0.3.0/tests/test_cgi_parse_deprecations.py +77 -0
  30. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_collections_abc_migrations.py +0 -2
  31. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_configparser_deprecations.py +0 -4
  32. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_datetime_utc.py +0 -4
  33. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_distutils_deprecations.py +0 -3
  34. openrewrite_migrate_python-0.3.0/tests/test_future_imports.py +117 -0
  35. openrewrite_migrate_python-0.3.0/tests/test_html_parser_deprecations.py +84 -0
  36. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_imp_migrations.py +0 -4
  37. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_langchain_classic_imports.py +0 -8
  38. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_langchain_community_imports.py +0 -2
  39. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_langchain_provider_imports.py +0 -3
  40. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_locale_deprecations.py +0 -2
  41. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_pep594_system_migrations.py +0 -13
  42. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_pkgutil_deprecations.py +0 -5
  43. openrewrite_migrate_python-0.3.0/tests/test_platform_deprecations.py +45 -0
  44. openrewrite_migrate_python-0.3.0/tests/test_re_deprecations.py +70 -0
  45. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_removed_modules_312.py +0 -6
  46. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_ssl_deprecations.py +0 -3
  47. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_string_formatting.py +0 -24
  48. openrewrite_migrate_python-0.3.0/tests/test_sys_last_deprecations.py +75 -0
  49. openrewrite_migrate_python-0.3.0/tests/test_tarfile_deprecations.py +55 -0
  50. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_threading_deprecations.py +0 -5
  51. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_typing_deprecations.py +0 -2
  52. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_upgrade_recipes.py +0 -3
  53. openrewrite_migrate_python-0.3.0/tests/test_xml_deprecations.py +106 -0
  54. openrewrite_migrate_python-0.2.0.dev20260304105122/src/openrewrite_migrate_python/migrate/platform_deprecations.py +0 -89
  55. openrewrite_migrate_python-0.2.0.dev20260304105122/src/openrewrite_migrate_python/migrate/sys_last_deprecations.py +0 -94
  56. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_cgi_parse_deprecations.py +0 -46
  57. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_html_parser_deprecations.py +0 -26
  58. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_platform_deprecations.py +0 -24
  59. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_re_deprecations.py +0 -33
  60. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_sys_last_deprecations.py +0 -35
  61. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_tarfile_deprecations.py +0 -24
  62. openrewrite_migrate_python-0.2.0.dev20260304105122/tests/test_xml_deprecations.py +0 -60
  63. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/setup.cfg +0 -0
  64. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/__init__.py +0 -0
  65. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/_markers.py +0 -0
  66. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/aifc_migrations.py +0 -0
  67. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/array_deprecations.py +0 -0
  68. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/ast_deprecations.py +0 -0
  69. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/asyncio_coroutine_to_async.py +0 -0
  70. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/asyncio_deprecations.py +0 -0
  71. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/calendar_deprecations.py +0 -0
  72. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/cgi_migrations.py +0 -0
  73. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/collections_abc_migrations.py +0 -0
  74. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/datetime_utc.py +0 -0
  75. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/distutils_deprecations.py +0 -0
  76. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/distutils_migrations.py +0 -0
  77. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/functools_deprecations.py +0 -0
  78. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/imp_migrations.py +0 -0
  79. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/langchain_classic_imports.py +0 -0
  80. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/langchain_community_imports.py +0 -0
  81. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/langchain_provider_imports.py +0 -0
  82. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/locale_getdefaultlocale_deprecation.py +0 -0
  83. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/macpath_deprecations.py +0 -0
  84. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/mailcap_migrations.py +0 -0
  85. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/nntplib_migrations.py +0 -0
  86. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/os_deprecations.py +0 -0
  87. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/pathlib_deprecations.py +0 -0
  88. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/pep594_system_migrations.py +0 -0
  89. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/pipes_migrations.py +0 -0
  90. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/removed_modules_312.py +0 -0
  91. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/shutil_deprecations.py +0 -0
  92. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/socket_deprecations.py +0 -0
  93. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/string_formatting.py +0 -0
  94. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/sys_deprecations.py +0 -0
  95. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/telnetlib_migrations.py +0 -0
  96. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/tempfile_deprecations.py +0 -0
  97. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/threading_deprecations.py +0 -0
  98. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/threading_is_alive_deprecation.py +0 -0
  99. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/typing_callable.py +0 -0
  100. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/typing_deprecations.py +0 -0
  101. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/typing_union_to_pipe.py +0 -0
  102. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/unittest_deprecations.py +0 -0
  103. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain02.py +0 -0
  104. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_langchain1.py +0 -0
  105. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python310.py +0 -0
  106. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python313.py +0 -0
  107. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/upgrade_to_python314.py +0 -0
  108. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/urllib_deprecations.py +0 -0
  109. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/uu_migrations.py +0 -0
  110. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python/migrate/xdrlib_migrations.py +0 -0
  111. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python.egg-info/dependency_links.txt +0 -0
  112. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python.egg-info/entry_points.txt +0 -0
  113. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python.egg-info/requires.txt +0 -0
  114. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/src/openrewrite_migrate_python.egg-info/top_level.txt +0 -0
  115. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_change_import_recipes.py +0 -0
  116. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_gettext_deprecations.py +0 -0
  117. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_locale_getdefaultlocale_deprecation.py +0 -0
  118. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_macpath_deprecations.py +0 -0
  119. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_os_deprecations.py +0 -0
  120. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_pep594_migrations.py +0 -0
  121. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_shutil_deprecations.py +0 -0
  122. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_sys_deprecations.py +0 -0
  123. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_threading_extended.py +0 -0
  124. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_threading_is_alive_deprecation.py +0 -0
  125. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_typing_callable.py +0 -0
  126. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_typing_union_to_pipe.py +0 -0
  127. {openrewrite_migrate_python-0.2.0.dev20260304105122 → openrewrite_migrate_python-0.3.0}/tests/test_unittest_deprecations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openrewrite-migrate-python
3
- Version: 0.2.0.dev20260304105122
3
+ Version: 0.3.0
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.2.0.dev20260304105122" # Updated dynamically during release
8
+ version = "0.3.0" # 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"
@@ -32,7 +32,7 @@ from .asyncio_coroutine_to_async import MigrateAsyncioCoroutine
32
32
  from .asyncio_deprecations import FindAsyncioCoroutineDecorator
33
33
  from .calendar_deprecations import ReplaceCalendarConstants
34
34
  from .cgi_migrations import FindCgiModule, FindCgitbModule
35
- from .cgi_parse_deprecations import FindCgiParseQs, FindCgiParseQsl
35
+ from .cgi_parse_deprecations import ReplaceCgiParseQs, ReplaceCgiParseQsl
36
36
  from .collections_abc_migrations import ReplaceCollectionsAbcImports
37
37
  from .configparser_deprecations import (
38
38
  ReplaceConfigparserReadfp,
@@ -42,8 +42,9 @@ from .datetime_utc import ReplaceDatetimeUtcFromTimestamp, ReplaceDatetimeUtcNow
42
42
  from .distutils_deprecations import FindDistutilsUsage
43
43
  from .distutils_migrations import ReplaceDistutilsVersion
44
44
  from .functools_deprecations import FindFunctoolsCmpToKey
45
+ from .future_imports import RemoveFutureImports
45
46
  from .gettext_deprecations import ReplaceGettextDeprecations
46
- from .html_parser_deprecations import FindHtmlParserUnescape
47
+ from .html_parser_deprecations import ReplaceHtmlParserUnescape
47
48
  from .imp_migrations import FindImpUsage
48
49
  from .locale_deprecations import ReplaceLocaleResetlocale
49
50
  from .locale_getdefaultlocale_deprecation import FindLocaleGetdefaultlocale
@@ -52,7 +53,7 @@ from .mailcap_migrations import FindMailcapModule
52
53
  from .nntplib_migrations import FindNntplibModule
53
54
  from .os_deprecations import FindOsPopen, FindOsSpawn
54
55
  from .pathlib_deprecations import FindPathlibLinkTo
55
- from .platform_deprecations import FindPlatformPopen
56
+ from .platform_deprecations import ReplacePlatformPopen
56
57
  from .pep594_system_migrations import (
57
58
  FindCryptModule,
58
59
  FindMsilibModule,
@@ -62,15 +63,15 @@ from .pep594_system_migrations import (
62
63
  )
63
64
  from .pipes_migrations import FindPipesModule
64
65
  from .pkgutil_deprecations import ReplacePkgutilFindLoader, ReplacePkgutilGetLoader
65
- from .re_deprecations import FindReTemplate
66
+ from .re_deprecations import ReplaceReTemplate
66
67
  from .removed_modules_312 import FindRemovedModules312
67
68
  from .shutil_deprecations import FindShutilRmtreeOnerror
68
69
  from .socket_deprecations import FindSocketGetFQDN
69
70
  from .ssl_deprecations import FindSslMatchHostname
70
71
  from .string_formatting import ReplacePercentFormatWithFString, ReplaceStrFormatWithFString
71
72
  from .sys_deprecations import FindSysCoroutineWrapper
72
- from .sys_last_deprecations import FindSysLastExcInfo
73
- from .tarfile_deprecations import FindTarfileFilemode
73
+ from .sys_last_deprecations import ReplaceSysLastExcInfo
74
+ from .tarfile_deprecations import ReplaceTarfileFilemode
74
75
  from .telnetlib_migrations import FindTelnetlibModule
75
76
  from .tempfile_deprecations import FindTempfileMktemp
76
77
  from .threading_is_alive_deprecation import ReplaceThreadIsAlive
@@ -100,7 +101,7 @@ from .upgrade_to_python314 import UpgradeToPython314
100
101
  from .urllib_deprecations import FindUrllibParseSplitFunctions, FindUrllibParseToBytes
101
102
  from .uu_migrations import FindUuModule
102
103
  from .xdrlib_migrations import FindXdrlibModule
103
- from .xml_deprecations import FindElementGetchildren, ReplaceElementGetiterator
104
+ from .xml_deprecations import ReplaceElementGetchildren, ReplaceElementGetiterator
104
105
 
105
106
  __all__ = [
106
107
  # String formatting auto-fix recipes
@@ -110,15 +111,16 @@ __all__ = [
110
111
  "FindAsyncioCoroutineDecorator",
111
112
  "FindAudioopModule",
112
113
  "FindCgiModule",
113
- "FindCgiParseQs",
114
- "FindCgiParseQsl",
114
+ "ReplaceCgiParseQs",
115
+ "ReplaceCgiParseQsl",
115
116
  "FindCgitbModule",
116
117
  "FindChunkModule",
117
118
  "FindCryptModule",
118
119
  "FindDistutilsUsage",
119
- "FindElementGetchildren",
120
+ "ReplaceElementGetchildren",
120
121
  "FindFunctoolsCmpToKey",
121
- "FindHtmlParserUnescape",
122
+ "RemoveFutureImports",
123
+ "ReplaceHtmlParserUnescape",
122
124
  "FindImghdrModule",
123
125
  "FindImpUsage",
124
126
  "FindMacpathModule",
@@ -132,18 +134,18 @@ __all__ = [
132
134
  "FindOssaudiodevModule",
133
135
  "FindPathlibLinkTo",
134
136
  "FindPipesModule",
135
- "FindPlatformPopen",
136
- "FindReTemplate",
137
+ "ReplacePlatformPopen",
138
+ "ReplaceReTemplate",
137
139
  "FindRemovedModules312",
138
140
  "FindShutilRmtreeOnerror",
139
141
  "FindSndhdrModule",
140
142
  "FindSocketGetFQDN",
141
143
  "FindSpwdModule",
142
144
  "FindSslMatchHostname",
143
- "FindSysLastExcInfo",
145
+ "ReplaceSysLastExcInfo",
144
146
  "FindSunauModule",
145
147
  "FindSysCoroutineWrapper",
146
- "FindTarfileFilemode",
148
+ "ReplaceTarfileFilemode",
147
149
  "FindTelnetlibModule",
148
150
  "FindTempfileMktemp",
149
151
  "ReplaceThreadGetName",
@@ -1,5 +1,6 @@
1
1
  """
2
- Recipes for migrating deprecated cgi module parse functions.
2
+ Recipes for replacing deprecated cgi.parse_qs/parse_qsl with
3
+ urllib.parse.parse_qs/parse_qsl.
3
4
 
4
5
  The following functions were deprecated in Python 3.2 and removed in Python 3.8:
5
6
  - cgi.parse_qs() -> urllib.parse.parse_qs()
@@ -16,9 +17,11 @@ from rewrite import ExecutionContext, Recipe, TreeVisitor
16
17
  from rewrite.category import CategoryDescriptor
17
18
  from rewrite.decorators import categorize
18
19
  from rewrite.marketplace import Python
20
+ from rewrite.markers import Markers
19
21
  from rewrite.python.visitor import PythonVisitor
20
- from rewrite.java.tree import Identifier, MethodInvocation
21
- from ._markers import mark_deprecated
22
+ from rewrite.java.support_types import JLeftPadded, Space
23
+ from rewrite.java.tree import FieldAccess, Identifier, MethodInvocation
24
+ from rewrite.utils import random_id
22
25
 
23
26
  # Define category path: Python > Migrate > Python 3.8
24
27
  _Python38 = [
@@ -28,10 +31,51 @@ _Python38 = [
28
31
  ]
29
32
 
30
33
 
34
+ def _create_urllib_parse_select(original_select: Identifier) -> FieldAccess:
35
+ """Create a FieldAccess node representing 'urllib.parse'.
36
+
37
+ Preserves the prefix/whitespace from the original select.
38
+ """
39
+ parse_identifier = Identifier(
40
+ _id=random_id(),
41
+ _prefix=Space.EMPTY,
42
+ _markers=Markers.EMPTY,
43
+ _annotations=[],
44
+ _simple_name="parse",
45
+ _type=None,
46
+ _field_type=None,
47
+ )
48
+
49
+ padded_parse = JLeftPadded(
50
+ _before=Space.EMPTY,
51
+ _element=parse_identifier,
52
+ _markers=Markers.EMPTY,
53
+ )
54
+
55
+ urllib_identifier = Identifier(
56
+ _id=random_id(),
57
+ _prefix=original_select.prefix,
58
+ _markers=Markers.EMPTY,
59
+ _annotations=[],
60
+ _simple_name="urllib",
61
+ _type=None,
62
+ _field_type=None,
63
+ )
64
+
65
+ return FieldAccess(
66
+ _id=random_id(),
67
+ _prefix=Space.EMPTY,
68
+ _markers=Markers.EMPTY,
69
+ _target=urllib_identifier,
70
+ _name=padded_parse,
71
+ _type=None,
72
+ )
73
+
74
+
31
75
  @categorize(_Python38)
32
- class FindCgiParseQs(Recipe):
76
+ class ReplaceCgiParseQs(Recipe):
33
77
  """
34
- Find usages of removed `cgi.parse_qs()`.
78
+ Replace `cgi.parse_qs()` with `urllib.parse.parse_qs()`.
35
79
 
36
80
  `cgi.parse_qs()` was deprecated in Python 3.2 and removed in Python 3.8.
37
81
  Use `urllib.parse.parse_qs()` instead.
@@ -43,24 +87,24 @@ class FindCgiParseQs(Recipe):
43
87
 
44
88
  After:
45
89
  from urllib.parse import parse_qs
46
- params = parse_qs(query_string)
47
-
48
- Note: This is a search recipe because the import change is non-trivial.
90
+ params = urllib.parse.parse_qs(query_string)
49
91
  """
50
92
 
51
93
  @property
52
94
  def name(self) -> str:
53
- return "org.openrewrite.python.migrate.FindCgiParseQs"
95
+ return "org.openrewrite.python.migrate.ReplaceCgiParseQs"
54
96
 
55
97
  @property
56
98
  def display_name(self) -> str:
57
- return "Find removed `cgi.parse_qs()` usage"
99
+ return "Replace `cgi.parse_qs()` with `urllib.parse.parse_qs()`"
58
100
 
59
101
  @property
60
102
  def description(self) -> str:
61
103
  return (
62
104
  "`cgi.parse_qs()` was removed in Python 3.8. "
63
- "Use `urllib.parse.parse_qs()` instead."
105
+ "Use `urllib.parse.parse_qs()` instead. "
106
+ "Note: this rewrites call sites but does not manage imports. "
107
+ "Use with `ChangeImport` in a composite recipe to update `from` imports."
64
108
  )
65
109
 
66
110
  @property
@@ -85,19 +129,19 @@ class FindCgiParseQs(Recipe):
85
129
  if select.simple_name != "cgi":
86
130
  return method
87
131
 
88
- return mark_deprecated(
89
- method,
90
- "cgi.parse_qs() was removed in Python 3.8. "
91
- "Use urllib.parse.parse_qs() instead."
92
- )
132
+ # Replace cgi with urllib.parse
133
+ new_select = _create_urllib_parse_select(select)
134
+ old_padded = method.padding.select
135
+ new_padded = old_padded.replace(_element=new_select)
136
+ return method.padding.replace(_select=new_padded)
93
137
 
94
138
  return Visitor()
95
139
 
96
140
 
97
141
  @categorize(_Python38)
98
- class FindCgiParseQsl(Recipe):
142
+ class ReplaceCgiParseQsl(Recipe):
99
143
  """
100
- Find usages of removed `cgi.parse_qsl()`.
144
+ Replace `cgi.parse_qsl()` with `urllib.parse.parse_qsl()`.
101
145
 
102
146
  `cgi.parse_qsl()` was deprecated in Python 3.2 and removed in Python 3.8.
103
147
  Use `urllib.parse.parse_qsl()` instead.
@@ -109,24 +153,24 @@ class FindCgiParseQsl(Recipe):
109
153
 
110
154
  After:
111
155
  from urllib.parse import parse_qsl
112
- params = parse_qsl(query_string)
113
-
114
- Note: This is a search recipe because the import change is non-trivial.
156
+ params = urllib.parse.parse_qsl(query_string)
115
157
  """
116
158
 
117
159
  @property
118
160
  def name(self) -> str:
119
- return "org.openrewrite.python.migrate.FindCgiParseQsl"
161
+ return "org.openrewrite.python.migrate.ReplaceCgiParseQsl"
120
162
 
121
163
  @property
122
164
  def display_name(self) -> str:
123
- return "Find removed `cgi.parse_qsl()` usage"
165
+ return "Replace `cgi.parse_qsl()` with `urllib.parse.parse_qsl()`"
124
166
 
125
167
  @property
126
168
  def description(self) -> str:
127
169
  return (
128
170
  "`cgi.parse_qsl()` was removed in Python 3.8. "
129
- "Use `urllib.parse.parse_qsl()` instead."
171
+ "Use `urllib.parse.parse_qsl()` instead. "
172
+ "Note: this rewrites call sites but does not manage imports. "
173
+ "Use with `ChangeImport` in a composite recipe to update `from` imports."
130
174
  )
131
175
 
132
176
  @property
@@ -151,10 +195,10 @@ class FindCgiParseQsl(Recipe):
151
195
  if select.simple_name != "cgi":
152
196
  return method
153
197
 
154
- return mark_deprecated(
155
- method,
156
- "cgi.parse_qsl() was removed in Python 3.8. "
157
- "Use urllib.parse.parse_qsl() instead."
158
- )
198
+ # Replace cgi with urllib.parse
199
+ new_select = _create_urllib_parse_select(select)
200
+ old_padded = method.padding.select
201
+ new_padded = old_padded.replace(_element=new_select)
202
+ return method.padding.replace(_select=new_padded)
159
203
 
160
204
  return Visitor()
@@ -73,37 +73,20 @@ def _replace_configparser_attribute(field_access: FieldAccess, new_name: str) ->
73
73
 
74
74
  def _is_configparser_method(method: MethodInvocation, method_name: str) -> bool:
75
75
  """
76
- Check if this is a method call on a ConfigParser instance.
77
-
78
- Since we can't always determine the type of the receiver, we check:
79
- 1. Method name matches
80
- 2. Type attribution if available
81
- 3. Common variable names like 'config', 'parser', 'cfg', 'cp'
76
+ Check if this is a method call on a ConfigParser instance using type attribution.
82
77
  """
83
78
  if not isinstance(method.name, Identifier):
84
79
  return False
85
80
  if method.name.simple_name != method_name:
86
81
  return False
87
82
 
88
- # Check if type info confirms this is a configparser method
89
- if method.method_type and method.method_type.declaring_type:
90
- dt = method.method_type.declaring_type
91
- if hasattr(dt, '_fully_qualified_name'):
92
- fqn = str(dt._fully_qualified_name)
93
- if 'configparser' in fqn.lower() or 'ConfigParser' in fqn:
94
- return True
95
-
96
- # Fallback: Check for common ConfigParser variable names
97
- select = method.select
98
- if select is None:
83
+ if not method.method_type or not method.method_type.declaring_type:
99
84
  return False
100
- if isinstance(select, Identifier):
101
- name = select.simple_name.lower()
102
- # Common names for ConfigParser instances
103
- if name in ('config', 'parser', 'cfg', 'cp', 'conf', 'configparser', 'safe_parser'):
104
- return True
105
-
106
- return False
85
+ dt = method.method_type.declaring_type
86
+ if not hasattr(dt, '_fully_qualified_name'):
87
+ return False
88
+ fqn = str(dt._fully_qualified_name)
89
+ return 'configparser' in fqn.lower() or 'ConfigParser' in fqn
107
90
 
108
91
 
109
92
  def _rename_method(method: MethodInvocation, new_name: str) -> MethodInvocation:
@@ -0,0 +1,153 @@
1
+ """
2
+ Recipe for removing obsolete __future__ imports.
3
+
4
+ In Python 3, many __future__ imports are no longer needed because their
5
+ features are enabled by default:
6
+ - print_function (default since Python 3.0)
7
+ - division (default since Python 3.0)
8
+ - unicode_literals (default since Python 3.0)
9
+ - absolute_import (default since Python 3.0)
10
+ - generators (default since Python 2.3)
11
+ - nested_scopes (default since Python 2.2)
12
+ - with_statement (default since Python 2.6)
13
+
14
+ The `annotations` import (PEP 563) is kept because it still has meaning
15
+ in Python 3.10+ (postponed evaluation of annotations).
16
+ """
17
+
18
+ from typing import Any, List, Optional
19
+
20
+ from rewrite import ExecutionContext, Recipe, TreeVisitor
21
+ from rewrite.category import CategoryDescriptor
22
+ from rewrite.decorators import categorize
23
+ from rewrite.marketplace import Python
24
+ from rewrite.python.visitor import PythonVisitor
25
+ from rewrite.python.tree import MultiImport
26
+ from rewrite.java.tree import FieldAccess, Identifier
27
+
28
+ # Define category path: Python > Migrate
29
+ _Migrate = [
30
+ *Python,
31
+ CategoryDescriptor(display_name="Migrate"),
32
+ ]
33
+
34
+ # __future__ imports that are obsolete in Python 3 and can be safely removed
35
+ OBSOLETE_FUTURE_IMPORTS = {
36
+ "print_function",
37
+ "division",
38
+ "unicode_literals",
39
+ "absolute_import",
40
+ "generators",
41
+ "nested_scopes",
42
+ "with_statement",
43
+ }
44
+
45
+
46
+ def _get_import_name(import_node: Any) -> Optional[str]:
47
+ """Extract the imported name from an import node's qualid."""
48
+ qualid = import_node.qualid
49
+ if isinstance(qualid, FieldAccess):
50
+ if isinstance(qualid.name, Identifier):
51
+ return qualid.name.simple_name
52
+ elif isinstance(qualid, Identifier):
53
+ return qualid.simple_name
54
+ return None
55
+
56
+
57
+ @categorize(_Migrate)
58
+ class RemoveFutureImports(Recipe):
59
+ """
60
+ Remove obsolete `from __future__ import ...` statements.
61
+
62
+ In Python 3, many `__future__` imports are enabled by default and
63
+ no longer needed. This recipe removes imports that are obsolete:
64
+ - `print_function` (default since 3.0)
65
+ - `division` (default since 3.0)
66
+ - `unicode_literals` (default since 3.0)
67
+ - `absolute_import` (default since 3.0)
68
+ - `generators` (default since 2.3)
69
+ - `nested_scopes` (default since 2.2)
70
+ - `with_statement` (default since 2.6)
71
+
72
+ The `annotations` import (PEP 563) is preserved.
73
+
74
+ Example:
75
+ Before:
76
+ from __future__ import print_function, division, annotations
77
+
78
+ After:
79
+ from __future__ import annotations
80
+ """
81
+
82
+ @property
83
+ def name(self) -> str:
84
+ return "org.openrewrite.python.migrate.RemoveFutureImports"
85
+
86
+ @property
87
+ def display_name(self) -> str:
88
+ return "Remove obsolete `__future__` imports"
89
+
90
+ @property
91
+ def description(self) -> str:
92
+ return (
93
+ "Remove `from __future__ import ...` statements for features that "
94
+ "are enabled by default in Python 3."
95
+ )
96
+
97
+ @property
98
+ def tags(self) -> List[str]:
99
+ return ["python", "migration", "future"]
100
+
101
+ def editor(self) -> TreeVisitor[Any, ExecutionContext]:
102
+ class Visitor(PythonVisitor[ExecutionContext]):
103
+ def visit_multi_import(
104
+ self, multi: MultiImport, p: ExecutionContext
105
+ ) -> Optional[MultiImport]:
106
+ multi = super().visit_multi_import(multi, p)
107
+
108
+ # Check if this is a 'from __future__ import ...' statement
109
+ from_part = multi.from_
110
+ if from_part is None:
111
+ return multi
112
+ if not isinstance(from_part, Identifier):
113
+ return multi
114
+ if from_part.simple_name != "__future__":
115
+ return multi
116
+
117
+ # Collect imported names and categorize them
118
+ names = multi.names
119
+ keep_indices = []
120
+ for i, import_node in enumerate(names):
121
+ imported_name = _get_import_name(import_node)
122
+ if imported_name is not None and imported_name not in OBSOLETE_FUTURE_IMPORTS:
123
+ keep_indices.append(i)
124
+
125
+ # If all names should be kept, no change
126
+ if len(keep_indices) == len(names):
127
+ return multi
128
+
129
+ # If all names should be removed, remove the entire statement
130
+ if len(keep_indices) == 0:
131
+ # noinspection PyTypeChecker
132
+ return None
133
+
134
+ # Partial removal: keep only non-obsolete names
135
+ old_container = multi.padding.names
136
+ old_elements = list(old_container.padding.elements)
137
+ new_elements = [old_elements[i] for i in keep_indices]
138
+
139
+ # Fix spacing: if the first remaining element had extra prefix
140
+ # from being after a comma, use the prefix of the original first element
141
+ if new_elements and keep_indices[0] != 0:
142
+ original_first = old_elements[0]
143
+ first = new_elements[0]
144
+ # Copy the prefix from original first element
145
+ new_first_elem = first.element.replace(
146
+ _prefix=original_first.element.prefix
147
+ )
148
+ new_elements[0] = first.replace(_element=new_first_elem)
149
+
150
+ new_container = old_container.replace(_elements=new_elements)
151
+ return multi.replace(_names=new_container)
152
+
153
+ return Visitor()
@@ -49,34 +49,20 @@ def _rename_method(method: MethodInvocation, new_name: str) -> MethodInvocation:
49
49
 
50
50
  def _is_gettext_method(method: MethodInvocation, method_name: str) -> bool:
51
51
  """
52
- Check if this is a gettext.method_name() call.
53
-
54
- Uses multiple strategies to identify the method:
55
- 1. Check method name matches
56
- 2. Check type attribution if available
57
- 3. Check simple name on select is 'gettext'
52
+ Check if this is a gettext.method_name() call using type attribution.
58
53
  """
59
54
  if not isinstance(method.name, Identifier):
60
55
  return False
61
56
  if method.name.simple_name != method_name:
62
57
  return False
63
58
 
64
- # Check if type info confirms this is a gettext method
65
- if method.method_type and method.method_type.declaring_type:
66
- dt = method.method_type.declaring_type
67
- if hasattr(dt, '_fully_qualified_name'):
68
- fqn = str(dt._fully_qualified_name)
69
- if 'gettext' in fqn:
70
- return True
71
-
72
- # Fallback: Check simple name on select
73
- select = method.select
74
- if select is None:
59
+ if not method.method_type or not method.method_type.declaring_type:
75
60
  return False
76
- if isinstance(select, Identifier) and select.simple_name == "gettext":
77
- return True
78
-
79
- return False
61
+ dt = method.method_type.declaring_type
62
+ if not hasattr(dt, '_fully_qualified_name'):
63
+ return False
64
+ fqn = str(dt._fully_qualified_name)
65
+ return 'gettext' in fqn
80
66
 
81
67
 
82
68
  @categorize(_Python311)
@@ -1,5 +1,5 @@
1
1
  """
2
- Recipe for migrating deprecated HTMLParser.unescape() method.
2
+ Recipe for replacing deprecated HTMLParser.unescape() with html.unescape().
3
3
 
4
4
  HTMLParser.unescape() was deprecated in Python 3.4 and removed in Python 3.9.
5
5
  Use html.unescape() instead.
@@ -15,7 +15,6 @@ from rewrite.decorators import categorize
15
15
  from rewrite.marketplace import Python
16
16
  from rewrite.python.visitor import PythonVisitor
17
17
  from rewrite.java.tree import Identifier, MethodInvocation
18
- from ._markers import mark_deprecated
19
18
 
20
19
  # Define category path: Python > Migrate > Python 3.9
21
20
  _Python39 = [
@@ -26,9 +25,9 @@ _Python39 = [
26
25
 
27
26
 
28
27
  @categorize(_Python39)
29
- class FindHtmlParserUnescape(Recipe):
28
+ class ReplaceHtmlParserUnescape(Recipe):
30
29
  """
31
- Find usages of removed `HTMLParser.unescape()`.
30
+ Replace `HTMLParser.unescape()` with `html.unescape()`.
32
31
 
33
32
  `HTMLParser.unescape()` was deprecated in Python 3.4 and removed in Python 3.9.
34
33
  Use `html.unescape()` instead.
@@ -42,18 +41,15 @@ class FindHtmlParserUnescape(Recipe):
42
41
  After:
43
42
  import html
44
43
  text = html.unescape(html_text)
45
-
46
- Note: This is a search recipe because the transformation requires
47
- changing from an instance method to a module function.
48
44
  """
49
45
 
50
46
  @property
51
47
  def name(self) -> str:
52
- return "org.openrewrite.python.migrate.FindHtmlParserUnescape"
48
+ return "org.openrewrite.python.migrate.ReplaceHtmlParserUnescape"
53
49
 
54
50
  @property
55
51
  def display_name(self) -> str:
56
- return "Find removed `HTMLParser.unescape()` usage"
52
+ return "Replace `HTMLParser.unescape()` with `html.unescape()`"
57
53
 
58
54
  @property
59
55
  def description(self) -> str:
@@ -78,23 +74,32 @@ class FindHtmlParserUnescape(Recipe):
78
74
  if method.name.simple_name != "unescape":
79
75
  return method
80
76
 
81
- # We can't easily distinguish HTMLParser.unescape from other
82
- # .unescape() calls without type information, so we mark all
83
- # .unescape() calls on non-module selects as potential matches
84
- # when the select is not a known module name like "html"
77
+ # Already using html.unescape(), no change needed
85
78
  select = method.select
86
79
  if isinstance(select, Identifier) and select.simple_name == "html":
87
- # Already using html.unescape(), no change needed
88
80
  return method
89
81
 
90
82
  # Skip if no select (bare function call)
91
- if method.select is None:
83
+ if select is None:
84
+ return method
85
+
86
+ # Require type info to confirm HTMLParser
87
+ if not method.method_type or not method.method_type.declaring_type:
92
88
  return method
89
+ dt = method.method_type.declaring_type
90
+ if not hasattr(dt, '_fully_qualified_name'):
91
+ return method
92
+ fqn = str(dt._fully_qualified_name)
93
+ if 'html.parser' not in fqn:
94
+ return method
95
+
96
+ # Replace select with html identifier
97
+ if isinstance(select, Identifier):
98
+ new_select = select.replace(_simple_name="html")
99
+ old_padded = method.padding.select
100
+ new_padded = old_padded.replace(_element=new_select)
101
+ return method.padding.replace(_select=new_padded)
93
102
 
94
- return mark_deprecated(
95
- method,
96
- "HTMLParser.unescape() was removed in Python 3.9. "
97
- "Use html.unescape() instead."
98
- )
103
+ return method
99
104
 
100
105
  return Visitor()
@@ -29,35 +29,20 @@ _Python311 = [
29
29
 
30
30
  def _is_locale_method(method: MethodInvocation, method_name: str) -> bool:
31
31
  """
32
- Check if this is a locale.method_name() call.
33
-
34
- Uses multiple strategies to identify the method:
35
- 1. Check method name matches
36
- 2. Check type attribution if available (FQN contains 'locale')
37
- 3. Check simple name on select is 'locale'
32
+ Check if this is a locale.method_name() call using type attribution.
38
33
  """
39
- # Check method name first
40
34
  if not isinstance(method.name, Identifier):
41
35
  return False
42
36
  if method.name.simple_name != method_name:
43
37
  return False
44
38
 
45
- # Check if type info confirms this is a locale method
46
- if method.method_type and method.method_type.declaring_type:
47
- dt = method.method_type.declaring_type
48
- if hasattr(dt, '_fully_qualified_name'):
49
- fqn = str(dt._fully_qualified_name)
50
- if 'locale' in fqn:
51
- return True
52
-
53
- # Fallback: Check simple name on select
54
- select = method.select
55
- if select is None:
39
+ if not method.method_type or not method.method_type.declaring_type:
56
40
  return False
57
- if isinstance(select, Identifier) and select.simple_name == "locale":
58
- return True
59
-
60
- return False
41
+ dt = method.method_type.declaring_type
42
+ if not hasattr(dt, '_fully_qualified_name'):
43
+ return False
44
+ fqn = str(dt._fully_qualified_name)
45
+ return 'locale' in fqn
61
46
 
62
47
 
63
48
  def _rename_method(method: MethodInvocation, new_name: str) -> MethodInvocation: