Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ea1e60e
mark version to 3.14
youknowone Jan 13, 2026
7594ef5
upgrade site to 3.14.2
youknowone Jan 13, 2026
280caea
upgrade venvlauncher
youknowone Jan 13, 2026
8d901a7
Implement bool(NotImplemented)
youknowone Jan 13, 2026
2fe140f
Fix bytes/bytearray fromhex
youknowone Jan 13, 2026
db01a1d
Remove pickle from itertools
youknowone Jan 13, 2026
eafa0c0
Fix int rounding
youknowone Jan 13, 2026
24bff8d
fix unsigned validation
youknowone Jan 13, 2026
f5b44f5
Fix Exception.__init__
youknowone Jan 13, 2026
0793bd3
co_consts
youknowone Jan 13, 2026
fdfede7
fix win clippy
youknowone Jan 16, 2026
51b6286
PEP 649 annotation phase 1
youknowone Jan 13, 2026
353a9f6
PEP 649 annotation phase 2
youknowone Jan 13, 2026
a78b569
PEP 649 annotation phase 3
youknowone Jan 13, 2026
566b6f4
PEP 649 annotation phase 4
youknowone Jan 14, 2026
dc93614
Add annotationlib,ann_module from 3.14.2
youknowone Jan 15, 2026
37cc6b4
fix whats_left to support __annotate__
youknowone Jan 16, 2026
96038e4
partially patch inspect for PEP 649 in 3.13
youknowone Jan 14, 2026
346481d
partially patch Lib/typing to 3.14
youknowone Jan 15, 2026
076d692
Upgrade string from CPython 3.14.2
Jan 15, 2026
5227938
mark and unmark unittest functions
youknowone Jan 17, 2026
33689c1
Fix jit failure
youknowone Jan 14, 2026
65e08c0
Update ensurepip from 3.14.2
Jan 16, 2026
ef22bf4
Update _colorize from CPython 3.14.2
Jan 16, 2026
d75f272
Update argparse from CPython 3.14.2
Jan 16, 2026
314a615
Update calendar from CPython 3.14.2
Jan 16, 2026
133bdf6
auto_mark_test uses regex to check Run tests? sequentially
youknowone Jan 17, 2026
faeed2c
clean up
youknowone Jan 17, 2026
60fb438
Auto-format: ruff check --select I --fix
github-actions[bot] Jan 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.dict/python-more.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ aexit
aiter
anext
anextawaitable
annotationlib
appendleft
argcount
arrayiterator
Expand Down
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document provides guidelines for working with GitHub Copilot when contribut

## Project Overview

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

- A complete Python-3 environment entirely in Rust (not CPython bindings)
- A clean implementation without compatibility hacks
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ env:
test.test_multiprocessing_spawn.test_processes
ENV_POLLUTING_TESTS_WINDOWS: >-
# Python version targeted by the CI.
PYTHON_VERSION: "3.13.1"
PYTHON_VERSION: "3.14.2"
X86_64_PC_WINDOWS_MSVC_OPENSSL_LIB_DIR: C:\Program Files\OpenSSL\lib\VC\x64\MD
X86_64_PC_WINDOWS_MSVC_OPENSSL_INCLUDE_DIR: C:\Program Files\OpenSSL\include

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cron-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ name: Periodic checks/tasks

env:
CARGO_ARGS: --no-default-features --features stdlib,importlib,encodings,ssl-rustls,jit
PYTHON_VERSION: "3.13.1"
PYTHON_VERSION: "3.14.2"

jobs:
# codecov collects code coverage data from the rust tests, python snippets and python test suite.
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/update-doc-db.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ on:
python-version:
description: Target python version to generate doc db for
type: string
default: "3.13.9"
default: "3.14.2"
ref:
description: Branch to commit to (leave empty for current branch)
type: string
Expand Down
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ RustPython requires the following:
stable version: `rustup update stable`
- If you do not have Rust installed, use [rustup](https://rustup.rs/) to
do so.
- CPython version 3.13 or higher
- CPython version 3.14 or higher
- CPython can be installed by your operating system's package manager,
from the [Python website](https://www.python.org/downloads/), or
using a third-party distribution, such as
Expand Down
263 changes: 253 additions & 10 deletions Lib/_colorize.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from __future__ import annotations
import io
import os
import sys

from collections.abc import Callable, Iterator, Mapping
from dataclasses import dataclass, field, Field

COLORIZE = True


# types
if False:
from typing import IO
from typing import IO, Self, ClassVar
_theme: Theme


class ANSIColors:
Expand All @@ -17,11 +20,13 @@ class ANSIColors:
BLUE = "\x1b[34m"
CYAN = "\x1b[36m"
GREEN = "\x1b[32m"
GREY = "\x1b[90m"
MAGENTA = "\x1b[35m"
RED = "\x1b[31m"
WHITE = "\x1b[37m" # more like LIGHT GRAY
YELLOW = "\x1b[33m"

BOLD = "\x1b[1m"
BOLD_BLACK = "\x1b[1;30m" # DARK GRAY
BOLD_BLUE = "\x1b[1;34m"
BOLD_CYAN = "\x1b[1;36m"
Expand Down Expand Up @@ -60,13 +65,196 @@ class ANSIColors:
INTENSE_BACKGROUND_YELLOW = "\x1b[103m"


ColorCodes = set()
NoColors = ANSIColors()

for attr in dir(NoColors):
for attr, code in ANSIColors.__dict__.items():
if not attr.startswith("__"):
ColorCodes.add(code)
setattr(NoColors, attr, "")


#
# Experimental theming support (see gh-133346)
#

# - Create a theme by copying an existing `Theme` with one or more sections
# replaced, using `default_theme.copy_with()`;
# - create a theme section by copying an existing `ThemeSection` with one or
# more colors replaced, using for example `default_theme.syntax.copy_with()`;
# - create a theme from scratch by instantiating a `Theme` data class with
# the required sections (which are also dataclass instances).
#
# Then call `_colorize.set_theme(your_theme)` to set it.
#
# Put your theme configuration in $PYTHONSTARTUP for the interactive shell,
# or sitecustomize.py in your virtual environment or Python installation for
# other uses. Your applications can call `_colorize.set_theme()` too.
#
# Note that thanks to the dataclasses providing default values for all fields,
# creating a new theme or theme section from scratch is possible without
# specifying all keys.
#
# For example, here's a theme that makes punctuation and operators less prominent:
#
# try:
# from _colorize import set_theme, default_theme, Syntax, ANSIColors
# except ImportError:
# pass
# else:
# theme_with_dim_operators = default_theme.copy_with(
# syntax=Syntax(op=ANSIColors.INTENSE_BLACK),
# )
# set_theme(theme_with_dim_operators)
# del set_theme, default_theme, Syntax, ANSIColors, theme_with_dim_operators
#
# Guarding the import ensures that your .pythonstartup file will still work in
# Python 3.13 and older. Deleting the variables ensures they don't remain in your
# interactive shell's global scope.

class ThemeSection(Mapping[str, str]):
"""A mixin/base class for theme sections.

It enables dictionary access to a section, as well as implements convenience
methods.
"""

# The two types below are just that: types to inform the type checker that the
# mixin will work in context of those fields existing
__dataclass_fields__: ClassVar[dict[str, Field[str]]]
_name_to_value: Callable[[str], str]

def __post_init__(self) -> None:
name_to_value = {}
for color_name in self.__dataclass_fields__:
name_to_value[color_name] = getattr(self, color_name)
super().__setattr__('_name_to_value', name_to_value.__getitem__)

def copy_with(self, **kwargs: str) -> Self:
color_state: dict[str, str] = {}
for color_name in self.__dataclass_fields__:
color_state[color_name] = getattr(self, color_name)
color_state.update(kwargs)
return type(self)(**color_state)

@classmethod
def no_colors(cls) -> Self:
color_state: dict[str, str] = {}
for color_name in cls.__dataclass_fields__:
color_state[color_name] = ""
return cls(**color_state)

def __getitem__(self, key: str) -> str:
return self._name_to_value(key)

def __len__(self) -> int:
return len(self.__dataclass_fields__)

def __iter__(self) -> Iterator[str]:
return iter(self.__dataclass_fields__)


@dataclass(frozen=True, kw_only=True)
class Argparse(ThemeSection):
usage: str = ANSIColors.BOLD_BLUE
prog: str = ANSIColors.BOLD_MAGENTA
prog_extra: str = ANSIColors.MAGENTA
heading: str = ANSIColors.BOLD_BLUE
summary_long_option: str = ANSIColors.CYAN
summary_short_option: str = ANSIColors.GREEN
summary_label: str = ANSIColors.YELLOW
summary_action: str = ANSIColors.GREEN
long_option: str = ANSIColors.BOLD_CYAN
short_option: str = ANSIColors.BOLD_GREEN
label: str = ANSIColors.BOLD_YELLOW
action: str = ANSIColors.BOLD_GREEN
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
class Syntax(ThemeSection):
prompt: str = ANSIColors.BOLD_MAGENTA
keyword: str = ANSIColors.BOLD_BLUE
keyword_constant: str = ANSIColors.BOLD_BLUE
builtin: str = ANSIColors.CYAN
comment: str = ANSIColors.RED
string: str = ANSIColors.GREEN
number: str = ANSIColors.YELLOW
op: str = ANSIColors.RESET
definition: str = ANSIColors.BOLD
soft_keyword: str = ANSIColors.BOLD_BLUE
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
class Traceback(ThemeSection):
type: str = ANSIColors.BOLD_MAGENTA
message: str = ANSIColors.MAGENTA
filename: str = ANSIColors.MAGENTA
line_no: str = ANSIColors.MAGENTA
frame: str = ANSIColors.MAGENTA
error_highlight: str = ANSIColors.BOLD_RED
error_range: str = ANSIColors.RED
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
class Unittest(ThemeSection):
passed: str = ANSIColors.GREEN
warn: str = ANSIColors.YELLOW
fail: str = ANSIColors.RED
fail_info: str = ANSIColors.BOLD_RED
reset: str = ANSIColors.RESET


@dataclass(frozen=True)
class Theme:
"""A suite of themes for all sections of Python.

When adding a new one, remember to also modify `copy_with` and `no_colors`
below.
"""
argparse: Argparse = field(default_factory=Argparse)
syntax: Syntax = field(default_factory=Syntax)
traceback: Traceback = field(default_factory=Traceback)
unittest: Unittest = field(default_factory=Unittest)

def copy_with(
self,
*,
argparse: Argparse | None = None,
syntax: Syntax | None = None,
traceback: Traceback | None = None,
unittest: Unittest | None = None,
) -> Self:
"""Return a new Theme based on this instance with some sections replaced.

Themes are immutable to protect against accidental modifications that
could lead to invalid terminal states.
"""
return type(self)(
argparse=argparse or self.argparse,
syntax=syntax or self.syntax,
traceback=traceback or self.traceback,
unittest=unittest or self.unittest,
)

@classmethod
def no_colors(cls) -> Self:
"""Return a new Theme where colors in all sections are empty strings.

This allows writing user code as if colors are always used. The color
fields will be ANSI color code strings when colorization is desired
and possible, and empty strings otherwise.
"""
return cls(
argparse=Argparse.no_colors(),
syntax=Syntax.no_colors(),
traceback=Traceback.no_colors(),
unittest=Unittest.no_colors(),
)


def get_colors(
colorize: bool = False, *, file: IO[str] | IO[bytes] | None = None
) -> ANSIColors:
Expand All @@ -76,22 +264,37 @@ def get_colors(
return NoColors


def decolor(text: str) -> str:
"""Remove ANSI color codes from a string."""
for code in ColorCodes:
text = text.replace(code, "")
return text


def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool:

def _safe_getenv(k: str, fallback: str | None = None) -> str | None:
"""Exception-safe environment retrieval. See gh-128636."""
try:
return os.environ.get(k, fallback)
except Exception:
return fallback

if file is None:
file = sys.stdout

if not sys.flags.ignore_environment:
if os.environ.get("PYTHON_COLORS") == "0":
if _safe_getenv("PYTHON_COLORS") == "0":
return False
if os.environ.get("PYTHON_COLORS") == "1":
if _safe_getenv("PYTHON_COLORS") == "1":
return True
if os.environ.get("NO_COLOR"):
if _safe_getenv("NO_COLOR"):
return False
if not COLORIZE:
return False
if os.environ.get("FORCE_COLOR"):
if _safe_getenv("FORCE_COLOR"):
return True
if os.environ.get("TERM") == "dumb":
if _safe_getenv("TERM") == "dumb":
return False

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

try:
return os.isatty(file.fileno())
except io.UnsupportedOperation:
except OSError:
return hasattr(file, "isatty") and file.isatty()


default_theme = Theme()
theme_no_color = default_theme.no_colors()


def get_theme(
*,
tty_file: IO[str] | IO[bytes] | None = None,
force_color: bool = False,
force_no_color: bool = False,
) -> Theme:
"""Returns the currently set theme, potentially in a zero-color variant.

In cases where colorizing is not possible (see `can_colorize`), the returned
theme contains all empty strings in all color definitions.
See `Theme.no_colors()` for more information.

It is recommended not to cache the result of this function for extended
periods of time because the user might influence theme selection by
the interactive shell, a debugger, or application-specific code. The
environment (including environment variable state and console configuration
on Windows) can also change in the course of the application life cycle.
"""
if force_color or (not force_no_color and
can_colorize(file=tty_file)):
return _theme
return theme_no_color


def set_theme(t: Theme) -> None:
global _theme

if not isinstance(t, Theme):
raise ValueError(f"Expected Theme object, found {t}")

_theme = t


set_theme(default_theme)
Loading
Loading