uv icon indicating copy to clipboard operation
uv copied to clipboard

Can't use tkinter with new venv set up with uv

Open dstansby opened this issue 1 year ago • 24 comments

uv venv
source .venv/bin/activate
python -c "from tkinter import Tk; window = Tk()" 

This gives me the error message:

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/dstansby/.local/share/uv/python/cpython-3.12.5-macos-aarch64-none/lib/python3.12/tkinter/__init__.py", line 2346, in __init__
    self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_tkinter.TclError: Can't find a usable init.tcl in the following directories: 
    /tools/deps/lib/tcl8.6 /Users/dstansby/.venv/lib/tcl8.6 /Users/dstansby/lib/tcl8.6 /Users/dstansby/.venv/library /Users/dstansby/library /Users/dstansby/tcl8.6.12/library /Users/tcl8.6.12/library



This probably means that Tcl wasn't installed properly.

I can successfully run python -c "from tkinter import Tk; window = Tk()" in a fresh conda environment, but when I install python on conda it also installs tk, so maybe that's why it works? Maybe this isn't an issue with uv?, but if anyone knows how to get this working I'd be very gateful!

uv 0.4.4 (Homebrew 2024-09-04) macOS

dstansby avatar Sep 04 '24 17:09 dstansby

This is a python-build-standalone quirk: https://gregoryszorc.com/docs/python-build-standalone/main/quirks.html#tcl-tk-support-files

We might need to set the variable as described there. 🤔

zanieb avatar Sep 04 '24 17:09 zanieb

For the record, rye did this https://github.com/astral-sh/rye/pull/233

bluss avatar Sep 04 '24 19:09 bluss

I'm not opposed to doing a similar thing here.

charliermarsh avatar Sep 04 '24 23:09 charliermarsh

to maybe point others to a workaround, all you'd need to do is to export an env variable called TCL_LIBRARY pointing to the correct tcl directory. to figure the correct one out, I did the following:

ATTENTION: this assumes that your .venv is based on the default python available to uv and also that tcl is at version 8.6, so keep that in mind

#!/bin/bash

# Get the Python path
PYTHON_PATH=$(cd ~ && uv run which python)

# Extract the base path
BASE_PATH=$(dirname "$(dirname "$PYTHON_PATH")")

# Construct the TCL_LIBRARY path
TCL_LIBRARY="$BASE_PATH/lib/tcl8.6"

# Set the environment variable
export TCL_LIBRARY

# Print the set variable (for verification)
echo "TCL_LIBRARY has been set to: $TCL_LIBRARY"

for comfort I've then just put the var in my .env file which gets automatically loaded by .envrc (direnv) - which btw would totally be an appealing feature for uv to replicate :P

mraesener-aubex avatar Sep 09 '24 07:09 mraesener-aubex

The quirks page says

Distributions produced from this project contain tcl/tk support files

However,

$ uv --version
uv 0.4.22
$ uv venv -p 3.9
Using CPython 3.9.19
Creating virtual environment at: .venv
Activate with: source .venv/bin/activate.fish
$ find .venv -name "*tcl*"
$ # ?

What am I doing wrong?

astrojuanlu avatar Oct 16 '24 07:10 astrojuanlu

$ find .venv -name "tcl" $ # ?


What am I doing wrong?

Those are in python installation directory not in virtualenv, I'm not next to linux box but under windows:

(venvflon) PS C:\Users\mplichta\Projects\venvflon> uv python list          
cpython-3.13.0-windows-x86_64-none     C:\Users\mplichta\AppData\Roaming\uv\python\cpython-3.13.0-windows-x86_64-none\python.exe
cpython-3.12.7-windows-x86_64-none     <download available>

So, path would be: C:\Users\mplichta\AppData\Roaming\uv\python\cpython-3.13.0-windows-x86_64-none\tcl\tcl8.6

As workaroud you can use tkinker like:

from os import environ
from pathlib import Path
from sys import base_prefix

environ["TCL_LIBRARY"] = str(Path(base_prefix) / "tcl" / "tcl8.6")
environ["TK_LIBRARY"] = str(Path(base_prefix) / "tcl" / "tk8.6")
import tkinter as tk

emcek avatar Oct 16 '24 08:10 emcek

Oh I understand now. On macOS they're under ~/.local/share/uv/python/cpython-*-macos-aarch64-none/lib/tcl8.6 👍🏼

astrojuanlu avatar Oct 16 '24 10:10 astrojuanlu

This is an important issue for me and my employers. I'm currently using the workaround suggeted above. I'm a CS major with 5 years of SWE xp in python, but no xp in rust yet. Learning rust is a current goal of mine. Would you consider this a good first issue for me or at least one that I could get along? If so, any special precautions?

giolucasd avatar Oct 17 '24 19:10 giolucasd

It'd be a stretch to say it's a good first issue, there's a lot of nuance to the possible solutions and I would have to do quite a bit of research to even understand what the trade-offs are. I think the first best step is to understand Rye's approach and if there are any alternatives. If it's the most compelling approach still, we should be able to port it over without too much difficulty.

zanieb avatar Oct 17 '24 19:10 zanieb

Since most people expect to use Tkinter that comes with the python, I guess including TCL_LIBRARY and TK_LIBRARY env path pointing to where uv installed python as @emcek described would be a good default solution? The uv can provide some override mechanism, like allowing use to specify custom path to these two environment variable in the .python_version file or maybe some other files? This way TCL should always work by default.

I tried @emcek method on Mac and the result is successful

from os import environ
from pathlib import Path
from sys import base_prefix

environ["TCL_LIBRARY"] = str(Path(base_prefix) / "lib" / "tcl8.6")
environ["TK_LIBRARY"] = str(Path(base_prefix) / "lib" / "tk8.6")

print(environ["TCL_LIBRARY"])
print(environ["TK_LIBRARY"])

from tkinter import *
from tkinter import ttk
root = Tk()
frm = ttk.Frame(root, padding=10)
frm.grid()
ttk.Label(frm, text="Hello World!").grid(column=0, row=0)
ttk.Button(frm, text="Quit", command=root.destroy).grid(column=1, row=0)
root.mainloop()

However I have changed the path of TCL_LIBRARY and TK_LIBRARY a bit as for mac its "lib" instead of "tcl", as pointed out by @astrojuanlu

Maybe uv can populate the two environmental variable by default?

harrylaulau avatar Oct 18 '24 07:10 harrylaulau

So... expanding on the workaround so it works on all platforms, doesn't clobber existing env vars, might work with tk 8.7+, and doesn't bother doing anything if tk is actually working already (given that just because you used uv to write your library there's no guarantee it will be run using the standalone Python builds that uv uses)... we have something like this.

import tkinter
from os import environ
from pathlib import Path
from sys import base_prefix
import platform

if not ("TCL_LIBRARY" in environ and "TK_LIBRARY" in environ):
    try:
        tkinter.Tk()
    except tkinter.TclError:
        tk_dir = "tcl" if platform.system() == "Windows" else "lib"
        tk_path = Path(base_prefix) / tk_dir
        environ["TCL_LIBRARY"] = str(next(tk_path.glob("tcl8.*")))
        environ["TK_LIBRARY"] = str(next(tk_path.glob("tk8.*")))

def main():
    tk = tkinter.Tk()
    tk.mainloop()


if __name__ == "__main__":
    main()

sonotley avatar Oct 27 '24 19:10 sonotley

Hmmm... my workaround above fixes tk, but when I try to use the tkagg backend for matplotlib things get broken again. I'll do some more investigation.

from . import _tkagg ImportError: initialization failed

sonotley avatar Nov 08 '24 20:11 sonotley

Hmmm... my workaround above fixes tk, but when I try to use the tkagg backend for matplotlib things get broken again. I'll do some more investigation.

from . import _tkagg ImportError: initialization failed

This issue is mentioned in #6893

Seems like Tkinter is just unusable for now.

harrylaulau avatar Nov 09 '24 16:11 harrylaulau

Broadly, we have two options:

  1. Add a .pth file, like in Rye, to set these environment variables.
  2. Patch CPython to set these if not set (in python-build-standalone) -- something like:
--- a/Lib/tkinter/__init__.py
+++ b/Lib/tkinter/__init__.py
@@ -54,6 +54,14 @@
 _magic_re = re.compile(r'([\\{}])')
 _space_re = re.compile(r'([\s])', re.ASCII)

+# Facilitate discovery of the Tcl/Tk libraries.
+import os
+
+if 'TCL_LIBRARY' not in os.environ:
+    os.environ['TCL_LIBRARY'] = sys.base_prefix + '/tcl/tcl' + _tkinter.TCL_VERSION
+if 'TK_LIBRARY' not in os.environ:
+    os.environ['TK_LIBRARY'] = sys.base_prefix + '/tcl/tk' + _tkinter.TK_VERSION
+

 def _join(value):
     """Internal function."""

The advantage of (1) is that it's slightly easier, doesn't require us to maintain a patch, and could be disabled in uv venv (like, we could have a flag to avoid creating it).

The advantage of (2) is that we'll only set those variables when tkinter is imported, and we won't have to add a .pth file to the environment, which may confuse users.

charliermarsh avatar Dec 15 '24 03:12 charliermarsh

I'd say having a .pth file is the "better" approach of the two in terms of best practices and style etc. so that's what I'd gravitate to...

but, thinking of ease of use especially for beginners and the "uv just works" experience there's probably no way around patching.

Is it possible to get a list of similar or identical use case? Is this a tkinter speciality because of the UI nature of it or would this affect lots of other builtins, too?

mraesener-aubex avatar Dec 15 '24 08:12 mraesener-aubex

I have a minor preference for the upstream patch, I think? There are consumers other than uv.

Perhaps a dumb question, but why do we need to set an environment variable? How does this work in the standard distributions?

zanieb avatar Dec 15 '24 17:12 zanieb

I'm not totally certain why this works in other Pythons (e.g., Framework Python distributions).

charliermarsh avatar Dec 15 '24 17:12 charliermarsh

So for the Framework Python at least, the paths appear to get compiled in to the binary:

❯ otool -L /Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload/_tkinter.cpython-312-darwin.so
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload/_tkinter.cpython-312-darwin.so (architecture x86_64):
	/Library/Frameworks/Python.framework/Versions/3.12/lib/libtcl8.6.dylib (compatibility version 8.6.0, current version 8.6.15)
	/Library/Frameworks/Python.framework/Versions/3.12/lib/libtk8.6.dylib (compatibility version 8.6.0, current version 8.6.15)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
/Library/Frameworks/Python.framework/Versions/3.12/lib/python3.12/lib-dynload/_tkinter.cpython-312-darwin.so (architecture arm64):
	/Library/Frameworks/Python.framework/Versions/3.12/lib/libtcl8.6.dylib (compatibility version 8.6.0, current version 8.6.15)
	/Library/Frameworks/Python.framework/Versions/3.12/lib/libtk8.6.dylib (compatibility version 8.6.0, current version 8.6.15)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)

charliermarsh avatar Dec 15 '24 18:12 charliermarsh

(I'll see if we can do something similar.)

charliermarsh avatar Dec 15 '24 18:12 charliermarsh

I think another thing we could do is symlink those files into .venv/lib, since tkl does look there.

charliermarsh avatar Dec 15 '24 18:12 charliermarsh

So if a fresh venv gets created the tcl dependencies are "copied" (symlinked) from somewhere else to the .venv/lib dir, or did I get this wrong?

mraesener-aubex avatar Dec 15 '24 19:12 mraesener-aubex

Ok, I have a patch that is working well here: https://github.com/indygreg/python-build-standalone/pull/421. It avoids all the disadvantages, at the cost of requiring us to maintain a CPython C patch.

charliermarsh avatar Dec 15 '24 23:12 charliermarsh

Thank you for looking into this an resolving it! As a user of uv I wonder when this patch will be available? Will I need to wait for new Python patch releases like 3.10.17? or can force uv to use an archive from a specific standalone release, marked by its date?

domfellner avatar Dec 17 '24 11:12 domfellner

It'll be available following a python-build-standalone release, then a uv release — probably relatively soon.

We don't support using arbitrary archives yet.

zanieb avatar Dec 17 '24 20:12 zanieb

I just released this in https://github.com/astral-sh/uv/releases/tag/0.5.11

zanieb avatar Dec 20 '24 00:12 zanieb

For the record, this won't work out of the box with Python versions that were already downloaded.

❯ uv self update
...
❯ uv --version
uv 0.5.11 (c4d0caaee 2024-12-19)
❯ uv venv -p 3.10
Using CPython 3.10.14
...
❯ uv pip install foxdot
...
❯ uv run python -m FoxDot
_tkinter.TclError: Can't find a usable init.tcl in the following directories: 
    /tools/deps/lib/tcl8.6 /Users/juan_cano/Projects/Personal/Music/.venv/lib/tcl8.6 /Users/juan_cano/Projects/Personal/Music/lib/tcl8.6 /Users/juan_cano/Projects/Personal/Music/.venv/library /Users/juan_cano/Projects/Personal/Music/library /Users/juan_cano/Projects/Personal/Music/tcl8.6.12/library /Users/juan_cano/Projects/Personal/tcl8.6.12/library

This probably means that Tcl wasn't installed properly.

But it does work with newly installed versions!

❯ uv python install 3.10.16
Installed Python 3.10.16 in 13.09s
 + cpython-3.10.16-macos-aarch64-none
❯ uv venv -p 3.10.16
Using CPython 3.10.16
...
❯ # Now everything works!

astrojuanlu avatar Dec 20 '24 15:12 astrojuanlu

You can uv python install --reinstall <version>

zanieb avatar Dec 20 '24 15:12 zanieb

I use uv 0.5.30 and updated all the python installs. Still i get the following error: from tkinter import Variable, StringVar, IntVar, DoubleVar, BooleanVar ImportError: cannot import name 'Variable' from 'tkinter' (unknown location)

Is this related?

Satge96 avatar Feb 17 '25 07:02 Satge96

Has this issue been resolved, or do we still need to rely on the https://github.com/astral-sh/uv/issues/7036#issuecomment-2440145724?

It's quite frustrating, especially since I rely on multiple Python versions — which is one of the main reasons I chose to use uv — and this error keeps appearing when I try to use matplotlib.

sanjeev-bhandari avatar May 12 '25 05:05 sanjeev-bhandari

I think this is still an issue, but not in uv https://github.com/astral-sh/python-build-standalone/issues/146

astrojuanlu avatar May 12 '25 06:05 astrojuanlu