uv
uv copied to clipboard
Can't use tkinter with new venv set up with uv
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
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. 🤔
For the record, rye did this https://github.com/astral-sh/rye/pull/233
I'm not opposed to doing a similar thing here.
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
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?
$ 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
Oh I understand now. On macOS they're under ~/.local/share/uv/python/cpython-*-macos-aarch64-none/lib/tcl8.6 👍🏼
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?
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.
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?
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()
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
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.
Broadly, we have two options:
- Add a
.pthfile, like in Rye, to set these environment variables. - 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.
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?
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?
I'm not totally certain why this works in other Pythons (e.g., Framework Python distributions).
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)
(I'll see if we can do something similar.)
I think another thing we could do is symlink those files into .venv/lib, since tkl does look there.
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?
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.
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?
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.
I just released this in https://github.com/astral-sh/uv/releases/tag/0.5.11
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!
You can uv python install --reinstall <version>
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?
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.
I think this is still an issue, but not in uv https://github.com/astral-sh/python-build-standalone/issues/146