Skip to content

Refactor turtle internals behind backend adapters and add headless recording backend tests#48

Draft
Copilot wants to merge 2177 commits intomainfrom
copilot/refactor-turtle-backend-support
Draft

Refactor turtle internals behind backend adapters and add headless recording backend tests#48
Copilot wants to merge 2177 commits intomainfrom
copilot/refactor-turtle-backend-support

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 10, 2026

  • Fetch and identify origin/master (the actual upstream with PEP 810 lazy import syntax)
  • Merge origin/master into branch; resolve single conflict in _iscolorstring (keep backend abstraction)
  • Apply PEP 810 explicit lazy imports in turtle.py:
    • Replace TK = None sentinel + inline import tkinter as TK with lazy import tkinter as TK at module level
    • Add lazy from tkinter import simpledialog at module level; remove inline from tkinter import simpledialog
    • Simplify _init_tk(): use _TK_INITIALIZED = False flag, remove internal import tkinter as TK
    • Remove inline import tkinter as TK from is_color, blank_image, load_image methods
    • Simplify __getattr__ (TK no longer needs lazy trigger via it)
Original prompt

Create a draft pull request against repository m-aciek/cpython (base branch: main) that refactors Lib/turtle.py to support multiple rendering backends with a minimal-diff, SVG-friendly interface approach.

Goals

  • Preserve turtle’s public API and default behavior (Tkinter remains the default backend).
  • Reduce direct coupling to tkinter Canvas by routing drawing, timers, and event wiring through a backend adapter interface.
  • Keep changes incremental/minimal in terms of behavior, while designing the backend interface to be SVG-friendly (retained-mode), not a 1:1 copy of Tk Canvas.
  • Add a headless/recording backend suitable for unit tests (no GUI required).

Deliverables

  1. Backend interface scaffolding
  • Introduce internal backend abstractions (e.g., _TurtleBackend, _Renderer, _EventLoop; naming up to you but keep them private) either within Lib/turtle.py or as an internal module under Lib/turtle/ (prefer minimal disruption).
  • The interface should support these operations at minimum:
    • draw_polyline(points, style) -> handle
    • draw_polygon(points, style) -> handle
    • draw_text(x, y, text, style) -> handle
    • update_geometry(handle, points) and/or update_style(handle, style) (optional but support current behavior)
    • delete(handle), clear()
    • set_background(color)
    • set_world_coordinates / transform mapping hooks as needed
    • flush() (can be no-op for Tk)
    • timer scheduling: call_later(ms, callback)
    • event bindings: mouse click/drag/release and key press/release hooks sufficient to support existing turtle Screen methods
  1. Tk backend implementation
  • Implement a Tk backend adapter that uses tkinter Canvas internally and satisfies the new interface.
  • Ensure existing features (tracer/update, onclick/ondrag, onkey) continue to work.
  • Ensure done()/mainloop semantics remain backward compatible.
  1. Headless backend
  • Implement a pure-Python recording backend that records draw operations into a list.
  • Use it in tests to validate core drawing semantics without Tk.
  1. Tests
  • Add unit tests under Lib/test/ (or appropriate location) that:
    • Instantiate turtle with the headless backend (provide an internal hook to inject backend; do not add a new public API unless absolutely necessary).
    • Verify that basic turtle commands (forward, left/right, penup/pendown, pencolor, pensize, begin_fill/end_fill for a simple polygon) produce expected recorded operations.

Constraints

  • Keep the refactor minimal: avoid large-scale re-architecture; focus on introducing an internal adapter boundary.
  • Avoid adding third-party dependencies.
  • Do not implement the SVG backend itself; only make turtle ready for it.

Notes

  • The interface should be SVG-friendly: it should not require Tk-only concepts (e.g., PhotoImage, itemconfig semantics) as the core dependency.
  • Where turtle currently relies on item IDs and mutation, provide a thin compatibility layer (handles) that can map to DOM nodes in a future SVG backend.

Please include in the PR:

  • Code changes
  • New tests
  • A PR description summarizing the approach and how to build confidence (how tests cover it).

The following is the prior conversation context from the user's chat exploration (may be truncated):

User: how could cpython's turtle be changed to handle more canvases (beyond tkinter) implementations more gracefully?

Assistant: CPython’s turtle is historically Tkinter-first: it directly assumes a Tk Canvas/Tk event loop in a lot of places, which makes “drop in a different canvas” hard. To support additional canvas implementations (Qt, wx, web/canvas, pygame, etc.) more gracefully, the cleanest direction is to turn turtle into a small, stable “turtle engine” plus pluggable backends.

Here are concrete, CPython-friendly changes that would get you there.

1) Define an explicit backend interface (protocol) instead of “Tk objects”

Create an internal module (e.g. turtle._backend) that defines the minimum set of operations the turtle engine needs. For example:

  • Drawing primitives
    • create_line(points, **style), create_polygon(points, **style), create_text(x, y, text, **style)
    • delete(item_id), clear()
    • set_background(color|image)
  • Coordinate + transform helpers
    • world_to_screen(x, y) and screen_to_world(x, y) (or a single transform object)
  • Timing / animation
    • ontimer(callback, ms)
    • “batching” hooks: begin_frame() / end_frame() or flush()
  • Events
    • bind_mouse(button, handler), bind_key(key, handler), bind_resize(handler)
  • Lifecycle
    • mainloop() (or return control to host loop), close()

Then refactor turtle.Screen / TurtleScreen so it talks only to that interface—not to a Tk Canvas.

Why this helps: every other UI toolkit can implement one adapter without patching turtle internals.

2) Split “model/state” from “rendering”

Right now, turtle mixes:

  • turtle state (pos, heading, pen state, shapes, undo buffer)
  • screen state (world coordinates,...

This pull request was created from Copilot chat.

NekoAsakura and others added 30 commits March 30, 2026 19:42
…onGH-144845)

The C accelerator implementations use PyArg_ParseTuple, which
inherently enforces positional-only parameters. The Python fallback
allowed these as keyword arguments, creating a behavioral mismatch.

Make the tag parameter of Element.__init__ and the parent and tag
parameters of SubElement positional-only to align with the C
accelerator.
…ythonGH-139634)

Change the `.netrc` security check to be run once per parse of the
default file rather than once per line inside the file.
Co-authored-by: Victor Stinner <vstinner@python.org>
Fix null derefs, missing decrefs, and unchecked returns from bug report.
Co-authored-by: Victor Stinner <vstinner@python.org>
…ython#145901)

The long_from_string_base() might return a small integer, when the
_pylong.py is used to do conversion.  Hence, we must be careful here to
not smash it "small int" bit by using the _PyLong_FlipSign().

Co-authored-by: Victor Stinner <vstinner@python.org>
…rowser_test (python#146048)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…Base XX codec (pythonGH-146434)

Add the wrapcol parameter to base64 functions b16encode(), b32encode(),
b32hexencode(), b85encode() and z85encode(), and binascii functions
b2a_base32() and b2a_base85().

Add the ignorechars parameter to base64 functions b16decode(), b32decode(),
b32hexdecode(), b85decode() and z85decode(), and binascii functions
a2b_hex(), unhexlify(), a2b_base32() and a2b_base85().
Accepted range for the bytes_per_sep argument of bytes.hex(),
bytearray.hex(), memoryview.hex(), and binascii.b2a_hex()
is now increased, so passing sys.maxsize and -sys.maxsize is now
valid.
…shared (python#147947)

Skip the test if Python is built with --enable-shared and "gcc -O2"
or "gcc -O3".
Signed-off-by: William Woodruff <william@yossarian.net>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
…pythonGH-146637)

- Add Py_TARGET_ABI3T macro.
- Add ".abi3t.so" to importlib EXTENSION_SUFFIXES.
- Remove ".abi3.so" from importlib EXTENSION_SUFFIXES on Free Threading.
- Adjust tests

This is part of the implementation for PEP-803.
Detailed documentation to come later.

Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
…k pointer (python#147945)

* Make _Py_get_machine_stack_pointer return the stack pointer (or close to it), not the frame pointer

* Make ``_Py_ReachedRecursionLimit`` inline again
* Remove ``_Py_MakeRecCheck`` relacing its use with ``_Py_ReachedRecursionLimit``
* Move stack swtiching check into ``_Py_CheckRecursiveCall``
Copilot AI and others added 2 commits April 14, 2026 07:07
…() with global declarations

Agent-Logs-Url: https://github.com/m-aciek/cpython/sessions/cf241a03-c5e4-4bb1-a83b-51d63f82ad52

Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com>
johnslavik and others added 21 commits April 14, 2026 10:29
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
… endings. (python#148193)

The Generator._make_boundary regex did not match on boundary phrases correctly when using CRLF line endings due to re.MULTILINE not considering \r\n as a line ending.
…eter (python#133313)

This option changes the behavior of --enable-shared to continue to build
the libpython3.x.so shared library, but not use it for linking the
python3 interpreter executable. Instead, the executable is linked
directly against the libpython .o files as it would be with
--disable-shared.

There are two benefits of this change. First, libpython uses
thread-local storage, which is noticeably slower when used in a loaded
module instead of in the main program, because the main program can take
advantage of constant offsets from the thread state pointer but loaded
modules have to dynamically call a function __tls_get_addr() to
potentially allocate their thread-local storage area. (There is another
thread-local storage model for dynamic libraries which mitigates most of
this performance hit, but it comes at the cost of preventing
dlopen("libpython3.x.so"), which is a use case we want to preserve.)

Second, this improves the user experience around relocatable Python a
little bit, in that we don't need to use an $ORIGIN-relative path to
locate libpython3.x.so, which has some mild benefits around musl (which
does not support $ORIGIN-relative DT_NEEDED, only $ORIGIN-relative
DT_RPATH/DT_RUNPATH), users who want to make the interpreter setuid or
setcap (which prevents processing $ORIGIN), etc.
…lling is possible for MSVC builds on Windows (python#148036)" (python#148558)

This reverts commit cbd81d5.
…ython#148534)

Co-authored-by: Savannah Ostrowski <savannah@python.org>
Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
…ely-referenced operands in place (JIT only) (pythonGH-146397)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… avoid security scanner noise (pythonGH-148578)

remove a mktemp use to avoid security scanner noise
…out year in `strptime()` (pythonGH-144570)

* Add deprecation for %e with no year
* schedule `%e` for 3.17, and remove `%d` now
…for tkinter

Co-authored-by: m-aciek <9288014+m-aciek@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.