|
| 1 | +import dataclasses |
| 2 | +import re |
| 3 | + |
| 4 | + |
| 5 | +@dataclasses.dataclass(slots=True, frozen=True) |
| 6 | +class Deprecated: |
| 7 | + name: str |
| 8 | + version: tuple[int] |
| 9 | + remove: tuple[int] | None |
| 10 | + message: str | None |
| 11 | + |
| 12 | + |
| 13 | +def _parse_version(version_str): |
| 14 | + version = tuple(int(part) for part in version_str.split('.')) |
| 15 | + if len(version) < 2: |
| 16 | + raise ValueError(f"invalid Python version: {version_str!r}") |
| 17 | + return version |
| 18 | + |
| 19 | + |
| 20 | +_DEPRECATIONS = {} |
| 21 | +_DEPRECATIONS_CAPI = {} |
| 22 | +_REGEX_NAME = r'[A-Za-z_][A-Za-z0-9_]*' |
| 23 | +_REGEX_QUALNAME = fr'^{_REGEX_NAME}(\.{_REGEX_NAME})*$' |
| 24 | + |
| 25 | + |
| 26 | +def _deprecate_api(api_dict, name, version, remove, replace): |
| 27 | + if not re.match(_REGEX_QUALNAME, name): |
| 28 | + raise ValueError(f"invalid name: {name!a}") |
| 29 | + version = _parse_version(version) |
| 30 | + if remove is not None: |
| 31 | + remove = _parse_version(remove) |
| 32 | + if replace is not None: |
| 33 | + msg = f'use {replace}' |
| 34 | + else: |
| 35 | + msg = None |
| 36 | + deprecated = Deprecated(name, version, remove, msg) |
| 37 | + api_dict[deprecated.name] = deprecated |
| 38 | + |
| 39 | + |
| 40 | +def _deprecate(name, version, *, remove=None, replace=None): |
| 41 | + global _DEPRECATIONS |
| 42 | + _deprecate_api(_DEPRECATIONS, name, version, remove, replace) |
| 43 | + |
| 44 | + |
| 45 | +def _deprecate_capi(name, version, *, remove=None, replace=None): |
| 46 | + global _DEPRECATIONS_CAPI |
| 47 | + _deprecate_api(_DEPRECATIONS_CAPI, name, version, remove, replace) |
| 48 | + |
| 49 | + |
| 50 | +# Python 2.6 |
| 51 | +_deprecate('gzip.GzipFile.filename', '2.6', remove='3.12', |
| 52 | + replace='gzip.GzipFile.name'), |
| 53 | + |
| 54 | +# Python 3.6 |
| 55 | +_deprecate('asyncore', '3.6', remove='3.12', replace='asyncio'), |
| 56 | +_deprecate('asynchat', '3.6', remove='3.12', replace='asyncio'), |
| 57 | +_deprecate('smtpd', '3.6', remove='3.12', replace='aiosmtp'), |
| 58 | +_deprecate('ssl.RAND_pseudo_bytes', '3.6', remove='3.12', |
| 59 | + replace='os.urandom()'), |
| 60 | + |
| 61 | +# Python 3.7 |
| 62 | +_deprecate('ssl.match_hostname', '3.7', remove='3.12'), |
| 63 | +_deprecate('ssl.wrap_socket', '3.7', remove='3.12', |
| 64 | + replace='ssl.SSLContext.wrap_socket()'), |
| 65 | +_deprecate('locale.format', '3.7', remove='3.12', |
| 66 | + replace='locale.format_string()'), |
| 67 | + |
| 68 | +# Python 3.10 |
| 69 | +_deprecate('io.OpenWrapper', '3.10', remove='3.12', replace='open()'), |
| 70 | +_deprecate('_pyio.OpenWrapper', '3.10', remove='3.12', replace='open()'), |
| 71 | +_deprecate('xml.etree.ElementTree.copy', '3.10', remove='3.12', |
| 72 | + replace='copy.copy()'), |
| 73 | +_deprecate('zipimport.zipimporter.find_loader', '3.10', remove='3.12', |
| 74 | + replace='find_spec() method: PEP 451'), |
| 75 | +_deprecate('zipimport.zipimporter.find_module', '3.10', remove='3.12', |
| 76 | + replace='find_spec() method: PEP 451'), |
| 77 | + |
| 78 | +# Python 3.11 |
| 79 | +_deprecate('aifc', '3.11', remove='3.13'), |
| 80 | +_deprecate('audioop', '3.11', remove='3.13'), |
| 81 | +_deprecate('cgi', '3.11', remove='3.13'), |
| 82 | +_deprecate('cgitb', '3.11', remove='3.13'), |
| 83 | +_deprecate('chunk', '3.11', remove='3.13'), |
| 84 | +_deprecate('crypt', '3.11', remove='3.13'), |
| 85 | +_deprecate('imghdr', '3.11', remove='3.13'), |
| 86 | +_deprecate('mailcap', '3.11', remove='3.13'), |
| 87 | +_deprecate('msilib', '3.11', remove='3.13'), |
| 88 | +_deprecate('nis', '3.11', remove='3.13'), |
| 89 | +_deprecate('nntplib', '3.11', remove='3.13'), |
| 90 | +_deprecate('ossaudiodev', '3.11', remove='3.13'), |
| 91 | +_deprecate('pipes', '3.11', remove='3.13'), |
| 92 | +_deprecate('sndhdr', '3.11', remove='3.13'), |
| 93 | +_deprecate('spwd', '3.11', remove='3.13'), |
| 94 | +_deprecate('sunau', '3.11', remove='3.13'), |
| 95 | +_deprecate('telnetlib', '3.11', remove='3.13'), |
| 96 | +_deprecate('uu', '3.11', remove='3.13'), |
| 97 | +_deprecate('xdrlib', '3.11', remove='3.13'), |
| 98 | + |
| 99 | +# Python 3.12 |
| 100 | +_deprecate('datetime.datetime.utcnow', '3.12', |
| 101 | + replace='datetime.datetime.now(tz=datetime.UTC)'), |
| 102 | +_deprecate('datetime.datetime.utcfromtimestamp', '3.12', |
| 103 | + replace='datetime.datetime.fromtimestamp(tz=datetime.UTC)'), |
| 104 | +_deprecate('calendar.January', '3.12'), |
| 105 | +_deprecate('calendar.February', '3.12'), |
| 106 | +_deprecate('sys.last_value', '3.12'), |
| 107 | +_deprecate('sys.last_traceback', '3.12'), |
| 108 | +_deprecate('sys.last_exc', '3.12'), |
| 109 | +_deprecate('xml.etree.ElementTree.__bool__', '3.12'), |
| 110 | + |
| 111 | +# Python 3.13 |
| 112 | +_deprecate('ctypes.SetPointerType', '3.13', remove='3.15'), |
| 113 | +_deprecate('ctypes.ARRAY', '3.13', remove='3.15'), |
| 114 | +_deprecate('wave.Wave_read.getmark', '3.13', remove='3.15'), |
| 115 | +_deprecate('wave.Wave_read.getmarkers', '3.13', remove='3.15'), |
| 116 | +_deprecate('wave.Wave_read.setmark', '3.13', remove='3.15'), |
| 117 | + |
| 118 | + |
| 119 | +# C API: Python 3.10 |
| 120 | +for name in ( |
| 121 | + 'PyUnicode_AS_DATA', |
| 122 | + 'PyUnicode_AS_UNICODE', |
| 123 | + 'PyUnicode_AsUnicodeAndSize', |
| 124 | + 'PyUnicode_AsUnicode', |
| 125 | + 'PyUnicode_FromUnicode', |
| 126 | + 'PyUnicode_GET_DATA_SIZE', |
| 127 | + 'PyUnicode_GET_SIZE', |
| 128 | + 'PyUnicode_GetSize', |
| 129 | + 'PyUnicode_IS_COMPACT', |
| 130 | + 'PyUnicode_IS_READY', |
| 131 | + 'PyUnicode_READY', |
| 132 | + 'Py_UNICODE_WSTR_LENGTH', |
| 133 | + '_PyUnicode_AsUnicode', |
| 134 | + 'PyUnicode_WCHAR_KIND', |
| 135 | + 'PyUnicodeObject', |
| 136 | + 'PyUnicode_InternImmortal', |
| 137 | +): |
| 138 | + _deprecate_capi(name, '3.10', remove='3.12') |
| 139 | + |
| 140 | +# C API: Python 3.12 |
| 141 | +_deprecate_capi('PyDictObject.ma_version_tag', '3.12', remove='3.14'), |
| 142 | +for name, replace in ( |
| 143 | + ('Py_DebugFlag', 'PyConfig.parser_debug'), |
| 144 | + ('Py_VerboseFlag', 'PyConfig.verbose'), |
| 145 | + ('Py_QuietFlag', 'PyConfig.quiet'), |
| 146 | + ('Py_InteractiveFlag', 'PyConfig.interactive'), |
| 147 | + ('Py_InspectFlag', 'PyConfig.inspect'), |
| 148 | + ('Py_OptimizeFlag', 'PyConfig.optimization_level'), |
| 149 | + ('Py_NoSiteFlag', 'PyConfig.site_import'), |
| 150 | + ('Py_BytesWarningFlag', 'PyConfig.bytes_warning'), |
| 151 | + ('Py_FrozenFlag', 'PyConfig.pathconfig_warnings'), |
| 152 | + ('Py_IgnoreEnvironmentFlag', 'PyConfig.use_environment'), |
| 153 | + ('Py_DontWriteBytecodeFlag', 'PyConfig.write_bytecode'), |
| 154 | + ('Py_NoUserSiteDirectory', 'PyConfig.user_site_directory'), |
| 155 | + ('Py_UnbufferedStdioFlag', 'PyConfig.buffered_stdio'), |
| 156 | + ('Py_HashRandomizationFlag', 'PyConfig.hash_seed'), |
| 157 | + ('Py_IsolatedFlag', 'PyConfig.isolated'), |
| 158 | + ('Py_LegacyWindowsFSEncodingFlag', 'PyPreConfig.legacy_windows_fs_encoding'), |
| 159 | + ('Py_LegacyWindowsStdioFlag', 'PyConfig.legacy_windows_stdio'), |
| 160 | + ('Py_FileSystemDefaultEncoding', 'PyConfig.filesystem_encoding'), |
| 161 | + ('Py_FileSystemDefaultEncodeErrors', 'PyConfig.filesystem_errors'), |
| 162 | + ('Py_UTF8Mode', 'PyPreConfig.utf8_mode'), |
| 163 | +): |
| 164 | + _deprecate_capi(name, '3.12', replace=replace) |
| 165 | + |
| 166 | +_deprecate_capi('PyErr_Display', '3.12', replace='PyErr_DisplayException()'), |
| 167 | +_deprecate_capi('_PyErr_ChainExceptions', '3.12', replace='_PyErr_ChainExceptions1()'), |
| 168 | + |
| 169 | + |
| 170 | +def get_deprecated(name): |
| 171 | + try: |
| 172 | + return _DEPRECATIONS[name] |
| 173 | + except KeyError: |
| 174 | + pass |
| 175 | + |
| 176 | + parts = name.split('.') |
| 177 | + if len(parts) == 1: |
| 178 | + return False |
| 179 | + |
| 180 | + module_name = parts[0] |
| 181 | + return _DEPRECATIONS.get(module_name) |
| 182 | + |
| 183 | + |
| 184 | +def get_capi_deprecated(name): |
| 185 | + return _DEPRECATIONS_CAPI.get(name) |
0 commit comments