Skip to content

Commit 4c0bd4f

Browse files
committed
gh-153333: Read tkinter profile scripts with the source file's encoding
Tk.readprofile execed the user's ~/.CLASSNAME.py and ~/.BASENAME.py profile scripts read via open() with no encoding argument, i.e. the locale default encoding. That emits an EncodingWarning under -X warn_default_encoding and mis-decodes a profile whose source carries a PEP 263 coding cookie. Read them with tokenize.open() instead, which decodes using the encoding detected from the file (its coding cookie, else UTF-8), matching how Python reads source. As an incidental effect of using the context-manager form, each script is now closed promptly after reading instead of at garbage collection, removing a ResourceWarning.
1 parent 754b9f2 commit 4c0bd4f

3 files changed

Lines changed: 27 additions & 2 deletions

File tree

Lib/test/test_tkinter/test_misc.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import collections.abc
22
import functools
3+
import os
34
import platform
45
import sys
56
import textwrap
67
import unittest
8+
import warnings
79
import weakref
810
import tkinter
911
from tkinter import TclError
@@ -364,6 +366,24 @@ def test_getdouble(self):
364366
self.assertEqual(self.root.getdouble(3), 3.0)
365367
self.assertRaises(ValueError, self.root.getdouble, 'spam')
366368

369+
def test_readprofile(self):
370+
# gh-153333: readprofile() reads the profile scripts with the source
371+
# file's encoding (here a Latin-1 coding cookie), not the locale
372+
# default encoding.
373+
name = 'readprofiletest'
374+
script = ("# -*- coding: latin-1 -*-\n"
375+
"self.readprofile_result = 'caf\xe9'\n")
376+
self.addCleanup(self.root.__dict__.pop, 'readprofile_result', None)
377+
with os_helper.temp_dir() as home:
378+
with open(os.path.join(home, '.%s.py' % name), 'wb') as f:
379+
f.write(script.encode('latin-1'))
380+
with os_helper.EnvironmentVarGuard() as env:
381+
env['HOME'] = home
382+
with warnings.catch_warnings():
383+
warnings.simplefilter('error', EncodingWarning)
384+
self.root.readprofile(name, name)
385+
self.assertEqual(self.root.readprofile_result, 'caf\xe9')
386+
367387
def test_getvar(self):
368388
self.root.setvar('test_var', 'hello')
369389
self.assertEqual(self.root.getvar('test_var'), 'hello')

Lib/tkinter/__init__.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2739,6 +2739,7 @@ def readprofile(self, baseName, className):
27392739
the Tcl Interpreter and calls exec on the contents of .BASENAME.py and
27402740
.CLASSNAME.py if such a file exists in the home directory."""
27412741
import os
2742+
import tokenize
27422743
if 'HOME' in os.environ: home = os.environ['HOME']
27432744
else: home = os.curdir
27442745
class_tcl = os.path.join(home, '.%s.tcl' % className)
@@ -2750,11 +2751,13 @@ def readprofile(self, baseName, className):
27502751
if os.path.isfile(class_tcl):
27512752
self.tk.call('source', class_tcl)
27522753
if os.path.isfile(class_py):
2753-
exec(open(class_py).read(), dir)
2754+
with tokenize.open(class_py) as f:
2755+
exec(f.read(), dir)
27542756
if os.path.isfile(base_tcl):
27552757
self.tk.call('source', base_tcl)
27562758
if os.path.isfile(base_py):
2757-
exec(open(base_py).read(), dir)
2759+
with tokenize.open(base_py) as f:
2760+
exec(f.read(), dir)
27582761

27592762
def report_callback_exception(self, exc, val, tb):
27602763
"""Report callback exception on sys.stderr.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The ``readprofile`` method of :class:`tkinter.Tk` now reads the user's
2+
profile scripts using :func:`tokenize.open`.

0 commit comments

Comments
 (0)