Skip to content

Commit 273c5a3

Browse files
authored
Merge pull request #6718 from youknowone/py-3.14
RustPython version to 3.14
2 parents 299f1ea + 60fb438 commit 273c5a3

81 files changed

Lines changed: 4662 additions & 1265 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.cspell.dict/python-more.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ aexit
55
aiter
66
anext
77
anextawaitable
8+
annotationlib
89
appendleft
910
argcount
1011
arrayiterator

‎.github/copilot-instructions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ This document provides guidelines for working with GitHub Copilot when contribut
44

55
## Project Overview
66

7-
RustPython is a Python 3 interpreter written in Rust, implementing Python 3.13.0+ compatibility. The project aims to provide:
7+
RustPython is a Python 3 interpreter written in Rust, implementing Python 3.14.0+ compatibility. The project aims to provide:
88

99
- A complete Python-3 environment entirely in Rust (not CPython bindings)
1010
- A clean implementation without compatibility hacks

‎.github/workflows/ci.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ env:
115115
test.test_multiprocessing_spawn.test_processes
116116
ENV_POLLUTING_TESTS_WINDOWS: >-
117117
# Python version targeted by the CI.
118-
PYTHON_VERSION: "3.13.1"
118+
PYTHON_VERSION: "3.14.2"
119119
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
120120
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include
121121

‎.github/workflows/cron-ci.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ name: Periodic checks/tasks
1313

1414
env:
1515
CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit
16-
PYTHON_VERSION: "3.13.1"
16+
PYTHON_VERSION: "3.14.2"
1717

1818
jobs:
1919
# codecov collects code coverage data from the rust tests, python snippets and python test suite.

‎.github/workflows/update-doc-db.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99
python-version:
1010
description: Target python version to generate doc db for
1111
type: string
12-
default: "3.13.9"
12+
default: "3.14.2"
1313
ref:
1414
description: Branch to commit to (leave empty for current branch)
1515
type: string

‎DEVELOPMENT.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ RustPython requires the following:
2525
stable version: `rustup update stable`
2626
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
2727
do so.
28-
- CPython version 3.13 or higher
28+
- CPython version 3.14 or higher
2929
- CPython can be installed by your operating system's package manager,
3030
from the [Python website](https://www.python.org/downloads/), or
3131
using a third-party distribution, such as

‎Lib/_colorize.py‎

Lines changed: 253 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
from __future__ import annotations
2-
import io
31
import os
42
import sys
53

4+
from collections.abc import Callable, Iterator, Mapping
5+
from dataclasses import dataclass, field, Field
6+
67
COLORIZE = True
78

9+
810
# types
911
if False:
10-
from typing import IO
12+
from typing import IO, Self, ClassVar
13+
_theme: Theme
1114

1215

1316
class ANSIColors:
@@ -17,11 +20,13 @@ class ANSIColors:
1720
BLUE = "\x1b[34m"
1821
CYAN = "\x1b[36m"
1922
GREEN = "\x1b[32m"
23+
GREY = "\x1b[90m"
2024
MAGENTA = "\x1b[35m"
2125
RED = "\x1b[31m"
2226
WHITE = "\x1b[37m" # more like LIGHT GRAY
2327
YELLOW = "\x1b[33m"
2428

29+
BOLD = "\x1b[1m"
2530
BOLD_BLACK = "\x1b[1;30m" # DARK GRAY
2631
BOLD_BLUE = "\x1b[1;34m"
2732
BOLD_CYAN = "\x1b[1;36m"
@@ -60,13 +65,196 @@ class ANSIColors:
6065
INTENSE_BACKGROUND_YELLOW = "\x1b[103m"
6166

6267

68+
ColorCodes = set()
6369
NoColors = ANSIColors()
6470

65-
for attr in dir(NoColors):
71+
for attr, code in ANSIColors.__dict__.items():
6672
if not attr.startswith("__"):
73+
ColorCodes.add(code)
6774
setattr(NoColors, attr, "")
6875

6976

77+
#
78+
# Experimental theming support (see gh-133346)
79+
#
80+
81+
# - Create a theme by copying an existing `Theme` with one or more sections
82+
# replaced, using `default_theme.copy_with()`;
83+
# - create a theme section by copying an existing `ThemeSection` with one or
84+
# more colors replaced, using for example `default_theme.syntax.copy_with()`;
85+
# - create a theme from scratch by instantiating a `Theme` data class with
86+
# the required sections (which are also dataclass instances).
87+
#
88+
# Then call `_colorize.set_theme(your_theme)` to set it.
89+
#
90+
# Put your theme configuration in $PYTHONSTARTUP for the interactive shell,
91+
# or sitecustomize.py in your virtual environment or Python installation for
92+
# other uses. Your applications can call `_colorize.set_theme()` too.
93+
#
94+
# Note that thanks to the dataclasses providing default values for all fields,
95+
# creating a new theme or theme section from scratch is possible without
96+
# specifying all keys.
97+
#
98+
# For example, here's a theme that makes punctuation and operators less prominent:
99+
#
100+
# try:
101+
# from _colorize import set_theme, default_theme, Syntax, ANSIColors
102+
# except ImportError:
103+
# pass
104+
# else:
105+
# theme_with_dim_operators = default_theme.copy_with(
106+
# syntax=Syntax(op=ANSIColors.INTENSE_BLACK),
107+
# )
108+
# set_theme(theme_with_dim_operators)
109+
# del set_theme, default_theme, Syntax, ANSIColors, theme_with_dim_operators
110+
#
111+
# Guarding the import ensures that your .pythonstartup file will still work in
112+
# Python 3.13 and older. Deleting the variables ensures they don't remain in your
113+
# interactive shell's global scope.
114+
115+
class ThemeSection(Mapping[str, str]):
116+
"""A mixin/base class for theme sections.
117+
118+
It enables dictionary access to a section, as well as implements convenience
119+
methods.
120+
"""
121+
122+
# The two types below are just that: types to inform the type checker that the
123+
# mixin will work in context of those fields existing
124+
__dataclass_fields__: ClassVar[dict[str, Field[str]]]
125+
_name_to_value: Callable[[str], str]
126+
127+
def __post_init__(self) -> None:
128+
name_to_value = {}
129+
for color_name in self.__dataclass_fields__:
130+
name_to_value[color_name] = getattr(self, color_name)
131+
super().__setattr__('_name_to_value', name_to_value.__getitem__)
132+
133+
def copy_with(self, **kwargs: str) -> Self:
134+
color_state: dict[str, str] = {}
135+
for color_name in self.__dataclass_fields__:
136+
color_state[color_name] = getattr(self, color_name)
137+
color_state.update(kwargs)
138+
return type(self)(**color_state)
139+
140+
@classmethod
141+
def no_colors(cls) -> Self:
142+
color_state: dict[str, str] = {}
143+
for color_name in cls.__dataclass_fields__:
144+
color_state[color_name] = ""
145+
return cls(**color_state)
146+
147+
def __getitem__(self, key: str) -> str:
148+
return self._name_to_value(key)
149+
150+
def __len__(self) -> int:
151+
return len(self.__dataclass_fields__)
152+
153+
def __iter__(self) -> Iterator[str]:
154+
return iter(self.__dataclass_fields__)
155+
156+
157+
@dataclass(frozen=True, kw_only=True)
158+
class Argparse(ThemeSection):
159+
usage: str = ANSIColors.BOLD_BLUE
160+
prog: str = ANSIColors.BOLD_MAGENTA
161+
prog_extra: str = ANSIColors.MAGENTA
162+
heading: str = ANSIColors.BOLD_BLUE
163+
summary_long_option: str = ANSIColors.CYAN
164+
summary_short_option: str = ANSIColors.GREEN
165+
summary_label: str = ANSIColors.YELLOW
166+
summary_action: str = ANSIColors.GREEN
167+
long_option: str = ANSIColors.BOLD_CYAN
168+
short_option: str = ANSIColors.BOLD_GREEN
169+
label: str = ANSIColors.BOLD_YELLOW
170+
action: str = ANSIColors.BOLD_GREEN
171+
reset: str = ANSIColors.RESET
172+
173+
174+
@dataclass(frozen=True)
175+
class Syntax(ThemeSection):
176+
prompt: str = ANSIColors.BOLD_MAGENTA
177+
keyword: str = ANSIColors.BOLD_BLUE
178+
keyword_constant: str = ANSIColors.BOLD_BLUE
179+
builtin: str = ANSIColors.CYAN
180+
comment: str = ANSIColors.RED
181+
string: str = ANSIColors.GREEN
182+
number: str = ANSIColors.YELLOW
183+
op: str = ANSIColors.RESET
184+
definition: str = ANSIColors.BOLD
185+
soft_keyword: str = ANSIColors.BOLD_BLUE
186+
reset: str = ANSIColors.RESET
187+
188+
189+
@dataclass(frozen=True)
190+
class Traceback(ThemeSection):
191+
type: str = ANSIColors.BOLD_MAGENTA
192+
message: str = ANSIColors.MAGENTA
193+
filename: str = ANSIColors.MAGENTA
194+
line_no: str = ANSIColors.MAGENTA
195+
frame: str = ANSIColors.MAGENTA
196+
error_highlight: str = ANSIColors.BOLD_RED
197+
error_range: str = ANSIColors.RED
198+
reset: str = ANSIColors.RESET
199+
200+
201+
@dataclass(frozen=True)
202+
class Unittest(ThemeSection):
203+
passed: str = ANSIColors.GREEN
204+
warn: str = ANSIColors.YELLOW
205+
fail: str = ANSIColors.RED
206+
fail_info: str = ANSIColors.BOLD_RED
207+
reset: str = ANSIColors.RESET
208+
209+
210+
@dataclass(frozen=True)
211+
class Theme:
212+
"""A suite of themes for all sections of Python.
213+
214+
When adding a new one, remember to also modify `copy_with` and `no_colors`
215+
below.
216+
"""
217+
argparse: Argparse = field(default_factory=Argparse)
218+
syntax: Syntax = field(default_factory=Syntax)
219+
traceback: Traceback = field(default_factory=Traceback)
220+
unittest: Unittest = field(default_factory=Unittest)
221+
222+
def copy_with(
223+
self,
224+
*,
225+
argparse: Argparse | None = None,
226+
syntax: Syntax | None = None,
227+
traceback: Traceback | None = None,
228+
unittest: Unittest | None = None,
229+
) -> Self:
230+
"""Return a new Theme based on this instance with some sections replaced.
231+
232+
Themes are immutable to protect against accidental modifications that
233+
could lead to invalid terminal states.
234+
"""
235+
return type(self)(
236+
argparse=argparse or self.argparse,
237+
syntax=syntax or self.syntax,
238+
traceback=traceback or self.traceback,
239+
unittest=unittest or self.unittest,
240+
)
241+
242+
@classmethod
243+
def no_colors(cls) -> Self:
244+
"""Return a new Theme where colors in all sections are empty strings.
245+
246+
This allows writing user code as if colors are always used. The color
247+
fields will be ANSI color code strings when colorization is desired
248+
and possible, and empty strings otherwise.
249+
"""
250+
return cls(
251+
argparse=Argparse.no_colors(),
252+
syntax=Syntax.no_colors(),
253+
traceback=Traceback.no_colors(),
254+
unittest=Unittest.no_colors(),
255+
)
256+
257+
70258
def get_colors(
71259
colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
72260
) -> ANSIColors:
@@ -76,22 +264,37 @@ def get_colors(
76264
return NoColors
77265

78266

267+
def decolor(text: str) -> str:
268+
"""Remove ANSI color codes from a string."""
269+
for code in ColorCodes:
270+
text = text.replace(code, "")
271+
return text
272+
273+
79274
def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
275+
276+
def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
277+
"""Exception-safe environment retrieval. See gh-128636."""
278+
try:
279+
return os.environ.get(k, fallback)
280+
except Exception:
281+
return fallback
282+
80283
if file is None:
81284
file = sys.stdout
82285

83286
if not sys.flags.ignore_environment:
84-
if os.environ.get("PYTHON_COLORS") == "0":
287+
if _safe_getenv("PYTHON_COLORS") == "0":
85288
return False
86-
if os.environ.get("PYTHON_COLORS") == "1":
289+
if _safe_getenv("PYTHON_COLORS") == "1":
87290
return True
88-
if os.environ.get("NO_COLOR"):
291+
if _safe_getenv("NO_COLOR"):
89292
return False
90293
if not COLORIZE:
91294
return False
92-
if os.environ.get("FORCE_COLOR"):
295+
if _safe_getenv("FORCE_COLOR"):
93296
return True
94-
if os.environ.get("TERM") == "dumb":
297+
if _safe_getenv("TERM") == "dumb":
95298
return False
96299

97300
if not hasattr(file, "fileno"):
@@ -108,5 +311,45 @@ def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:
108311

109312
try:
110313
return os.isatty(file.fileno())
111-
except io.UnsupportedOperation:
314+
except OSError:
112315
return hasattr(file, "isatty") and file.isatty()
316+
317+
318+
default_theme = Theme()
319+
theme_no_color = default_theme.no_colors()
320+
321+
322+
def get_theme(
323+
*,
324+
tty_file: IO[str] | IO[bytes] | None = None,
325+
force_color: bool = False,
326+
force_no_color: bool = False,
327+
) -> Theme:
328+
"""Returns the currently set theme, potentially in a zero-color variant.
329+
330+
In cases where colorizing is not possible (see `can_colorize`), the returned
331+
theme contains all empty strings in all color definitions.
332+
See `Theme.no_colors()` for more information.
333+
334+
It is recommended not to cache the result of this function for extended
335+
periods of time because the user might influence theme selection by
336+
the interactive shell, a debugger, or application-specific code. The
337+
environment (including environment variable state and console configuration
338+
on Windows) can also change in the course of the application life cycle.
339+
"""
340+
if force_color or (not force_no_color and
341+
can_colorize(file=tty_file)):
342+
return _theme
343+
return theme_no_color
344+
345+
346+
def set_theme(t: Theme) -> None:
347+
global _theme
348+
349+
if not isinstance(t, Theme):
350+
raise ValueError(f"Expected Theme object, found {t}")
351+
352+
_theme = t
353+
354+
355+
set_theme(default_theme)

0 commit comments

Comments
 (0)